1 module binary.pack;
2 
3 import std.ascii;
4 import std.algorithm;
5 import std.array;
6 import std.stdio;
7 import std.range;
8 import std.typetuple;
9 import std.typecons;
10 import std.traits;
11 import std..string;
12 import binary.common;
13 import binary.writer;
14 import binary.format;
15 
16 
17 
18 /**
19  * Encodes data to binary.
20  * 
21  * This function packs all specified values into binary data and returns it.
22  * Any invalid character specified in format string results in static assert failure.
23  * 
24  * Available modifier characters
25  * 
26  * Character   | Effect
27  * ------------|--------------------
28  * `=`         | Change to native endian
29  * `<`         | Change to little endian
30  * `>`         | Change to big endian
31  * '@'         | Change to network byte order(big endian)
32  * 
33  *  
34  * Available type specifiers
35  * 
36  * Character  | Type       | Size  
37  * -----------|------------|----------
38  * `c`        | `char`     | 1
39  * `b`        | `byte`     | 1
40  * `B`        | `ubyte`    | 1
41  * `h`        | `short`    | 2 
42  * `H`        | `ushort`   | 2
43  * `i`        | `int`      | 4
44  * `I`        | `uint`     | 4
45  * `p`        | `ptrdiff_t`| 4/8
46  * `P`        | `size_t`   | 4/8
47  * `l`        | `long`     | 8
48  * `L`        | `ulong`    | 8
49  * `f`        | `float`    | 4
50  * `d`        | `double`   | 8
51  * `s`        | `string`   | string length + nul
52  * `S`        | `string`   | string length
53  * `x`        | -          | 1 (zero byte)
54  * `X`        | -          | Skip/Pad to position
55  * 
56  * 
57  * Array behavior (All examples in little endian)
58  * 
59  * Type spec.      | Data       | Description                   | Encoded        
60  * ----------------|------------|-------------------------------|---------------
61  * `int`(`i`)      | 12         | Binary encoded integer        | `[0, 0, 0, 12]`
62  * `int`(`i`)      | [12, 21]   | Written element by element    | `[0, 0, 0, 12, 0, 0, 0, 21]`
63  * `int[]`(`*i`)   | [12, 21]   | Length and elements written   | `[0, 0, 0, 2, 0, 0, 0, 12, 0, 0, 0, 21]`
64  * `int[2]`(`2i`)  | [12, 21]   | Written same as `i`           | `[0, 0, 0, 12, 0, 0, 0, 21]`
65  * `int[1]`(`1i`)  | [12, 21]   | Only 1 element is written     | `[0, 0, 0, 12]`
66  * 
67  * Quick Notes:
68  *  - `s` is an alias for `Sx`
69  *  - To write strings like all other arrays use `*c` format.
70  *  - In `pack` using `#i` on array of elements will write exacly # elements.
71  *    If array is too big it is sliced, if too small range error is thrown.
72  *
73  * Params:
74  *  format     = Format specifier
75  *  endianess  = Endianess to use, Endian.Native is default
76  *  values...  = Values to encode
77  */
78 ubyte[] pack(string format, ByteOrder byteOrder = ByteOrder.Native, V...)(V values)
79 	if (!isFirstArgFile!V)
80 {
81 	BinaryWriter writer = BinaryWriter(byteOrder);
82 	pack!(format)(writer, values);
83 	return writer.buffer;
84 }
85 
86 /**
87  * Binary encodes data to file
88  * 
89  * Specified file must be opened with write access.
90  * 
91  * Params:
92  *  file   = File to write to
93  *  values = Values to encode
94  */
95 void pack(string format, ByteOrder byteOrder = ByteOrder.Native, V...)(File file, V values)
96 {
97 	BinaryWriter writer = BinaryWriter(byteOrder);
98 	pack!(format)(writer, values);
99 	file.rawWrite(writer.buffer);
100 }
101 
102 
103 /**
104  * Binary encodes data.
105  * 
106  * In this overload format string is infered from V type tuple.
107  * 
108  * Params:
109  *  value = Values to encode
110  */
111 ubyte[] pack(ByteOrder byteOrder = ByteOrder.Native, V...)(V values)
112 	if(!isFirstArgFile!V)
113 {
114 	return pack!(formatOf!V, byteOrder)(values);
115 }
116 
117 
118 /**
119  * Binary encodes data.
120  * 
121  * In this overload format string is infered from V type tuple.
122  * 
123  * Params:
124  *  file  = File to write to
125  *  value = Values to encode
126  */
127 ubyte[] pack(ByteOrder byteOrder = ByteOrder.Native, V...)(File file, V values)
128 {
129 	return pack!(formatOf!V, byteOrder)(file, values);
130 }
131 
132 
133 void pack(string format, V...)(ref BinaryWriter writer, V values)
134 	if(format.length == 0)
135 {
136 }
137 
138 /**
139  * Encodes data to binary.
140  * 
141  * Writes all encoded `values` to `writer`.
142  * 
143  * Params:
144  *  writer = Writer to write to
145  *  values = Values to encode
146  */
147 void pack(string format, V...)(ref BinaryWriter writer, V values)
148 	if (format.length > 0)
149 {
150 	enum char current = format[0];
151 
152 	// Ignore whitespaces
153 	static if (isWhite(current))
154 		pack!(format[1..$])(writer, values);
155 
156 	// Endianess modifiers
157 	else static if (formatCharToEndian!current != -1) {
158 		writer.byteOrder = formatCharToEndian!current;
159 		pack!(format[1..$])(writer, values);
160 	}
161 
162 	// Dynamic arrays
163 	else static if (current == '*') {
164 		static assert(format.length > 1, "Expected star to be followed by type character");
165 		static assert(V.length > 0, "Missing parameter for type character *"~format[1]);
166 		static assert(isArray!(V[0]), .format("Expected parameter to be an array, %s given", V[0].stringof));
167 		writer.write(cast(formatTypeOf!(format[1])[])values[0]);
168 
169 		static if (format.length > 2)
170 		pack!(format[2..$])(writer, values[1..$]);
171 	}
172 
173 	// Static arrays
174 	else static if (isDigit(current))
175 	{
176 		// Creates result* variables in local scope
177 		mixin formatRepeatCount!format;
178 		static if(resultChar == 'x')
179 		{
180 			writer.writeArray( (cast(ubyte)0).repeat(resultCount).array );
181 			pack!(resultRest)(writer, values);
182 		}
183 		else static if(resultChar == 'X')
184 		{
185 			writer.padFill(resultCount);
186 			pack!(resultRest)(writer, values);
187 		}
188 		else
189 		{
190 			static assert(V.length > 0, .format("No parameter specified for type %c", resultChar));
191 			alias T = V[0];
192 			
193 			static if(isArray!T) {
194 				alias TargetType = formatTypeOf!resultChar;
195 				auto sliced = values[0][0..resultCount];
196 				writer.writeArray(sliced.map!(x => cast(TargetType)x).array);
197 				pack!(resultRest)(writer, values[1..$]);
198 			}
199 			else
200 				static assert(0, .format("Specified static array in format string but parameter is not an array"));
201 		}
202 	}
203 
204 	// Pad byte.
205 	else static if (current == 'x')
206 	{
207 		writer.write!byte(0);
208 		pack!(format[1..$])(writer, values);
209 	}
210 	
211 	else static if (current == 'X')
212 	{
213 		static assert(0, "Format character 'X' must be preceded by number.");
214 	}
215 
216 	// Type characters
217 	else static if ( !is(formatTypeOf!(current) == void) )
218 	{
219 		static assert(V.length > 0, .format("No parameter specified for character '%c'", current));
220 
221 		static if (isArray!(V[0])) {
222 			static if (current == 's')
223 				writer.writeString(values[0]);
224 			else static if (current == 'S')
225 				writer.writeArray(values[0]);
226 			else
227 				writer.writeArray(values[0][].map!(x => cast(formatTypeOf!current)x).array);
228 		}
229 		// If value is convertible to format character
230 		else static if (__traits(compiles, cast(formatTypeOf!(current))values[0])) {
231 			writer.write(cast(formatTypeOf!current)values[0]);
232 		}
233 		else
234 		{
235 			static assert(0, .format("Incompatible types: %s and %s, format character '%c'",
236 			                         V[0].stringof, formatTypeOf!(current).stringof, format[0]));
237 		}
238 		
239 		pack!(format[1..$])(writer, values[1..$]);
240 	}
241 	else {
242 		static assert (0, .format("Invalid format specifier %c", current));
243 	}
244 }
245 
246 
247 
248 private template isFirstArgFile(V...)
249 {
250 	static if(V.length > 0 && is(V[0] == File))
251 		enum bool isFirstArgFile = true;
252 	else
253 		enum bool isFirstArgFile = false;
254 }
255 
256 
257 unittest
258 {
259 	/// TODO: Check every static if
260 
261 	import binary.unpack;
262 
263 	assert(pack!(ByteOrder.LittleEndian)(10, '0') == [10, 0, 0, 0, '0']);
264 	
265 	{
266 		ubyte[] bytes = pack!`c c`('a', 'b');
267 		assert(bytes == ['a', 'b']);
268 		assert(bytes.unpack!`cc` == tuple('a', 'b'));
269 		assert(bytes == []);
270 	}
271 	
272 	
273 	{
274 		ubyte[] bytes = pack!`<h8Xi`(18, -3);
275 		assert(bytes == [18, 0, 0, 0,  0, 0, 0, 0,  253, 255, 255, 255]);
276 		assert(bytes.unpack!`<h8Xi` == tuple(18, -3));
277 		assert(bytes == []);
278 	}
279 
280 	{
281 		ubyte[] bytes = pack!`<oboh`(true, true, false, false);
282 		assert(bytes == [1, 1, 0,  0, 0]);
283 		assert(bytes.unpack!`<oboh` == tuple(true, true, false, 0));
284 		assert(bytes == []);
285 	}
286 	
287 	{
288 		long l;
289 		int i;
290 		char a, z;
291 		
292 		ubyte[] bytes = pack(1, 22L, 'a', 'z');
293 		bytes.unpackTo(i, l, a, z);
294 		assert(i == 1);
295 		assert(l == 22);
296 		assert(a == 'a');
297 		assert(z == 'z');
298 	}
299 	
300 	{
301 		ubyte[] bytes = pack!`4xx`;
302 		assert(bytes == [0, 0, 0, 0, 0]);
303 		assert(bytes.save.unpack!`5c` == tuple(['\0', '\0', '\0', '\0', '\0']));
304 		ubyte[5] arr;
305 		bytes.unpackTo!`5c`(arr);
306 		assert(arr[0] == 0);
307 		assert(arr[1] == 0);
308 		assert(arr[2] == 0);
309 		assert(arr[3] == 0);
310 		assert(arr[4] == 0);
311 		assert(bytes == []);
312 	}
313 
314 	{
315 		ubyte[] bytes = pack!`<h>h`(15, 30);
316 		assert(bytes == [15, 0, 0, 30]);
317 		assert(bytes.save.unpack!`<h>h` == tuple(15, 30));
318 		assert(bytes.unpack!`>I`() == tuple(251658270));
319 		assert(bytes == []);
320 	}
321 
322 	{
323 		ubyte[] bytes = pack!`s`("a");
324 		assert(bytes == ['a', 0]);
325 		assert(bytes.unpack!`s`() == tuple("a"));
326 		assert(bytes == []);
327 	}
328 
329 	{
330 		ubyte[] bytes = pack!`<h3xcc`(56, 'a', 'c');
331 		assert(bytes == [56, 0,  0, 0, 0,  'a', 'c']);
332 		assert(bytes.unpack!`h3x2c`() == tuple(56, ['a', 'c']));
333 		assert(bytes == []);
334 	}
335 
336 	{
337 		ubyte[] bytes = pack!`S`("Hello");
338 		assert(bytes == ['H', 'e', 'l', 'l', 'o']);
339 		auto values = bytes.save.unpack!`5c`();
340 		assert(values == tuple(['H', 'e', 'l', 'l', 'o']));
341 		assert(bytes.unpack!`s`() == tuple("Hello"));
342 	}
343 
344 	
345 	
346 	{
347 		auto file = File.tmpfile;
348 		scope(exit) file.close();
349 		
350 		file.pack!`<hxsH`(95, "Hello", 42);
351 		file.rewind();
352 		assert(file.unpack!`<hxsH` == tuple(95, "Hello", 42));
353 		assert(file.eof);
354 	}
355 	
356 	{
357 		auto file = File.tmpfile;
358 		scope(exit) file.close();
359 		
360 		file.pack!`<hxhx`(95, 51);
361 		file.rewind();
362 		assert(file.unpack!`<hx` == tuple(95));
363 		assert(file.unpack!`<hx` == tuple(51));
364 		assert(file.eof);
365 	}
366 	
367 	{
368 		ubyte[] bytes = pack!`<bhsbhs`(65, 105, "Hello", 'z', 510, " World");
369 		
370 		auto unpacker = unpacker!`<bhs`(bytes);
371 		static assert(isInputRange!(typeof(unpacker)));
372 		assert(!unpacker.empty);
373 		assert(unpacker.front == tuple(65, 105, "Hello"));
374 		unpacker.popFront();
375 		assert(!unpacker.empty);
376 		assert(unpacker.front == tuple('z', 510, " World"));
377 		unpacker.popFront();
378 		assert(unpacker.empty);
379 	}
380 	
381 	{
382 		File file = File.tmpfile;
383 		scope(exit) file.close;
384 		file.pack!`<bhsbhs`(65, 105, "Hello", 'z', 510, " World");
385 		
386 		file.rewind();
387 		auto unpacker = unpacker!`<bhs`(file);
388 		static assert(isInputRange!(typeof(unpacker)));
389 		assert(!unpacker.empty);
390 		assert(unpacker.front == tuple(65, 105, "Hello"));
391 		unpacker.popFront();
392 		assert(!unpacker.empty);
393 		assert(unpacker.front == tuple('z', 510, " World"));
394 		unpacker.popFront();
395 		assert(unpacker.empty);
396 		assert(file.eof);
397 	}
398 
399 	{
400 		ubyte[] bytes = pack!`>*i2h`([12, 22, 32], [88, 99]);
401 		assert(bytes == [0, 0, 0, 3,  0, 0, 0, 12, 0, 0, 0, 22, 0, 0, 0, 32, 0, 88, 0, 99]);
402 		assert(bytes.save.unpack!`>*i2h` == tuple([12, 22, 32], [88, 99]));
403 	}
404 	
405 	{
406 		ubyte[] bytes = pack!`>*c`("foobar");
407 		assert(bytes == [0, 0, 0, 6,  'f', 'o', 'o', 'b', 'a', 'r']);
408 		assert(bytes.save.unpack!`>*c` == tuple("foobar"));
409 	}
410 	
411 	{
412 		ubyte[] bytes = pack!`>5c`("foobar");
413 		assert(bytes == ['f', 'o', 'o', 'b', 'a']);
414 		assert(bytes.save.unpack!`>5c` == tuple("fooba"));
415 	}
416 	
417 	{
418 		ubyte[] bytes = pack!`>*s`(["c", "c++", "d"]);
419 		assert(bytes == [0, 0, 0, 3,  0, 0, 0, 1, 'c',  0, 0, 0, 3, 'c', '+', '+',  0, 0, 0, 1, 'd']);
420 		assert(bytes.save.unpack!`>*s` == tuple(["c", "c++", "d"]));
421 	}
422 
423 	{
424 		ushort[3] arr = [13, 31, 33];
425 		ubyte[] bytes = pack!`<*H`(arr);
426 		assert(bytes == [3, 0, 0, 0, 13, 0, 31, 0, 33, 0]);
427 		assert(bytes.save.unpack!`<I3H` == tuple(3, [13, 31, 33]));
428 
429 		bytes = pack!`<H`(arr);
430 		assert(bytes == [13, 0, 31, 0, 33, 0]);
431 		assert(bytes.unpack!`<3H` == tuple(arr));
432 	}
433 
434 	{
435 		// Issue #4
436 		ubyte[] bytes = pack!`>L`(12);
437 		assert(bytes == [0, 0, 0, 0,  0, 0, 0, 12]);
438 		assert(bytes.unpack!`>L`[0] == 12UL);
439 	}
440 }