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 }