1 /// Simple binary [de]serialization 2 module sbin; 3 4 import std.array : appender; 5 import std.bitmanip : nativeToLittleEndian, littleEndianToNative; 6 import std.exception : enforce, assertThrown; 7 import std.range; 8 import std.string : format; 9 import std.traits; 10 11 version (sbin_ulong_length) alias length_t = ulong; /// 12 else alias length_t = uint; /// 13 14 version (unittest) 15 pragma(msg, "sbin type of serialized length: ", length_t); 16 17 private alias pack = nativeToLittleEndian; 18 19 private auto unpack(T, size_t N)(ubyte[N] arr) 20 if (N == T.sizeof) 21 { 22 static if (T.sizeof == 1) return cast(T)arr[0]; 23 else return littleEndianToNative!T(arr); 24 } 25 26 /// 27 class SBinException : Exception 28 { 29 /// 30 @safe @nogc pure nothrow 31 this(string msg, string file=__FILE__, size_t line=__LINE__) 32 { super(msg, file, line); } 33 } 34 35 /// 36 class SBinDeserializeException : SBinException 37 { 38 /// 39 @safe @nogc pure nothrow 40 this(string msg, string file=__FILE__, size_t line=__LINE__) 41 { super(msg, file, line); } 42 } 43 44 /// 45 class SBinDeserializeEmptyRangeException : SBinDeserializeException 46 { 47 /// 48 string mainType, fieldName, fieldType; 49 /// 50 size_t readed, expected, fullReaded; 51 /// 52 this(string mainType, string fieldName, string fieldType, 53 size_t readed, size_t expected, size_t fullReaded) @safe pure 54 { 55 this.mainType = mainType; 56 this.fieldName = fieldName; 57 this.fieldType = fieldType; 58 this.readed = readed; 59 this.expected = expected; 60 this.fullReaded = fullReaded; 61 super(format("empty input range while "~ 62 "deserialize '%s' element %s:%s %d/%d (readed/expected), "~ 63 "readed message %d bytes", mainType, fieldName, 64 fieldType, readed, expected, fullReaded)); 65 } 66 } 67 68 private bool isVoidArray(T)() 69 { 70 static if( (is(T U == U[]) || is(T U == U[N], size_t N)) && 71 is(Unqual!U == void)) return true; 72 else return false; 73 } 74 75 private template EnumNumType(T) if (is(T == enum)) 76 { 77 enum EMC = [EnumMembers!T].length; 78 static if (EMC <= ubyte.max) alias Type = ubyte; 79 else static if (EMC <= ushort.max) alias Type = ushort; 80 else static if (EMC <= uint.max) alias Type = uint; 81 else alias Type = ulong; 82 alias EnumNumType = Type; 83 } 84 85 unittest 86 { 87 enum Color 88 { 89 black = "#000000", 90 red = "#ff0000", 91 green = "#00ff00", 92 blue = "#0000ff", 93 white = "#ffffff" 94 } 95 96 static assert(is(EnumNumType!Color == ubyte)); 97 98 enum Level { low, medium, high } 99 100 static assert(is(EnumNumType!Level == ubyte)); 101 102 static string bigElems() pure 103 { 104 import std.format : formattedWrite; 105 auto buf = appender!(char[]); 106 formattedWrite(buf, "enum Big { "); 107 foreach (i; 0 .. 300) 108 formattedWrite(buf, "e%d,", i); 109 buf.put(" }"); 110 return buf.data.idup; 111 } 112 113 mixin(bigElems()); 114 115 static assert(is(EnumNumType!Big == ushort)); 116 } 117 118 // only for serialize enums based on strings 119 private auto getEnumNum(T)(T val) @safe @nogc pure nothrow 120 if (is(T == enum)) 121 { 122 alias Ret = EnumNumType!T; 123 static foreach (Ret i; 0 .. [EnumMembers!T].length) 124 if ((EnumMembers!T)[i] == val) return i; 125 return Ret.max; 126 } 127 128 /++ Serialize to output ubyte range 129 130 Params: 131 val - serializible value 132 r - output range 133 +/ 134 void sbinSerialize(R, Ts...)(ref R r, auto ref const Ts vals) 135 if (isOutputRange!(R, ubyte) && Ts.length) 136 { 137 static if (Ts.length == 1) 138 { 139 alias T = Unqual!(Ts[0]); 140 alias val = vals[0]; 141 static if (is(T == enum)) 142 { 143 put(r, getEnumNum(val).pack[]); 144 } 145 else static if (is(T : double) || is(T : long)) 146 { 147 put(r, val.pack[]); 148 } 149 else static if (isVoidArray!T) 150 { 151 static if (isDynamicArray!T) 152 put(r, (cast(length_t)val.length).pack[]); 153 put(r, cast(ubyte[])val[]); 154 } 155 else static if (isStaticArray!T) 156 { 157 foreach (ref v; val) 158 sbinSerialize(r, v); 159 } 160 else static if (isSomeString!T) 161 { 162 put(r, (cast(length_t)val.length).pack[]); 163 put(r, cast(ubyte[])val); 164 } 165 else static if (isDynamicArray!T) 166 { 167 put(r, (cast(length_t)val.length).pack[]); 168 foreach (ref v; val) 169 sbinSerialize(r, v); 170 } 171 else static if (isAssociativeArray!T) 172 { 173 put(r, (cast(length_t)val.length).pack[]); 174 foreach (k, ref v; val) 175 { 176 sbinSerialize(r, k); 177 sbinSerialize(r, v); 178 } 179 } 180 else static if (is(typeof(val.sbinCustomSerialize(r)))) 181 { 182 val.sbinCustomSerialize(r); 183 } 184 else static if (is(T == struct)) 185 { 186 foreach (ref v; val.tupleof) 187 sbinSerialize(r, v); 188 } 189 else static assert(0, "unsupported type: " ~ T.stringof); 190 } 191 else foreach (ref v; vals) sbinSerialize(r, v); 192 } 193 194 /++ Serialize to ubyte[] 195 196 using `appender!(ubyte[])` as output range 197 198 Params: 199 val = serializible value 200 201 Returns: 202 serialized data 203 +/ 204 ubyte[] sbinSerialize(T)(auto ref const T val) 205 { 206 auto buf = appender!(ubyte[]); 207 sbinSerialize(buf, val); 208 return buf.data.dup; 209 } 210 211 /++ Deserialize `Target` value 212 213 Params: 214 range = input range with serialized data (not saved before work) 215 216 Returns: 217 deserialized value 218 +/ 219 Target sbinDeserialize(Target, R)(R range) 220 { 221 Unqual!Target ret; 222 sbinDeserialize(range, ret); 223 return ret; 224 } 225 226 /++ Deserialize `Target` value 227 228 Params: 229 range = input range with serialized data (not saved before work) 230 target = reference to result object 231 232 Returns: 233 deserialized value 234 +/ 235 void sbinDeserialize(R, Target...)(R range, ref Target target) 236 if (isInputRange!R && is(Unqual!(ElementType!R) == ubyte)) 237 { 238 size_t cnt; 239 240 ubyte pop(ref R rng, lazy string field, lazy string type, 241 lazy size_t vcnt, lazy size_t vexp) 242 { 243 enforce (!rng.empty, new SBinDeserializeEmptyRangeException( 244 Target.stringof, field, type, vcnt, vexp, cnt)); 245 auto ret = rng.front; 246 rng.popFront(); 247 cnt++; 248 return ret; 249 } 250 251 auto impl(T)(ref R r, ref T trg, lazy string field) 252 { 253 string ff(lazy string n) { return field ~ "." ~ n; } 254 string fi(size_t i) { return field ~ format("[%d]", i); } 255 256 static if (is(T == enum)) 257 { 258 alias ENT = EnumNumType!T; 259 enum EM = [EnumMembers!T]; 260 ubyte[ENT.sizeof] tmp; 261 version (LDC) auto _field = "<LDC-1.4.0 workaround>"; 262 else alias _field = field; 263 foreach (i, ref v; tmp) 264 v = pop(r, _field, T.stringof, i, T.sizeof); 265 trg = EM[tmp.unpack!ENT]; 266 } 267 else static if (is(T : double) || is(T : long)) 268 { 269 ubyte[T.sizeof] tmp; 270 version (LDC) auto _field = "<LDC-1.4.0 workaround>"; 271 else alias _field = field; 272 foreach (i, ref v; tmp) 273 v = pop(r, _field, T.stringof, i, T.sizeof); 274 trg = tmp.unpack!T; 275 } 276 else static if (isVoidArray!T) 277 { 278 static if (isDynamicArray!T) 279 { 280 length_t l; 281 impl(r, l, ff("length")); 282 if (trg.length != l) 283 trg.length = l; 284 } 285 286 auto tmp = cast(ubyte[])trg[]; 287 foreach (i, ref v; tmp) 288 impl(r, v, fi(i)); 289 } 290 else static if (isStaticArray!T) 291 { 292 foreach (i, ref v; trg) 293 impl(r, v, fi(i)); 294 } 295 else static if (isSomeString!T) 296 { 297 length_t l; 298 impl(r, l, ff("length")); 299 auto tmp = new ubyte[](cast(size_t)l); 300 foreach (i, ref v; tmp) 301 v = pop(r, fi(i), T.stringof, i, l); 302 trg = cast(T)tmp; 303 } 304 else static if (isDynamicArray!T) 305 { 306 length_t l; 307 impl(r, l, ff("length")); 308 if (trg.length != l) 309 trg.length = cast(size_t)l; 310 foreach (i, ref v; trg) 311 impl(r, v, fi(i)); 312 } 313 else static if (isAssociativeArray!T) 314 { 315 length_t l; 316 impl(r, l, ff("length")); 317 auto length = cast(size_t)l; 318 319 trg.clear(); 320 321 foreach (i; 0 .. length) 322 { 323 KeyType!T k; 324 ValueType!T v; 325 impl(r, k, fi(i)~".key"); 326 impl(r, v, fi(i)~".val"); 327 trg[k] = v; 328 } 329 330 trg.rehash(); 331 } 332 else static if (is(typeof(T.sbinCustomDeserialize(r, trg)))) 333 { 334 T.sbinCustomDeserialize(r, trg); 335 } 336 else static if (is(T == struct)) 337 { 338 foreach (i, ref v; trg.tupleof) 339 impl(r, v, ff(__traits(identifier, trg.tupleof[i]))); 340 } 341 else static assert(0, "unsupported type: " ~ T.stringof); 342 } 343 344 static if (Target.length == 1) 345 impl(range, target[0], typeof(target[0]).stringof); 346 else foreach (ref v; target) 347 impl(range, v, typeof(v).stringof); 348 349 enforce(range.empty, new SBinDeserializeException( 350 format("input range not empty after full '%s' deserialize", Target.stringof))); 351 } 352 353 version (unittest) import std.algorithm : equal; 354 355 unittest 356 { 357 auto a = 123; 358 assert(a.sbinSerialize.sbinDeserialize!int == a); 359 } 360 361 unittest 362 { 363 auto a = 123; 364 auto as = a.sbinSerialize; 365 int x; 366 sbinDeserialize(as, x); 367 assert(a == x); 368 } 369 370 unittest 371 { 372 auto s = "hello world"; 373 assert(equal(s.sbinSerialize.sbinDeserialize!string, s)); 374 } 375 376 unittest 377 { 378 immutable(int[]) a = [1,2,3,2,3,2,1]; 379 assert(a.sbinSerialize.sbinDeserialize!(int[]) == a); 380 } 381 382 unittest 383 { 384 int[5] a = [1,2,3,2,3]; 385 assert(a.sbinSerialize.sbinDeserialize!(typeof(a)) == a); 386 } 387 388 unittest 389 { 390 enum Color 391 { 392 black = "#000000", 393 red = "#ff0000", 394 green = "#00ff00", 395 blue = "#0000ff", 396 white = "#ffffff" 397 } 398 399 enum Level { low, medium, high } 400 401 struct Foo 402 { 403 ulong a; 404 float b, c; 405 ushort d; 406 string str; 407 Color color; 408 } 409 410 const foo1 = Foo(10, 3.14, 2.17, 8, "s1", Color.red); 411 412 // a b c d 413 const foo1Size = ulong.sizeof + float.sizeof * 2 + ushort.sizeof + 414 // str color 415 (length_t.sizeof + foo1.str.length) + ubyte.sizeof; 416 417 // color is ubyte because [EnumMembers!Color].length < ubyte.max 418 419 const foo1Data = foo1.sbinSerialize; 420 421 assert(foo1Data.length == foo1Size); 422 assert(foo1Data.sbinDeserialize!Foo == foo1); 423 424 const foo2 = Foo(2, 2.22, 2.22, 2, "str2", Color.green); 425 426 const foo2Size = ulong.sizeof + float.sizeof * 2 + ushort.sizeof + 427 (length_t.sizeof + foo2.str.length) + ubyte.sizeof; 428 429 const foo2Data = foo2.sbinSerialize; 430 431 assert(foo2Data.length == foo2Size); 432 assert(foo2Data.sbinDeserialize!Foo == foo2); 433 434 struct Bar 435 { 436 ulong a; 437 float b; 438 Level level; 439 Foo[] foos; 440 } 441 442 auto bar = Bar(123, 3.14, Level.high, [ foo1, foo2 ]); 443 444 // a b level 445 const barSize = ulong.sizeof + float.sizeof + ubyte.sizeof + 446 // foos 447 (length_t.sizeof + foo1Size + foo2Size); 448 449 assert(bar.sbinSerialize.length == barSize); 450 451 auto data = [ 452 bar, 453 Bar(23, 454 31.4, Level.high, 455 [ 456 Foo(10, .11, .22, 50, "1one1"), 457 Foo(20, .13, .25, 70, "2two2", Color.black), 458 Foo(30, .15, .28, 30, "3three3", Color.white), 459 ] 460 ), 461 ]; 462 463 auto sdata = data.sbinSerialize; 464 assert( equal(sdata.sbinDeserialize!(Bar[]), data)); 465 data[0].foos[1].d = 12345; 466 assert(!equal(sdata.sbinDeserialize!(Bar[]), data)); 467 } 468 469 unittest 470 { 471 static void foo(int a=123, string b="hello") 472 { assert(a==123); assert(b=="hello"); } 473 474 auto a = ParameterDefaults!foo; 475 476 import std.typecons; 477 auto sa = tuple(a).sbinSerialize; 478 479 Parameters!foo b; 480 b = sa.sbinDeserialize!(typeof(tuple(b))); 481 assert(a == b); 482 foo(b); 483 484 a[0] = 234; 485 a[1] = "okda"; 486 auto sn = tuple(a).sbinSerialize; 487 488 sn.sbinDeserialize(b); 489 490 assert(b[0] == 234); 491 assert(b[1] == "okda"); 492 } 493 494 unittest 495 { 496 auto a = [1,2,3,4]; 497 auto as = a.sbinSerialize; 498 auto as_tr = as[0..17]; 499 assertThrown!SBinDeserializeEmptyRangeException(as_tr.sbinDeserialize!(typeof(a))); 500 } 501 502 unittest 503 { 504 auto a = [1,2,3,4]; 505 auto as = a.sbinSerialize; 506 auto as_tr = as ~ as; 507 assertThrown!SBinDeserializeException(as_tr.sbinDeserialize!(typeof(a))); 508 } 509 510 unittest 511 { 512 auto a = ["hello" : 123, "ok" : 43]; 513 auto as = a.sbinSerialize; 514 515 auto b = as.sbinDeserialize!(typeof(a)); 516 assert(b["hello"] == 123); 517 assert(b["ok"] == 43); 518 } 519 520 unittest 521 { 522 static struct X 523 { 524 string[int] one; 525 int[string] two; 526 } 527 528 auto a = X([3: "hello", 8: "abc"], ["ok": 1, "no": 2]); 529 auto b = X([8: "abc", 15: "ololo"], ["zb": 10]); 530 531 auto as = a.sbinSerialize; 532 auto bs = b.sbinSerialize; 533 534 auto c = as.sbinDeserialize!X; 535 536 import std.algorithm; 537 assert(equal(sort(a.one.keys.dup), sort(c.one.keys.dup))); 538 assert(equal(sort(a.one.values.dup), sort(c.one.values.dup))); 539 540 bs.sbinDeserialize(c); 541 542 assert(equal(sort(b.one.keys.dup), sort(c.one.keys.dup))); 543 assert(equal(sort(b.one.values.dup), sort(c.one.values.dup))); 544 } 545 546 unittest 547 { 548 enum T { one, two, three } 549 T[] a; 550 with(T) a = [one, two, three, two, three, two, one]; 551 auto as = a.sbinSerialize; 552 553 auto b = as.sbinDeserialize!(typeof(a)); 554 assert(equal(a, b)); 555 } 556 557 unittest 558 { 559 enum T { one="one", two="2", three="III" } 560 T[] a; 561 with(T) a = [one, two, three, two, three, two, one]; 562 auto as = a.sbinSerialize; 563 564 assert(as.length == 7 + length_t.sizeof); 565 566 auto b = as.sbinDeserialize!(typeof(a)); 567 assert(equal(a, b)); 568 } 569 570 unittest 571 { 572 int ai = 543; 573 auto as = "hello"; 574 575 import std.typecons; 576 auto buf = sbinSerialize(tuple(ai, as)); 577 578 int bi; 579 string bs; 580 sbinDeserialize(buf, bi, bs); 581 582 assert(ai == bi); 583 assert(bs == as); 584 } 585 586 unittest 587 { 588 int ai = 543; 589 auto as = "hello"; 590 591 auto buf = appender!(ubyte[]); 592 sbinSerialize(buf, ai, as); 593 594 int bi; 595 string bs; 596 sbinDeserialize(buf.data, bi, bs); 597 598 assert(ai == bi); 599 assert(bs == as); 600 } 601 602 unittest 603 { 604 static struct ImplaceAppender(Arr) 605 { 606 Arr _data; 607 size_t cur; 608 609 this(Arr arr) { _data = arr; } 610 611 @safe @nogc pure nothrow 612 { 613 void put(E)(E e) 614 if (is(typeof(_data[0] = e))) 615 { 616 _data[cur] = e; 617 cur++; 618 } 619 620 bool filled() const @property 621 { return cur == data.length; } 622 623 inout(Arr) data() inout { return _data[0..cur]; } 624 625 void clear() { cur = 0; } 626 } 627 } 628 629 alias Buffer = ImplaceAppender!(ubyte[]); 630 631 static assert(isOutputRange!(Buffer, ubyte)); 632 633 enum State 634 { 635 one = "ONE", 636 two = "TWO", 637 three = "THREE", 638 } 639 640 struct Cell 641 { 642 ulong id; 643 float volt, temp; 644 ushort soc, soh; 645 string strData; 646 State state; 647 } 648 649 struct Line 650 { 651 ulong id; 652 float volt, curr; 653 Cell[] cells; 654 } 655 656 auto lines = [ 657 Line(123, 658 3.14, 2.17, 659 [ 660 Cell(1, 1.1, 2.2, 5, 8, "one", State.one), 661 Cell(2, 1.3, 2.5, 7, 9, "two", State.two), 662 Cell(3, 1.5, 2.8, 3, 7, "three", State.three), 663 ] 664 ), 665 Line(23, 666 31.4, 21.7, 667 [ 668 Cell(10, .11, .22, 50, 80, "1one1", State.two), 669 Cell(20, .13, .25, 70, 90, "2two2", State.three), 670 Cell(30, .15, .28, 30, 70, "3three3", State.one), 671 ] 672 ), 673 ]; 674 675 ubyte[300] bdata; 676 auto buffer = Buffer(bdata[]); 677 678 ubyte[] sdata; 679 680 () @nogc { buffer.sbinSerialize(lines); }(); 681 682 assert(equal(buffer.data.sbinDeserialize!(typeof(lines)), lines)); 683 } 684 685 unittest 686 { 687 static bool ser, deser; 688 static struct Foo 689 { 690 ulong id; 691 void sbinCustomSerialize(R)(ref R r) const 692 { 693 r.put(cast(ubyte)id); 694 ser = true; 695 } 696 static void sbinCustomDeserialize(R)(ref R r, ref Foo foo) 697 { 698 foo.id = r.front(); 699 r.popFront(); 700 deser = true; 701 } 702 } 703 704 auto foo = Foo(12); 705 706 assert(foo.sbinSerialize.sbinDeserialize!Foo == foo); 707 assert(ser); 708 assert(deser); 709 } 710 711 unittest 712 { 713 static bool ser, deser; 714 static class Foo 715 { 716 ulong id; 717 this(ulong v) { id = v; } 718 void sbinCustomSerialize(R)(ref R r) const 719 { 720 r.put(cast(ubyte)id); 721 ser = true; 722 } 723 static void sbinCustomDeserialize(R)(ref R r, ref Foo foo) 724 { 725 foo = new Foo(r.front()); 726 r.popFront(); 727 deser = true; 728 } 729 } 730 731 auto foo = new Foo(12); 732 733 assert(foo.sbinSerialize.sbinDeserialize!Foo.id == 12); 734 assert(ser); 735 assert(deser); 736 737 Foo[] fooArr; 738 foreach (i; 0 .. 10) 739 fooArr ~= new Foo(i); 740 741 import std.algorithm : map; 742 743 auto fooArr2 = fooArr.sbinSerialize.sbinDeserialize!(Foo[]); 744 assert(equal(fooArr.map!"a.id", fooArr2.map!"a.id")); 745 } 746 747 unittest 748 { 749 // for classes need 750 // void sbinCustomSerialize(R)(ref R r) const 751 // static void sbinCustomDeserialize(R)(ref R r, ref Foo foo) 752 static class Foo 753 { 754 ulong id; 755 this(ulong v) { id = v; } 756 } 757 758 auto foo = new Foo(12); 759 760 static assert(!is(typeof(foo.sbinSerialize.sbinDeserialize!Foo.id))); 761 } 762 763 unittest 764 { 765 import std.bitmanip : bitfields; 766 767 static struct Foo 768 { 769 mixin(bitfields!( 770 bool, "a", 1, 771 bool, "b", 1, 772 ubyte, "c", 4, 773 ubyte, "d", 2 774 )); 775 } 776 777 static assert(Foo.sizeof == 1); 778 779 Foo foo; 780 foo.a = true; 781 foo.b = false; 782 foo.c = 9; 783 foo.d = 3; 784 785 assert(foo.a); 786 assert(foo.b == false); 787 assert(foo.c == 9); 788 assert(foo.d == 3); 789 790 auto sfoo = foo.sbinSerialize; 791 792 assert(sfoo.length == 1); 793 794 auto bar = sfoo.sbinDeserialize!Foo; 795 796 assert(bar.a); 797 assert(bar.b == false); 798 assert(bar.c == 9); 799 assert(bar.d == 3); 800 801 assert(foo == bar); 802 } 803 804 unittest 805 { 806 struct Foo 807 { 808 ubyte[] a, b; 809 } 810 811 auto arr = cast(ubyte[])[1,2,3,4,5,6]; 812 auto foo = Foo(arr, arr[0..2]); 813 assert (foo.a.ptr == foo.b.ptr); 814 815 auto foo2 = foo.sbinSerialize.sbinDeserialize!Foo; 816 assert(foo == foo2); 817 assert(foo.a.ptr != foo2.a.ptr); 818 assert(foo.b.ptr != foo2.b.ptr); 819 assert(foo2.a.ptr != foo2.b.ptr); 820 } 821 822 unittest 823 { 824 struct Foo { void[] a; } 825 auto foo = Foo("hello".dup); 826 auto foo2 = foo.sbinSerialize.sbinDeserialize!Foo; 827 assert(equal(cast(ubyte[])foo.a, cast(ubyte[])foo2.a)); 828 } 829 830 unittest 831 { 832 struct Foo { void[5] a; } 833 auto foo = Foo(cast(void[5])"hello"); 834 auto foo2 = foo.sbinSerialize.sbinDeserialize!Foo; 835 assert(equal(cast(ubyte[])foo.a, cast(ubyte[])foo2.a)); 836 } 837 838 unittest 839 { 840 struct Foo { void[] a; void[5] b; } 841 auto foo = Foo("hello".dup, cast(void[5])"world"); 842 auto foo2 = foo.sbinSerialize.sbinDeserialize!Foo; 843 assert(equal(cast(ubyte[])foo.a, cast(ubyte[])foo2.a)); 844 assert(equal(cast(ubyte[])foo.b, cast(ubyte[])foo2.b)); 845 }