1 module discord.w.minietf; 2 3 import std.bigint; 4 import std.bitmanip; 5 import std.conv; 6 import std.datetime; 7 import std.math; 8 import std.meta; 9 import std.range; 10 import std.traits; 11 import std.typecons; 12 import std.utf; 13 import std.variant; 14 15 import vibe.core.log; 16 17 class BufferResizeAttemptException : Exception 18 { 19 this(string msg, string file = __FILE__, size_t line = __LINE__) pure nothrow @nogc @safe 20 { 21 super(msg, file, line); 22 } 23 } 24 25 enum ETFHeader : ubyte 26 { 27 // see https://github.com/discordapp/erlpack 28 // and http://erlang.org/doc/apps/erts/erl_ext_dist.html 29 30 formatVersion = 131, /// first byte of buffer 31 newFloatExt = 'F', /// 70 [Float64:IEEE float] 32 bitBinaryExt = 'M', /// 77 [UInt32:Len, UInt8:Bits, Len:Data] 33 smallIntegerExt = 'a', /// 97 [UInt8:Int] 34 integerExt = 'b', /// 98 [Int32:Int] 35 floatExt = 'c', /// 99 [31:Float String] Float in string format (formatted "%.20e", sscanf "%lf"). Superseded by newFloatExt 36 atomExt = 'd', /// 100 [UInt16:Len, Len:AtomName] max Len is 255 37 referenceExt = 'e', /// 101 [atom:Node, UInt32:ID, UInt8:Creation] 38 portExt = 'f', /// 102 [atom:Node, UInt32:ID, UInt8:Creation] 39 pidExt = 'g', /// 103 [atom:Node, UInt32:ID, UInt32:Serial, UInt8:Creation] 40 smallTupleExt = 'h', /// 104 [UInt8:Arity, N:Elements] 41 largeTupleExt = 'i', /// 105 [UInt32:Arity, N:Elements] 42 nilExt = 'j', /// 106 empty list 43 stringExt = 'k', /// 107 [UInt16:Len, Len:Characters] 44 listExt = 'l', /// 108 [UInt32:Len, Elements, Tail] 45 binaryExt = 'm', /// 109 [UInt32:Len, Len:Data] 46 smallBigExt = 'n', /// 110 [UInt8:n, UInt8:Sign, n:nums] 47 largeBigExt = 'o', /// 111 [UInt32:n, UInt8:Sign, n:nums] 48 newFunExt = 'p', /// 112 [UInt32:Size, UInt8:Arity, 16*Uint6-MD5:Uniq, UInt32:Index, UInt32:NumFree, atom:Module, int:OldIndex, int:OldUniq, pid:Pid, NunFree*ext:FreeVars] 49 exportExt = 'q', /// 113 [atom:Module, atom:Function, smallint:Arity] 50 newReferenceExt = 'r', /// 114 [UInt16:Len, atom:Node, UInt8:Creation, Len*UInt32:ID] 51 smallAtomExt = 's', /// 115 [UInt8:Len, Len:AtomName] 52 mapExt = 't', /// 116 [UInt32:Airty, N:Pairs] 53 funExt = 'u', /// 117 [UInt4:NumFree, pid:Pid, atom:Module, int:Index, int:Uniq, NumFree*ext:FreeVars] 54 atomUTF8Ext = 'v', /// 118 [UInt16:Len, Len:AtomName (UTF-8)] 55 smallAtomUTF8Ext = 'w', /// 119 [UInt8:Len, Len:AtomName (UTF-8)] 56 compressed = 'P', /// 80 [UInt4:UncompressedSize, N:ZlibCompressedData] 57 } 58 59 /// typesafe alias for Atom 60 enum Atom : string 61 { 62 empty = "" 63 } 64 65 Atom atom(string s) @safe 66 { 67 return cast(Atom) s; 68 } 69 70 struct EncodeFunc 71 { 72 string func; 73 } 74 75 EncodeFunc encodeFunc(string func) 76 { 77 return EncodeFunc(func); 78 } 79 80 struct DecodeFunc 81 { 82 string func; 83 } 84 85 DecodeFunc decodeFunc(string func) 86 { 87 return DecodeFunc(func); 88 } 89 90 struct EncodeName 91 { 92 string name; 93 } 94 95 EncodeName encodeName(string name) 96 { 97 return EncodeName(name); 98 } 99 100 struct KeyValuePair(K, V) 101 { 102 K key; 103 V value; 104 } 105 106 template isKeyValueArray(T) 107 { 108 enum isKeyValueArray = is(T : KeyValuePair!(K, V), K, V); 109 } 110 111 KeyValuePair!(K, V) kv(K, V)(K key, V value) 112 { 113 return KeyValuePair!(K, V)(key, value); 114 } 115 116 struct ETFBuffer 117 { 118 // unused if allowResize is true 119 ubyte[] full; 120 ubyte[] buffer; 121 size_t index; 122 bool allowResize; 123 124 inout(ubyte[]) bytes() inout @property @safe 125 { 126 return buffer; 127 } 128 129 void put(T)(auto ref T data) 130 { 131 unsafeReserveBuffer(data.length); 132 putUnsafe(data); 133 } 134 135 void unsafeReserveBuffer(size_t extra) 136 { 137 assert(buffer.length == index, "buffer length mismatch (finish putUnsafe before call)"); 138 if (allowResize) 139 buffer.length += extra; 140 else 141 { 142 if (buffer.length + extra > full.length) 143 throw new BufferResizeAttemptException("Buffer resize attempt"); 144 buffer = full[0 .. buffer.length + extra]; 145 } 146 } 147 148 void putUnsafe(T)(auto ref T data) 149 { 150 assert(index + data.length <= buffer.length, 151 "invalid putUnsafe call (call unsafeReserveBuffer first)"); 152 buffer[index .. index += data.length] = data; 153 } 154 155 inout(ubyte[]) peek(size_t n, size_t offset = 0) inout 156 { 157 if (offset + n > buffer.length) 158 throw new Exception("Attempted to peek over buffer length"); 159 return buffer[offset .. offset + n]; 160 } 161 162 ubyte[] read(size_t n) 163 { 164 if (n > buffer.length) 165 throw new Exception("Attempted to read over buffer length"); 166 auto ret = buffer[0 .. n]; 167 buffer = buffer[n .. $]; 168 return ret; 169 } 170 171 void putVersion() 172 { 173 put([cast(ubyte) ETFHeader.formatVersion]); 174 } 175 176 void readVersion() 177 { 178 auto header = cast(ETFHeader) peek(1)[0]; 179 if (header != cast(ubyte) ETFHeader.formatVersion) 180 throw new Exception("Expected ETF header but got " ~ header.to!string); 181 read(1); 182 } 183 184 void putBool(bool value) 185 { 186 if (value) 187 put(cast(ubyte[])[cast(ubyte) ETFHeader.smallAtomExt, 4, 't', 'r', 'u', 'e']); 188 else 189 put(cast(ubyte[])[cast(ubyte) ETFHeader.smallAtomExt, 5, 'f', 'a', 'l', 's', 'e']); 190 } 191 192 bool readBool() 193 { 194 auto saved = buffer; 195 scope (failure) 196 buffer = saved; // PogChamp 197 198 auto a = readAtom(); 199 200 if (a != atom("true") && a != atom("false")) 201 throw new Exception("Expected boolean atom but got " ~ cast(string) a); 202 203 return a == atom("true"); 204 } 205 206 void putNull() 207 { 208 put(cast(ubyte[])[cast(ubyte) ETFHeader.smallAtomExt, 3, 'n', 'i', 'l']); 209 } 210 211 auto readNull() 212 { 213 auto header = cast(ETFHeader) peek(1)[0]; 214 215 if (header == ETFHeader.nilExt) 216 { 217 read(1); 218 return null; 219 } 220 auto saved = buffer; 221 scope (failure) 222 buffer = saved; // PogChamp 223 224 auto a = readAtom(); 225 if (a != "nil") 226 throw new Exception("Expected nil atom but got " ~ cast(string) a); 227 return null; 228 } 229 230 void putEmptyList() 231 { 232 put([cast(ubyte) ETFHeader.nilExt]); 233 } 234 235 auto readEmptyList() 236 { 237 return readNull(); 238 } 239 240 void putByte(ubyte v) 241 { 242 put([cast(ubyte) ETFHeader.smallIntegerExt, v]); 243 } 244 245 ubyte readByte() 246 { 247 auto header = cast(ETFHeader) peek(1)[0]; 248 if (header == ETFHeader.smallIntegerExt) 249 return read(2)[1]; 250 else 251 return cast(ubyte) readInt(); 252 } 253 254 void putInt(int v) 255 { 256 ubyte[1 + 4] data; 257 data[0] = cast(ubyte) ETFHeader.integerExt; 258 data[1 .. 5] = nativeToBigEndian(v); 259 put(data[]); 260 } 261 262 int readInt() 263 { 264 auto header = cast(ETFHeader) peek(1)[0]; 265 if (header == ETFHeader.smallIntegerExt) 266 return readByte(); 267 else if (header == ETFHeader.integerExt) 268 return read(5)[1 .. 5].bigEndianToNative!int; 269 else 270 return cast(int) readLong(); 271 } 272 273 void putULong(ulong v) 274 { 275 ubyte[1 + 2 + ulong.sizeof] data; 276 data[0] = cast(ubyte) ETFHeader.smallBigExt; 277 ubyte len; 278 while (v > 0) 279 { 280 data[3 + len] = v & 0xFF; 281 v >>= 8; 282 len++; 283 } 284 data[1] = len; 285 put(data[0 .. 1 + 2 + len]); 286 } 287 288 ulong readULong() 289 { 290 auto header = cast(ETFHeader) peek(1)[0]; 291 if (header == ETFHeader.integerExt || header == ETFHeader.smallIntegerExt) 292 return cast(long) readInt(); 293 static if (is(typeof(readBigInt))) 294 if (header == ETFHeader.largeBigExt) 295 return readBigInt().toLong; 296 if (header != ETFHeader.smallBigExt) 297 throw new Exception("Expected smallBigExt header + 1 byte length but got " ~ header 298 .to!string); 299 auto info = peek(2, 1); 300 if (info[0] > 8) 301 throw new Exception("Can't read more than 8 bytes into a ulong"); 302 if (info[1] == 1) 303 throw new Exception("Received negative bignum when reading ulong"); 304 auto data = read(3 + info[0])[3 .. $]; 305 ulong ret; 306 foreach (i, b; data) 307 ret |= (cast(ulong) b) << (8UL * i); 308 return ret; 309 } 310 311 void putLong(long v) 312 { 313 ubyte[1 + 2 + ulong.sizeof] data; 314 data[0] = cast(ubyte) ETFHeader.smallBigExt; 315 if (v < 0) 316 { 317 data[2] = 1; 318 v = -v; 319 } 320 ubyte len; 321 while (v > 0) 322 { 323 data[3 + len] = v & 0xFF; 324 v >>= 8; 325 len++; 326 } 327 data[1] = len; 328 put(data[0 .. 1 + 2 + len]); 329 } 330 331 long readLong() 332 { 333 auto header = cast(ETFHeader) peek(1)[0]; 334 if (header == ETFHeader.integerExt || header == ETFHeader.smallIntegerExt) 335 return cast(long) readInt(); 336 static if (is(typeof(readBigInt))) 337 if (header == ETFHeader.largeBigExt) 338 return readBigInt().toLong; 339 if (header != ETFHeader.smallBigExt) 340 throw new Exception("Expected smallBigExt header + 1 byte length but got " ~ header 341 .to!string); 342 auto info = peek(2, 1); 343 if (info[1] > 8) 344 throw new Exception("Can't read more than 8 bytes into a long"); 345 bool negative = info[1] == 1; 346 auto data = read(3 + info[0])[3 .. $]; 347 long ret; 348 foreach (i, b; data) 349 ret |= (cast(long) b) << (8UL * i); 350 return negative ? -ret : ret; 351 } 352 353 static if (is(typeof(BigInt.init.getDigit))) 354 { 355 void putBigInt(BigInt v) 356 { 357 auto l = v.ulongLength; 358 assert(l > 0); 359 auto lastDigit = v.getDigit(l - 1); 360 ubyte bytesInLastBlock = lastDigit == 0 ? 1 361 : 8 - lastDigit.nativeToBigEndian.countUntil!(a => a != 0); 362 auto len = (l - 1) * 8 + bytesInLastBlock; 363 ubyte sign = v.isNegative ? 1 : 0; 364 if (len <= ubyte.max) 365 { 366 unsafeReserveBuffer(3 + len); 367 putUnsafe([cast(ubyte) ETFHeader.smallBigExt, cast(ubyte) len, sign]); 368 } 369 else 370 { 371 if (len > uint.max) 372 throw new Exception("BigInt too large"); 373 unsafeReserveBuffer(6 + len); 374 ubyte[6] header; 375 header[0] = cast(ubyte) ETFHeader.largeBigExt; 376 header[1 .. 5] = nativeToBigEndian(cast(uint) len); 377 header[5] = sign; 378 putUnsafe(header); 379 } 380 foreach (i; 0 .. l - 1) 381 putUnsafe(nativeToLittleEndian(v.getDigit(i))); 382 ubyte[8] last = nativeToLittleEndian(v.getDigit(l - 1)); 383 putUnsafe(last[0 .. 8 - bytesInLastBlock]); 384 } 385 386 BigInt readBigInt() 387 { 388 auto header = cast(ETFHeader) peek(1)[0]; 389 if (header == ETFHeader.integerExt) 390 return cast(long) readInt(); 391 static if (is(typeof(readBigInt))) 392 if (header == ETFHeader.largeBigExt) 393 return readBigInt().toLong; 394 if (header != ETFHeader.smallBigExt) 395 throw new Exception( 396 "Expected smallBigExt header + 1 byte length but got " ~ header.to!string); 397 auto info = peek(2, 1); 398 if (info[1] > 8) 399 throw new Exception("Can't read more than 8 bytes into a long"); 400 auto data = read(3 + info[1])[3 .. $]; 401 char[] str = new char[info[0] + 2 + data.length * 2]; 402 size_t i; 403 if (info[0] == 1) 404 str[i++] = '-'; 405 str[i++] = '0'; 406 str[i++] = 'x'; 407 408 char toHexChar(ubyte b) 409 { 410 if (b >= 0 && b <= 9) 411 return '0' + b; 412 else 413 return 'a' - 10 + b; 414 } 415 416 foreach (i, b; data) 417 { 418 str[$ - (i + 1) * 2] = toHexChar(b & 0xF); 419 str[$ - (i + 1) * 2 + 1] = toHexChar((b >> 4) & 0xF); 420 } 421 return BigInt(data); 422 } 423 } 424 425 void putDouble(double d) 426 { 427 ubyte[1 + 8] data; 428 data[0] = cast(ubyte) ETFHeader.newFloatExt; 429 data[1 .. 9] = nativeToBigEndian(d); 430 put(data[]); 431 } 432 433 double readDouble() 434 { 435 auto header = cast(ETFHeader) peek(1)[0]; 436 if (header != ETFHeader.newFloatExt) 437 throw new Exception("Expected newFloatExt header but got " ~ header.to!string); 438 439 return read(9)[1 .. 9].bigEndianToNative!double; 440 } 441 442 void putAtom(Atom atom) 443 { 444 if (atom.length <= ubyte.max) 445 { 446 unsafeReserveBuffer(2 + atom.length); 447 putUnsafe([cast(ubyte) ETFHeader.smallAtomExt, cast(ubyte) atom.length]); 448 putUnsafe(cast(ubyte[]) atom); 449 } 450 else 451 { 452 if (atom.length > ushort.max) 453 throw new Exception("Attempted to put too long atom"); 454 unsafeReserveBuffer(3 + atom.length); 455 putUnsafe([cast(ubyte) ETFHeader.atomExt]); 456 putUnsafe(nativeToBigEndian(cast(ushort) atom.length)); 457 putUnsafe(cast(ubyte[]) atom); 458 } 459 } 460 461 Atom readAtom() 462 { 463 auto header = cast(ETFHeader) peek(1)[0]; 464 if (header == ETFHeader.smallAtomUTF8Ext || header == ETFHeader.smallAtomExt) 465 { 466 auto len = peek(1, 1)[0]; 467 return cast(Atom) read(2 + len)[2 .. $].idup; 468 } 469 else if (header == ETFHeader.atomUTF8Ext || header == ETFHeader.atomExt) 470 { 471 auto len = peek(2, 1)[0 .. 2].bigEndianToNative!ushort; 472 return cast(Atom) read(3 + len)[3 .. $].idup; 473 } 474 else if (header == ETFHeader.binaryExt) 475 return cast(Atom) readBinary; 476 else if (header == ETFHeader.stringExt) 477 return cast(Atom) readString; 478 else if (header == ETFHeader.nilExt) 479 return cast(Atom) null; 480 else 481 throw new Exception("Expected atom header + length but got " ~ header.to!string); 482 } 483 484 void putBinary(ubyte[] data) 485 { 486 unsafeReserveBuffer(5 + data.length); 487 putUnsafe([cast(ubyte) ETFHeader.binaryExt]); 488 putUnsafe(nativeToBigEndian(cast(uint) data.length)); 489 putUnsafe(data); 490 } 491 492 ubyte[] readBinaryUnsafe() 493 { 494 auto header = cast(ETFHeader) peek(1)[0]; 495 if (header == ETFHeader.binaryExt) 496 { 497 auto len = peek(4, 1)[0 .. 4].bigEndianToNative!uint; 498 return read(5 + len)[5 .. $]; 499 } 500 else if (header == ETFHeader.stringExt) 501 return readString(); 502 else if (header == ETFHeader.atomExt || header == ETFHeader.atomUTF8Ext 503 || header == ETFHeader.smallAtomExt || header == ETFHeader.smallAtomUTF8Ext) 504 return cast(ubyte[]) readAtom(); 505 else if (header == ETFHeader.nilExt) 506 return null; 507 else 508 throw new Exception("Expected binaryExt header but got " ~ header.to!string); 509 } 510 511 ubyte[] readBinary() 512 { 513 return readBinaryUnsafe.dup; 514 } 515 516 void putString(ubyte[] data) 517 { 518 unsafeReserveBuffer(3 + data.length); 519 putUnsafe([cast(ubyte) ETFHeader.stringExt]); 520 putUnsafe(nativeToBigEndian(cast(ushort) data.length)); 521 putUnsafe(data); 522 } 523 524 ubyte[] readString() 525 { 526 auto header = cast(ETFHeader) peek(1)[0]; 527 if (header == ETFHeader.stringExt) 528 { 529 auto len = peek(2, 1)[0 .. 2].bigEndianToNative!ushort; 530 return read(3 + len)[3 .. $].dup; 531 } 532 else if (header == ETFHeader.atomExt || header == ETFHeader.atomUTF8Ext 533 || header == ETFHeader.smallAtomExt || header == ETFHeader.smallAtomUTF8Ext) 534 return cast(ubyte[]) readAtom(); 535 else if (header == ETFHeader.binaryExt) 536 return readBinary(); 537 else if (header == ETFHeader.nilExt) 538 return null; 539 else 540 throw new Exception("Expected stringExt header but got " ~ header.to!string); 541 } 542 543 void startTuple(size_t length) 544 { 545 if (length <= ubyte.max) 546 { 547 put([cast(ubyte) ETFHeader.smallTupleExt, cast(ubyte) length]); 548 } 549 else 550 { 551 ubyte[1 + 4] data; 552 data[0] = cast(ubyte) ETFHeader.largeTupleExt; 553 data[1 .. 5] = nativeToBigEndian(cast(uint) length); 554 put(data[]); 555 } 556 } 557 558 size_t readTupleLength() 559 { 560 auto header = cast(ETFHeader) peek(1)[0]; 561 if (header == ETFHeader.smallTupleExt) 562 return cast(size_t) read(2)[1]; 563 else if (header == ETFHeader.largeTupleExt) 564 return read(5)[1 .. 5].bigEndianToNative!int; 565 else 566 throw new Exception("Expected tuple header but got " ~ header.to!string); 567 } 568 569 void startList(size_t length) 570 { 571 ubyte[1 + 4] data; 572 data[0] = cast(ubyte) ETFHeader.listExt; 573 data[1 .. 5] = nativeToBigEndian(cast(uint) length); 574 put(data[]); 575 } 576 577 size_t readListLength(out bool needsNil) 578 { 579 auto header = cast(ETFHeader) peek(1)[0]; 580 needsNil = header == ETFHeader.listExt; 581 if (header == ETFHeader.listExt || header == ETFHeader.largeTupleExt) 582 return read(5)[1 .. 5].bigEndianToNative!int; 583 else if (header == ETFHeader.smallTupleExt) 584 return read(2)[1]; 585 else if (header == ETFHeader.nilExt) 586 { 587 read(1); 588 return 0; 589 } 590 else 591 throw new Exception("Expected listExt header but got " ~ header.to!string); 592 } 593 594 void startMap(size_t length) 595 { 596 ubyte[1 + 4] data; 597 data[0] = cast(ubyte) ETFHeader.mapExt; 598 data[1 .. 5] = nativeToBigEndian(cast(uint) length); 599 put(data[]); 600 } 601 602 size_t readMapLength() 603 { 604 auto header = cast(ETFHeader) peek(1)[0]; 605 if (header == ETFHeader.mapExt) 606 return read(5)[1 .. 5].bigEndianToNative!int; 607 else 608 throw new Exception("Expected mapExt header but got " ~ header.to!string); 609 } 610 611 alias EncodableSimpleTypes = AliasSeq!(Atom, bool, byte, ubyte, ubyte[], 612 string, wstring, dstring, BigInt, short, ushort, int, uint, long, ulong, 613 float, double, real, Variant[], Variant[Variant], Variant[string], 614 KeyValuePair!(Variant, Variant)[], typeof(null), string[], int[], 615 ulong[], SysTime, Nullable!SysTime); 616 617 void encode(T)(T value) 618 { 619 static if (is(T == typeof(null))) 620 putNull(); 621 else static if (is(T : Nullable!U, U)) 622 { 623 if (value.isNull) 624 putNull(); 625 else 626 encode!U(value.get); 627 } 628 else static if (is(T : Atom)) 629 putAtom(value); 630 else static if (is(T : bool)) 631 putBool(value); 632 else static if (is(T == byte) || is(T == ubyte)) 633 putByte(cast(ubyte) value); 634 else static if (is(T : ubyte[])) 635 putBinary(value); 636 else static if (is(T : SysTime)) 637 putBinary(cast(ubyte[]) value.toISOExtString); 638 else static if (is(T : string)) 639 putBinary(cast(ubyte[]) value); 640 else static if (is(T : wstring) || is(T : dstring)) 641 putBinary(cast(ubyte[]) value.toUTF8); 642 else static if (isAssociativeArray!T) 643 { 644 startMap(value.length); 645 foreach (ref key, ref item; value) 646 { 647 encode(key); 648 encode(item); 649 } 650 } 651 else static if (is(T : KeyValuePair!(K, V)[], K, V)) 652 { 653 startMap(value.length); 654 foreach (ref v; value) 655 { 656 encode(v.key); 657 encode(v.value); 658 } 659 } 660 else static if (isArray!T) 661 { 662 if (value.length) 663 startList(value.length); 664 static if (is(ElementType!T : void)) 665 assert(value.length == 0); 666 else 667 foreach (ref elem; value) 668 encode(elem); 669 putEmptyList(); 670 } 671 else static if (is(T : Tuple!U, U...)) 672 { 673 startTuple(value.expand.length); 674 foreach (i, V; value.expand) 675 encode(value[i]); 676 } 677 else static if (is(T : BigInt) && is(typeof(putBigInt))) 678 putBigInt(value); 679 else static if (isFloatingPoint!T) 680 putDouble(cast(double) value); 681 else static if (isIntegral!T) 682 { 683 if (value <= ubyte.max && value >= 0) 684 putByte(cast(ubyte) value); 685 else if (value <= int.max && value >= int.min) 686 putInt(cast(int) value); 687 else if (value > 0) 688 putULong(cast(ulong) value); 689 else 690 putLong(cast(ulong) value); 691 } 692 else static if (is(T : VariantN!(size, Allowed), size_t size, Allowed...)) 693 { 694 static if (__traits(compiles, value == null)) 695 if (value == null) 696 { 697 putNull(); 698 return; 699 } 700 if (!value.hasValue) 701 { 702 putNull(); 703 return; 704 } 705 static if (Allowed.length > 0) 706 { 707 static foreach (T; Allowed) 708 static if (__traits(compiles, encode!T)) 709 { 710 if (value.convertsTo!T) 711 { 712 encode(value.get!T); 713 return; 714 } 715 } 716 else 717 pragma(msg, __FILE__, "(", __LINE__, 718 "): Warning: allowed variant type ", T.stringof, " is not encodable"); 719 throw new Exception("Attempted to encode unencodable variant " ~ (cast() value).toString); 720 } 721 else 722 { 723 static foreach (T; EncodableSimpleTypes) 724 { 725 if (value.convertsTo!T) 726 { 727 encode(value.get!T); 728 return; 729 } 730 } 731 throw new Exception("Attempted to encode unencodable variant " ~ (cast() value).toString); 732 } 733 } 734 else static if (is(T == struct)) 735 { 736 static if (is(typeof(value.erlpack))) 737 { 738 value.erlpack(this); 739 } 740 else 741 { 742 size_t numMembers; 743 foreach (member; FieldNameTuple!T) 744 static if (__traits(compiles, mixin("value." ~ member))) 745 numMembers++; 746 startMap(numMembers); 747 foreach (member; FieldNameTuple!T) 748 static if (__traits(compiles, mixin("value." ~ member))) 749 { 750 alias names = getUDAs!(__traits(getMember, T, member), EncodeName); 751 alias funcs = getUDAs!(__traits(getMember, T, member), EncodeFunc); 752 753 static assert(names.length == 0 || names.length == 1); 754 static assert(funcs.length == 0 || funcs.length == 1); 755 756 static if (names.length == 1) 757 const string name = names[0].name; 758 else 759 const string name = member; 760 761 putBinary(cast(ubyte[]) name); 762 static if (funcs.length == 1) 763 mixin(funcs[0].func ~ "(value." ~ member ~ ");"); 764 else 765 encode(mixin("value." ~ member)); 766 } 767 } 768 } 769 else 770 static assert(false, "Can't serialize " ~ T.stringof); 771 } 772 773 T decode(T = Variant)(lazy string debugInfo = "") 774 { 775 scope (failure) 776 logDebugV("Error when decoding " ~ T.stringof ~ " " ~ debugInfo); 777 static if (is(T == typeof(null))) 778 return readNull(); 779 else static if (is(T : Nullable!U, U)) 780 { 781 try 782 { 783 readNull(); 784 return Nullable!U.init; 785 } 786 catch (Exception) 787 { 788 return Nullable!U(decode!U); 789 } 790 } 791 else static if (is(T : Atom)) 792 return cast(T) readAtom(); 793 else static if (is(T : bool)) 794 return cast(T) readBool(); 795 else static if (is(T == byte) || is(T == ubyte)) 796 return cast(T) readByte(); 797 else static if (is(T : ubyte[])) 798 return cast(T) readBinary(); 799 else static if (is(T : string)) 800 return cast(T) readBinary().idup; 801 else static if (is(T : SysTime)) 802 return cast(T) SysTime.fromISOExtString(cast(string) readBinary()); 803 else static if (is(T : wstring)) 804 return cast(T)(cast(string) readBinary().idup).toUTF16; 805 else static if (is(T : dstring)) 806 return cast(T)(cast(string) readBinary().idup).toUTF32; 807 else static if (isAssociativeArray!T) 808 { 809 ubyte[] saved = buffer; 810 scope (failure) 811 buffer = saved; 812 auto len = readMapLength(); 813 T ret; 814 foreach (i; 0 .. len) 815 { 816 auto key = decode!(KeyType!T)("key #" ~ i.to!string); 817 auto item = decode!(ValueType!T)("value #" ~ i.to!string); 818 ret[key] = item; 819 } 820 return ret; 821 } 822 else static if (is(T : KeyValuePair!(K, V)[], K, V)) 823 { 824 ubyte[] saved = buffer; 825 scope (failure) 826 buffer = saved; 827 auto len = readMapLength(); 828 T ret; 829 foreach (i; 0 .. len) 830 { 831 auto key = decode!K("key #" ~ i.to!string); 832 auto item = decode!V("value #" ~ i.to!string); 833 ret ~= kv(key, item); 834 } 835 return ret; 836 } 837 else static if (isArray!T) 838 { 839 ubyte[] saved = buffer; 840 scope (failure) 841 buffer = saved; 842 bool needsNil; 843 auto len = readListLength(needsNil); 844 T ret; 845 if (len != 0) 846 { 847 static if (is(ElementType!T : void)) 848 { 849 throw new Exception("Expected empty list but got " ~ len.to!string ~ " Elements"); 850 } 851 else 852 { 853 static if (isDynamicArray!T) 854 ret = new ElementType!T[len]; 855 foreach (i; 0 .. len) 856 ret[i] = decode!(ElementType!T)("element #" ~ i.to!string); 857 } 858 } 859 if (needsNil) 860 readEmptyList(); 861 return ret; 862 } 863 else static if (is(T : Tuple!U, U...)) 864 { 865 ubyte[] saved = buffer; 866 scope (failure) 867 buffer = saved; 868 auto len = readTupleLength; 869 T ret; 870 if (len != T.expand.length) 871 throw new Exception( 872 "Expected tuple of length " ~ T.expand.length.to!string ~ ", but got " ~ len.to!string); 873 foreach (i, T; T.Types) 874 ret[i] = decode!T("element #" ~ i.to!string); 875 return ret; 876 } 877 else static if (is(T : BigInt) && is(typeof(readBigInt))) 878 return cast(T) readBigInt(); 879 else static if (isFloatingPoint!T) 880 return cast(T) readDouble(); 881 else static if (is(T : long)) 882 return cast(T) readLong(); 883 else static if (is(T : ulong)) 884 return cast(T) readULong(); 885 else static if (isIntegral!T) 886 return cast(T) readInt(); 887 else static if (is(T : VariantN!(size, Allowed), size_t size, Allowed...)) 888 { 889 auto type = cast(ETFHeader) peek(1)[0]; 890 switch (type) 891 { 892 case ETFHeader.atomExt: 893 case ETFHeader.atomUTF8Ext: 894 case ETFHeader.smallAtomExt: 895 case ETFHeader.smallAtomUTF8Ext: 896 auto a = readAtom; 897 static if (__traits(compiles, T(true))) 898 { 899 if (a == atom("true")) 900 return T(true); 901 else if (a == atom("false")) 902 return T(false); 903 } 904 static if (__traits(compiles, T(null))) 905 { 906 if (a == atom("nil")) 907 return T(null); 908 } 909 910 static if (__traits(compiles, T(Atom.init))) 911 return T(a); 912 else static if (__traits(compiles, T(Variant.init))) 913 return T(Variant(a)); 914 else 915 throw new Exception("Got an atom where it isn't allowed"); 916 case ETFHeader.binaryExt: 917 static if (__traits(compiles, T(cast(ubyte[])[]))) 918 return T(readBinary); 919 else static if (__traits(compiles, T(Variant.init))) 920 return T(Variant(readBinary)); 921 else 922 throw new Exception("Got binary where it isn't allowed"); 923 case ETFHeader.stringExt: 924 static if (__traits(compiles, T(cast(ubyte[])[]))) 925 return T(readString); 926 else static if (__traits(compiles, T(cast(string)[]))) 927 return T(cast(string) readString); 928 else static if (__traits(compiles, T(Variant.init))) 929 return T(Variant(readString)); 930 else 931 throw new Exception("Got binary where it isn't allowed"); 932 case ETFHeader.newFloatExt: 933 static if (__traits(compiles, T(double.init))) 934 return T(readDouble); 935 else static if (__traits(compiles, T(Variant.init))) 936 return T(Variant(readDouble)); 937 else 938 throw new Exception("Got double where it isn't allowed"); 939 case ETFHeader.integerExt: 940 case ETFHeader.smallIntegerExt: 941 static if (__traits(compiles, T(long.init))) 942 return T(readLong); 943 else static if (__traits(compiles, T(int.init))) 944 return T(readInt); 945 else static if (__traits(compiles, T(ubyte.init))) 946 return T(readByte); 947 else static if (__traits(compiles, T(Variant.init))) 948 return T(Variant(readLong)); 949 else 950 throw new Exception("Got integer where it isn't allowed"); 951 case ETFHeader.smallBigExt: 952 case ETFHeader.largeBigExt: 953 static if (is(typeof(readBigInt)) && __traits(compiles, T(BigInt.init))) 954 return T(readBigInt); 955 else static if (__traits(compiles, T(long.init))) 956 return T(readLong); 957 else static if (is(typeof(readBigInt)) && __traits(compiles, T(Variant.init))) 958 return T(Variant(readBigInt)); 959 else static if (__traits(compiles, T(Variant.init))) 960 return T(Variant(readLong)); 961 else 962 throw new Exception("Got integer where it isn't allowed"); 963 case ETFHeader.listExt: 964 case ETFHeader.smallTupleExt: 965 case ETFHeader.largeTupleExt: 966 static foreach (V; Allowed) 967 static if (__traits(compiles, T(V.init)) && __traits(compiles, decode!V) && isArray!V) 968 try 969 { 970 return T(decode!V); 971 } 972 catch (Exception) 973 { 974 } 975 static if (__traits(compiles, T(cast(Variant[])[]))) 976 return T(decode!(Variant[])); 977 else static if (__traits(compiles, T(Variant.init))) 978 return T(Variant(decode!(Variant[]))); 979 else 980 throw new Exception("Got list where it isn't allowed"); 981 case ETFHeader.mapExt: 982 static foreach (V; Allowed) 983 static if (__traits(compiles, T(V.init)) && __traits(compiles, 984 decode!V) && (isAssociativeArray!V || (isArray!V 985 && isKeyValueArray!(ElementType!V)))) 986 try 987 { 988 return T(decode!V); 989 } 990 catch (Exception) 991 { 992 } 993 static if (__traits(compiles, T(cast(Variant[Variant])[]))) 994 return T(decode!(Variant[Variant])); 995 else static if (__traits(compiles, T(Variant.init))) 996 return T(Variant(decode!(Variant[Variant]))); 997 else 998 throw new Exception("Got map where it isn't allowed"); 999 case ETFHeader.nilExt: 1000 readNull(); 1001 return T.init; 1002 default: 1003 throw new Exception("Invalid ETF header type for " ~ T.stringof ~ ": " ~ type.to!string); 1004 } 1005 } 1006 else static if (is(T == struct)) 1007 { 1008 static if (is(typeof(T.erlunpack))) 1009 { 1010 return T.erlunpack(this); 1011 } 1012 else 1013 { 1014 auto len = readMapLength; 1015 T value; 1016 foreach (i; 0 .. len) 1017 { 1018 bool found; 1019 auto str = cast(string) readBinaryUnsafe; 1020 foreach (member; FieldNameTuple!T) 1021 static if (__traits(compiles, mixin("value." ~ member))) 1022 { 1023 alias names = getUDAs!(__traits(getMember, T, member), EncodeName); 1024 alias funcs = getUDAs!(__traits(getMember, T, member), DecodeFunc); 1025 1026 static assert(names.length == 0 || names.length == 1); 1027 static assert(funcs.length == 0 || funcs.length == 1); 1028 1029 static if (names.length == 1) 1030 const string name = names[0].name; 1031 else 1032 const string name = member; 1033 1034 if (!found && str == name) 1035 { 1036 static if (funcs.length == 1) 1037 mixin("value." ~ member ~ " = " ~ funcs[0].func ~ "();"); 1038 else 1039 mixin("value." ~ member) = decode!(typeof(mixin("value." ~ member)))( 1040 "member " ~ member ~ " (name=" ~ name ~ ")"); 1041 found = true; 1042 } 1043 } 1044 } 1045 return value; 1046 } 1047 } 1048 else 1049 static assert(false, "Can't deserialize " ~ T.stringof); 1050 } 1051 1052 static ETFBuffer serialize(T)(T value, size_t buffer = 1024, bool allowResize = true) @trusted 1053 { 1054 ETFBuffer ret; 1055 if (allowResize) 1056 ret.buffer.reserve(buffer); 1057 else 1058 ret.full.length = buffer; 1059 ret.allowResize = allowResize; 1060 ret.putVersion(); 1061 ret.encode(value); 1062 return ret; 1063 } 1064 1065 static T deserialize(T = Variant)(ubyte[] buffer, bool start = true) @trusted 1066 { 1067 ETFBuffer ret; 1068 ret.buffer = buffer; 1069 ret.allowResize = false; 1070 if (start) 1071 ret.readVersion(); 1072 return ret.decode!T; 1073 } 1074 1075 static ETFNode deserialzeTree()(auto ref ubyte[] buffer) @trusted 1076 { 1077 ETFNode root; 1078 root.bufferStart = buffer; 1079 root.type = cast(ETFHeader) 0; 1080 if (buffer.length && buffer[0] == ETFHeader.formatVersion) 1081 { 1082 root.type = ETFHeader.formatVersion; 1083 buffer = buffer[1 .. $]; 1084 } 1085 while (buffer.length) 1086 { 1087 ubyte[] start = buffer; 1088 ETFHeader type = cast(ETFHeader) buffer[0]; 1089 buffer = buffer[1 .. $]; 1090 ETFNode[] keys, children; 1091 size_t length; 1092 switch (type) 1093 { 1094 case ETFHeader.nilExt: 1095 length = 0; 1096 break; 1097 case ETFHeader.smallIntegerExt: 1098 length = 1; 1099 break; 1100 case ETFHeader.integerExt: 1101 length = 4; 1102 break; 1103 case ETFHeader.newFloatExt: 1104 length = 8; 1105 break; 1106 case ETFHeader.floatExt: 1107 length = 31; 1108 break; 1109 case ETFHeader.smallAtomExt: 1110 case ETFHeader.smallAtomUTF8Ext: 1111 if (buffer.length < 1) 1112 break; 1113 length = buffer[0]; 1114 buffer = buffer[1 .. $]; 1115 break; 1116 case ETFHeader.atomExt: 1117 case ETFHeader.atomUTF8Ext: 1118 case ETFHeader.stringExt: 1119 if (buffer.length < 2) 1120 break; 1121 length = buffer[0 .. 2].bigEndianToNative!ushort; 1122 buffer = buffer[2 .. $]; 1123 break; 1124 case ETFHeader.binaryExt: 1125 if (buffer.length < 4) 1126 break; 1127 length = buffer[0 .. 4].bigEndianToNative!uint; 1128 buffer = buffer[4 .. $]; 1129 break; 1130 case ETFHeader.smallTupleExt: 1131 if (buffer.length < 1) 1132 break; 1133 auto elems = buffer[0]; 1134 buffer = buffer[1 .. $]; 1135 length = 0; 1136 foreach (i; 0 .. elems) 1137 children ~= deserialzeTree(buffer); 1138 break; 1139 case ETFHeader.largeTupleExt: 1140 if (buffer.length < 4) 1141 break; 1142 auto elems = buffer[0 .. 4].bigEndianToNative!uint; 1143 buffer = buffer[4 .. $]; 1144 length = 0; 1145 foreach (i; 0 .. elems) 1146 children ~= deserialzeTree(buffer); 1147 break; 1148 case ETFHeader.listExt: 1149 if (buffer.length < 4) 1150 break; 1151 auto elems = buffer[0 .. 4].bigEndianToNative!uint; 1152 buffer = buffer[4 .. $]; 1153 length = 1; 1154 foreach (i; 0 .. elems) 1155 children ~= deserialzeTree(buffer); 1156 break; 1157 case ETFHeader.smallBigExt: 1158 if (buffer.length < 1) 1159 break; 1160 length = cast(int) buffer[0] + 1; 1161 buffer = buffer[1 .. $]; 1162 break; 1163 case ETFHeader.largeBigExt: 1164 if (buffer.length < 4) 1165 break; 1166 length = cast(size_t) buffer[0 .. 4].bigEndianToNative!uint + 1; 1167 buffer = buffer[4 .. $]; 1168 break; 1169 case ETFHeader.mapExt: 1170 if (buffer.length < 4) 1171 break; 1172 auto elems = buffer[0 .. 4].bigEndianToNative!uint; 1173 buffer = buffer[4 .. $]; 1174 length = 0; 1175 foreach (i; 0 .. elems) 1176 { 1177 keys ~= deserialzeTree(buffer); 1178 children ~= deserialzeTree(buffer); 1179 } 1180 break; 1181 case ETFHeader.newFunExt: 1182 case ETFHeader.exportExt: 1183 case ETFHeader.newReferenceExt: 1184 case ETFHeader.funExt: 1185 case ETFHeader.portExt: 1186 case ETFHeader.pidExt: 1187 case ETFHeader.compressed: 1188 case ETFHeader.referenceExt: 1189 case ETFHeader.bitBinaryExt: 1190 default: 1191 length = 0; 1192 break; 1193 } 1194 1195 auto data = buffer[0 .. length > $ ? $ : length]; 1196 buffer = buffer[data.length .. $]; 1197 1198 root.children ~= ETFNode(type, start, data, keys, children); 1199 1200 if (root.type == cast(ETFHeader) 0) 1201 break; 1202 } 1203 if (root.type == cast(ETFHeader) 0 && root.children.length) 1204 return root.children[0]; 1205 else 1206 return root; 1207 } 1208 } 1209 1210 private string indent(string s) 1211 { 1212 import std.algorithm; 1213 import std..string; 1214 1215 return s.lineSplitter!(KeepTerminator.yes).map!(a => '\t' ~ a).join(); 1216 } 1217 1218 string binToString(in ubyte[] b) 1219 { 1220 import std.format; 1221 1222 string s; 1223 foreach (c; b) 1224 { 1225 if (c < 128 && c >= 32) 1226 s ~= cast(char) c; 1227 else 1228 s ~= "\\x" ~ format("%02x", c); 1229 } 1230 return s; 1231 } 1232 1233 struct ETFNode 1234 { 1235 ETFHeader type; 1236 ubyte[] bufferStart; 1237 ubyte[] data; 1238 ETFNode[] keys; 1239 ETFNode[] children; 1240 1241 ETFNode opIndex(T)(T index) 1242 { 1243 if (keys.length) 1244 { 1245 foreach (i, key; keys) 1246 { 1247 try 1248 { 1249 if (key.get!T == index) 1250 return children[i]; 1251 } 1252 catch (Exception) 1253 { 1254 } 1255 } 1256 throw new Exception("Index out of bounds"); 1257 } 1258 else 1259 { 1260 static if (isIntegral!T) 1261 { 1262 if (index < 0 || index >= children.length) 1263 throw new Exception("Index out of bounds"); 1264 return children[index]; 1265 } 1266 throw new Exception("Index out of bounds"); 1267 } 1268 } 1269 1270 T get(T)() 1271 { 1272 static if (is(T == ETFNode)) 1273 return this; 1274 else 1275 { 1276 switch (type) 1277 { 1278 case ETFHeader.smallIntegerExt: 1279 static if (isIntegral!T 1280 || isFloatingPoint!T) 1281 return cast(T) data[0]; 1282 else 1283 break; 1284 case ETFHeader.integerExt: 1285 static if (isIntegral!T 1286 || isFloatingPoint!T) 1287 return cast(T) data[0 .. 4].bigEndianToNative!int; 1288 else 1289 break; 1290 case ETFHeader.atomExt: 1291 case ETFHeader.atomUTF8Ext: 1292 case ETFHeader.smallAtomExt: 1293 case ETFHeader.smallAtomUTF8Ext: 1294 case ETFHeader.binaryExt: 1295 case ETFHeader.stringExt: 1296 static if (isSomeString!T) 1297 return (cast(string) data).to!T; 1298 else 1299 break; 1300 case ETFHeader.newFloatExt: 1301 static if (isIntegral!T 1302 || isFloatingPoint!T) 1303 return cast(T) data[0 .. 8].bigEndianToNative!double; 1304 else 1305 break; 1306 case ETFHeader.nilExt: 1307 static if (__traits(compiles, { T a = null; })) 1308 return null; 1309 else 1310 break; 1311 case ETFHeader.smallTupleExt: 1312 case ETFHeader.largeTupleExt: 1313 case ETFHeader.listExt: 1314 static if (isArray!T && !isSomeString!T) 1315 { 1316 T ret; 1317 static if (isDynamicArray!T) 1318 ret.length = children.length; 1319 else if (children.length != ret.length) 1320 throw new Exception("List does not have expected number of elements"); 1321 foreach (i, ref elem; ret) 1322 elem = children[i].get!(ElementType!T); 1323 return ret; 1324 } 1325 else static if (is(T : Tuple!U, U...)) 1326 { 1327 T ret; 1328 if (children.length != U.length) 1329 throw new Exception("List does not have expected number of elements"); 1330 foreach (i, T; U) 1331 ret[i] = children[i].get!T; 1332 return ret; 1333 } 1334 else 1335 break; 1336 case ETFHeader.mapExt: 1337 static if (isAssociativeArray!T) 1338 { 1339 T ret; 1340 foreach (i; 0 .. keys.length < children.length ? keys.length : children.length) 1341 ret[keys[i].get!(KeyType!T)] = children[i].get!(ValueType!T); 1342 return ret; 1343 } 1344 else static if (isArray!T && !isSomeString!T) 1345 { 1346 T ret; 1347 static if (isDynamicArray!T) 1348 ret.length = children.length; 1349 foreach (i, ref elem; ret) 1350 elem = children[i].get!(ElementType!T); 1351 return ret; 1352 } 1353 else 1354 break; 1355 default: 1356 break; 1357 } 1358 throw new Exception("Can't convert " ~ type.to!string ~ " to " ~ T.stringof); 1359 } 1360 } 1361 1362 string toString() const 1363 { 1364 string s; 1365 if (type == ETFHeader.binaryExt) 1366 s = "b"; 1367 else 1368 s = type.to!string; 1369 string dataStr; 1370 switch (type) 1371 { 1372 case ETFHeader.smallIntegerExt: 1373 dataStr = data[0].to!string; 1374 break; 1375 case ETFHeader.integerExt: 1376 dataStr = data[0 .. 4].bigEndianToNative!int.to!string; 1377 break; 1378 case ETFHeader.atomExt: 1379 case ETFHeader.atomUTF8Ext: 1380 case ETFHeader.smallAtomExt: 1381 case ETFHeader.smallAtomUTF8Ext: 1382 case ETFHeader.binaryExt: 1383 case ETFHeader.stringExt: 1384 dataStr = '"' ~ binToString(data) ~ '"'; 1385 break; 1386 case ETFHeader.newFloatExt: 1387 dataStr = data[0 .. 8].bigEndianToNative!double.to!string; 1388 break; 1389 case ETFHeader.nilExt: 1390 dataStr = "<empty list>"; 1391 break; 1392 case ETFHeader.listExt: 1393 dataStr = (data == [cast(ubyte) ETFHeader.nilExt]) 1394 ? "valid" : ("invalid - " ~ data.to!string); 1395 break; 1396 default: 1397 dataStr = data.to!string; 1398 break; 1399 } 1400 if (children.length > 0) 1401 { 1402 s ~= "[" ~ children.length.to!string ~ "]"; 1403 if (data.length) 1404 s ~= "(" ~ dataStr ~ ")"; 1405 s ~= ":\n"; 1406 foreach (i, child; children) 1407 { 1408 if (i < keys.length) 1409 s ~= (keys[i].toString[0 .. $ - 1] ~ ": " ~ child.toString).indent; 1410 else 1411 s ~= child.toString.indent; 1412 } 1413 } 1414 else if (data.length) 1415 s ~= "(" ~ dataStr ~ ")\n"; 1416 else 1417 s ~= '\n'; 1418 assert(s[$ - 1] == '\n'); 1419 return s; 1420 } 1421 } 1422 1423 unittest 1424 { 1425 import std.exception; 1426 1427 ETFBuffer buffer; 1428 buffer.full.length = 64; 1429 1430 assertNotThrown!BufferResizeAttemptException(buffer.putLong(long.max)); // now 11 1431 assertNotThrown!BufferResizeAttemptException(buffer.putLong(long.max)); // now 22 1432 assertNotThrown!BufferResizeAttemptException(buffer.putLong(long.max)); // now 33 1433 assertNotThrown!BufferResizeAttemptException(buffer.putLong(long.max)); // now 44 1434 assertNotThrown!BufferResizeAttemptException(buffer.putLong(long.max)); // now 55 1435 assertThrown!BufferResizeAttemptException(buffer.putLong(long.max)); // should throw 1436 assertNotThrown!BufferResizeAttemptException(buffer.putInt(6)); // now 60 1437 assertNotThrown!BufferResizeAttemptException(buffer.putByte(7)); // now 62 1438 assertNotThrown!BufferResizeAttemptException(buffer.putByte(8)); // now 64 1439 assertThrown!BufferResizeAttemptException(buffer.putByte(9)); // should throw 1440 } 1441 1442 unittest 1443 { 1444 import std.exception; 1445 1446 assert(ETFBuffer.serialize(atom("Hello World")).bytes == cast(ubyte[]) "\x83s\x0BHello World"); 1447 1448 KeyValuePair!(Variant, Variant)[] map; 1449 map ~= kv(Variant("a"), Variant(1)); 1450 map ~= kv(Variant(2), Variant(2)); 1451 map ~= kv(Variant(3), Variant(variantArray(1, 2, 3))); 1452 import std.stdio; 1453 1454 assert(ETFBuffer.serialize(map) 1455 .bytes == cast(ubyte[]) "\x83t\x00\x00\x00\x03m\x00\x00\x00\x01aa\x01a\x02a\x02a\x03l\x00\x00\x00\x03a\x01a\x02a\x03j"); 1456 1457 struct S 1458 { 1459 void erlpack(ref ETFBuffer b) 1460 { 1461 b.encode([kv(atom("name"), Variant("jake")), kv(atom("age"), Variant(23))]); 1462 } 1463 } 1464 1465 assert(ETFBuffer.serialize(S.init) 1466 .bytes == cast(ubyte[]) "\x83t\x00\x00\x00\x02s\x04namem\x00\x00\x00\x04jakes\x03agea\x17"); 1467 1468 assert(ETFBuffer.serialize(false).bytes == cast(ubyte[]) "\x83s\x05false"); 1469 1470 assert(ETFBuffer.serialize(2.5).bytes == cast(ubyte[]) "\x83F@\x04\x00\x00\x00\x00\x00\x00"); 1471 assert(ETFBuffer.serialize(51_512_123_841_234.31423412341435123412341342) 1472 .bytes == cast(ubyte[]) "\x83FB\xc7l\xcc\xeb\xedi("); 1473 1474 assert(ETFBuffer.serialize("string").bytes == cast(ubyte[]) "\x83m\x00\x00\x00\x06string"); 1475 assert(assertNotThrown(ETFBuffer.deserialize( 1476 cast(ubyte[]) "\x83m\x00\x00\x00\x06string") == Variant(cast(ubyte[]) "string"))); 1477 assert(assertNotThrown(ETFBuffer.deserialize!string( 1478 cast(ubyte[]) "\x83m\x00\x00\x00\x06string") == "string")); 1479 assert(assertNotThrown(ETFBuffer.deserialize!wstring( 1480 cast(ubyte[]) "\x83m\x00\x00\x00\x06string") == "string"w)); 1481 assert(assertNotThrown(ETFBuffer.deserialize!dstring( 1482 cast(ubyte[]) "\x83m\x00\x00\x00\x06string") == "string"d)); 1483 assertThrown(ETFBuffer.deserialize!int(cast(ubyte[]) "\x83m\x00\x00\x00\x06string")); 1484 assertThrown(ETFBuffer.deserialize!string(cast(ubyte[]) "\x83")); 1485 assertThrown(ETFBuffer.deserialize!(typeof(null))(cast(ubyte[]) "\x83")); 1486 1487 assert(ETFBuffer.serialize([]).bytes == cast(ubyte[]) "\x83j"); 1488 assert(ETFBuffer.deserialize!(void[])(cast(ubyte[]) "\x83j") == []); 1489 1490 assert(ETFBuffer.serialize(127552384489488384L) 1491 .bytes == cast(ubyte[]) "\x83n\x08\x00\x00\x00\xc0\xc77(\xc5\x01"); 1492 assert(ETFBuffer.deserialize!long( 1493 cast(ubyte[]) "\x83n\x08\x00\x00\x00\xc0\xc77(\xc5\x01") == 127552384489488384L); 1494 1495 assert(ETFBuffer.serialize(atom("hi")).bytes == cast(ubyte[]) "\x83s\x02hi"); 1496 assert(ETFBuffer.deserialize!Atom(cast(ubyte[]) "\x83w\x02hi") == atom("hi")); 1497 assert(ETFBuffer.deserialize!Atom(cast(ubyte[]) "\x83s\x02hi") == atom("hi")); 1498 1499 assert(ETFBuffer.serialize(123.45).bytes == cast(ubyte[]) "\x83F@^\xdc\xcc\xcc\xcc\xcc\xcd"); 1500 assert(ETFBuffer.deserialize!double(cast(ubyte[]) "\x83F@^\xdc\xcc\xcc\xcc\xcc\xcd") == 123.45); 1501 1502 assert(ETFBuffer.serialize(cast(ubyte[]) "alsdjaljf") 1503 .bytes == cast(ubyte[]) "\x83m\x00\x00\x00\talsdjaljf"); 1504 assert(ETFBuffer.deserialize!(ubyte[])( 1505 cast(ubyte[]) "\x83m\x00\x00\x00\talsdjaljf") == cast(ubyte[]) "alsdjaljf"); 1506 1507 assert(ETFBuffer.serialize(12345).bytes == cast(ubyte[]) "\x83b\x00\x0009"); 1508 assert(ETFBuffer.deserialize!int(cast(ubyte[]) "\x83b\x00\x0009") == 12345); 1509 1510 assert(ETFBuffer.serialize(tuple()).bytes == cast(ubyte[]) "\x83h\x00"); 1511 assertNotThrown(ETFBuffer.deserialize!(Tuple!())(cast(ubyte[]) "\x83h\x00")); 1512 1513 struct MyObject 1514 { 1515 struct Child 1516 { 1517 string a; 1518 Tuple!(string, string[], string[]) also; 1519 ubyte[] with_; 1520 1521 string[Variant] map; 1522 } 1523 1524 Atom e1; 1525 Tuple!(Atom, Atom, string) e2; 1526 Variant[] e3; 1527 Tuple!(string, Tuple!(Atom, string[]), typeof(null)) e4; 1528 long e5; 1529 double e6; 1530 int e7; 1531 int e8; 1532 double e9; 1533 long e10; 1534 Child e11; 1535 } 1536 1537 MyObject obj; 1538 obj.e1 = atom("someatom"); 1539 obj.e2 = tuple(atom("some"), atom("other"), "tuple"); 1540 obj.e3 = variantArray(cast(ubyte[])("maybe"), 1, null); 1541 obj.e4 = tuple("with", tuple(atom("embedded"), ["tuples and lists"]), null); 1542 obj.e5 = 127542384389482384L; 1543 obj.e6 = 5334.32; 1544 obj.e7 = 102; 1545 obj.e8 = -1394; 1546 obj.e9 = -349.2; 1547 obj.e10 = -498384595043; 1548 obj.e11.a = "map"; 1549 obj.e11.also = tuple("tuples", ["and"], ["lists"]); 1550 obj.e11.with_ = cast(ubyte[]) "binaries"; 1551 obj.e11.map = [Variant(cast(ubyte[])("a")) : "anotherone", Variant(3) 1552 : "int keys", Variant(atom("something")) : "else"]; 1553 1554 assert(ETFBuffer.serialize(obj).bytes == cast(ubyte[]) "\x83t\x00\x00\x00\x0bm\x00\x00\x00\x02e1s\x08someatomm\x00\x00\x00\x02e2h\x03s\x04somes\x05otherm\x00\x00\x00\x05tuplem\x00\x00\x00\x02e3l\x00\x00\x00\x03m\x00\x00\x00\x05maybea\x01s\x03niljm\x00\x00\x00\x02e4h\x03m\x00\x00\x00\x04withh\x02s\x08embeddedl\x00\x00\x00\x01m\x00\x00\x00\x10tuples and listsjs\x03nilm\x00\x00\x00\x02e5n\x08\x00\x90gWs\x1f\x1f\xc5\x01m\x00\x00\x00\x02e6F@\xb4\xd6Q\xeb\x85\x1e\xb8m\x00\x00\x00\x02e7afm\x00\x00\x00\x02e8b\xff\xff\xfa\x8em\x00\x00\x00\x02e9F\xc0u\xd333333m\x00\x00\x00\x03e10n\x05\x01ch\x09\x0atm\x00\x00\x00\x03e11t\x00\x00\x00\x04m\x00\x00\x00\x01am\x00\x00\x00\x03mapm\x00\x00\x00\x04alsoh\x03m\x00\x00\x00\x06tuplesl\x00\x00\x00\x01m\x00\x00\x00\x03andjl\x00\x00\x00\x01m\x00\x00\x00\x05listsjm\x00\x00\x00\x05with_m\x00\x00\x00\x08binariesm\x00\x00\x00\x03mapt\x00\x00\x00\x03l\x00\x00\x00\x01aajm\x00\x00\x00\x0aanotheronea\x03m\x00\x00\x00\x08int keyss\x09somethingm\x00\x00\x00\x04else"); 1555 auto obj2 = ETFBuffer.deserialize!MyObject(cast(ubyte[]) "\x83t\x00\x00\x00\x0bm\x00\x00\x00\x02e1s\x08someatomm\x00\x00\x00\x02e2h\x03s\x04somes\x05otherm\x00\x00\x00\x05tuplem\x00\x00\x00\x02e3l\x00\x00\x00\x03m\x00\x00\x00\x05maybea\x01s\x03niljm\x00\x00\x00\x02e4h\x03m\x00\x00\x00\x04withh\x02s\x08embeddedl\x00\x00\x00\x01m\x00\x00\x00\x10tuples and listsjs\x03nilm\x00\x00\x00\x02e5n\x08\x00\x90gWs\x1f\x1f\xc5\x01m\x00\x00\x00\x02e6F@\xb4\xd6Q\xeb\x85\x1e\xb8m\x00\x00\x00\x02e7afm\x00\x00\x00\x02e8b\xff\xff\xfa\x8em\x00\x00\x00\x02e9F\xc0u\xd333333m\x00\x00\x00\x03e10n\x05\x01ch\x09\x0atm\x00\x00\x00\x03e11t\x00\x00\x00\x04m\x00\x00\x00\x01am\x00\x00\x00\x03mapm\x00\x00\x00\x04alsoh\x03m\x00\x00\x00\x06tuplesl\x00\x00\x00\x01m\x00\x00\x00\x03andjl\x00\x00\x00\x01m\x00\x00\x00\x05listsjm\x00\x00\x00\x05with_m\x00\x00\x00\x08binariesm\x00\x00\x00\x03mapt\x00\x00\x00\x03l\x00\x00\x00\x01aajm\x00\x00\x00\x0aanotheronea\x03m\x00\x00\x00\x08int keyss\x09somethingm\x00\x00\x00\x04else"); 1556 assert(obj.e1 == obj2.e1); 1557 assert(obj.e2 == obj2.e2); 1558 assert(obj.e3 == obj2.e3); 1559 assert(obj.e4 == obj2.e4); 1560 assert(obj.e5 == obj2.e5); 1561 assert(obj.e6 == obj2.e6); 1562 assert(obj.e7 == obj2.e7); 1563 assert(obj.e8 == obj2.e8); 1564 assert(obj.e9 == obj2.e9); 1565 assert(obj.e10 == obj2.e10); 1566 assert(obj.e11.a == obj2.e11.a); 1567 assert(obj.e11.also == obj2.e11.also); 1568 assert(obj.e11.with_ == obj2.e11.with_); 1569 assert(obj.e11.map.length == obj2.e11.map.length); 1570 1571 /*alias Var3 = Algebraic!(string, Tuple!(string, string[], string[])); 1572 alias Var2 = Algebraic!(Tuple!(Variant, Variant, string), 1573 Variant, Tuple!(string, Tuple!(Variant, string[]), typeof(null)), long, 1574 double, int, KeyValuePair!(Variant, 1575 string)[], string[Tuple!Variant], KeyValuePair!(Variant, Var3)[], Variant[]); 1576 alias Var = Algebraic!(Var2[], Tuple!(Variant, Variant, string), Variant, 1577 Tuple!(string, Tuple!(Variant, string[]), typeof(null)), long, double, int, 1578 KeyValuePair!(Variant, string)[], 1579 string[Tuple!Variant], Variant[], KeyValuePair!(Variant, Variant)[]); 1580 //dfmt off 1581 Var[] obj = [ 1582 Var(Variant(atom("someatom"))), 1583 Var(tuple(Variant(atom("some")), 1584 Variant(atom("other")), "tuple")), 1585 Var(variantArray("maybe", 1, null)), 1586 Var(tuple("with", tuple(Variant(atom("embedded")), ["tuples and lists"]), null)), 1587 Var(127542384389482384L), 1588 Var(5334.32), 1589 Var(102), 1590 Var(-1394), 1591 Var(-349.2), 1592 Var(-498384595043), 1593 Var([ 1594 Var2([ 1595 kv(Variant(atom("a")), Var3("map")), 1596 kv(Variant(atom("also")), Var3(tuple("tuples", ["and"], ["lists"]))), 1597 kv(Variant(atom("with")), Var3("binaries")) 1598 ]), 1599 Var2([ 1600 kv(Variant(Variant(atom("a"))), "anotherone"), 1601 kv(Variant(3), "int keys") 1602 ]), 1603 Var2([tuple(Variant(atom("something"))) : "else"]) 1604 ]) 1605 ]; 1606 //dfmt on 1607 assert(ETFBuffer.serialize(obj).bytes == "\x83l\x00\x00\x00\x0bs\x08someatomh\x03s\x04somes\x05otherm\x00\x00\x00\x05tuplel\x00\x00\x00\x03m\x00\x00\x00\x05maybea\x01s\x03niljh\x03m\x00\x00\x00\x04withh\x02s\x08embeddedl\x00\x00\x00\x01m\x00\x00\x00\x10tuples and listsjs\x03niln\x08\x00\x90gWs\x1f\x1f\xc5\x01F@\xb4\xd6Q\xeb\x85\x1e\xb8afb\xff\xff\xfa\x8eF\xc0u\xd333333n\x05\x01ch\x09\x0atl\x00\x00\x00\x03t\x00\x00\x00\x03s\x01am\x00\x00\x00\x03maps\x04alsoh\x03m\x00\x00\x00\x06tuplesl\x00\x00\x00\x01m\x00\x00\x00\x03andjl\x00\x00\x00\x01m\x00\x00\x00\x05listsjs\x04withm\x00\x00\x00\x08binariest\x00\x00\x00\x02s\x01am\x00\x00\x00\x0aanotheronea\x03m\x00\x00\x00\x08int keyst\x00\x00\x00\x01h\x01s\x09somethingm\x00\x00\x00\x04elsejj"); 1608 assert(obj == ETFBuffer.deserialize!(Var[])(cast(ubyte[]) "\x83l\x00\x00\x00\x0bs\x08someatomh\x03s\x04somes\x05otherm\x00\x00\x00\x05tuplel\x00\x00\x00\x03m\x00\x00\x00\x05maybea\x01s\x03niljh\x03m\x00\x00\x00\x04withh\x02s\x08embeddedl\x00\x00\x00\x01m\x00\x00\x00\x10tuples and listsjs\x03niln\x08\x00\x90gWs\x1f\x1f\xc5\x01F@\xb4\xd6Q\xeb\x85\x1e\xb8afb\xff\xff\xfa\x8eF\xc0u\xd333333n\x05\x01ch\x09\x0atl\x00\x00\x00\x03t\x00\x00\x00\x03s\x01am\x00\x00\x00\x03maps\x04alsoh\x03m\x00\x00\x00\x06tuplesl\x00\x00\x00\x01m\x00\x00\x00\x03andjl\x00\x00\x00\x01m\x00\x00\x00\x05listsjs\x04withm\x00\x00\x00\x08binariest\x00\x00\x00\x02s\x01am\x00\x00\x00\x0aanotheronea\x03m\x00\x00\x00\x08int keyst\x00\x00\x00\x01h\x01s\x09somethingm\x00\x00\x00\x04elsejj"));*/ 1609 1610 foreach (i; 0 .. 256) 1611 { 1612 assert(ETFBuffer.serialize(i).bytes == cast(ubyte[]) "\x83a" ~ cast(ubyte) i); 1613 assert(ETFBuffer.deserialize!ubyte(cast(ubyte[]) "\x83a" ~ cast(ubyte) i) == i); 1614 } 1615 assert(ETFBuffer.serialize(1024).bytes == cast(ubyte[]) "\x83b\x00\x00\x04\x00"); 1616 assert(ETFBuffer.serialize(-2147483648).bytes == cast(ubyte[]) "\x83b\x80\x00\x00\x00"); 1617 assert(ETFBuffer.serialize(2147483647).bytes == cast(ubyte[]) "\x83b\x7f\xff\xff\xff"); 1618 assert(ETFBuffer.serialize(2147483648UL).bytes == cast(ubyte[]) "\x83n\x04\x00\x00\x00\x00\x80"); 1619 assert(ETFBuffer.serialize(1230941823049123411UL) 1620 .bytes == cast(ubyte[]) "\x83n\x08\x00S\xc6\x03\xf6\x10/\x15\x11"); 1621 assert(ETFBuffer.serialize(-2147483649L).bytes == cast(ubyte[]) "\x83n\x04\x01\x01\x00\x00\x80"); 1622 assert(ETFBuffer.serialize(-123094182304912341L) 1623 .bytes == cast(ubyte[]) "\x83n\x08\x01\xd5\x933\xb2\x81Q\xb5\x01"); 1624 1625 assert(ETFBuffer.serialize(variantArray(1, "two", 3.0, "four", ["five"])).bytes == cast(ubyte[]) "\x83l\x00\x00\x00\x05a\x01m\x00\x00\x00\x03twoF@\x08\x00\x00\x00\x00\x00\x00m\x00\x00\x00\x04fourl\x00\x00\x00\x01m\x00\x00\x00\x04fivejj"); 1626 1627 assert(ETFBuffer.serialize(null).bytes == cast(ubyte[]) "\x83s\x03nil"); 1628 assert(ETFBuffer.serialize(true).bytes == cast(ubyte[]) "\x83s\x04true"); 1629 assert(ETFBuffer.serialize(false).bytes == cast(ubyte[]) "\x83s\x05false"); 1630 1631 assert(ETFBuffer.serialize("hello world").bytes == cast(ubyte[]) "\x83m\x00\x00\x00\x0bhello world"); 1632 assert(ETFBuffer.serialize("hello\0 world") 1633 .bytes == cast(ubyte[]) "\x83m\x00\x00\x00\x0chello\0 world"); 1634 1635 assert(ETFBuffer.serialize(tuple(1, 2, 3)).bytes == cast(ubyte[]) "\x83h\x03a\x01a\x02a\x03"); 1636 1637 assert(ETFBuffer.serialize("hello world").bytes == cast(ubyte[]) "\x83m\x00\x00\x00\x0bhello world"); 1638 assert(ETFBuffer.serialize("hello world\u202e") 1639 .bytes == cast(ubyte[]) "\x83m\x00\x00\x00\x0ehello world\xe2\x80\xae"); 1640 1641 struct Message 1642 { 1643 enum Type 1644 { 1645 text, 1646 image 1647 } 1648 1649 string text; 1650 string url; 1651 Type type; 1652 } 1653 1654 Message msg; 1655 msg.text = "Hello"; 1656 msg.type = Message.Type.image; 1657 assert(ETFBuffer.serialize(msg).bytes == cast(ubyte[]) "\x83t\x00\x00\x00\x03m\x00\x00\x00\x04textm\x00\x00\x00\x05Hellom\x00\x00\x00\x03urlm\x00\x00\x00\x00m\x00\x00\x00\x04typea\x01"); 1658 } 1659 1660 unittest 1661 { 1662 import discord.w.gateway; 1663 import discord.w.types; 1664 1665 struct TestFrame(T) 1666 { 1667 T d; 1668 } 1669 1670 //ETFBuffer.deserialize!(TestFrame!PresenceUpdate)(cast(ubyte[]) "\x83t\x00\x00\x00\x04d\x00\x01dt\x00\x00\x00\x06d\x00\x04gamet\x00\x00\x00\x0ad\x00\x06assetst\x00\x00\x00\x02d\x00\x0blarge_imagem\x00\x00\x000spotify:ee4bc034f20c9681516cb4c1e62d2c7ac273483bd\x00\x0alarge_textm\x00\x00\x00\x10Metal Resistanced\x00\x07detailsm\x00\x00\x00\x12Road of Resistanced\x00\x05flagsa0d\x00\x04namem\x00\x00\x00\x07Spotifyd\x00\x05partyt\x00\x00\x00\x01d\x00\x02idm\x00\x00\x00\x1aspotify:142041528875876352d\x00\x0asession_idm\x00\x00\x00 8e2664f120d92d911731afcddf5551b2d\x00\x05statem\x00\x00\x00 BABYMETAL; Herman Li; Sam Totmand\x00\x07sync_idm\x00\x00\x00\x161A41ABZ7cZsujSRJZYLMold\x00\x0atimestampst\x00\x00\x00\x02d\x00\x03endn\x06\x00\xb3\x16uma\x01d\x00\x05startn\x06\x00A2pma\x01d\x00\x04typea\x02d\x00\x08guild_idn\x08\x00\x00\x00\x82\xe3\xa8L\xb5\x04d\x00\x04nickm\x00\x00\x00\x07repskekd\x00\x05rolesl\x00\x00\x00\x07n\x08\x00\x00\x00B\x87\xa2M\xb5\x04n\x08\x00\x01\x00\xc0\xbcaC\xce\x04n\x08\x00\x00\x00\xc0\xcd\x03>\x0c\x05n\x08\x00\x0a\x00D*\x8c\x8b\x1d\x05n\x08\x00\x00\x00@]|O/\x05n\x08\x00\x1e\x00BN\x91E\xa9\x05n\x08\x00\x00\x00\x82^\xa1\x82\xaa\x05jd\x00\x06statusd\x00\x06onlined\x00\x04usert\x00\x00\x00\x01d\x00\x02idn\x08\x00\x00\x00\x00\x98\x04\xa2\xf8\x01d\x00\x02opa\x00d\x00\x01sa\xccd\x00\x01td\x00\x0fPRESENCE_UPDATE"); 1671 //ETFBuffer.deserialize!(TestFrame!Message)(cast(ubyte[]) "\x83t\x00\x00\x00\x04d\x00\x01dt\x00\x00\x00\x10m\x00\x00\x00\x08activityt\x00\x00\x00\x02m\x00\x00\x00\x08party_idm\x00\x00\x00\x1aspotify:142041528875876352m\x00\x00\x00\x04typea\x03m\x00\x00\x00\x0battachmentsjm\x00\x00\x00\x06authort\x00\x00\x00\x04m\x00\x00\x00\x06avatarm\x00\x00\x00 ee07fc538dfa22501cccdd49afc6dbe5m\x00\x00\x00\x0ddiscriminatorm\x00\x00\x00\x040001m\x00\x00\x00\x02idn\x08\x00\x00\x00\x00\x98\x04\xa2\xf8\x01m\x00\x00\x00\x08usernamem\x00\x00\x00\x0arespektivem\x00\x00\x00\x0achannel_idn\x08\x00\x00\x00\x82\xe3\xa8L\xb5\x04m\x00\x00\x00\x07contentm\x00\x00\x00\x00m\x00\x00\x00\x10edited_timestampd\x00\x03nilm\x00\x00\x00\x06embedsjm\x00\x00\x00\x02idn\x08\x00\x0c\x00@1f\xa9\xb2\x05m\x00\x00\x00\x10mention_everyoned\x00\x05falsem\x00\x00\x00\x0dmention_rolesjm\x00\x00\x00\x08mentionsjm\x00\x00\x00\x05noncem\x00\x00\x00\x12410576769154809856m\x00\x00\x00\x06pinnedd\x00\x05falsem\x00\x00\x00\x09timestampm\x00\x00\x00 2018-02-06T23:25:30.693000+00:00m\x00\x00\x00\x03ttsd\x00\x05falsem\x00\x00\x00\x04typea\x00d\x00\x02opa\x00d\x00\x01sa\xcdd\x00\x01td\x00\x0eMESSAGE_CREATE"); 1672 }