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 }