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 }