1 /// General-purpose binary serializer and deserializer for arbitrary data types.
2 module tern.serialization;
3 
4 // TODO: Json, yaml, etc.
5 public import tern.object : Endianness;
6 import tern.object : factory, makeEndian;
7 import tern.traits;
8 
9 public:
10 static:
11 pure:
12 /**
13  * Recursively serializes `val` with the provided endianness.
14  *
15  * Params:
16  *  RAW = If true, arrays won't have their length serialized. Defaults to false.
17  *  val = The value to be serialized.
18  *  endianness = The endianness to be serialized to. Defaults to native.
19  * 
20  * Returns:
21  *  Serialized byte array.
22  */
23 @trusted ubyte[] serialize(bool RAW = false, T)(T val, Endianness endianness = Endianness.Native)
24 {
25     static if (isArray!T)
26     {
27         ubyte[] bytes;
28         static if (!RAW)
29             bytes ~= val.length.makeEndian(endianness).serialize!RAW();
30             
31         foreach (u; val)
32             bytes ~= u.makeEndian(endianness).serialize!RAW();
33         return bytes;
34     }
35     else static if (hasChildren!T)
36     {
37         ubyte[] bytes;
38         foreach (field; Fields!T)
39         {
40             static if (!isEnum!(getChild!(T, field)))
41                 bytes ~= __traits(getMember, val, field).makeEndian(endianness).serialize!RAW();
42         }
43         return bytes;
44     }
45     else
46     {
47         T t = val.makeEndian(endianness);
48         return (cast(ubyte*)&t)[0..T.sizeof].dup;
49     }
50 }
51 
52 /**
53  * Recursively deserializes `val` with the provided endianness.
54  *
55  * Params:
56  *  T = Type to deserialize to.
57  *  bytes = The bytes to be deserialized.
58  *  len = The length of the deserialized data as it were if a `T[]`. Defaults to -1
59  *  endianness = The endianness to be serialized to. Defaults to native.
60  * 
61  * Returns:
62  *  The deserialized value of `T`.
63  */
64 @trusted deserialize(T, B)(B bytes, size_t len = -1, Endianness endianness = Endianness.Native)
65     if (isDynamicArray!B && (is(ElementType!B == ubyte) || is(ElementType!B == byte)))
66 {
67     T ret = factory!T;
68     size_t offset;
69     static if (isArray!T)
70     {
71         static if (isDynamicArray!T && isMutable!(ElementType!T))
72         {
73             if (len == -1)
74                 len = deserialize!size_t(bytes[offset..(offset += size_t.sizeof)]).makeEndian(endianness);
75             ret = new T(len);
76         }
77         else static if (isStaticArray!T)
78         {
79             len = Length!T;
80         }
81 
82         if (bytes.length < len * ElementType!T.sizeof)
83             bytes ~= new ubyte[(len * ElementType!T.sizeof) - bytes.length];
84 
85         foreach (i; 0..len)
86         static if (isMutable!(ElementType!T))
87             ret[i] = bytes[offset..(offset += ElementType!T.sizeof)].deserialize!(ElementType!T).makeEndian(endianness);
88         else
89             ret ~= bytes[offset..(offset += ElementType!T.sizeof)].deserialize!(ElementType!T).makeEndian(endianness);
90 
91         return ret;
92     }
93     else static if (hasChildren!T)
94     {
95         if (bytes.length < sizeof!T)
96             bytes ~= new ubyte[sizeof!T - bytes.length];
97 
98         foreach (field; Fields!T)
99         {
100             static if (isMutable!(getChild!(T, field)))
101                 __traits(getMember, ret, field) = deserialize!(TypeOf!(T, field))(bytes[offset..(offset += TypeOf!(T, field).sizeof)]).makeEndian(endianness);
102         }
103         return ret;
104     }
105     else
106     {
107         if (bytes.length < T.sizeof)
108             bytes ~= new ubyte[T.sizeof - bytes.length];
109 
110         return (*cast(T*)bytes[offset..(offset += T.sizeof)].ptr).makeEndian(endianness);
111     }
112 }
113 
114 /**
115  * Pads `data` right to `size` with zeroes.
116  *
117  * Params:
118  *  data = The bytes to be padded.
119  *  size = The size of `data` after padding.
120  */
121 void sachp(ref ubyte[] data, size_t size)
122 {
123     data ~= new ubyte[data.length % size == 0 ? 0 : size - (data.length % size)];
124 }
125 
126 /**
127  * Pads `data` right to `size` with metadata after for later unpadding.
128  *
129  * Params:
130  *  data = The bytes to be padded.
131  *  size = The size of `data` after padding.
132  */
133 void vacpp(ref ubyte[] data, size_t size)
134 {
135     if (size < 8 || size > 2 ^^ 24)
136         throw new Throwable("Invalid vacpp padding size!");
137 
138     size_t margin = size - (data.length % size) + size;
139     data ~= new ubyte[margin == size ? 0 : margin];
140     data[$-5..$] = cast(ubyte[])"VacPp";
141     data[$-8..$-5] = margin.serialize!true()[0..3];
142 }
143 
144 /**
145  * Unpads `data` assuming it was padded previously with `vacpp`.
146  *
147  * Params:
148  *  data = The bytes to be unpadded.
149  */
150 void unvacpp(ref ubyte[] data) 
151 {
152     if (data.length < 8)
153         throw new Throwable("Invalid data length for vacpp!");
154 
155     if (data[$-5..$] != cast(ubyte[])"VacPp")
156         throw new Throwable("Invalid padding signature in vacpp!");
157 
158     uint margin = data[$-8..$-5].deserialize!uint();
159     data = data[0..(data.length - margin)];
160 }
161 
162 /**
163  * Encodes `val` as a variable length integer.
164  *
165  * Params:
166  *  val = The integer to be encoded.
167  *
168  * Returns:
169  *  Array of bytes representing `val` in its variable length form.
170  */
171 ubyte[] varEncode(T)(T val)
172     if (isIntegral!T)
173 {
174     if (val == 0)
175         return [0];
176 
177     val <<= 3;
178     ubyte[] bytes;
179     while (val > 0)
180     {
181         bytes ~= cast(ubyte)(val & 0xFF);
182         val >>= 8;
183     }
184     // Encode the number of bytes to read after this as the first 3 bits
185     bytes[0] |= ((bytes.length - 1) & 0b0000_0111);
186     return bytes;
187 }
188 
189 /**
190  * Decodes `bytes` as a variable length integer into a ulong.
191  *
192  * Params:
193  *  bytes = Bytes representing a variable length integer.
194  * 
195  * Returns:
196  *  A ulong as `bytes` decoded.
197  */
198 ulong varDecode(ubyte[] bytes)
199 {
200     ulong ret;
201     // Extract the number of bytes used to represent the value from the first byte
202     ubyte numBytes = (bytes[0] & 0b0000_0111) + 1;
203     foreach_reverse (b; bytes[1..numBytes])
204         ret = (ret << 8) | b;
205     ret = (ret << 8) | bytes[0];
206     return ret >> 3;
207 }