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 }