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