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 }