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