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 }