The OpenD Programming Language

1 /++
2 $(H4 High level Msgpack serialization API)
3 
4 Macros:
5 IONREF = $(REF_ALTTEXT $(TT $2), $2, mir, ion, $1)$(NBSP)
6 +/
7 module mir.ser.msgpack;
8 
9 import mir.ion.exception: IonException;
10 import mir.serde: SerdeTarget;
11 
12 version(D_Exceptions) {
13     import mir.exception: toMutable;
14     private static immutable bigIntConvException = new IonException("Overflow when converting BigInt");
15     private static immutable msgpackAnnotationException = new IonException("MsgPack can store exactly one annotation.");
16     private static immutable stringTooLargeException = new IonException("Too large of a string for MessagePack");
17     private static immutable blobTooLargeException = new IonException("Too large of a blob for MessagePack");
18     private static immutable mapTooLargeException = new IonException("Too large of a map for MessagePack");
19     private static immutable arrayTooLargeException = new IonException("Too large of an array for MessagePack");
20 }
21 
22 /++ MessagePack support +/
23 
24 enum MessagePackFmt : ubyte
25 {
26     /++ Integers +/
27     
28     /++ 7-bit positive integer +/
29     fixint = 0x00,
30     /++ 5-bit negative integer (???) +/
31     fixnint = 0xe0,
32     /++ 8-bit unsigned integer +/
33     uint8 = 0xcc,
34     /++ 16-bit unsigned integer +/
35     uint16 = 0xcd,
36     /++ 32-bit unsigned integer +/
37     uint32 = 0xce,
38     /++ 64-bit unsigned integer +/
39     uint64 = 0xcf,
40     /++ 8-bit signed integer +/
41     int8 = 0xd0,
42     /++ 16-bit signed integer +/
43     int16 = 0xd1,
44     /++ 32-bit signed integer +/
45     int32 = 0xd2,
46     /++ 64-bit signed integer +/
47     int64 = 0xd3,
48 
49     /++ Maps +/
50 
51     /++ Map with a maximum length of 15 key-value pairs +/
52     fixmap = 0x80,
53     /++ Map with a maximum length of 65535 key-value pairs +/
54     map16 = 0xde,
55     /++ Map with a maximum length of 4294967295 key-value pairs +/
56     map32 = 0xdf,
57 
58     /++ Arrays +/
59 
60     /++ Array with a maximum length of 15 elements +/
61     fixarray = 0x90,
62     /++ Array with a maximum length of 65535 elements +/
63     array16 = 0xdc,
64     /++ Array with a maximum length of 4294967295 elements +/ 
65     array32 = 0xdd,
66     
67     /++ Strings +/
68 
69     /++ String with a maximum length of 31 bytes +/
70     fixstr = 0xa0,
71     /++ String with a maximum length of 255 (1 << 8 - 1) bytes +/
72     str8 = 0xd9,
73     /++ String with a maximum length of 65535 (1 << 16 - 1) bytes +/
74     str16 = 0xda,
75     /++ String with a maximum length of 4294967295 (1 << 32 - 1) bytes +/
76     str32 = 0xdb,
77 
78     /++ Nil +/
79     nil = 0xc0,
80 
81     /++ Boolean values +/
82     false_ = 0xc2,
83     true_ = 0xc3,
84 
85     /++ Binary (byte array) +/
86 
87     /++ Byte array with a maximum length of 255 bytes +/
88     bin8 = 0xc4,
89     /++ Byte array with a maximum length of 65535 bytes +/
90     bin16 = 0xc5,
91     /++ Byte array with a maximum length of 4294967295 bytes +/
92     bin32 = 0xc6,
93 
94     /++ Implementation-specific extensions +/
95     
96     /++ Integer & byte array whose length is 1 byte +/
97     fixext1 = 0xd4,
98     /++ Integer & byte array whose length is 2 bytes +/
99     fixext2 = 0xd5,
100     /++ Integer & byte array whose length is 4 bytes +/ 
101     fixext4 = 0xd6,
102     /++ Integer & byte array whose length is 8 bytes +/
103     fixext8 = 0xd7,
104     /++ Integer & byte array whose length is 16 bytes +/
105     fixext16 = 0xd8,
106     /++ Integer & byte array whose maximum length is 255 bytes +/
107     ext8 = 0xc7,
108     /++ Integer & byte array whose maximum length is 65535 bytes +/
109     ext16 = 0xc8,
110     /++ Integer & byte array whose maximum length is 4294967295 bytes +/
111     ext32 = 0xc9,
112 
113     /++ Floats +/
114 
115     /++ Single-precision IEEE 754 floating point number +/
116     float32 = 0xca,
117     /++ Double-precision IEEE 754 floating point number +/
118     float64 = 0xcb,
119 }
120 
121 /++
122 Msgpack serialization back-end
123 +/
124 struct MsgpackSerializer(Appender)
125 {
126         import mir.appender: ScopedBuffer;
127         import mir.bignum.decimal: Decimal;
128         import mir.bignum.integer: BigInt;
129         import mir.ion.symbol_table: IonSymbolTable, IonSystemSymbolTable_v1;
130         import mir.ion.tape;
131         import mir.ion.type_code;
132         import mir.lob;
133         import mir.serde: SerdeTarget;
134         import mir.timestamp: Timestamp;
135         import mir.utility: _expect;
136         import std.traits: isNumeric;
137 
138         Appender* buffer;
139         ScopedBuffer!(char, 128) strBuf;
140         ScopedBuffer!(uint, 128) lengths;
141 
142         /// Mutable value used to choose format specidied or user-defined serialization specializations
143         int serdeTarget = SerdeTarget.msgpack;
144         private bool _annotation;
145 
146         @safe pure:
147 
148         this(Appender* app) @trusted
149         {
150             this.buffer = app;
151             lengths.initialize;
152             strBuf.initialize;
153         }
154 
155 scope:
156 
157         size_t aggrBegin(string packerMethod)(size_t length = size_t.max)
158         {
159             lengths.put(0);
160             __traits(getMember, this, packerMethod)(length == size_t.max ? uint.max : length);
161             return length == size_t.max ? buffer.data.length : size_t.max;
162         }
163 
164         @trusted
165         void aggrEnd(string packerMethod)(size_t state)
166         {
167             import core.stdc.string: memmove;
168             auto length = lengths.data[$ - 1];
169             lengths.popBackN(1);
170             if (state != size_t.max)
171             {
172                 if (length < 16)
173                 {
174                     auto data = buffer.data[state .. $];
175                     memmove(data.ptr - 4, data.ptr, data.length);
176                     buffer.popBackN(4);
177                 }
178                 else
179                 if (length < 65536)
180                 {
181                     auto data = buffer.data[state .. $];
182                     memmove(data.ptr - 2, data.ptr, data.length);
183                     buffer.popBackN(2);
184                 }
185                 auto appLength = buffer.data.length;
186                 buffer._currentLength = state - 5;
187                 __traits(getMember, this, packerMethod)(length);
188                 buffer._currentLength = appLength;
189             }
190         }
191 
192         private void beginMap(size_t size)
193         {
194             if (size < 16)
195             {
196                 buffer.put(cast(ubyte)(MessagePackFmt.fixmap | cast(ubyte)size));
197             }
198             else if (size <= ushort.max)
199             {
200                 buffer.put(MessagePackFmt.map16);
201                 buffer.put(packMsgPackExt(cast(ushort)size));
202             }
203             else if (size <= uint.max)
204             {
205                 buffer.put(MessagePackFmt.map32);
206                 buffer.put(packMsgPackExt(cast(uint)size));
207             }
208             else
209             {
210                 version(D_Exceptions)
211                     throw mapTooLargeException.toMutable;
212                 else
213                     assert(0, "Too large of a map for MessagePack");
214             }
215         }
216 
217         private void beginArray(size_t size)
218         {
219             if (size < 16)
220             {
221                 buffer.put(MessagePackFmt.fixarray | cast(ubyte)size);
222             }
223             else if (size <= ushort.max)
224             {
225                 buffer.put(MessagePackFmt.array16);
226                 buffer.put(packMsgPackExt(cast(ushort)size));
227             }
228             else if (size <= uint.max)
229             {
230                 buffer.put(MessagePackFmt.array32);
231                 buffer.put(packMsgPackExt(cast(uint)size));
232             }
233             else
234             {
235                 version(D_Exceptions)
236                     throw arrayTooLargeException.toMutable;
237                 else
238                     assert(0, "Too large of an array for MessagePack");
239             }
240         }
241 
242         ///
243         alias structBegin = aggrBegin!"beginMap";
244         ///
245         alias structEnd = aggrEnd!"beginMap";
246 
247         ///
248         alias listBegin = aggrBegin!"beginArray";
249         ///
250         alias listEnd = aggrEnd!"beginArray";
251 
252         ///
253         alias sexpBegin = listBegin;
254 
255         ///
256         alias sexpEnd = listEnd;
257 
258         ///
259         size_t stringBegin()
260         {
261             strBuf.reset;
262             return 0;
263         }
264 
265         /++
266         Puts string part. The implementation allows to split string unicode points.
267         +/
268         void putStringPart(scope const(char)[] str)
269         {
270             strBuf.put(str);
271         }
272 
273         ///
274         void stringEnd(size_t state) @trusted
275         {
276             putValue(strBuf.data);
277         }
278 
279         ///
280         auto annotationsEnd(size_t state)
281         {
282             _annotation = false;
283             return 0;
284         }
285 
286         ///
287         size_t annotationWrapperBegin()
288         {
289             return structBegin(1);
290         }
291 
292         ///
293         void annotationWrapperEnd(size_t, size_t state)
294         {
295             return structEnd(state);
296         }
297 
298         ///
299         void putKey(scope const char[] key)
300         {
301             elemBegin;
302             putValue(key);
303         }
304 
305         ///
306         void putAnnotation(scope const(char)[] annotation)
307         {
308             if (_annotation)
309                 throw msgpackAnnotationException.toMutable;
310             _annotation = true;
311             putKey(annotation);
312         }
313 
314         ///
315         void putSymbol(scope const char[] symbol)
316         {
317             putValue(symbol);
318         }
319 
320         void putValue(ubyte num)
321         {
322             if ((num & 0x80) == 0)
323             {
324                 buffer.put(cast(ubyte)(MessagePackFmt.fixint | num));
325                 return;
326             }
327 
328             buffer.put(MessagePackFmt.uint8);
329             buffer.put(num);
330         }
331 
332         void putValue(ushort num)
333         {
334             if ((num & 0xff00) == 0)
335             {
336                 putValue(cast(ubyte)num);
337                 return;
338             }
339 
340             buffer.put(MessagePackFmt.uint16);
341             buffer.put(packMsgPackExt(num));
342         }
343 
344         void putValue(uint num)
345         {
346             if ((num & 0xffff0000) == 0)
347             {
348                 putValue(cast(ushort)num);
349                 return;
350             }
351 
352             buffer.put(MessagePackFmt.uint32);
353             buffer.put(packMsgPackExt(num));
354         }
355 
356         void putValue(ulong num)
357         {
358             if ((num & 0xffffffff00000000) == 0)
359             {
360                 putValue(cast(uint)num);
361                 return;
362             }
363 
364             buffer.put(MessagePackFmt.uint64);
365             buffer.put(packMsgPackExt(num));    
366         }
367 
368         void putValue(byte num)
369         {
370             // check if we're a negative byte
371             if (num < 0)
372             {
373                 // if this has bit 7 and 6 set, then we can
374                 // fit this into a fixnint, so do so here
375                 if ((num & (1 << 6)) && (num & (1 << 5)))
376                 {
377                     buffer.put(cast(ubyte)(num | MessagePackFmt.fixnint));
378                     return;
379                 }
380                 // otherwise, write it out as a full int8
381                 buffer.put(MessagePackFmt.int8);
382                 buffer.put(cast(ubyte)num);
383                 return;
384             }
385             // we can always fit a non-negative byte into the
386             // fixint, so just pass it down the chain to handle
387             putValue(cast(ubyte)num);
388         }
389 
390         void putValue(short num)
391         {
392             // check if this can fit into the space of a byte 
393             if (num == cast(byte)num)
394             {
395                 putValue(cast(byte)num);
396                 return;
397             }
398 
399             buffer.put(MessagePackFmt.int16);
400             buffer.put(packMsgPackExt(cast(ushort)num));
401         }
402 
403         void putValue(int num)
404         {
405             if (num == cast(short)num)
406             {
407                 putValue(cast(short)num);
408                 return;
409             }
410 
411             buffer.put(MessagePackFmt.int32);
412             buffer.put(packMsgPackExt(cast(uint)num));
413         }
414 
415         void putValue(long num)
416         {
417             if (num == cast(int)num)
418             {
419                 putValue(cast(int)num);
420                 return;
421             }
422 
423             buffer.put(MessagePackFmt.int64);
424             buffer.put(packMsgPackExt(cast(ulong)num));
425         }
426 
427         void putValue(float num)
428         {
429             buffer.put(MessagePackFmt.float32);
430             // XXX: better way to do this?
431             uint v = () @trusted {return *cast(uint*)&num;}();
432             buffer.put(packMsgPackExt(v));
433         }
434 
435         void putValue(double num)
436         {
437             buffer.put(MessagePackFmt.float64);
438             // XXX: better way to do this?
439             ulong v = () @trusted {return *cast(ulong*)&num;}();
440             buffer.put(packMsgPackExt(v));
441         } 
442 
443         void putValue(real num)
444         {
445             // MessagePack does not support 80-bit floating point numbers,
446             // so we'll have to convert down here (and lose a fair bit of precision).
447             putValue(cast(double)num);
448         }
449 
450         ///
451         void putValue(size_t size)(auto ref const BigInt!size num)
452         {
453             auto res = cast(long)num;
454             if (res != num)
455                 throw bigIntConvException.toMutable;
456             putValue(res);
457         }
458 
459         ///
460         void putValue(size_t size)(auto ref const Decimal!size num)
461         {
462             putValue(cast(double) num);
463         }
464 
465         ///
466         void putValue(typeof(null))
467         {
468             buffer.put(MessagePackFmt.nil);
469         }
470 
471         ///
472         void putNull(IonTypeCode code)
473         {
474             putValue(null);
475         }
476 
477         ///
478         void putValue(bool b)
479         {
480             buffer.put(cast(ubyte)(MessagePackFmt.false_ | b));
481         }
482 
483         ///
484         void putValue(scope const(char)[] value)
485         {
486             if (value.length <= 31)
487             {
488                 buffer.put(MessagePackFmt.fixstr | cast(ubyte)value.length);
489             }
490             else if (value.length <= ubyte.max)
491             {
492                 buffer.put(MessagePackFmt.str8);
493                 buffer.put(cast(ubyte)value.length);
494             }
495             else if (value.length <= ushort.max)
496             {
497                 buffer.put(MessagePackFmt.str16);
498                 buffer.put(packMsgPackExt(cast(ushort)value.length));
499             }
500             else if (value.length <= uint.max)
501             {
502                 buffer.put(MessagePackFmt.str32);
503                 buffer.put(packMsgPackExt(cast(uint)value.length));
504             }
505             else
506             {
507                 version(D_Exceptions)
508                     throw stringTooLargeException.toMutable;
509                 else
510                     assert(0, "Too large of a string for MessagePack");
511             }
512 
513             () @trusted { buffer.put(cast(ubyte[])value); }();
514         }
515 
516         ///
517         void putValue(scope Clob value)
518         {
519             putValue(value.data);
520         }
521 
522         ///
523         void putValue(scope Blob value)
524         {
525             if (value.data.length <= ubyte.max)
526             {
527                 buffer.put(MessagePackFmt.bin8);
528                 buffer.put(cast(ubyte)value.data.length);
529             }
530             else if (value.data.length <= ushort.max)
531             {
532                 buffer.put(MessagePackFmt.bin16);
533                 buffer.put(packMsgPackExt(cast(ushort)value.data.length));
534             }
535             else if (value.data.length <= uint.max)
536             {
537                 buffer.put(MessagePackFmt.bin32);
538                 buffer.put(packMsgPackExt(cast(uint)value.data.length));
539             }
540             else
541             {
542                 version(D_Exceptions)
543                     throw blobTooLargeException.toMutable;
544                 else
545                     assert(0, "Too big of a blob for MessagePack");
546             }
547 
548             buffer.put(value.data);
549         }
550 
551         private ubyte[T.sizeof] packMsgPackExt(T)(const T num)
552             if (__traits(isUnsigned, T))
553         {
554             T ret = num;
555             version (LittleEndian)
556             {
557                 import core.bitop : bswap, byteswap;
558                 static if (T.sizeof >= 4) {
559                     ret = bswap(ret);
560                 } else static if (T.sizeof == 2) {
561                     ret = byteswap(ret);
562                 }
563             }
564             return cast(typeof(return))cast(T[1])[ret];
565         }
566 
567         ///
568         void putValue(Timestamp value)
569         {
570             auto sec = value.toUnixTime;
571             auto nanosec = cast(uint)value.getFraction!9;
572             if ((sec >> 34) == 0)
573             {
574                 ulong data64 = (ulong(nanosec) << 34) | sec;
575                 // If there are no bits in the top 32 bits, then automatically
576                 // write out the smaller data type (in this case, timestamp32) 
577                 if ((data64 & 0xffffffff00000000L) == 0)
578                 {
579                     buffer.put(MessagePackFmt.fixext4);
580                     buffer.put(cast(ubyte)-1);
581                     buffer.put(packMsgPackExt(cast(uint)data64));
582                 }
583                 else
584                 {
585                     buffer.put(MessagePackFmt.fixext8);
586                     buffer.put(cast(ubyte)-1);
587                     buffer.put(packMsgPackExt(data64));
588                 }
589             }
590             else
591             {
592                 // timestamp 96
593                 ubyte[12] data;
594                 data[0 .. 4] = packMsgPackExt(nanosec);
595                 data[4 .. 12] = packMsgPackExt(ulong(sec));
596 
597                 buffer.put(MessagePackFmt.ext8);
598                 buffer.put(12);
599                 buffer.put(cast(ubyte)-1);
600                 buffer.put(data);
601             }
602         }
603 
604         ///
605         void elemBegin()
606         {
607             lengths.data[$ - 1]++;
608         }
609 
610         ///
611         alias sexpElemBegin = elemBegin;
612 
613         ///
614         void nextTopLevelValue()
615         {
616         }
617 }
618 
619 @safe pure
620 version(mir_ion_test) unittest
621 {
622     import mir.appender : ScopedBuffer;
623     import mir.ser.interfaces: SerializerWrapper;
624     MsgpackSerializer!(ScopedBuffer!ubyte) serializer;
625     scope s = new SerializerWrapper!(MsgpackSerializer!(ScopedBuffer!ubyte))(serializer);
626 }
627 
628 ///
629 void serializeMsgpack(Appender, T)(scope ref Appender appender, auto ref T value, int serdeTarget = SerdeTarget.msgpack)
630 {
631     import mir.ser : serializeValue;
632     auto serializer = ((()@trusted => &appender)()).MsgpackSerializer!(Appender);
633     serializer.serdeTarget = serdeTarget;
634     serializeValue(serializer, value);
635 }
636 
637 ///
638 immutable(ubyte)[] serializeMsgpack(T)(auto ref T value, int serdeTarget = SerdeTarget.msgpack)
639 {
640     import mir.appender : ScopedBuffer, scopedBuffer;
641     auto app = scopedBuffer!ubyte;
642     serializeMsgpack!(ScopedBuffer!ubyte, T)(app, value, serdeTarget);
643     return (()@trusted => app.data.idup)();
644 }
645 
646 /// Test serializing booleans
647 @safe pure
648 version(mir_ion_test) unittest
649 {
650     assert(serializeMsgpack(true) == [0xc3]);
651     assert(serializeMsgpack(false) == [0xc2]);
652 }
653 
654 /// Test serializing nulls
655 @safe pure
656 version(mir_ion_test) unittest
657 {
658     assert(serializeMsgpack(null) == [0xc0]);
659 }
660 
661 /// Test serializing signed integral types
662 @safe pure
663 version(mir_ion_test) unittest
664 {
665     // Bytes
666     assert(serializeMsgpack(byte.min) == [0xd0, 0x80]);
667     assert(serializeMsgpack(byte.max) == [0x7f]);
668 
669     // Shorts
670     assert(serializeMsgpack(short(byte.max)) == [0x7f]);
671     assert(serializeMsgpack(short(byte.max) + 1) == [0xd1, 0x00, 0x80]);
672     assert(serializeMsgpack(short.min) == [0xd1, 0x80, 0x00]);
673     assert(serializeMsgpack(short.max) == [0xd1, 0x7f, 0xff]);
674 
675     // Integers
676     assert(serializeMsgpack(int(-32)) == [0xe0]);
677     assert(serializeMsgpack(int(byte.max)) == [0x7f]);
678     assert(serializeMsgpack(int(short.max)) == [0xd1, 0x7f, 0xff]);
679     assert(serializeMsgpack(int(short.max) + 1) == [0xd2, 0x00, 0x00, 0x80, 0x00]);
680     assert(serializeMsgpack(int.min) == [0xd2, 0x80, 0x00, 0x00, 0x00]);
681     assert(serializeMsgpack(int.max) == [0xd2, 0x7f, 0xff, 0xff, 0xff]);
682 
683     // Long integers
684     assert(serializeMsgpack(long(int.max)) == [0xd2, 0x7f, 0xff, 0xff, 0xff]);
685     assert(serializeMsgpack(long(int.max) + 1) == [0xd3, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00]);
686     assert(serializeMsgpack(long.max) == [0xd3, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
687     assert(serializeMsgpack(long.min) == [0xd3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
688 }
689 
690 /// Test serializing unsigned integral types
691 @safe pure
692 version(mir_ion_test) unittest
693 {
694     // Unsigned bytes
695     assert(serializeMsgpack(ubyte.min) == [0x00]);
696     assert(serializeMsgpack(ubyte((1 << 7) - 1)) == [0x7f]);
697     assert(serializeMsgpack(ubyte((1 << 7))) == [0xcc, 0x80]);
698     assert(serializeMsgpack(ubyte.max) == [0xcc, 0xff]);
699     
700     // Unsigned shorts
701     assert(serializeMsgpack(ushort(ubyte.max)) == [0xcc, 0xff]);
702     assert(serializeMsgpack(ushort(ubyte.max + 1)) == [0xcd, 0x01, 0x00]);
703     assert(serializeMsgpack(ushort.min) == [0x00]);
704     assert(serializeMsgpack(ushort.max) == [0xcd, 0xff, 0xff]); 
705 
706     // Unsigned integers
707     assert(serializeMsgpack(uint(ubyte.max)) == [0xcc, 0xff]);
708     assert(serializeMsgpack(uint(ushort.max)) == [0xcd, 0xff, 0xff]);
709     assert(serializeMsgpack(uint(ushort.max + 1)) == [0xce, 0x00, 0x01, 0x00, 0x00]);
710     assert(serializeMsgpack(uint.min) == [0x00]);
711     assert(serializeMsgpack(uint.max) == [0xce, 0xff, 0xff, 0xff, 0xff]);
712 
713     // Long unsigned integers
714     assert(serializeMsgpack(ulong(ubyte.max)) == [0xcc, 0xff]);
715     assert(serializeMsgpack(ulong(ushort.max)) == [0xcd, 0xff, 0xff]);
716     assert(serializeMsgpack(ulong(uint.max)) == [0xce, 0xff, 0xff, 0xff, 0xff]);
717     assert(serializeMsgpack(ulong(uint.max) + 1) == [0xcf, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00]);
718     assert(serializeMsgpack(ulong.min) == [0x00]);
719     assert(serializeMsgpack(ulong.max) == [0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
720 
721     // Mir's BigIntView
722     import mir.bignum.integer : BigInt;
723     assert(serializeMsgpack(BigInt!2(0xDEADBEEF)) == [0xd3, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef]);
724 }
725 
726 /// Test serializing floats / doubles / reals
727 @safe pure
728 version(mir_ion_test) unittest
729 {
730     import mir.test;
731 
732     assert(serializeMsgpack(float.min_normal) == [0xca, 0x00, 0x80, 0x00, 0x00]);
733     assert(serializeMsgpack(float.max) == [0xca, 0x7f, 0x7f, 0xff, 0xff]);
734     assert(serializeMsgpack(double.min_normal) == [0xcb, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
735     assert(serializeMsgpack(double.max) == [0xcb, 0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
736     static if (real.mant_dig == 64)
737     {
738         assert(serializeMsgpack(real.min_normal) == [0xcb,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]);
739         assert(serializeMsgpack(real.max) == [0xcb,0x7f,0xf0,0x00,0x00,0x00,0x00,0x00,0x00]);
740     }
741 
742     // Mir's Decimal
743     import mir.bignum.decimal : Decimal;
744     serializeMsgpack(Decimal!2("777.777")).should == [0xcb,0x40,0x88,0x4e,0x37,0x4b,0xc6,0xa7,0xf0];
745     serializeMsgpack(Decimal!2("-777.7")).should == [0xcb,0xc0,0x88,0x4d,0x99,0x99,0x99,0x99,0x9a];
746 }
747 
748 /// Test serializing timestamps
749 @safe pure
750 version(mir_ion_test) unittest
751 {
752     import mir.timestamp : Timestamp;
753     assert(serializeMsgpack(Timestamp(1970, 1, 1, 0, 0, 0)) == [0xd6, 0xff, 0x00, 0x00, 0x00, 0x00]);
754     assert(serializeMsgpack(Timestamp(2038, 1, 19, 3, 14, 7)) == [0xd6, 0xff, 0x7f, 0xff, 0xff, 0xff]);
755     assert(serializeMsgpack(Timestamp(2299, 12, 31, 23, 59, 59)) == [0xd7, 0xff, 0x00, 0x00, 0x00, 0x02, 0x6c, 0xb5, 0xda, 0xff]);
756     assert(serializeMsgpack(Timestamp(3000, 12, 31, 23, 59, 59)) == [0xc7, 0x0c, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x93, 0x3f, 0xff, 0x7f]);
757 }
758 
759 /// Test serializing strings
760 @safe pure
761 version(mir_ion_test) unittest
762 {
763     import std.array : replicate;
764     assert(serializeMsgpack("a") == [0xa1, 0x61]);
765 
766     // These need to be trusted because we cast const(char)[] to ubyte[] (which is fine here!)
767     () @trusted {
768         auto a = "a".replicate(32);
769         assert(serializeMsgpack(a) == 
770             cast(ubyte[])[0xd9, 0x20] ~ cast(ubyte[])a);
771     } ();
772 
773     () @trusted {
774         auto a = "a".replicate(ushort.max);
775         assert(serializeMsgpack(a) == 
776             cast(ubyte[])[0xda, 0xff, 0xff] ~ cast(ubyte[])a);
777     } ();
778 
779     () @trusted {
780         auto a = "a".replicate(ushort.max + 1);
781         assert(serializeMsgpack(a) == 
782             cast(ubyte[])[0xdb, 0x00, 0x01, 0x00, 0x00] ~ cast(ubyte[])a);
783     } ();
784 }
785 
786 /// Test serializing blobs / clobs
787 @safe pure
788 version(mir_ion_test) unittest
789 {
790     import mir.lob : Blob, Clob;
791     import std.array : replicate;
792 
793     // Blobs
794     // These need to be trusted because we cast const(char)[] to ubyte[] (which is fine here!)
795     () @trusted {
796         auto de = "\xde".replicate(32);
797         assert(serializeMsgpack(Blob(cast(ubyte[])de)) ==
798             cast(ubyte[])[0xc4, 0x20] ~ cast(ubyte[])de);
799     } ();
800     
801     () @trusted {
802         auto de = "\xde".replicate(ushort.max);
803         assert(serializeMsgpack(Blob(cast(ubyte[])de)) ==
804             cast(ubyte[])[0xc5, 0xff, 0xff] ~ cast(ubyte[])de);
805     } ();
806 
807     () @trusted {
808         auto de = "\xde".replicate(ushort.max + 1);
809         assert(serializeMsgpack(Blob(cast(ubyte[])de)) ==
810             cast(ubyte[])[0xc6, 0x00, 0x01, 0x00, 0x00] ~ cast(ubyte[])de);
811     } ();
812 
813     // Clobs (serialized just as regular strings here)
814     () @trusted {
815         auto de = "\xde".replicate(32);
816         assert(serializeMsgpack(Clob(de)) == 
817             cast(ubyte[])[0xd9, 0x20] ~ cast(ubyte[])de);
818     } ();
819 }
820 
821 /// Test serializing arrays
822 @safe pure
823 version(mir_ion_test) unittest
824 {
825     // nested arrays
826     assert(serializeMsgpack([["foo"], ["bar"], ["baz"]]) == [0x93, 0x91, 0xa3, 0x66, 0x6f, 0x6f, 0x91, 0xa3, 0x62, 0x61, 0x72, 0x91, 0xa3, 0x62, 0x61, 0x7a]);
827     assert(serializeMsgpack([0xDEADBEEF, 0xCAFEBABE, 0xAAAA_AAAA]) == [0x93, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xce, 0xca, 0xfe, 0xba, 0xbe, 0xce, 0xaa, 0xaa, 0xaa, 0xaa]);
828     assert(serializeMsgpack(["foo", "bar", "baz"]) == [0x93, 0xa3, 0x66, 0x6f, 0x6f, 0xa3, 0x62, 0x61, 0x72, 0xa3, 0x62, 0x61, 0x7a]);
829     assert(serializeMsgpack([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]) == [0xdc,0x00,0x11,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11]);
830 }
831 
832 /// Test serializing enums
833 @safe pure
834 version(mir_ion_test) unittest
835 {
836     enum Foo
837     {
838         Bar,
839         Baz
840     }
841 
842     assert(serializeMsgpack(Foo.Bar) == [0xa3,0x42,0x61,0x72]);
843     assert(serializeMsgpack(Foo.Baz) == [0xa3,0x42,0x61,0x7a]);
844 }
845 
846 /// Test serializing maps (structs)
847 @safe pure
848 version(mir_ion_test) unittest
849 {
850     static struct Book
851     {
852         string title;
853         bool wouldRecommend;
854         string description;
855         uint numberOfNovellas;
856         double price;
857         float weight;
858         string[] tags;
859     }
860 
861     Book book = Book("A Hero of Our Time", true, "", 5, 7.99, 6.88, ["russian", "novel", "19th century"]);
862 
863     // This will probably break if you modify how any of the data types
864     // are serialized.
865     assert(serializeMsgpack(book) == [0x87,0xa5,0x74,0x69,0x74,0x6c,0x65,0xb2,0x41,0x20,0x48,0x65,0x72,0x6f,0x20,0x6f,0x66,0x20,0x4f,0x75,0x72,0x20,0x54,0x69,0x6d,0x65,0xae,0x77,0x6f,0x75,0x6c,0x64,0x52,0x65,0x63,0x6f,0x6d,0x6d,0x65,0x6e,0x64,0xc3,0xab,0x64,0x65,0x73,0x63,0x72,0x69,0x70,0x74,0x69,0x6f,0x6e,0xa0,0xb0,0x6e,0x75,0x6d,0x62,0x65,0x72,0x4f,0x66,0x4e,0x6f,0x76,0x65,0x6c,0x6c,0x61,0x73,0x05,0xa5,0x70,0x72,0x69,0x63,0x65,0xcb,0x40,0x1f,0xf5,0xc2,0x8f,0x5c,0x28,0xf6,0xa6,0x77,0x65,0x69,0x67,0x68,0x74,0xca,0x40,0xdc,0x28,0xf6,0xa4,0x74,0x61,0x67,0x73,0x93,0xa7,0x72,0x75,0x73,0x73,0x69,0x61,0x6e,0xa5,0x6e,0x6f,0x76,0x65,0x6c,0xac,0x31,0x39,0x74,0x68,0x20,0x63,0x65,0x6e,0x74,0x75,0x72,0x79]);
866 }
867 
868 /// Test serializing a large map (struct)
869 @safe pure
870 version(mir_ion_test) unittest
871 {
872     static struct HugeStruct
873     {
874         bool a;
875         bool b;
876         bool c;
877         bool d;
878         bool e;
879         string f;
880         string g;
881         string h;
882         string i;
883         string j;
884         int k;
885         int l;
886         int m;
887         int n;
888         int o;
889         long p;
890     }
891 
892     HugeStruct s = HugeStruct(true, true, true, true, true, "", "", "", "", "", 123, 456, 789, 123, 456, 0xDEADBEEF);
893     assert(serializeMsgpack(s) == [0xde,0x00,0x10,0xa1,0x61,0xc3,0xa1,0x62,0xc3,0xa1,0x63,0xc3,0xa1,0x64,0xc3,0xa1,0x65,0xc3,0xa1,0x66,0xa0,0xa1,0x67,0xa0,0xa1,0x68,0xa0,0xa1,0x69,0xa0,0xa1,0x6a,0xa0,0xa1,0x6b,0x7b,0xa1,0x6c,0xd1,0x01,0xc8,0xa1,0x6d,0xd1,0x03,0x15,0xa1,0x6e,0x7b,0xa1,0x6f,0xd1,0x01,0xc8,0xa1,0x70,0xd3,0x00,0x00,0x00,0x00,0xde,0xad,0xbe,0xef]);
894 }
895 
896 /// Test serializing annotated structs
897 @safe pure
898 version(mir_ion_test) unittest
899 {
900     import mir.algebraic;
901     import mir.serde : serdeAlgebraicAnnotation;
902 
903     @serdeAlgebraicAnnotation("Foo")
904     static struct Foo
905     {
906         string bar;
907     }
908 
909     @serdeAlgebraicAnnotation("Fooz")
910     static struct Fooz
911     {
912         long bar;
913     }
914 
915     alias V = Variant!(Foo, Fooz);
916     auto foo = V(Foo("baz"));
917 
918     assert(serializeMsgpack(foo) == [0x81,0xa3,0x46,0x6f,0x6f,0x81,0xa3,0x62,0x61,0x72,0xa3,0x62,0x61,0x7a]);
919 }
920 
921 /// Test custom serialize function with MessagePack
922 @safe pure
923 version(mir_ion_test) unittest
924 {
925     static class MyExampleClass
926     {
927         string text;
928 
929         this(string text)
930         {
931             this.text = text;
932         }
933 
934         void serialize(S)(scope ref S serializer) scope const
935         {
936             auto state = serializer.stringBegin;
937             serializer.putStringPart("Hello! ");
938             serializer.putStringPart("String passed: ");
939             serializer.putStringPart(this.text);
940             serializer.stringEnd(state);
941 
942             import mir.ion.type_code : IonTypeCode;
943             serializer.putNull(IonTypeCode..string);
944         }
945     }
946 
947     assert(serializeMsgpack(new MyExampleClass("foo bar baz")) == [0xd9,0x21,0x48,0x65,0x6c,0x6c,0x6f,0x21,0x20,0x53,0x74,0x72,0x69,0x6e,0x67,0x20,0x70,0x61,0x73,0x73,0x65,0x64,0x3a,0x20,0x66,0x6f,0x6f,0x20,0x62,0x61,0x72,0x20,0x62,0x61,0x7a,0xc0]);
948 }
949 
950 /// Test excessively large struct
951 @safe pure
952 static if (size_t.sizeof > uint.sizeof)
953 version(D_Exceptions)
954 version(mir_ion_test) unittest
955 {
956     import mir.ion.exception : IonException;
957 
958     static class HugeStruct
959     {
960         void serialize(S)(scope ref S serializer) scope const
961         {
962             auto state = serializer.structBegin(size_t(uint.max) + 1);
963         }
964     }
965 
966     bool caught = false;
967     try
968     {
969         serializeMsgpack(new HugeStruct());
970     }
971     catch (IonException e)
972     {
973         caught = true;
974     }
975 
976     assert(caught);
977 }
978 
979 /// Test excessively large array
980 @safe pure
981 static if (size_t.sizeof > uint.sizeof)
982 version(D_Exceptions)
983 version(mir_ion_test) unittest
984 {
985     import mir.ion.exception : IonException;
986 
987     static class HugeArray
988     {
989         void serialize(S)(scope ref S serializer) scope const
990         {
991             auto state = serializer.listBegin(size_t(uint.max) + 1); 
992         }
993     }
994 
995     bool caught = false;
996     try 
997     {
998         serializeMsgpack(new HugeArray());
999     }
1000     catch (IonException e)
1001     {
1002         caught = true;
1003     }
1004 
1005     assert(caught);
1006 }
1007 
1008 /// Test invalidly large BigInt
1009 @safe pure
1010 version(D_Exceptions)
1011 version(mir_ion_test) unittest
1012 {
1013     import mir.ion.exception : IonException;
1014     import mir.bignum.integer : BigInt;
1015 
1016     bool caught = false;
1017     try
1018     {
1019         serializeMsgpack(BigInt!4.fromHexString("c39b18a9f06fd8e962d99935cea0707f79a222050aaeaaaed17feb7aa76999d7"));
1020     }
1021     catch (IonException e)
1022     {
1023         caught = true;
1024     }
1025     assert(caught);
1026 }