1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 /**
6  * Internet utilities.
7  *
8  * Copyright: Eugene Wissner 2016-2020.
9  * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
10  *                  Mozilla Public License, v. 2.0).
11  * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
12  * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/net/inet.d,
13  *                 tanya/net/inet.d)
14  */
15 module tanya.net.inet;
16 
17 import tanya.meta.trait;
18 import tanya.meta.transform;
19 import tanya.range;
20 
21 /**
22  * Represents an unsigned integer as an $(D_KEYWORD ubyte) range.
23  *
24  * The range is bidirectional. The byte order is always big-endian.
25  *
26  * It can accept any unsigned integral type but the value should fit
27  * in $(D_PARAM L) bytes.
28  *
29  * Params:
30  *  L = Desired range length.
31  */
32 struct NetworkOrder(uint L)
33 if (L > ubyte.sizeof && L <= ulong.sizeof)
34 {
35     static if (L > uint.sizeof)
36     {
37         private alias StorageType = ulong;
38     }
39     else static if (L > ushort.sizeof)
40     {
41         private alias StorageType = uint;
42     }
43     else static if (L > ubyte.sizeof)
44     {
45         private alias StorageType = ushort;
46     }
47     else
48     {
49         private alias StorageType = ubyte;
50     }
51 
52     private StorageType value;
53     private size_t size = L;
54 
55     invariant
56     {
57         assert(this.size <= L);
58     }
59 
60     /**
61      * Constructs a new range.
62      *
63      * $(D_PARAM T) can be any unsigned type but $(D_PARAM value) cannot be
64      * larger than the maximum can be stored in $(D_PARAM L) bytes. Otherwise
65      * an assertion failure will be caused.
66      *
67      * Params:
68      *  T      = Value type.
69      *  value  = The value should be represented by this range.
70      *
71      * Precondition: $(D_INLINECODE value <= (2 ^^ (L * 8)) - 1).
72      */
73     this(T)(T value)
74     if (isUnsigned!T)
75     in
76     {
77         assert(value <= (2 ^^ (L * 8)) - 1);
78     }
79     do
80     {
81         this.value = value & StorageType.max;
82     }
83 
84     /**
85      * Returns: LSB.
86      *
87      * Precondition: $(D_INLINECODE length > 0).
88      */
89     @property ubyte back() const
90     in
91     {
92         assert(this.length > 0);
93     }
94     do
95     {
96         return this.value & 0xff;
97     }
98 
99     /**
100      * Returns: MSB.
101      *
102      * Precondition: $(D_INLINECODE length > 0).
103      */
104     @property ubyte front() const
105     in
106     {
107         assert(this.length > 0);
108     }
109     do
110     {
111         return (this.value >> ((this.length - 1) * 8)) & 0xff;
112     }
113 
114     /**
115      * Eliminates the LSB.
116      *
117      * Precondition: $(D_INLINECODE length > 0).
118      */
119     void popBack()
120     in
121     {
122         assert(this.length > 0);
123     }
124     do
125     {
126         this.value >>= 8;
127         --this.size;
128     }
129 
130     /**
131      * Eliminates the MSB.
132      *
133      * Precondition: $(D_INLINECODE length > 0).
134      */
135     void popFront()
136     in
137     {
138         assert(this.length > 0);
139     }
140     do
141     {
142         this.value &= StorageType.max >> ((StorageType.sizeof - this.length) * 8);
143         --this.size;
144     }
145 
146     /**
147      * Returns: Copy of this range.
148      */
149     typeof(this) save() const
150     {
151         return this;
152     }
153 
154     /**
155      * Returns: Whether the range is empty.
156      */
157     @property bool empty() const
158     {
159         return this.length == 0;
160     }
161 
162     /**
163      * Returns: Byte length.
164      */
165     @property size_t length() const
166     {
167         return this.size;
168     }
169 }
170 
171 ///
172 @nogc nothrow pure @safe unittest
173 {
174     auto networkOrder = NetworkOrder!3(0xae34e2u);
175     assert(!networkOrder.empty);
176     assert(networkOrder.front == 0xae);
177 
178     networkOrder.popFront();
179     assert(networkOrder.length == 2);
180     assert(networkOrder.front == 0x34);
181     assert(networkOrder.back == 0xe2);
182 
183     networkOrder.popBack();
184     assert(networkOrder.length == 1);
185     assert(networkOrder.front == 0x34);
186     assert(networkOrder.front == 0x34);
187 
188     networkOrder.popFront();
189     assert(networkOrder.empty);
190 }
191 
192 /**
193  * Converts the $(D_KEYWORD ubyte) input range $(D_PARAM range) to
194  * $(D_PARAM T).
195  *
196  * The byte order of $(D_PARAM r) is assumed to be big-endian. The length
197  * cannot be larger than $(D_INLINECODE T.sizeof). Otherwise an assertion
198  * failure will be caused.
199  *
200  * Params:
201  *  T     = Desired return type.
202  *  R     = Range type.
203  *  range = Input range.
204  *
205  * Returns: Integral representation of $(D_PARAM range) with the host byte
206  *          order.
207  */
208 T toHostOrder(T = size_t, R)(R range)
209 if (isInputRange!R
210  && !isInfinite!R
211  && is(Unqual!(ElementType!R) == ubyte)
212  && isUnsigned!T)
213 {
214     T ret;
215     ushort pos = T.sizeof * 8;
216 
217     for (; !range.empty && range.front == 0; pos -= 8, range.popFront())
218     {
219     }
220     for (; !range.empty; range.popFront())
221     {
222         assert(pos != 0);
223         pos -= 8;
224         ret |= (cast(T) range.front) << pos;
225     }
226 
227     return ret >> pos;
228 }
229 
230 ///
231 @nogc nothrow pure @safe unittest
232 {
233     const value = 0xae34e2u;
234     auto networkOrder = NetworkOrder!4(value);
235     assert(networkOrder.toHostOrder() == value);
236 }