1 /// Simple binary serialization/deserialization
2 module drmi.sbin;
3 
4 import std.bitmanip : nativeToLittleEndian, littleEndianToNative;
5 import std.exception : enforce, assertThrown;
6 import std.range;
7 import std..string : format;
8 import std.traits;
9 
10 ///
11 alias length_t = ulong;
12 ///
13 alias pack = nativeToLittleEndian;
14 ///
15 alias unpack = littleEndianToNative;
16 
17 ///
18 class SBinException : Exception
19 {
20     this(string msg, string file=__FILE__, size_t line=__LINE__) @safe @nogc pure nothrow
21     { super(msg, file, line); }
22 }
23 
24 ///
25 class SBinDeserializeException : SBinException
26 {
27     ///
28     this(string msg, string file=__FILE__, size_t line=__LINE__) @safe @nogc pure nothrow
29     { super(msg, file, line); }
30 }
31 
32 ///
33 class SBinDeserializeFieldException : SBinDeserializeException
34 {
35     ///
36     string mainType, fieldName, fieldType;
37     ///
38     size_t readed, expected, fullReaded;
39     ///
40     this(string mainType, string fieldName, string fieldType,
41          size_t readed, size_t expected, size_t fullReaded) @safe pure
42     {
43         this.mainType = mainType;
44         this.fieldName = fieldName;
45         this.fieldType = fieldType;
46         this.readed = readed;
47         this.expected = expected;
48         this.fullReaded = fullReaded;
49         super(format("empty input range while "~
50                 "deserialize '%s' element %s:%s %d/%d (readed/expected), "~
51                 "readed message %d bytes", mainType, fieldName,
52                 fieldType, readed, expected, fullReaded));
53     }
54 }
55 
56 // only for serialize enums based on strings
57 private ulong[T] getEnumNums(T)() if (is(T == enum))
58 {
59     ulong[T] ret;
60     foreach (i, v; [EnumMembers!T])
61         ret[v] = i;
62     ret.rehash();
63     return ret;
64 }
65 
66 /++ Serialize to output ubyte range
67 
68     Params:
69         val - serializible value
70         r - output range
71 +/
72 void sbinSerialize(R, Ts...)(ref R r, auto ref const Ts vals)
73     if (isOutputRange!(R, ubyte) && Ts.length)
74 {
75     static if (Ts.length == 1)
76     {
77         alias T = Unqual!(Ts[0]);
78         alias val = vals[0];
79         static if (is(T : double) || is(T : long))
80             r.put(val.pack[]);
81         else static if (is(T == enum))
82         {
83             enum en = getEnumNums!T();
84             r.put(en[val].pack[]);
85         }
86         else static if (isStaticArray!T)
87             foreach (ref v; val) r.sbinSerialize(v);
88         else static if (isSomeString!T)
89         {
90             r.put((cast(length_t)val.length).pack[]);
91             r.put(cast(ubyte[])val);
92         }
93         else static if (isDynamicArray!T)
94         {
95             r.put((cast(length_t)val.length).pack[]);
96             foreach (ref v; val) r.sbinSerialize(v);
97         }
98         else static if (isAssociativeArray!T)
99         {
100             r.put((cast(length_t)val.length).pack[]);
101             foreach (k, ref v; val)
102             {
103                 r.sbinSerialize(k);
104                 r.sbinSerialize(v);
105             }
106         }
107         else static if (is(T == struct) || isTypeTuple!T)
108             foreach (ref v; val.tupleof) r.sbinSerialize(v);
109         else static assert(0, "unsupported type: " ~ T.stringof);
110     }
111     else foreach (ref v; vals) r.sbinSerialize(v);
112 }
113 
114 /++ Serialize to ubyte[]
115 
116     using `appender!(ubyte[])` as output range
117 
118     Params:
119         val = serializible value
120 
121     Returns:
122         serialized data
123 +/
124 ubyte[] sbinSerialize(T)(auto ref const T val)
125 {
126     import std.array : appender;
127     auto buf = appender!(ubyte[]);
128     buf.sbinSerialize(val);
129     return buf.data.dup;
130 }
131 
132 /++ Deserialize `Target` value
133 
134     Params:
135         range = input range with serialized data (not saved before work)
136 
137     Returns:
138         deserialized value
139  +/
140 Target sbinDeserialize(Target, R)(R range)
141 {
142     Unqual!Target ret;
143     range.sbinDeserialize(ret);
144     return ret;
145 }
146 
147 /++ Deserialize `Target` value
148 
149     Params:
150         range = input range with serialized data (not saved before work)
151         target = reference to result object
152 
153     Returns:
154         deserialized value
155  +/
156 void sbinDeserialize(R, Target...)(R range, ref Target target)
157 {
158     size_t cnt;
159 
160     ubyte pop(ref R rng, lazy string field, lazy string type,
161                 lazy size_t vcnt, lazy size_t vexp)
162     {
163         enforce (!rng.empty, new SBinDeserializeFieldException(
164                     Target.stringof, field, type, vcnt, vexp, cnt));
165         auto ret = rng.front;
166         rng.popFront();
167         cnt++;
168         return ret;
169     }
170 
171     auto impl(T)(ref R r, ref T trg, lazy string field)
172     {
173         string ff(lazy string n) { return field ~ "." ~ n; }
174         string fi(size_t i) { return field ~ format("[%d]", i); }
175 
176         static if (is(T : double) || is(T : long))
177         {
178             ubyte[T.sizeof] tmp;
179             version (LDC) auto _field = "<LDC-1.4.0 workaround>";
180             else alias _field = field;
181             foreach (i, ref v; tmp) v = pop(r, _field, T.stringof, i, T.sizeof);
182             trg = tmp.unpack!T;
183         }
184         else static if (is(T == enum))
185         {
186             ubyte[ulong.sizeof] tmp;
187             version (LDC) auto _field = "<LDC-1.4.0 workaround>";
188             else alias _field = field;
189             foreach (i, ref v; tmp) v = pop(r, _field, T.stringof, i, T.sizeof);
190             trg = [EnumMembers!T][(unpack!ulong(tmp))];
191         }
192         else static if (isSomeString!T)
193         {
194             length_t l;
195             impl(r, l, ff("length"));
196             auto length = cast(size_t)l;
197             auto tmp = new ubyte[](length);
198             foreach (i, ref v; tmp) v = pop(r, fi(i), T.stringof, i, length);
199             trg = cast(T)tmp;
200         }
201         else static if (isStaticArray!T)
202         {
203             foreach (i, ref v; trg) impl(r, v, fi(i));
204         }
205         else static if (isDynamicArray!T)
206         {
207             length_t l;
208             impl(r, l, ff("length"));
209             auto length = cast(size_t)l;
210             if (trg.length != length) trg.length = length;
211             foreach (i, ref v; trg) impl(r, v, fi(i));
212         }
213         else static if (isAssociativeArray!T)
214         {
215             length_t l;
216             impl(r, l, ff("length"));
217             auto length = cast(size_t)l;
218 
219             trg.clear();
220 
221             foreach (i; 0 .. length)
222             {
223                 KeyType!T k;
224                 ValueType!T v;
225                 impl(r, k, fi(i)~".key");
226                 impl(r, v, fi(i)~".val");
227                 trg[k] = v;
228             }
229 
230             trg.rehash();
231         }
232         else static if (is(T == struct) || isTypeTuple!T)
233         {
234             foreach (i, ref v; trg.tupleof)
235                 impl(r, v, ff(__traits(identifier, trg.tupleof[i])));
236         }
237         else static assert(0, "unsupported type: " ~ T.stringof);
238     }
239 
240     static if (Target.length == 1)
241         impl(range, target[0], typeof(target[0]).stringof);
242     else foreach (ref v; target)
243         impl(range, v, typeof(v).stringof);
244 
245     enforce(range.empty, new SBinDeserializeException(
246         format("input range not empty after full '%s' deserialize", Target.stringof)));
247 }
248 
249 version (unittest) import std.algorithm : equal;
250 
251 unittest
252 {
253     auto a = 123;
254     assert(a.sbinSerialize.sbinDeserialize!int == a);
255 }
256 
257 unittest
258 {
259     auto a = 123;
260     auto as = a.sbinSerialize;
261     int x;
262     sbinDeserialize(as, x);
263     assert(a == x);
264 }
265 
266 unittest
267 {
268     auto s = "hello world";
269     assert(equal(s.sbinSerialize.sbinDeserialize!string, s));
270 }
271 
272 unittest
273 {
274     immutable(int[]) a = [1,2,3,2,3,2,1];
275     assert(a.sbinSerialize.sbinDeserialize!(int[]) == a);
276 }
277 
278 unittest
279 {
280     int[5] a = [1,2,3,2,3];
281     assert(a.sbinSerialize.sbinDeserialize!(typeof(a)) == a);
282 }
283 
284 unittest
285 {
286     import std.array : appender;
287     auto ap = appender!(ubyte[]);
288 
289     struct Cell
290     {
291         ulong id;
292         float volt, temp;
293         ushort soc, soh;
294         string strData;
295         bool tr;
296     }
297 
298     struct Line
299     {
300         ulong id;
301         float volt, curr;
302         Cell[] cells;
303     }
304 
305     auto lines = [
306         Line(123,
307             3.14, 2.17,
308             [
309                 Cell(1, 1.1, 2.2, 5, 8, "one", true),
310                 Cell(2, 1.3, 2.5, 7, 9, "two"),
311                 Cell(3, 1.5, 2.8, 3, 7, "three"),
312             ]
313         ),
314         Line(23,
315             31.4, 21.7,
316             [
317                 Cell(10, .11, .22, 50, 80, "1one1"),
318                 Cell(20, .13, .25, 70, 90, "2two2", true),
319                 Cell(30, .15, .28, 30, 70, "3three3"),
320             ]
321         ),
322     ];
323 
324     auto sdata = lines.sbinSerialize;
325     assert( equal(sdata.sbinDeserialize!(Line[]), lines));
326     lines[0].cells[1].soh = 123;
327     assert(!equal(sdata.sbinDeserialize!(Line[]), lines));
328 }
329 
330 unittest
331 {
332     static void foo(int a=123, string b="hello")
333     { assert(a==123); assert(b=="hello"); }
334 
335     auto a = ParameterDefaults!foo;
336 
337     import std.typecons;
338     auto sa = tuple(a).sbinSerialize;
339 
340     Parameters!foo b;
341     b = sa.sbinDeserialize!(typeof(tuple(b)));
342     assert(a == b);
343     foo(b);
344 
345     a[0] = 234;
346     a[1] = "okda";
347     auto sn = tuple(a).sbinSerialize;
348 
349     sn.sbinDeserialize(b);
350 
351     assert(b[0] == 234);
352     assert(b[1] == "okda");
353 }
354 
355 unittest
356 {
357     auto a = [1,2,3,4];
358     auto as = a.sbinSerialize;
359     auto as_tr = as[0..17];
360     assertThrown!SBinDeserializeFieldException(as_tr.sbinDeserialize!(typeof(a)));
361 }
362 
363 unittest
364 {
365     auto a = [1,2,3,4];
366     auto as = a.sbinSerialize;
367     auto as_tr = as ~ as;
368     assertThrown!SBinDeserializeException(as_tr.sbinDeserialize!(typeof(a)));
369 }
370 
371 unittest
372 {
373     auto a = ["hello" : 123, "ok" : 43];
374     auto as = a.sbinSerialize;
375 
376     auto b = as.sbinDeserialize!(typeof(a));
377     assert(b["hello"] == 123);
378     assert(b["ok"] == 43);
379 }
380 
381 unittest
382 {
383     static struct X
384     {
385         string[int] one;
386         int[string] two;
387     }
388 
389     auto a = X([3: "hello", 8: "abc"], ["ok": 1, "no": 2]);
390     auto b = X([8: "abc", 15: "ololo"], ["zb": 10]);
391 
392     auto as = a.sbinSerialize;
393     auto bs = b.sbinSerialize;
394 
395     auto c = as.sbinDeserialize!X;
396 
397     import std.algorithm;
398     assert(equal(sort(a.one.keys.dup), sort(c.one.keys.dup)));
399     assert(equal(sort(a.one.values.dup), sort(c.one.values.dup)));
400 
401     bs.sbinDeserialize(c);
402 
403     assert(equal(sort(b.one.keys.dup), sort(c.one.keys.dup)));
404     assert(equal(sort(b.one.values.dup), sort(c.one.values.dup)));
405 }
406 
407 unittest
408 {
409     enum T { one, two, three }
410     T[] a;
411     with(T) a = [one, two, three, two, three, two, one];
412     auto as = a.sbinSerialize;
413 
414     auto b = as.sbinDeserialize!(typeof(a));
415     assert(equal(a, b));
416 }
417 
418 unittest
419 {
420     enum T { one="one", two="2", three="III" }
421     T[] a;
422     with(T) a = [one, two, three, two, three, two, one];
423     auto as = a.sbinSerialize;
424 
425     auto b = as.sbinDeserialize!(typeof(a));
426     assert(equal(a, b));
427 }
428 
429 unittest
430 {
431     int ai = 543;
432     auto as = "hello";
433 
434     import std.typecons;
435     auto buf = sbinSerialize(tuple(ai, as));
436 
437     int bi;
438     string bs;
439     sbinDeserialize(buf, bi, bs);
440 
441     assert(ai == bi);
442     assert(bs == as);
443 }
444 
445 unittest
446 {
447     int ai = 543;
448     auto as = "hello";
449 
450     import std.array : appender;
451     auto buf = appender!(ubyte[]);
452     sbinSerialize(buf, ai, as);
453 
454     int bi;
455     string bs;
456     sbinDeserialize(buf.data, bi, bs);
457 
458     assert(ai == bi);
459     assert(bs == as);
460 }