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*)#}(); 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*)#}(); 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 }