1 /// Deserialize a message buffer into a D struct, class or array.
2 module deserialize;
3 
4 import std.conv, std.traits, std.string, std.outbuffer;
5 
6 import leb128, common;
7 
8 /// Deserialize top-level, either array, associative array or struct from
9 /// a given ubyte array.
10 /// For aggregate types like structs, deserialize each member.
11 auto fromMsgBuffer(T, MsgBufferType E = MsgBufferType.Var)(const ubyte[] msg) {
12 	size_t processed = 0;
13 	return fromMsgBuffer!(T, E)(msg, processed);
14 }	// fromMsgBuffer()
15 
16 /// Deserialize top-level, either array, associative array or struct from
17 /// a given OutBuffer.
18 /// For aggregate types like structs, deserialize each member.
19 auto fromMsgBuffer(T, MsgBufferType E = MsgBufferType.Var)(const OutBuffer buf) {
20 	size_t processed = 0;
21 	return fromMsgBuffer!(T, E)(buf.toBytes, processed);
22 }	// fromMsgBuffer()
23 
24 // Deserialise a number from an integer value.
25 pragma(inline, true)
26 const(T) deserializeInt(MsgBufferType E, T)(const ubyte[] msg, ref size_t processed) {
27 	static if (E == MsgBufferType.Flat) {
28 		// See: https://github.com/KabukiStarship/KabukiToolkit/wiki/Fastest-Method-to-Align-Pointers
29 		processed += (-processed) & (T.alignof - 1);
30 		immutable n = *cast(T *)&msg[processed];
31 		processed += n.sizeof;
32 	} else {
33 		immutable n = fromLEB128!T(msg, processed);
34 	}
35 	return n;
36 }	// deserializeInt()
37 
38 // Deserialize a single value, either a string, a floating point value or a scalar, e.g.
39 // int, bool, long, etc. If it is neither, then forward to top-level deserialize.
40 auto deserializeValue(T, MsgBufferType E)(const ubyte[] msg, ref size_t processed) {
41 	if (processed >= msg.length) {
42 		// check for out of data, e.g. when trying to read a newer message format with more
43 		// fields from data that has been serialised using an older message format version
44 		T val;
45 		return val;
46 	}
47 	static if (isSomeString!(T)) {
48 		immutable n = deserializeInt!(E, uint)(msg, processed);
49 		immutable s = processed;
50 		processed += n;
51 		return to!T(msg[s..processed].assumeUTF);
52 	} else static if (isBoolean!(T)) {
53 		immutable n = processed;
54 		processed += T.sizeof;
55 		static assert(ubyte.sizeof == bool.sizeof);
56 		return *cast(T *)&msg[n];
57 	} else static if (is(T == enum)) {
58 		static if (__traits(compiles, to!int(EnumMembers!T[0]))) {
59 			static if (E == MsgBufferType.Flat) {
60 				processed += (-processed) & (int.alignof - 1);
61 				immutable n = processed;
62 				processed += int.sizeof;
63 				return *cast(T *)&msg[n];
64 			} else {
65 				return to!T(fromLEB128!int(msg, processed));
66 			}
67 		} else {
68 			static assert(0, "only integral enums supported for enum " ~ T.stringof);
69 		}
70 	} else static if (isScalarType!(T) || isFloatingPoint!(T)) {
71 		static if (!isFloatingPoint!(T) && T.sizeof > 1 && E == MsgBufferType.Var) {
72 			return fromLEB128!T(msg, processed);
73 		} else {
74 			processed += (-processed) & (T.alignof - 1);
75 			immutable n = processed;
76 			processed += T.sizeof;
77 			return *cast(T *)&msg[n];
78 		}
79 	} else static if (isArray!(T)) {
80 		T val;
81 		auto n = deserializeInt!(E, uint)(msg, processed);
82 		static if (isDynamicArray!(T))
83 			val = new typeof(val[0])[n];
84 		static if (isScalarType!(typeof(val[0])) || isFloatingPoint!(typeof(val[0]))) {
85 			static if (!isFloatingPoint!(typeof(val[0])) && typeof(val[0]).sizeof > 1 && E == MsgBufferType.Var) {
86 				arrayFromLEB128!T(msg, processed, val);
87 			} else {
88 				processed += (-processed) & (T.alignof - 1);
89 				import core.stdc.string : memcpy;
90 				memcpy(&val[0], &msg[processed], val.length * typeof(val[0]).sizeof);
91 				// I think memcpy() is faster than the below code, but I might be wrong...
92 				// val[0..n][] = (cast(const(typeof(val[0]))*)&msg[processed])[0..n];
93 				processed += val.length * typeof(val[0]).sizeof;
94 			}
95 		} else {
96 			static foreach (i; 0..n)
97 				val[i] = deserializeValue!(typeof(val[i]), E)(msg, processed);
98 		}
99 		return val;
100 	} else static if (isAssociativeArray!(T)) {
101 		T val;
102 		immutable n = deserializeInt!(E, uint)(msg, processed);
103 		foreach (i; 0..n) {
104 			auto k = deserializeValue!(KeyType!T, E)(msg, processed);
105 			val[k] = deserializeValue!(ValueType!T, E)(msg, processed);
106 		}
107 		return val;
108 	} else static if (is(T == struct) && T.stringof.startsWith("Oneof")) {
109 		T val;
110 		immutable oneOfIdx = deserializeInt!(E, ubyte)(msg, processed);
111 		if (oneOfIdx > 0) {
112 			static foreach (idx, s; T.tupleof) {{
113 				alias OneofMemberType = typeof(s);
114 				enum OneofMemberName = __traits(identifier, s);
115 				static if (!is(OneofMemberType == TypeInfo)) {
116 					if (OneofMemberName.startsWith("___data_field_" ~ to!string(oneOfIdx-1))) {
117 						val = deserializeValue!(OneofMemberType, E)(msg, processed);
118 					}
119 				}
120 			}}
121 		}
122 		return val;
123 	} else {
124 		return fromMsgBuffer!(T, E)(msg, processed);
125 	}
126 }	// deserializeValue()
127 
128 auto fromMsgBuffer(T, MsgBufferType E)(const ubyte[] msg, ref size_t processed) {
129 	T val;
130 	static if (isAggregateType!(T) && !is(T == class)) {
131 		static foreach (v; T.tupleof)
132 			mixin("val." ~ __traits(identifier, v)) = deserializeValue!(typeof(v), E)(msg, processed);
133 	} else static if (isArray!(T) || isAssociativeArray!(T)) {
134 		val = deserializeValue!(T, E)(msg, processed);
135 	} else {
136 		static assert(0, "expected struct or array, not " ~ T.stringof);
137 	}
138 	return val;
139 }	// fromMsgBuffer()