1 /**
2  * Binary tooling
3  *
4  * Authors: Tristan Brice Velloza Kildaire (deavmi)
5  */
6 module niknaks.bits;
7 
8 version(unittest)
9 {
10     import std.stdio : writeln;
11 }
12 
13 /** 
14  * Flips the given integral value
15  *
16  * Params:
17  *   bytesIn = the integral value
18  * Returns: the flipped integral
19  */
20 public T flip(T)(T bytesIn) if(__traits(isIntegral, T))
21 {
22     T copy = bytesIn;
23 
24     ubyte* base = (cast(ubyte*)&bytesIn)+T.sizeof-1;
25     ubyte* baseCopy = cast(ubyte*)©
26 
27     for(ulong idx = 0; idx < T.sizeof; idx++)
28     {
29         *(baseCopy+idx) = *(base-idx);
30     }
31 
32     return copy;
33 }
34 
35 /**
36  * Tests the `flip!(T)(T)` function
37  */
38 unittest
39 {
40     version(BigEndian)
41     {
42         ushort i = 1;
43         ushort flipped = flip(i);
44         assert(flipped == 256);
45     }
46     else version(LittleEndian)
47     {
48         ushort i = 1;
49         ushort flipped = flip(i);
50         assert(flipped == 256);
51     }
52 }
53 
54 /** 
55  * Ordering
56  */
57 public enum Order
58 {
59     /**
60      * Little endian
61      */
62     LE,
63 
64     /**
65      * Big endian
66      */
67     BE
68 }
69 
70 /** 
71  * Swaps the bytes to the given ordering but does a no-op
72  * if the ordering requested is the same as that of the 
73  * system's
74  *
75  * Params:
76  *   bytesIn = the integral value to swap
77  *   order = the byte ordering to request
78  * Returns: the integral value
79  */
80 public T order(T)(T bytesIn, Order order) if(__traits(isIntegral, T))
81 {
82     version(LittleEndian)
83     {
84         if(order == Order.LE)
85         {
86             return bytesIn;
87         }
88         else
89         {
90             return flip(bytesIn);
91         }
92     }
93     else version(BigEndian)
94     {
95         if(order == Order.BE)
96         {
97             return bytesIn;
98         }
99         else
100         {
101             return flip(bytesIn);
102         }
103     }
104 }
105 
106 /**
107  * Tests the `order!(T)(T, Order)`
108  *
109  * To Big Endian testing
110  */
111 unittest
112 {
113     version(LittleEndian)
114     {
115         ushort i = 1;
116         writeln("Pre-order: ", i);
117         ushort ordered = order(i, Order.BE);
118         writeln("Post-order: ", ordered);
119         assert(ordered == 256);
120     }
121     else version(BigEndian)
122     {
123         ushort i = 1;
124         writeln("Pre-order: ", i);
125         ushort ordered = order(i, Order.BE);
126         writeln("Post-order: ", ordered);
127         assert(ordered == i);
128     }
129 }
130 
131 /**
132  * Tests the `order!(T)(T, Order)`
133  *
134  * To Little Endian testing
135  */
136 unittest
137 {
138     version(LittleEndian)
139     {
140         ushort i = 1;
141         writeln("Pre-order: ", i);
142         ushort ordered = order(i, Order.LE);
143         writeln("Post-order: ", ordered);
144         assert(ordered == i);
145     }
146     else version(BigEndian)
147     {
148         ushort i = 1;
149         writeln("Pre-order: ", i);
150         ushort ordered = order(i, Order.LE);
151         writeln("Post-order: ", ordered);
152         assert(ordered == 256);
153     }
154 }
155 
156 /** 
157  * Converts the given integral value  to
158  * its byte encoding
159  *
160  * Params:
161  *   integral = the integral value
162  * Returns: a `ubyte[]` of the value
163  */
164 public ubyte[] toBytes(T)(T integral) if(__traits(isIntegral, T))
165 {
166     ubyte[] bytes;
167 
168     static if(integral.sizeof == 1)
169     {
170         bytes = [cast(ubyte)integral];
171     }
172     else static if(integral.sizeof == 2)
173     {
174         ubyte* ptrBase = cast(ubyte*)&integral;
175         bytes = [*ptrBase, *(ptrBase+1)];
176     }
177     else static if(integral.sizeof == 4)
178     {
179         ubyte* ptrBase = cast(ubyte*)&integral;
180         bytes = [*ptrBase, *(ptrBase+1), *(ptrBase+2), *(ptrBase+3)];
181     }
182     else static if(integral.sizeof == 8)
183     {
184         ubyte* ptrBase = cast(ubyte*)&integral;
185         bytes = [*ptrBase, *(ptrBase+1), *(ptrBase+2), *(ptrBase+3), *(ptrBase+4), *(ptrBase+5), *(ptrBase+6), *(ptrBase+7)];
186     }
187     else
188     {
189         pragma(msg, "toBytes cannot support integral types greater than 8 bytes");
190         static assert(false);
191     }
192     
193     return bytes;
194 }
195 
196 /**
197  * Tests the `toBytes!(T)(T)` function
198  */
199 unittest
200 {
201     version(LittleEndian)
202     {
203         ulong value = 1;
204         ubyte[] bytes = toBytes(value);
205 
206         assert(bytes == [1, 0, 0, 0, 0, 0, 0, 0]);
207     }
208     else version(BigEndian)
209     {
210         ulong value = 1;
211         ubyte[] bytes = toBytes(value);
212 
213         assert(bytes == [0, 0, 0, 0, 0, 0, 0, 1]);
214     }
215 }
216 
217 /** 
218  * Takes an array of bytes and dereferences
219  * then to an integral of your choosing
220  *
221  * Params:
222  *   T = the integral type to go to
223  *   bytes = the bytes to copy
224  * Returns: the integral but `T.init` if
225  * the provided size would cause an overrun
226  * read
227  */
228 public T bytesToIntegral(T)(ubyte[] bytes) if(__traits(isIntegral, T))
229 {
230     if(bytes.length >= T.sizeof)
231     {
232         return *cast(T*)bytes.ptr;
233     }
234     else
235     {
236         return T.init;
237     }
238 }
239 
240 /**
241  * Tests taking a byte array and then
242  * decoding it into the requested type
243  * by using `bytesToIntegral!(T)(ubyte[])`
244  *
245  * In this case provided bytes match the
246  * given integral to-type
247  */
248 unittest
249 {
250     version(LittleEndian)
251     {
252         ubyte[] bytes = [1, 0];
253         ushort to = bytesToIntegral!(ushort)(bytes);
254         assert(to == 1);
255     }
256     else version(BigEndian)
257     {
258         ubyte[] bytes = [1, 0];
259         ushort to = bytesToIntegral!(ushort)(bytes);
260         assert(to == 256);
261     }
262 }
263 
264 /**
265  * Tests taking a byte array and then
266  * decoding it into the requested type
267  * by using `bytesToIntegral!(T)(ubyte[])`
268  *
269  * In this case provided bytes DO NOT
270  * match the given integral to-type
271  * and therefore to-type.init is returned
272  */
273 unittest
274 {
275     version(LittleEndian)
276     {
277         ubyte[] bytes = [1];
278         ushort to = bytesToIntegral!(ushort)(bytes);
279         assert(to == ushort.init);
280     }
281     else version(BigEndian)
282     {
283         ubyte[] bytes = [1];
284         ushort to = bytesToIntegral!(ushort)(bytes);
285         assert(to == ushort.init);
286     }
287 }