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 }