1 module lmpl4d.unpacker;
2 
3 import lmpl4d.common;
4 
5 struct Unpacker(Stream = const(ubyte)[]) if(isInputBuffer!(Stream, ubyte))
6 {
7 	Stream buf;
8 	size_t pos;
9 	this(Stream stream) { buf = stream; }
10 
11 	version(betterC){
12 		void rollback(size_t size, string expected, Format actual = Format.NONE) {
13 			pos -= size + 1;
14 		}
15 
16 		void check(size_t size = 1) {}
17 	} else {
18 		T unpack(T)()
19 		if (is(Unqual!T == enum) || isPointer!T || isSomeChar!T || isNumeric!T || is(Unqual!T == bool))
20 		{
21 			static if (is(Unqual!T == enum))
22 				return cast(T)unpack!(OriginalType!T);
23 			else static if (isPointer!T) {
24 				T val;
25 				if (unpackNil(val))
26 					return val;
27 				throw new UnpackException("Can't deserialize a pointer that is not null");
28 			} else static if (is(Unqual!T == char))
29 				return cast(T)unpack!ubyte;
30 			else static if (is(Unqual!T == wchar))
31 				return cast(T)unpack!ushort;
32 			else static if (is(Unqual!T == dchar))
33 				return cast(T)unpack!uint;
34 			else static if (isNumeric!T || is(Unqual!T == bool)) {
35 				check();
36 				int header = read();
37 				static if (isIntegral!T) {
38 					if (header <= 0x7f)
39 						return cast(T)header;
40 				}
41 				switch (header) {
42 					static if (is(Unqual!T == bool)) {
43 						case Format.TRUE:
44 							return true;
45 						case Format.FALSE:
46 							return false;
47 					} else static if (isIntegral!T) {
48 						case Format.UINT8:
49 							check(ubyte.sizeof);
50 							return read();
51 						case Format.UINT16:
52 							check(ushort.sizeof);
53 							auto val = load!ushort(read(ushort.sizeof));
54 							if (val > T.max)
55 								rollback(ushort.sizeof, T.stringof, Format.UINT16);
56 							return cast(T)val;
57 						case Format.UINT32:
58 							check(uint.sizeof);
59 							auto val = load!uint(read(uint.sizeof));
60 							if (val > T.max)
61 								rollback(uint.sizeof, T.stringof, Format.UINT32);
62 							return cast(T)val;
63 						case Format.UINT64:
64 							check(ulong.sizeof);
65 							auto val = load!ulong(read(ulong.sizeof));
66 							if (val > T.max)
67 								rollback(ulong.sizeof, T.stringof, Format.UINT64);
68 							return cast(T)val;
69 						case Format.INT8:
70 							check(byte.sizeof);
71 							return cast(T)read();
72 						case Format.INT16:
73 							check(short.sizeof);
74 							auto val = load!short(read(short.sizeof));
75 							if (val < T.min || T.max < val)
76 								rollback(short.sizeof, T.stringof, Format.INT16);
77 							return cast(T)val;
78 						case Format.INT32:
79 							check(int.sizeof);
80 							auto val = load!int(read(int.sizeof));
81 							if (val < T.min || T.max < val)
82 								rollback(int.sizeof, T.stringof, Format.INT32);
83 							return cast(T)val;
84 						case Format.INT64:
85 							check(long.sizeof);
86 							auto val = load!long(read(long.sizeof));
87 							if (val < T.min || T.max < val)
88 								rollback(long.sizeof, T.stringof, Format.INT64);
89 							return cast(T)val;
90 					} else static if (isFloatingPoint!T) {
91 						case Format.FLOAT:
92 							_f val;
93 							check(uint.sizeof);
94 							val.i = load!uint(read(uint.sizeof));
95 							return val.f;
96 						case Format.DOUBLE:
97 							// check precision loss
98 							static if (is(Unqual!T == float))
99 								rollback(0, T.stringof, Format.DOUBLE);
100 
101 							_d val;
102 							check(ulong.sizeof);
103 							val.i = load!ulong(read(ulong.sizeof));
104 							return val.f;
105 						case Format.REAL:
106 							static if (!EnableReal) {
107 								rollback(0, "real is disabled", Format.REAL);
108 								break;
109 							}
110 							else
111 							{
112 								// check precision loss
113 								static if (is(Unqual!T == float) || is(Unqual!T == double))
114 									rollback(0, T.stringof, Format.REAL);
115 								check(real.sizeof);
116 								version (NonX86)
117 								{
118 									CustomFloat!80 tmp;
119 
120 									const frac = load!ulong (read(ulong.sizeof));
121 									const exp  = load!ushort(read(ushort.sizeof));
122 
123 									tmp.significand = frac;
124 									tmp.exponent    = exp & 0x7fff;
125 									tmp.sign        = (exp & 0x8000) != 0;
126 
127 									// NOTE: tmp.get!real is inf on non-x86 when deserialized value is larger than double.max.
128 									return tmp.get!real;
129 								}
130 								else
131 								{
132 									_r tmp;
133 
134 									tmp.fraction = load!(typeof(tmp.fraction))(read(tmp.fraction.sizeof));
135 									tmp.exponent = load!(typeof(tmp.exponent))(read(tmp.exponent.sizeof));
136 
137 									return tmp.f;
138 								}
139 							}
140 						}
141 						default: break;
142 					}
143 					rollback(0, T.stringof, cast(Format)header);
144 				}
145 			assert(0, "Unsupported type");
146 		}
147 
148 		void rollback(size_t size, string expected, Format actual = Format.NONE) {
149 			import std.conv : text;
150 			pos -= size + 1;
151 			throw new MessagePackException(text("Attempt to unpack with non-compatible type: ",
152 				actual ? text("expected = ", expected, ", got = ", actual) : expected));
153 		}
154 		void check(size_t size = 1) {
155 			if(!canRead(size)) throw new UnpackException("Insufficient buffer");
156 		}
157 	}
158 
159 	T unpack(T)(T defValue) nothrow
160 	if (is(Unqual!T == enum) || isPointer!T || isTuple!T || isSomeChar!T || isNumeric!T || is(Unqual!T == bool))
161 	{
162 		static if (is(Unqual!T == enum))
163 			return cast(T)unpack(cast(OriginalType!T)defValue);
164 		else static if (isPointer!T) {
165 			T val;
166 			return unpackNil(val) ? val : defValue;
167 		} else static if (isTuple!T) {
168 			T val;
169 			unpackArray!(T.Types)(val.field);
170 			return val;
171 		} else static if (is(Unqual!T == char))
172 			return cast(T)unpack(cast(ubyte)defValue);
173 		else static if (is(Unqual!T == wchar))
174 			return cast(T)unpack(cast(ushort)defValue);
175 		else static if (is(Unqual!T == dchar))
176 			return cast(T)unpack(cast(uint)defValue);
177 		else static if (isNumeric!T || is(Unqual!T == bool)) {
178 			if(!canRead) return defValue;
179 			int header = read();
180 			static if (isIntegral!T) {
181 				if (header <= 0x7f)
182 					return cast(T)header;
183 			}
184 			switch (header) {
185 			static if (is(Unqual!T == bool)) {
186 				case Format.TRUE:
187 					return true;
188 				case Format.FALSE:
189 					return false;
190 			} else static if (isIntegral!T) {
191 				case Format.UINT8:
192 					if(!canRead(ubyte.sizeof)) return defValue;
193 					return read();
194 				case Format.UINT16:
195 					if(!canRead(ushort.sizeof)) return defValue;
196 					auto val = load!ushort(read(ushort.sizeof));
197 					if (val > T.max)
198 						return defValue;
199 					return cast(T)val;
200 				case Format.UINT32:
201 					if(!canRead(uint.sizeof)) return defValue;
202 					auto val = load!uint(read(uint.sizeof));
203 					if (val > T.max)
204 						return defValue;
205 					return cast(T)val;
206 				case Format.UINT64:
207 					if(!canRead(ulong.sizeof)) return defValue;
208 					auto val = load!ulong(read(ulong.sizeof));
209 					if (val > T.max)
210 						return defValue;
211 					return cast(T)val;
212 				case Format.INT8:
213 					if(!canRead(byte.sizeof)) return defValue;
214 					return cast(T)read();
215 				case Format.INT16:
216 					if(!canRead(short.sizeof)) return defValue;
217 					auto val = load!short(read(short.sizeof));
218 					if (val < T.min || T.max < val)
219 						return defValue;
220 					return cast(T)val;
221 				case Format.INT32:
222 					if(!canRead(int.sizeof)) return defValue;
223 					auto val = load!int(read(int.sizeof));
224 					if (val < T.min || T.max < val)
225 						return defValue;
226 					return cast(T)val;
227 				case Format.INT64:
228 					if(!canRead(long.sizeof)) return defValue;
229 					auto val = load!long(read(long.sizeof));
230 					if (val < T.min || T.max < val)
231 						return defValue;
232 					return cast(T)val;
233 			} else static if (isFloatingPoint!T) {
234 				case Format.FLOAT:
235 					_f val;
236 					if(!canRead(uint.sizeof)) return defValue;
237 					val.i = load!uint(read(uint.sizeof));
238 					return val.f;
239 				case Format.DOUBLE:
240 					// check precision loss
241 					static if (is(Unqual!T == float))
242 						return defValue;
243 					else {
244 						_d val = void;
245 						if(!canRead(ulong.sizeof)) return defValue;
246 						val.i = load!ulong(read(ulong.sizeof));
247 						return val.f;
248 					}
249 				case Format.REAL:
250 					static if (!EnableReal) return defValue;
251 					else
252 					{
253 						// check precision loss
254 						static if (is(Unqual!T == float) || is(Unqual!T == double))
255 							return defValue;
256 						if(!canRead(real.sizeof)) return defValue;
257 						version (NonX86)
258 						{
259 							CustomFloat!80 tmp;
260 
261 							const frac = load!ulong (read(ulong.sizeof));
262 							const exp  = load!ushort(read(ushort.sizeof));
263 
264 							tmp.significand = frac;
265 							tmp.exponent    = exp & 0x7fff;
266 							tmp.sign        = (exp & 0x8000) != 0;
267 
268 							// NOTE: tmp.get!real is inf on non-x86 when deserialized value is larger than double.max.
269 							return tmp.get!real;
270 						}
271 						else
272 						{
273 							_r tmp = void;
274 
275 							tmp.fraction = load!(typeof(tmp.fraction))(read(tmp.fraction.sizeof));
276 							tmp.exponent = load!(typeof(tmp.exponent))(read(tmp.exponent.sizeof));
277 
278 							return tmp.f;
279 						}
280 					}
281 				}
282 				default: return defValue;
283 			}
284 		}
285 	}
286 
287 	/// ditto
288 	ref typeof(this) unpack(Types...)(ref Types objects) if (Types.length > 1)
289 	{
290 		foreach (i, T; Types)
291 			objects[i] = unpack!T;
292 		return this;
293 	}
294 
295 	T unpack(T)() if (isSomeArray!T)
296 	{
297 		if (checkNil()) {
298 			static if (isStaticArray!T) {
299 				pos++;
300 				rollback(0, "static array", Format.NIL);
301 			}
302 			else {
303 				T array = void;
304 				unpackNil(array);
305 				return array;
306 			}
307 		}
308 		alias U = typeof(T.init[0]);
309 		enum RawBytes = isRawByte!U;
310 		static if (RawBytes)
311 			auto length = beginRaw();
312 		else
313 			auto length = beginArray();
314 		version(betterC) {} else {
315 			static if (__traits(compiles, buf.length))
316 			if (pos + length > buf.length) {
317 				import std.conv: text;
318 				throw new MessagePackException(text("Invalid array size in byte stream: Length (", length,
319 													") is larger than internal buffer size (", buf.length, ")"));
320 			}
321 		}
322 		static if (isStaticArray!T)
323 			T array = void;
324 		else {
325 			import std.array;
326 			T array = uninitializedArray!T(length);
327 		}
328 		if (length == 0)
329 			return array;
330 		static if (RawBytes) {
331 			auto offset = calculateSize!(true)(length);
332 			check(length + offset);
333 			static if (isStaticArray!T)
334 				array = (cast(U[])read(length))[0 .. T.length];
335 			else
336 				array = cast(T)read(length);
337 		} else
338 			foreach (ref a; array)
339 				a = unpack!U;
340 		return array;
341 	}
342 
343 	bool unpack(T)(ref T array) nothrow if (isSomeArray!T)
344 	{
345 		import std.array;
346 
347 		alias U = typeof(T.init[0]);
348 		const spos = pos;
349 		if (checkNil()) {
350 			static if (isStaticArray!T)
351 				return false;
352 			else
353 				return unpackNil(array);
354 		}
355 		if (!canRead)
356 			return false;
357 
358 		enum RawBytes = isRawByte!U;
359 		static if (RawBytes)
360 			auto length = beginRaw();
361 		else
362 			auto length = beginArray();
363 		version(betterC) {} else {
364 			static if (__traits(compiles, buf.length))
365 				if (length > buf.length) {
366 					pos = spos;
367 					return false;
368 				}
369 		}
370 		if (length == 0)
371 			return true;
372 		static if (!isStaticArray!T)
373 			if (array.length != length)
374 				array = uninitializedArray!T(length);
375 		static if (RawBytes) {
376 			auto offset = calculateSize!(true)(length);
377 			if(!canRead(length + offset)) {
378 				pos = spos;
379 				return false;
380 			}
381 			static if (isStaticArray!T)
382 				array = (cast(U[])read(length))[0 .. T.length];
383 			else
384 				array = cast(T)read(length);
385 		} else
386 			foreach (ref a; array)
387 				a = unpack!U;
388 		return true;
389 	}
390 
391 	/// ditto
392 	T unpack(T)() if (isAssociativeArray!T)
393 	{
394 		alias K = typeof(T.init.keys[0]),
395 			  V = typeof(T.init.values[0]);
396 		T array;
397 
398 		if (unpackNil(array))
399 			return array;
400 
401 		auto length = beginMap();
402 		if (length == 0)
403 			return array;
404 
405 		foreach (i; 0..length) {
406 			K k = unpack!K;
407 			array[k] = unpack!V;
408 		}
409 
410 		return array;
411 	}
412 
413 	/**
414 	 * Deserializes the container object and assigns to each argument.
415 	 *
416 	 * These methods check the length. Do rollback if
417 	 * the length of arguments is different from length of deserialized object.
418 	 *
419 	 * In unpackMap, the number of arguments must be even.
420 	 *
421 	 * Params:
422 	 *  objects = the references of object to assign.
423 	 *
424 	 * Returns: true if succeed
425 	 */
426 	bool unpackArray(Types...)(ref Types objects) nothrow if (Types.length > 1)
427 	{
428 		auto length = beginArray();
429 		const spos = pos;
430 		if (length != Types.length) {
431 			//the number of deserialized objects is mismatched
432 			pos = spos;
433 			return false;
434 		}
435 
436 		foreach (i, T; Types)
437 			try {
438 				objects[i] = unpack!T;
439 			} catch (Exception e) {
440 				pos = spos;
441 				return false;
442 			}
443 		// unpack(objects);  // slow :(
444 
445 		return true;
446 	}
447 
448 	/// ditto
449 	bool unpackMap(Types...)(ref Types objects) nothrow if (Types.length > 1)
450 	{
451 		static assert(Types.length % 2 == 0, "The number of arguments must be even");
452 
453 		auto length = beginMap();
454 		const spos = pos;
455 		if (length != Types.length >> 1) {
456 			// the number of deserialized objects is mismatched
457 			pos = spos;
458 			return false;
459 		}
460 
461 		foreach (i, T; Types)
462 			try {
463 				objects[i] = unpack!T;
464 			} catch (Exception e) {
465 				pos = spos;
466 				return false;
467 			}
468 
469 		return this;
470 	}
471 
472 	/// ditto
473 	bool unpackAA(K, V)(K[V] array) nothrow
474 	{
475 		if (unpackNil(array))
476 			return true;
477 
478 		auto length = beginMap();
479 		if (length == 0)
480 			return true;
481 
482 		foreach (i; 0..length) {
483 			try {
484 				K k = unpack!K;
485 				array[k] = unpack!V;
486 			} catch (Exception e) {
487 				return false;
488 			}
489 		}
490 
491 		return true;
492 	}
493 
494 	size_t begin(int s1 = 0xa0, int s2 = 0xbf, Format f = Format.ARRAY16)() nothrow
495 	{
496 		enum Raw = s1 == 0xa0 && s2 == 0xbf;
497 		if(!canRead) return 0;
498 		int header = read();
499 
500 		if (s1 <= header && header <= s2)
501 			return header & (s2 - s1);
502 		switch (header) {
503 			static if(Raw) {
504 				case Format.BIN8, Format.STR8:
505 				if(!canRead(ubyte.sizeof)) return 0;
506 				return read();
507 				case Format.BIN16, Format.RAW16:
508 			} else {
509 				case f:
510 			}
511 			if(!canRead(ushort.sizeof)) return 0;
512 			return load!ushort(read(ushort.sizeof));
513 			static if(Raw) {
514 				case Format.BIN32, Format.RAW32:
515 			} else {
516 				case cast(Format)(f + 1):
517 			}
518 			if(!canRead(uint.sizeof)) return 0;
519 			return load!uint(read(uint.sizeof));
520 		case Format.NIL:
521 			return 0;
522 		default:
523 			pos--;
524 			import std.conv: text;
525 			assert(0, text("Attempt to unpack with non-compatible type: expected = ",
526 				f.stringof, ", got = ", header));
527 		}
528 	}
529 
530 	/*
531 	 * Deserializes type-information of raw type.
532 	 */
533 	alias beginRaw = begin!();
534 	/**
535 	 * Deserializes the type-information of container.
536 	 *
537 	 * These methods don't deserialize contents.
538 	 * You need to call unpack method to deserialize contents at your own risk.
539 	 *
540 	 * Returns:
541 	 *  the container size.
542 	 */
543 	alias beginArray = begin!(0x90, 0x9f);
544 
545 	/// ditto
546 	alias beginMap = begin!(0x80, 0x8f, Format.MAP16);
547 
548 	version(NoPackingStruct) {}
549 	else {
550 		T unpack(T)() if (is(Unqual!T == struct)) {
551 			T val;
552 			if (auto len = beginArray()) {
553 				if (len != NumOfSerializingMembers!T)
554 					rollback(calculateSize(len), "the number of struct fields is mismatched");
555 
556 				foreach (i, ref member; val.tupleof)
557 					static if (isPackedField!(T.tupleof[i]))
558 						member = unpack!(typeof(member));
559 			}
560 			return val;
561 		}
562 
563 		bool unpackObj(T)(ref T obj) if (is(Unqual!T == struct)) {
564 			auto len = beginArray();
565 			if (len == 0)
566 				return true;
567 			if (len != NumOfSerializingMembers!T) {
568 				pos -= calculateSize(len) + 1;
569 				return false; // the number of struct fields is mismatched
570 			}
571 
572 			foreach (i, ref member; obj.tupleof)
573 				static if (isPackedField!(T.tupleof[i]))
574 					member = unpack!(typeof(member));
575 			return true;
576 		}
577 	}
578 
579 	nothrow:
580 
581 	/**
582 	 * Unpacks an EXT value into $(D type) and $(D data).
583 	 * Returns: true if succeed
584 	 */
585 	bool unpackExt(T)(ref byte type, ref T data) if(isOutputBuffer!(T, ubyte))
586 	{
587 		if(!canRead) return false;
588 		int header = read();
589 		import std.conv : text;
590 
591 		uint len = void;
592 		uint rollbackLen = 1;
593 		if (header >= Format.EXT && header <= Format.EXT + 4) {
594 			// Fixed
595 			len = 1 << (header - Format.EXT);
596 		} else
597 			// Dynamic length
598 			switch (header)
599 			{
600 				case Format.EXT8:
601 					if(!canRead(1 + 1)) {
602 						pos--;
603 						return false;
604 					}
605 					len = read();
606 					rollbackLen++;
607 					break;
608 				case Format.EXT16:
609 					if(!canRead(2 + 1)) {
610 						pos--;
611 						return false;
612 					}
613 					len = load!ushort(read(2));
614 					rollbackLen += 2;
615 					break;
616 				case Format.EXT32:
617 					if(!canRead(4 + 1)) {
618 						pos--;
619 						return false;
620 					}
621 					len = load!uint(read(4));
622 					rollbackLen += 4;
623 					break;
624 				default:
625 					pos--;
626 					return false;
627 			}
628 
629 		if(!canRead(len + 1)) {
630 			pos -= rollbackLen;
631 			return false;
632 		}
633 
634 		// Read type
635 		type = read();
636 		// Read and check data
637 		AOutputBuf!T(data) ~= read(len);
638 		return true;
639 	}
640 
641 	/*
642 	 * Reads value from buffer and advances offset.
643 	 */
644 	ubyte read()
645 	{
646 		return buf[pos++];
647 	}
648 
649 	auto read(size_t size)
650 	{
651 		auto result = buf[pos..pos+size];
652 		pos += size;
653 		return result;
654 	}
655 
656 	/*
657 	 * Reading test.
658 	 *
659 	 * Params:
660 	 *  size   = the size to read.
661 	 */
662 	bool canRead(size_t size = 1) const
663 	{
664 		static if (__traits(compiles, buf.length))
665 			return pos + size <= buf.length;
666 		else
667 			return true;
668 	}
669 
670 	/*
671 	 * Next object is nil?
672 	 *
673 	 * Returns:
674 	 *  true if next object is nil.
675 	 */
676 	bool checkNil()
677 	{
678 		return canRead && buf[pos] == Format.NIL;
679 	}
680 
681 	/*
682 	 * Deserializes nil object and assigns to $(D_PARAM value).
683 	 *
684 	 * Params:
685 	 *  value = the reference of value to assign.
686 	 *
687 	 * Returns:
688 	 *  true if next object is nil.
689 	 */
690 	bool unpackNil(T)(ref T value)
691 	{
692 		if(!canRead)
693 			return false;
694 
695 		if (buf[pos] == Format.NIL) {
696 			value = null;
697 			pos++;
698 			return true;
699 		}
700 		return false;
701 	}
702 
703 	/*
704 	 * Loads $(D_PARAM T) type value from $(D_PARAM buffer).
705 	 *
706 	 * Params:
707 	 *  buffer = the serialized contents.
708 	 *
709 	 * Returns:
710 	 *  the Endian-converted value.
711 	 */
712 	package T load(T)(in ubyte[] buf)
713 	{
714 		static if (isIntegral!T && T.sizeof == 2)
715 			enum bit = 16;
716 		else static if (isIntegral!T && T.sizeof == 4)
717 			enum bit = 32;
718 		else static if (isIntegral!T && T.sizeof == 8)
719 			enum bit = 64;
720 		else static assert(0, "Unsupported type");
721 		return convertEndianTo!bit(*cast(const T*)buf.ptr);
722 	}
723 }
724 
725 version(unittest)
726 	import
727 		lmpl4d.packer,
728 		std.exception;
729 
730 unittest
731 {
732 	{ // unique
733 		mixin DefinePacker;
734 
735 		auto test = tuple(true, false);
736 
737 		packer.pack(test);
738 
739 		mixin TestUnpacker;
740 	}
741 	{ // uint *
742 		mixin DefinePacker;
743 
744 		auto test = tuple(ubyte.max, ushort.max, uint.max, ulong.max);
745 		packer.pack(test);
746 
747 		mixin TestUnpacker;
748 	}
749 	{ // int *
750 		mixin DefinePacker;
751 
752 		auto test = tuple(byte.min, short.min, int.min, long.min);
753 
754 		packer.pack(test);
755 
756 		mixin TestUnpacker;
757 	}
758 }
759 unittest
760 {
761 	{ // floating point
762 		mixin DefinePacker;
763 
764 		static if (real.sizeof == double.sizeof || !EnableReal)
765 		{
766 			alias R = double;
767 		}
768 		else
769 		{
770 			alias R = real;
771 		}
772 		Tuple!(float, double, R) test = tuple(float.min_normal, double.max, cast(real)R.min_normal);
773 
774 		packer.pack(test);
775 
776 		mixin TestUnpacker;
777 	}
778 	{ // enum
779 		enum   : float { D = 0.5 }
780 		enum E : ulong { U = 100 }
781 
782 		mixin DefinePacker;
783 
784 		float f = D,   resultF;
785 		E     e = E.U, resultE;
786 
787 		packer.pack(D, e);
788 
789 		mixin TestUnpacker;
790 
791 		unpacker.unpack(resultF, resultE);
792 		assert(f == resultF);
793 		assert(e == resultE);
794 	}
795 }
796 
797 version(NoPackingStruct) {}
798 else unittest
799 {
800 	struct Test
801 	{
802 		string f1;
803 		@nonPacked int f2;
804 	}
805 
806 	mixin DefinePacker;
807 
808 	Test s = Test("foo", 10), r;
809 
810 	auto buf = packer.pack(s).buf[];
811 	auto unpacker = Unpacker!()(buf);
812 	r = unpacker.unpack!Test;
813 	assert(s.f1 == r.f1);
814 	assert(s.f2 != r.f2);
815 	assert(r.f2 == int.init);
816 
817 	auto arr2 = Array!ubyte();
818 	auto packer2 = Packer!(Array!ubyte)(arr2);
819 	assert(packer2.pack(Test.init).buf.length < buf.length);
820 }
821 
822 unittest
823 {
824 	import std.conv : text;
825 	{ // container
826 		mixin DefinePacker;
827 
828 		Tuple!(ulong[], int[uint], string, bool[2], char[2]) test =
829 			tuple([1UL, 2], [3U:4, 5:6, 7:8], "MessagePack", [true, false], "D!");
830 
831 		packer.pack(test);
832 
833 		mixin TestUnpacker;
834 	}
835 	{ // ext
836 
837 		// Try a variety of lengths, making sure to hit all the fixexts
838 		foreach (L; AliasSeq!(1, 2, 3, 4, 5, 8, 9, 16, 32, 512, 2^^16))
839 		{
840 			mixin DefinePacker;
841 
842 			auto data = new ubyte[L];
843 			data.fillData;
844 			packer.packExt(7, data);
845 
846 			auto unpacker = Unpacker!()(packer.buf[]);
847 			byte type;
848 			ubyte[] deserializedData;
849 
850 			assert(unpacker.unpackExt(type, deserializedData));
851 			assert(type == 7, text("type: ", type));
852 			assert(data == deserializedData, text(data, "\nExpected: ", deserializedData));
853 		}
854 	}
855 }