1 module lmpl4d.packer;
2 
3 import lmpl4d.common;
4 
5 struct Packer(Stream = ubyte[]) if(isOutputBuffer!(Stream, ubyte))
6 {
7 	AOutputBuf!Stream buf;
8 
9 	@property auto opSlice() { return buf[]; }
10 
11 	alias TThis = typeof(this);
12 
13 	this(ref Stream stream)
14 	{
15 		buf = AOutputBuf!Stream(stream);
16 	};
17 
18 	/**
19 	 * Serializes argument and writes to stream.
20 	 *
21 	 * If the argument is the pointer type, dereferences the pointer and serializes pointed value.
22 	 * -----
23 	 * int  a = 10;
24 	 * int* b = &b;
25 	 *
26 	 * packer.pack(b);  // serializes 10, not address of a
27 	 * -----
28 	 * Serializes nil if the argument of nullable type is null.
29 	 *
30 	 * NOTE:
31 	 *  MessagePack doesn't define $(D_KEYWORD real) type format.
32 	 *  Don't serialize $(D_KEYWORD real) if you communicate with other languages.
33 	 *  Transfer $(D_KEYWORD double) serialization if $(D_KEYWORD real) on your environment equals $(D_KEYWORD double).
34 	 *
35 	 * Params:
36 	 *  value = the content to serialize.
37 	 *
38 	 * Returns:
39 	 *  self, i.e. for method chaining.
40 	 */
41 	ref TThis pack(T)(in T value) if (is(Unqual!T == bool))
42 	{
43 		buf ~= value ? Format.TRUE : Format.FALSE;
44 		return this;
45 	}
46 
47 	/// ditto
48 	ref TThis pack(T)(in T value) if (isUnsigned!T && !is(Unqual!T == enum))
49 	{
50 		// ulong < ulong is slower than uint < uint
51 		static if (is(Unqual!T  == ulong)) {
52 			if (value < (1UL << 8)) {
53 				if (value < (1UL << 7)) {
54 					// fixnum
55 					buf ~= take8from!64(value);
56 				} else {
57 					buf ~= Format.UINT8;
58 					buf ~= take8from!64(value);
59 				}
60 			} else
61 				if (value < (1UL << 16)) {
62 					buf ~= Format.UINT16;
63 					buf ~= convertEndianTo!16(value);
64 				} else if (value < (1UL << 32)){
65 					buf ~= Format.UINT32;
66 					buf ~= convertEndianTo!32(value);
67 				} else {
68 					buf ~= Format.UINT64;
69 					buf ~= convertEndianTo!64(value);
70 				}
71 		} else {
72 			enum Bits = T.sizeof * 8;
73 
74 			if (value < (1 << 8)) {
75 				if (value < (1 << 7)) {
76 					// fixnum
77 					buf ~= take8from!Bits(value);
78 				} else {
79 					buf ~= Format.UINT8;
80 					buf ~= take8from!Bits(value);
81 				}
82 			} else
83 				if (value < (1 << 16)) {
84 					buf ~= Format.UINT16;
85 					buf ~= convertEndianTo!16(value);
86 				} else {
87 					buf ~= Format.UINT32;
88 					buf ~= convertEndianTo!32(value);
89 				}
90 		}
91 		return this;
92 	}
93 
94 	/// ditto
95 	ref TThis pack(T)(in T value) if (isSigned!T && isIntegral!T && !is(Unqual!T == enum))
96 	{
97 		// long < long is slower than int < int
98 		static if (is(Unqual!T == long)) {
99 			if (value < -(1L << 5)) {
100 				if (value < -(1L << 15)) {
101 					if (value < -(1L << 31)) {
102 						buf ~= Format.INT64;
103 						buf ~= convertEndianTo!64(value);
104 					} else {
105 						buf ~= Format.INT32;
106 						buf ~= convertEndianTo!32(value);
107 					}
108 				} else
109 					if (value < -(1L << 7)) {
110 						buf ~= Format.INT16;
111 						buf ~= convertEndianTo!16(value);
112 					} else {
113 						buf ~= Format.INT8;
114 						buf ~= take8from!64(value);
115 					}
116 			} else if (value < (1L << 7)) {
117 				// fixnum
118 				buf ~= take8from!64(value);
119 			} else {
120 				if (value < (1L << 16)) {
121 					if (value < (1L << 8)) {
122 						buf ~= Format.UINT8;
123 						buf ~= take8from!64(value);
124 					} else {
125 						buf ~= Format.UINT16;
126 						buf ~= convertEndianTo!16(value);
127 					}
128 				} else
129 					if (value < (1L << 32)) {
130 						buf ~= Format.UINT32;
131 						buf ~= convertEndianTo!32(value);
132 					} else {
133 						buf ~= Format.UINT64;
134 						buf ~= convertEndianTo!64(value);
135 					}
136 			}
137 		} else {
138 			enum Bits = T.sizeof << 3;
139 
140 			if (value < -(1 << 5)) {
141 				if (value < -(1 << 15)) {
142 					buf ~= Format.INT32;
143 					buf ~= convertEndianTo!32(value);
144 				} else if (value < -(1 << 7)) {
145 					buf ~= Format.INT16;
146 					buf ~= convertEndianTo!16(value);
147 				} else {
148 					buf ~= Format.INT8;
149 					buf ~= take8from!Bits(value);
150 				}
151 			} else if (value < (1 << 7)) {
152 				// fixnum
153 				buf ~= take8from!Bits(value);
154 			} else
155 				if (value < (1 << 8)) {
156 					buf ~= Format.UINT8;
157 					buf ~= take8from!Bits(value);
158 				} else if (value < (1 << 16)) {
159 					buf ~= Format.UINT16;
160 					buf ~= convertEndianTo!16(value);
161 				} else {
162 					buf ~= Format.UINT32;
163 					buf ~= convertEndianTo!32(value);
164 				}
165 		}
166 
167 		return this;
168 	}
169 
170 	/// ditto
171 	ref TThis pack(T)(in T value) if (isFloatingPoint!T && !is(Unqual!T == enum))
172 	{
173 		static if (is(Unqual!T == float)) {
174 			buf ~= Format.FLOAT;
175 			buf ~= convertEndianTo!32(_f(value).i);
176 		} else static if (is(Unqual!T == double) || !EnableReal
177 			|| real.sizeof == double.sizeof) { // Non-x86 CPUs, real type equals double type.
178 			buf ~= Format.DOUBLE;
179 			buf ~= convertEndianTo!64(_d(value).i);
180 		} else {
181 			buf ~= Format.REAL;
182 			const tmp = _r(value);
183 			buf ~= convertEndianTo!64(tmp.fraction);
184 			buf ~= convertEndianTo!16(tmp.exponent);
185 		}
186 
187 		return this;
188 	}
189 
190 	ref TThis pack(T)(in T value) if (is(Unqual!T == enum))
191 	{
192 		pack(cast(OriginalType!T)value);
193 		return this;
194 	}
195 
196 	/*
197 	 * Serializes the nil value.
198 	*/
199 	ref TThis pack(T)(in T value) if (is(Unqual!T == typeof(null)))
200 	{
201 		buf ~= Format.NIL;
202 		return this;
203 	}
204 
205 	ref TThis pack(T)(in T value) if (isPointer!T)
206 	{
207 		if (value is null)
208 			buf ~= Format.NIL;
209 		else
210 			pack(mixin(AsteriskOf!T ~ "value"));
211 		return this;
212 	}
213 
214 	/// ditto
215 	ref TThis pack(T)(in T array) if (isSomeArray!T)
216 	{
217 		import std.range;
218 
219 		if (array.empty)
220 			return pack(null);
221 
222 		// Raw bytes
223 		static if (isRawByte!(typeof(T.init[0]))) {
224 			auto raw = cast(ubyte[])array;
225 
226 			beginRaw(raw.length);
227 			buf ~= raw;
228 		} else {
229 			beginArray(array.length);
230 			foreach (elem; array)
231 				pack(elem);
232 		}
233 		return this;
234 	}
235 
236 	/// ditto
237 	ref TThis pack(T)(in T array) if (isAssociativeArray!T)
238 	{
239 		if (array is null)
240 			return pack(null);
241 
242 		beginMap(array.length);
243 		foreach (key, value; array) {
244 			pack(key);
245 			pack(value);
246 		}
247 
248 		return this;
249 	}
250 
251 	/// ditto
252 	ref TThis pack(Types...)(auto ref const Types objects) if (Types.length > 1)
253 	{
254 		foreach (i, T; Types)
255 			pack(objects[i]);
256 
257 		return this;
258 	}
259 
260 	/// ditto
261 	ref TThis pack(T)(in T value) if (isSomeChar!T && !is(Unqual!T == enum))
262 	{
263 		static if (is(Unqual!T == char))
264 			return pack(cast(ubyte)(value));
265 		else static if (is(Unqual!T == wchar))
266 			return pack(cast(ushort)(value));
267 		else static if (is(Unqual!T == dchar))
268 			return pack(cast(uint)(value));
269 	}
270 
271 	version(NoPackingStruct) {}
272 	else {
273 		ref TThis pack(T)(in T obj) if (is(Unqual!T == struct)) {
274 			if (T.init == obj) {
275 				beginArray(0);
276 				return this;
277 			}
278 			beginArray(NumOfSerializingMembers!T);
279 			foreach (f; obj.tupleof)
280 				static if (isPackedField!f && __traits(compiles, { pack(f); }))
281 					pack(f);
282 			return this;
283 		}
284 	}
285 
286 	/**
287 	 * Serializes the arguments as container to buf.
288 	 *
289 	 * -----
290 	 * packer.packArray(true, 1);  // -> [true, 1]
291 	 * packer.packMap("Hi", 100);  // -> ["Hi":100]
292 	 * -----
293 	 *
294 	 * In packMap, the number of arguments must be even.
295 	 *
296 	 * Params:
297 	 *  objects = the contents to serialize.
298 	 *
299 	 * Returns:
300 	 *  self, i.e. for method chaining.
301 	 */
302 	ref TThis packArray(Types...)(auto ref const Types objects)
303 	{
304 		beginArray(Types.length);
305 		foreach (i, T; Types)
306 			pack(objects[i]);
307 		//pack(objects);  // slow :(
308 
309 		return this;
310 	}
311 
312 	/// ditto
313 	ref TThis packMap(Types...)(auto ref const Types objects)
314 	{
315 		static assert((Types.length & 1) == 0, "The number of arguments must be even");
316 
317 		beginMap(Types.length >> 1);
318 		foreach (i, T; Types)
319 			pack(objects[i]);
320 
321 		return this;
322 	}
323 
324 	ref TThis packExt(in byte type, const ubyte[] data) return
325 	{
326 		ref TThis packExtFixed(int fmt)
327 		{
328 			buf ~= cast(ubyte)fmt;
329 			buf ~= type;
330 			buf ~= data;
331 			return this;
332 		}
333 
334 		// Try packing to a fixed-length type
335 		switch (data.length) {
336 			case 1: return packExtFixed(Format.EXT    );
337 			case 2: return packExtFixed(Format.EXT + 1);
338 			case 4: return packExtFixed(Format.EXT + 2);
339 			case 8: return packExtFixed(Format.EXT + 3);
340 			case 16:return packExtFixed(Format.EXT + 4);
341 			default: break;
342 		}
343 
344 		int typeByte = void;
345 		if (data.length <= 0xff)
346 		{
347 			buf ~= Format.EXT8;
348 			buf ~= cast(ubyte)data.length;
349 			typeByte = 2;
350 		} else if (data.length <= 0xffff) {
351 			buf ~= Format.EXT16;
352 			buf ~= convertEndianTo!16(data.length);
353 			typeByte = 3;
354 		} else if (data.length <= 0x7fffffff) {
355 			buf ~= Format.EXT32;
356 			buf ~= convertEndianTo!32(data.length);
357 			typeByte = 5;
358 		} //else
359 			//throw new Exception("Data too large to pack as EXT");
360 		buf ~= type;
361 		buf ~= data;
362 
363 		return this;
364 	}
365 
366 	ref TThis begin(Format f1, Format f2, size_t llen = 16)(size_t len) return
367 	{
368 		if (len < llen) {
369 			buf ~= take8from(f1 | cast(ubyte)len);
370 		} else if (len < 65536) {
371 			buf ~= f2;
372 			buf ~= convertEndianTo!16(len);
373 		} else {
374 			if (len > 0xffffffff)
375 				assert(0, "size of array is too long to pack, should be <= 0xffffffff");
376 
377 			buf ~= cast(Format)(f2 + 1);
378 			buf ~= convertEndianTo!32(len);
379 		}
380 
381 		return this;
382 	}
383 
384 	/*
385 	 * Serializes raw type-information to buf for binary type.
386 	 */
387 	alias beginRaw = begin!(Format.RAW, Format.RAW16, 32);
388 
389 	/// ditto
390 	alias beginArray = begin!(Format.ARRAY, Format.ARRAY16);
391 
392 	/// ditto
393 	alias beginMap = begin!(Format.MAP, Format.MAP16);
394 }
395 
396 unittest // unique value
397 {
398 	mixin DefinePacker;
399 
400 	enum ubyte[] result = [Format.NIL, Format.TRUE, Format.FALSE];
401 
402 	packer.pack(null, true, false);
403 	assert(packer[] == result);
404 }
405 unittest
406 {
407 	{ // uint *
408 		static struct UTest { ubyte format; ulong value; }
409 
410 		enum : ulong { A = ubyte.max, B = ushort.max, C = uint.max, D = ulong.max }
411 
412 		static UTest[][] utests = [
413 			[{Format.UINT8, A}],
414 			[{Format.UINT8, A}, {Format.UINT16, B}],
415 			[{Format.UINT8, A}, {Format.UINT16, B}, {Format.UINT32, C}],
416 			[{Format.UINT8, A}, {Format.UINT16, B}, {Format.UINT32, C}, {Format.UINT64, D}],
417 		];
418 
419 		foreach (I, T; AliasSeq!(ubyte, ushort, uint, ulong)) {
420 			foreach (i, test; utests[I]) {
421 				mixin DefinePacker;
422 
423 				packer.pack(cast(T)test.value);
424 				assert(packer.buf[0] == test.format);
425 
426 				switch (i) {
427 				case 0:
428 					auto answer = take8from!(T.sizeof * 8)(test.value);
429 					assert(memcmp(&packer.buf[1], &answer, ubyte.sizeof) == 0);
430 					break;
431 				case 1:
432 					auto answer = convertEndianTo!16(test.value);
433 					assert(memcmp(&packer.buf[1], &answer, ushort.sizeof) == 0);
434 					break;
435 				case 2:
436 					auto answer = convertEndianTo!32(test.value);
437 					assert(memcmp(&packer.buf[1], &answer, uint.sizeof) == 0);
438 					break;
439 				default:
440 					auto answer = convertEndianTo!64(test.value);
441 					assert(memcmp(&packer.buf[1], &answer, ulong.sizeof) == 0);
442 				}
443 			}
444 		}
445 	}
446 	{ // int *
447 		static struct STest { ubyte format; long value; }
448 
449 		enum : long { A = byte.min, B = short.min, C = int.min, D = long.min }
450 
451 		static STest[][] stests = [
452 			[{Format.INT8, A}],
453 			[{Format.INT8, A}, {Format.INT16, B}],
454 			[{Format.INT8, A}, {Format.INT16, B}, {Format.INT32, C}],
455 			[{Format.INT8, A}, {Format.INT16, B}, {Format.INT32, C}, {Format.INT64, D}],
456 		];
457 
458 		foreach (I, T; AliasSeq!(byte, short, int, long)) {
459 			foreach (i, test; stests[I]) {
460 				mixin DefinePacker;
461 
462 				packer.pack(cast(T)test.value);
463 				assert(packer.buf[0] == test.format);
464 
465 				switch (i) {
466 				case 0:
467 					auto answer = take8from!(T.sizeof * 8)(test.value);
468 					assert(memcmp(&packer.buf[1], &answer, byte.sizeof) == 0);
469 					break;
470 				case 1:
471 					auto answer = convertEndianTo!16(test.value);
472 					assert(memcmp(&packer.buf[1], &answer, short.sizeof) == 0);
473 					break;
474 				case 2:
475 					auto answer = convertEndianTo!32(test.value);
476 					assert(memcmp(&packer.buf[1], &answer, int.sizeof) == 0);
477 					break;
478 				default:
479 					auto answer = convertEndianTo!64(test.value);
480 					assert(memcmp(&packer.buf[1], &answer, long.sizeof) == 0);
481 				}
482 			}
483 		}
484 	}
485 }
486 unittest // float, double
487 {
488 	static if ((real.sizeof == double.sizeof) || !EnableReal)
489 	{
490 		alias AliasSeq!(float, double, double) FloatingTypes;
491 		static struct FTest { ubyte format; double value; }
492 
493 		static FTest[] ftests = [
494 			{Format.FLOAT,  float.min_normal},
495 			{Format.DOUBLE, double.max},
496 			{Format.DOUBLE, double.max},
497 		];
498 	}
499 	else
500 	{
501 		alias AliasSeq!(float, double, real) FloatingTypes;
502 		static struct FTest { ubyte format; real value; }
503 
504 		static FTest[] ftests = [
505 			{Format.FLOAT,  float.min_normal},
506 			{Format.DOUBLE, double.max},
507 			{Format.REAL,   real.max},
508 		];
509 	}
510 
511 	foreach (I, T; FloatingTypes) {
512 		mixin DefinePacker;
513 
514 		packer.pack(cast(T)ftests[I].value);
515 		assert(packer.buf[0] == ftests[I].format);
516 
517 		switch (I) {
518 		case 0:
519 			const answer = convertEndianTo!32(_f(cast(T)ftests[I].value).i);
520 			assert(memcmp(&packer.buf[1], &answer, float.sizeof) == 0);
521 			break;
522 		case 1:
523 			const answer = convertEndianTo!64(_d(cast(T)ftests[I].value).i);
524 			assert(memcmp(&packer.buf[1], &answer, double.sizeof) == 0);
525 			break;
526 		default:
527 			static if (EnableReal)
528 			{
529 				const t = _r(cast(T)ftests[I].value);
530 				const f = convertEndianTo!64(t.fraction);
531 				const e = convertEndianTo!16(t.exponent);
532 				assert(memcmp(&packer.buf[1],            &f, f.sizeof) == 0);
533 				assert(memcmp(&packer.buf[1 + f.sizeof], &e, e.sizeof) == 0);
534 			}
535 			else
536 			{
537 				const answer = convertEndianTo!64(_d(cast(T)ftests[I].value).i);
538 				assert(memcmp(&packer.buf[1], &answer, double.sizeof) == 0);
539 			}
540 		}
541 	}
542 }
543 unittest // pointer
544 {
545 	static struct PTest
546 	{
547 		ubyte format;
548 
549 		union
550 		{
551 			ulong*  p0;
552 			long*   p1;
553 			double* p2;
554 		}
555 	}
556 
557 	PTest[] ptests = [PTest(Format.UINT64), PTest(Format.INT64), PTest(Format.DOUBLE)];
558 
559 	ulong  v0 = ulong.max;
560 	long   v1 = long.min;
561 	double v2 = double.max;
562 
563 	foreach (I, Index; AliasSeq!("0", "1", "2")) {
564 		mixin DefinePacker;
565 
566 		mixin("ptests[I].p" ~ Index ~ " = &v" ~ Index ~ ";");
567 
568 		packer.pack(mixin("ptests[I].p" ~ Index));
569 		assert(packer.buf[0] == ptests[I].format);
570 
571 		switch (I) {
572 		case 0:
573 			auto answer = convertEndianTo!64(*ptests[I].p0);
574 			assert(memcmp(&packer.buf[1], &answer, ulong.sizeof) == 0);
575 			break;
576 		case 1:
577 			auto answer = convertEndianTo!64(*ptests[I].p1);
578 			assert(memcmp(&packer.buf[1], &answer, long.sizeof) == 0);
579 			break;
580 		default:
581 			const answer = convertEndianTo!64(_d(*ptests[I].p2).i);
582 			assert(memcmp(&packer.buf[1], &answer, double.sizeof) == 0);
583 		}
584 	}
585 }
586 unittest
587 {
588 	{ // enum
589 		enum E : ubyte { A = ubyte.max }
590 
591 		mixin DefinePacker; E e = E.A;
592 
593 		packer.pack(e);
594 		assert(packer.buf[0] == Format.UINT8);
595 
596 		auto answer = E.A;
597 		assert(memcmp(&packer.buf[1], &answer, (OriginalType!E).sizeof) == 0);
598 	}
599 	{ // enum with string
600 		enum E2 : string { A = "test" }
601 
602 		mixin DefinePacker; E2 e = E2.A;
603 
604 		packer.pack(e);
605 		assert(packer.buf[0] == (Format.RAW | 0x04));
606 	}
607 }
608 unittest
609 {
610 	// container
611 	static struct CTest { ubyte format; size_t value; }
612 
613 	enum : ulong { A = 16 / 2, B = ushort.max, C = uint.max }
614 
615 	enum CTest[][] ctests = [
616 		[{Format.ARRAY | A, Format.ARRAY | A}, {Format.ARRAY16, B}, {Format.ARRAY32, C}],
617 		[{Format.MAP   | A, Format.MAP   | A}, {Format.MAP16,   B}, {Format.MAP32,   C}],
618 		[{Format.RAW   | A, Format.RAW   | A}, {Format.RAW16,   B}, {Format.RAW32,   C}],
619 	];
620 
621 	foreach (I, Name; AliasSeq!("Array", "Map", "Raw")) {
622 		auto test = ctests[I];
623 
624 		foreach (i, T; AliasSeq!(ubyte, ushort, uint)) {
625 			mixin DefinePacker;
626 			mixin("packer.begin" ~ Name ~ "(i ? test[i].value : A);");
627 
628 			assert(packer.buf[0] == test[i].format);
629 
630 			switch (i) {
631 			case 0:
632 				auto answer = take8from(test[i].value);
633 				assert(memcmp(&packer.buf[0], &answer, ubyte.sizeof) == 0, Name);
634 				break;
635 			case 1:
636 				auto answer = convertEndianTo!16(test[i].value);
637 				assert(memcmp(&packer.buf[1], &answer, ushort.sizeof) == 0, Name);
638 				break;
639 			default:
640 				auto answer = convertEndianTo!32(test[i].value);
641 				assert(memcmp(&packer.buf[1], &answer, uint.sizeof) == 0, Name);
642 			}
643 		}
644 	}
645 	{ // ext types
646 		import std.conv : text;
647 
648 		byte type = 7; // an arbitrary type value
649 
650 		// fixexts
651 		{
652 			ubyte[16] data = void;
653 			data.fillData;
654 			foreach (L; AliasSeq!(1, 2, 4, 8, 16))
655 			{
656 				mixin DefinePacker;
657 				packer.packExt(type, data[0 .. L]);
658 
659 				// format, type, data
660 				assert(packer.buf.length == 2 + L);
661 				const l = 1 << (packer.buf[0] - Format.EXT);
662 				assert(l == L);
663 				assert(packer.buf[1] == type);
664 				assert(packer.buf[2 .. 2+l] == data[0 .. L]);
665 			}
666 		}
667 
668 		ubyte[] data;
669 		// ext8
670 		foreach (L; AliasSeq!(3, 7, 255))
671 		{
672 			data = new ubyte[L];
673 			data.fillData;
674 
675 			mixin DefinePacker;
676 			packer.packExt(type, data[0 .. L]);
677 
678 			// format, length, type, data
679 			assert(packer.buf.length == 3 + L);
680 			assert(packer.buf[0] == Format.EXT8);
681 			assert(packer.buf[1] == L);
682 			assert(packer.buf[2] == type);
683 			assert(packer.buf[3 .. 3 + L] == data);
684 		}
685 
686 		// ext16
687 		foreach (L; AliasSeq!(256, 0xffff))
688 		{
689 			data = new ubyte[L];
690 			data.fillData;
691 
692 			mixin DefinePacker;
693 			packer.packExt(type, data[0 .. L]);
694 
695 			// format, length, type, data
696 			assert(packer.buf.length == 4 + L, text(packer.buf.length));
697 			assert(packer.buf[0] == Format.EXT16);
698 
699 			ushort l = convertEndianTo!16(L);
700 			assert(memcmp(&packer.buf[1], &l, ushort.sizeof) == 0);
701 			assert(packer.buf[3] == type);
702 			assert(packer.buf[4 .. 4 + L] == data);
703 		}
704 
705 		// ext32
706 		foreach (L; AliasSeq!(2^^16, 2^^17))
707 		{
708 			data = new ubyte[L];
709 			data.fillData;
710 
711 			mixin DefinePacker;
712 			packer.packExt(type, data[0 .. L]);
713 
714 			// format, length, type, data
715 			assert(packer.buf.length == 6 + L, text(packer.buf.length));
716 			assert(packer.buf[0] == Format.EXT32);
717 
718 			uint l = convertEndianTo!32(L);
719 			assert(memcmp(&packer.buf[1], &l, uint.sizeof) == 0);
720 			assert(packer.buf[5] == type);
721 			assert(packer.buf[6 .. 6 + L] == data);
722 		}
723 	}
724 }