1 module binary.unpack;
2 
3 import std.ascii;
4 import std.algorithm;
5 import std.stdio;
6 import std.range;
7 import std.typecons;
8 import std.traits;
9 import std..string;
10 import binary.common;
11 import binary.format;
12 import binary.reader;
13 
14 
15 /**
16  * Decodes binary data.
17  * 
18  * This function unpacks binary encoded data from `range` and puts them
19  * into variables passed as arguments. If `range` is too small, DecodeException
20  * is thrown.
21  *
22  * Throws:
23  *  DecodeException
24  * 
25  * Params:
26  *  format     = Format specifier
27  *  byteOrder  = Byte Order to use, ByteOrder.Native is default.
28  *  range      = Data to unpack
29  *  values...  = Values to un pack to
30  */
31 void unpackTo(string format, ByteOrder byteOrder = ByteOrder.Native, Range, V...)(auto ref Range range, ref V values)
32 	if (isInputRange!Range && is(ElementType!Range == ubyte))
33 {
34 	auto reader = binaryReader(range, byteOrder);
35 	unpackTo!(format)(reader, values);
36 	range = reader.source;
37 }
38 
39 
40 /**
41  * Decodes binary data from file.
42  * 
43  * This function reads data from file, unpacks binary encoded data and puts them
44  * into variables passed as arguments. If read data is too small, DecodeException
45  * is thrown.
46  * 
47  * Passed file must be opened with read access.
48  * 
49  * Throws:
50  *  DecodeException
51  * 
52  * Params:
53  *  format     = Unpack format
54  *  byteOrder  = Byte Order to use, ByteOrder.Native is default.
55  *  file       = File to read from
56  *  values...  = Values to un pack to
57  */
58 void unpackTo(string format, ByteOrder byteOrder = ByteOrder.Native, V...)(File file, ref V values)
59 {
60 	if (file.tell > 0) file.seek(-1, SEEK_CUR);
61 
62 	auto reader = binaryReader(file.byChunk(1).joiner, byteOrder);
63 	unpackTo!(format)(reader, values);
64 }
65 
66 
67 /**
68  * Decodes binary data.
69  * 
70  * This function unpacks binary encoded data from `range` and puts them
71  * into variables passed as arguments. If `range` is too small, DecodeException
72  * is thrown.
73  * 
74  * Format string is implied from passed parameter types.
75  *
76  * Throws:
77  *  DecodeException
78  * 
79  * Params:
80  *  byteOrder  = Byte Order to use, ByteOrder.Native is default.
81  *  range      = Data to unpack
82  *  values...  = Values to unpack to
83  */
84 void unpackTo(ByteOrder byteOrder = ByteOrder.Native, Range, V...)(auto ref Range range, ref V values)
85 	if (isInputRange!Range && is(ElementType!Range == ubyte))
86 {
87 	unpackTo!(formatOf!V, byteOrder)(range, values);
88 }
89 
90 
91 /**
92  * Decodes binary data from file.
93  * 
94  * This function reads data from file, unpacks binary encoded data and puts them
95  * into variables passed as arguments. If read data is too small, DecodeException
96  * is thrown.
97  * 
98  * Passed file must be opened with read access.
99  * Format string is implied from passed parameter types.
100  * 
101  * Throws:
102  *  DecodeException
103  * 
104  * Params:
105  *  byteOrder  = Byte Order to use, ByteOrder.Native is default.
106  *  file       = File to read from
107  *  values...  = Values to un pack to
108  */
109 void unpackTo(ByteOrder byteOrder = ByteOrder.Native, Range, V...)(File file, ref V values)
110 {
111 	unpackTo!(formatOf!V, byteOrder)(file, values);
112 }
113 
114 
115 
116 void unpackTo(string format, Range, V...)(ref BinaryReader!Range reader, ref V values)
117 	if(format.length == 0)
118 {
119 }
120 
121 
122 
123 /**
124  * Decodes binary data.
125  * 
126  * Throws:
127  *  DecodeException
128  * 
129  * Params:
130  *  format     = Format specifier
131  *  reader     = Reader instance to use
132  *  values...  = Values to un pack to
133  */
134 void unpackTo(string format, Range, V...)(ref BinaryReader!Range reader, ref V values)
135 	if(format.length > 0)
136 {
137 	enum char current = format[0];
138 	
139 	// Ignore whitespaces
140 	static if (isWhite(current))
141 		unpackTo!(format[1..$])(reader, values);
142 	
143 	// Endianess modifiers
144 	else static if (formatCharToEndian!current != -1) {
145 		reader.byteOrder = formatCharToEndian!current;
146 		unpackTo!(format[1..$])(reader, values);
147 	}
148 
149 	// Dynamic arrays
150 	else static if (current == '*') {
151 		static assert(format.length > 1, "Expected star to be followed by type character");
152 		static assert(V.length > 0, "Missing parameter for type character *"~format[1]);
153 		static assert(isArray!(V[0]), .format("Expected parameter to be an array, %s given", V[0].stringof));
154 		alias TargetType = formatTypeOf!(format[1]);
155 		reader.read(cast(TargetType[])values[0]);
156 		
157 		static if (format.length > 2)
158 			unpackTo!(format[2..$])(reader, values[1..$]);
159 	}
160 
161 	// Static arrays
162 	else static if (isDigit(current))
163 	{
164 		// Creates result* variables in local scope
165 		mixin formatRepeatCount!format;
166 		static if(resultChar == 'x')
167 		{
168 			reader.skipBytes(resultCount);
169 			unpackTo!(resultRest)(reader, values);
170 		}
171 		else static if(resultChar == 'X')
172 		{
173 			reader.skipTo(resultCount);
174 			unpackTo!(resultRest)(reader, values);
175 		}
176 		else
177 		{
178 			static assert(V.length > 0, .format("No parameter specified for type %c", resultChar));
179 			alias T = V[0];
180 			
181 			static if(isArray!T) {
182 				reader.read(values[0]);
183 				unpackTo!(resultRest)(reader, values[1..$]);
184 			}
185 			else
186 			{
187 				static assert(values.length + 1 >= resultCount, .format("Expected %d parameters", resultCount));
188 				reader.read(values[0..resultCount]);
189 				
190 				unpackTo!(resultRest)(reader, values[resultCount..$]);
191 			}
192 		}
193 	}
194 	
195 	// Pad byte.
196 	else static if (current == 'x')
197 	{
198 		reader.skipBytes(1);
199 		unpackTo!(format[1..$])(reader, values);
200 	}
201 	
202 	else static if (current == 'X')
203 	{
204 		static assert(0, "Format character 'X' must be preceded by number.");
205 	}
206 	
207 	// Type characters
208 	else static if ( !is(formatTypeOf!(current) == void) )
209 	{
210 		static assert(V.length > 0, .format("No parameter specified for character '%c'", current));
211 		
212 		// If value is convertible to format character
213 		static if (__traits(compiles, cast(formatTypeOf!current)values[0])) {
214 			formatTypeOf!current val;
215 			static if (current == 's')
216 				reader.readString(val);
217 			else static if (current == 'S') {
218 				if (val.length == 0) {
219 					throw new DecodeException("Reading string with length 0 ('S' passed to unpack and parameter array length is 0)");
220 				}
221 				reader.readArray(val, val.length);
222 			}
223 			else
224 				reader.read(val);
225 
226 			values[0] = cast(V[0])val;
227 		}
228 		else
229 		{
230 			static assert(0, .format("Incompatible types: %s and %s, format character '%c'",
231 			                         V[0].stringof, formatTypeOf!(current).stringof, format[0]));
232 		}
233 
234 		unpackTo!(format[1..$])(reader, values[1..$]);
235 	}
236 	else {
237 		static assert (0, .format("Invalid format specifier %c", current));
238 	}
239 }
240 
241 
242 /**
243  * Decodes binary data.
244  * 
245  * This function works similar to other `unpack` functions, except that read data
246  * is returned as tuple.
247  * 
248  * Params:
249  *  format     = Format specifier
250  *  endianess  = Endianess to use, Endian.Native is default.
251  *  data       = Binary encoded data
252  * 
253  * Returns:
254  *  Tuple with read data.
255  */
256 auto unpack(string format, ByteOrder byteOrder = ByteOrder.Native, Range)(auto ref Range data)
257 	if(isInputRange!Range && is(ElementType!Range == ubyte))
258 {
259 	Tuple!(formatTypeTupleOf!format) tup;
260 	unpackTo!(format, byteOrder)(data, tup.expand);
261 	return tup;
262 }
263 
264 
265 /**
266  * Decodes binary data from file.
267  * 
268  * This function works similar to other `unpack` functions, except that read data
269  * is returned as tuple.
270  * 
271  * Params:
272  *  format     = Format specifier
273  *  endianess  = Endianess to use, Endian.Native is default.
274  *  file       = File to read from
275  * 
276  * Returns:
277  *  Tuple with read data.
278  */
279 auto unpack(string format, ByteOrder byteOrder = ByteOrder.Native)(File file)
280 {
281 	Tuple!(formatTypeTupleOf!format) tup;
282 	unpackTo!(format, byteOrder)(file, tup.expand);
283 	return tup;
284 }
285 
286 
287 
288 /**
289  * Returns an instance of unpacker of T.
290  * 
291  * Params:
292  *  format     = Format specifier
293  *  byteOrder  = Byte order to use
294  *  range      = Range to read from
295  */
296 auto unpacker(string format, ByteOrder byteOrder = ByteOrder.Native, R)(auto ref R range)
297 	if (isInputRange!R && is(ElementType!R == ubyte))
298 {
299 	return Unpacker!(format, byteOrder, R)(range);
300 }
301 
302 /**
303  * Returns an instance of unpacker of T.
304  * 
305  * Params:
306  *  format     = Format specifier
307  *  byteOrder  = Byte order to use
308  *  file       = File to read from
309  */
310 auto unpacker(string format, ByteOrder byteOrder = ByteOrder.Native)(File file)
311 {
312 	return Unpacker!(format, byteOrder, File)(file);
313 }
314 
315 
316 /**
317  * Unpacker range.
318  * 
319  * Allows to unpack repeated binary encoded entries with range interface.
320  * 
321  * Examples:
322  * ----
323  * ubyte[] bytes = pack!`<hshs`(1, "one", 2, "two");
324  * 
325  * foreach(num, str; bytes) {
326  *        writeln(num, " ", str);
327  * }
328  * ----
329  */
330 struct Unpacker(string format, ByteOrder byteOrder = ByteOrder.Native, R)
331 	if ((isInputRange!R && is(ElementType!R == ubyte)) || is(R == File))
332 {
333 	/**
334 	 * Alias for type tuple
335 	 */
336 	alias Type = formatTypeTupleOf!format;
337 	
338 	/**
339 	 * Source range/file to read from.
340 	 */
341 	R source;
342 	
343 	
344 	/**
345 	 * Tuple of unpacked elements
346 	 */
347 	Tuple!Type front;
348 	
349 	
350 	/**
351 	 * Determines if more data can be unpacked.
352 	 */
353 	bool empty;
354 	
355 	
356 	/**
357 	 * Creates instance of Unpacker.
358 	 * 
359 	 * Params:
360 	 *  range = Range of ubytes to unpack from.
361 	 */
362 	this(R range)
363 	{
364 		source = range;
365 		popFront();
366 	}
367 	
368 	
369 	/**
370 	 * Unpacks next element from source range.
371 	 */
372 	void popFront()
373 	{
374 		static if(is(R == File)) {
375 			empty = source.eof;
376 		}
377 		else {
378 			empty = source.empty;
379 		}
380 		
381 		if (empty) return;
382 		
383 		front.expand = front.expand.init;
384 		source.unpackTo!(format, byteOrder)(front.expand);
385 	}
386 }