1 module cerealed.cereal;
2 
3 public import cerealed.attrs;
4 import cerealed.traits;
5 import std.traits;
6 import std.conv;
7 import std.algorithm;
8 import std.range;
9 import std.typetuple;
10 
11 class CerealException: Exception {
12     this(string msg) @safe pure {
13         super(msg);
14     }
15 }
16 
17 enum CerealType { WriteBytes, ReadBytes };
18 
19 void grain(C, T)(auto ref C cereal, ref T val) if(isCereal!C && is(T == ubyte)) {
20     cereal.grainUByte(val);
21 }
22 
23 //catch all signed numbers and forward to reinterpret
24 void grain(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && !is(T == enum) &&
25                                                         (isSigned!T || isBoolean!T ||
26                                                          is(T == char) || isFloatingPoint!T)) {
27     cereal.grainReinterpret(val);
28 }
29 
30 // If the type is an enum, get the unqualified base type and cast it to that.
31 void grain(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == enum)) {
32     alias Unqual!(OriginalType!(T)) BaseType;
33     cereal.grain( cast(BaseType)val );
34 }
35 
36 
37 void grain(C, T)(auto ref C cereal, ref T val) @trusted if(isCereal!C && is(T == wchar)) {
38     cereal.grain(*cast(ushort*)&val);
39 }
40 
41 void grain(C, T)(auto ref C cereal, ref T val) @trusted if(isCereal!C && is(T == dchar)) {
42     cereal.grain(*cast(uint*)&val);
43 }
44 
45 void grain(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == ushort)) {
46     ubyte valh = (val >> 8);
47     ubyte vall = val & 0xff;
48     cereal.grainUByte(valh);
49     cereal.grainUByte(vall);
50     val = (valh << 8) + vall;
51 }
52 
53 void grain(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == uint)) {
54     ubyte val0 = (val >> 24);
55     ubyte val1 = cast(ubyte)(val >> 16);
56     ubyte val2 = cast(ubyte)(val >> 8);
57     ubyte val3 = val & 0xff;
58     cereal.grainUByte(val0);
59     cereal.grainUByte(val1);
60     cereal.grainUByte(val2);
61     cereal.grainUByte(val3);
62     val = (val0 << 24) + (val1 << 16) + (val2 << 8) + val3;
63 }
64 
65 void grain(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == ulong)) {
66     T newVal;
67     for(int i = 0; i < T.sizeof; ++i) {
68         immutable shiftBy = 64 - (i + 1) * T.sizeof;
69         ubyte byteVal = (val >> shiftBy) & 0xff;
70         cereal.grainUByte(byteVal);
71         newVal |= (cast(T)byteVal << shiftBy);
72     }
73     val = newVal;
74 }
75 
76 enum hasByteElement(T) = is(Unqual!(ElementType!T): ubyte);
77 
78 void grain(C, T, U = ushort)(auto ref C cereal, ref T val) @trusted if(isCerealiser!C &&
79                                                                        isInputRange!T && !isInfinite!T &&
80                                                                        !is(T == string) &&
81                                                                        !isStaticArray!T &&
82                                                                        !isAssociativeArray!T) {
83     enum hasLength = is(typeof(() { auto l = val.length; }));
84     static assert(hasLength, text("Only InputRanges with .length accepted, not the case for ",
85                                   fullyQualifiedName!T));
86     U length = cast(U)val.length;
87     assert(length == val.length, "overflow");
88     cereal.grain(length);
89 
90     static if(hasSlicing!(Unqual!T) && hasByteElement!T)
91         cereal.grainRaw(cast(ubyte[])val.array);
92     else
93         foreach(ref e; val) cereal.grain(e);
94 }
95 
96 void grain(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && isStaticArray!T) {
97     static if(hasByteElement!T)
98         cereal.grainRaw(cast(ubyte[])val);
99     else
100         foreach(ref e; val) cereal.grain(e);
101 }
102 
103 void grain(C, T, U = ushort)(auto ref C cereal, ref T val) @trusted if(isDecerealiser!C &&
104                                                                        !isStaticArray!T &&
105                                                                        isOutputRange!(T, ubyte)) {
106     U length = void;
107     cereal.grain(length);
108 
109     static if(isArray!T) {
110         decerealiseArrayImpl(cereal, val, length);
111     } else {
112         for(U i = 0; i < length; ++i) {
113             ubyte b = void;
114             cereal.grain(b);
115 
116             enum hasOpOpAssign = is(typeof(() { val ~= b; }));
117             static if(hasOpOpAssign) {
118                 val ~= b;
119             } else {
120                 val.put(b);
121             }
122         }
123     }
124 }
125 
126 private void decerealiseArrayImpl(C, T, U = ushort)(auto ref C cereal, ref T val, U length) @safe
127     if(is(T == E[], E)) {
128 
129     static if(hasByteElement!T) {
130         val = cereal.grainRaw(length).dup;
131     } else {
132         if(val.length != length) val.length = cast(uint)length;
133         assert(length == val.length, "overflow");
134 
135         foreach(ref e; val) cereal.grain(e);
136     }
137 }
138 
139 void grain(C, T, U = ushort)(auto ref C cereal, ref T val) @trusted if(isDecerealiser!C &&
140                                                                        !isOutputRange!(T, ubyte) &&
141                                                                        isDynamicArray!T && !is(T == string)) {
142     U length = void;
143     cereal.grain(length);
144     decerealiseArrayImpl(cereal, val, length);
145 }
146 
147 void grain(C, T, U = ushort)(auto ref C cereal, ref T val) @trusted if(isCereal!C && is(T == string)) {
148     U length = cast(U)val.length;
149     assert(length == val.length, "overflow");
150     cereal.grain(length);
151 
152     static if(isCerealiser!C)
153         cereal.grainRaw(cast(ubyte[])val);
154     else
155         val = cast(string)cereal.grainRaw(length);
156 }
157 
158 
159 void grain(C, T, U = ushort)(auto ref C cereal, ref T val) @trusted if(isCereal!C && isAssociativeArray!T) {
160     U length = cast(U)val.length;
161     assert(length == val.length, "overflow");
162     cereal.grain(length);
163     const keys = val.keys;
164 
165     for(U i = 0; i < length; ++i) {
166         KeyType!T k = keys.length ? keys[i] : KeyType!T.init;
167         auto v = keys.length ? val[k] : ValueType!T.init;
168 
169         cereal.grain(k);
170         cereal.grain(v);
171         val[k] = v;
172     }
173 }
174 
175 void grain(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && isPointer!T) {
176     import std.traits;
177     alias ValueType = PointerTarget!T;
178     static if(isDecerealiser!C) {
179         if(val is null) val = new ValueType;
180     }
181     cereal.grain(*val);
182 }
183 
184 private template canCall(C, T, string func) {
185     enum canCall = is(typeof(() { auto cer = C(); auto val = T.init; mixin("val." ~ func ~ "(cer);"); }));
186     static if(!canCall && __traits(hasMember, T, func)) {
187         pragma(msg, "Warning: '" ~ func ~
188                "' function defined for ", T, ", but does not compile for Cereal ", C);
189     }
190 }
191 
192 void grain(C, T)(auto ref C cereal, ref T val) @trusted if(isCereal!C && isAggregateType!T &&
193                                                            !isInputRange!T && !isOutputRange!(T, ubyte)) {
194     enum canAccept   = canCall!(C, T, "accept");
195     enum canPreBlit = canCall!(C, T, "preBlit");
196     enum canPostBlit = canCall!(C, T, "postBlit");
197 
198     static if(canAccept) { //custom serialisation
199         static assert(!canPostBlit && !canPreBlit, "Cannot define both accept and pre/postBlit");
200         val.accept(cereal);
201     } else { //normal serialisation, go through each member and possibly serialise
202         static if(canPreBlit) {
203             val.preBlit(cereal);
204         }
205 
206         cereal.grainAllMembers(val);
207         static if(canPostBlit) { //semi-custom serialisation, do post blit
208             val.postBlit(cereal);
209         }
210     }
211 }
212 
213 void grainAllMembers(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == struct)) {
214     cereal.grainAllMembersImpl!T(val);
215 }
216 
217 
218 void grainAllMembers(C, T)(auto ref C cereal, ref T val) @trusted if(isCereal!C && is(T == class)) {
219     static if(isCerealiser!C) {
220         assert(val !is null, "null value cannot be serialised");
221     }
222 
223     enum hasDefaultConstructor = is(typeof(() { val = new T; }));
224     static if(hasDefaultConstructor && isDecerealiser!C) {
225         if(val is null) val = new T;
226     } else {
227         assert(val !is null, text("Cannot deserialise into null value. ",
228                                   "Possible cause: no default constructor for ",
229                                   fullyQualifiedName!T, "."));
230     }
231 
232     cereal.grainClass(val);
233 }
234 
235 
236 void grainMemberWithAttr(string member, C, T)(auto ref C cereal, ref T val) @trusted if(isCereal!C) {
237     /**(De)serialises one member taking into account its attributes*/
238     enum noCerealIndex = staticIndexOf!(NoCereal, __traits(getAttributes,
239                                                            __traits(getMember, val, member)));
240     enum rawArrayIndex = staticIndexOf!(RawArray, __traits(getAttributes,
241                                                            __traits(getMember, val, member)));
242     //only serialise if the member doesn't have @NoCereal
243     static if(noCerealIndex == -1) {
244         alias bitsAttrs = Filter!(isABitsStruct, __traits(getAttributes,
245                                                       __traits(getMember, val, member)));
246         static assert(bitsAttrs.length == 0 || bitsAttrs.length == 1,
247                       "Too many Bits!N attributes!");
248 
249         alias arrayLengths = Filter!(isArrayLengthStruct,
250                                      __traits(getAttributes,
251                                               __traits(getMember, val, member)));
252         static assert(arrayLengths.length == 0 || arrayLengths.length == 1,
253                       "Too many ArrayLength attributes");
254 
255         alias lengthInBytes = Filter!(isLengthInBytesStruct,
256                                       __traits(getAttributes,
257                                                __traits(getMember, val, member)));
258         static assert(lengthInBytes.length == 0 || lengthInBytes.length == 1,
259                       "Too many LengthInBytes attributes");
260 
261         static if(bitsAttrs.length == 1) {
262 
263             grainWithBitsAttr!(member, bitsAttrs[0])(cereal, val);
264 
265         } else static if(rawArrayIndex != -1) {
266 
267             cereal.grainRawArray(__traits(getMember, val, member));
268 
269         } else static if(arrayLengths.length > 0) {
270 
271             grainWithArrayLengthAttr!(member, arrayLengths[0].member)(cereal, val);
272 
273         } else static if(lengthInBytes.length > 0) {
274 
275             grainWithLengthInBytesAttr!(member, lengthInBytes[0].member)(cereal, val);
276 
277         } else {
278 
279             cereal.grain(__traits(getMember, val, member));
280 
281         }
282     }
283 }
284 
285 private void grainWithBitsAttr(string member, alias bitsAttr, C, T)(
286     auto ref C cereal, ref T val) @safe if(isCereal!C) {
287 
288     enum numBits = getNumBits!(bitsAttr);
289     enum sizeInBits = __traits(getMember, val, member).sizeof * 8;
290     static assert(numBits <= sizeInBits,
291                   text(fullyQualifiedName!T, ".", member, " is ", sizeInBits,
292                        " bits long, which is not enough to store @Bits!", numBits));
293     cereal.grainBitsT(__traits(getMember, val, member), numBits);
294 }
295 
296 private void grainWithArrayLengthAttr(string member, string lengthMember, C, T)
297     (auto ref C cereal, ref T val) @safe if(isCereal!C) {
298 
299     checkArrayAttrType!member(cereal, val);
300 
301     static if(isCerealiser!C) {
302         cereal.grainRawArray(__traits(getMember, val, member));
303     } else {
304         immutable length = lengthOfArray!(member, lengthMember)(cereal, val);
305         alias E = ElementType!(typeof(__traits(getMember, val, member)));
306 
307         if(length * E.sizeof  > cereal.bytesLeft) {
308             throw new CerealException(text("@ArrayLength of ", length, " units of type ",
309                                            E.stringof,
310                                            " (", length * E.sizeof, " bytes) ",
311                                            "larger than remaining byte array (",
312                                            cereal.bytesLeft, " bytes)"));
313         }
314 
315         mixin(q{__traits(getMember, val, member).length = length;});
316 
317         foreach(ref e; __traits(getMember, val, member)) cereal.grain(e);
318     }
319 }
320 
321 private void grainWithLengthInBytesAttr(string member, string lengthMember, C, T)
322     (auto ref C cereal, ref T val) @safe if(isCereal!C) {
323 
324     checkArrayAttrType!member(cereal, val);
325 
326     static if(isCerealiser!C) {
327         cereal.grainRawArray(__traits(getMember, val, member));
328     } else {
329         immutable length = lengthOfArray!(member, lengthMember)(cereal, val); //error handling
330 
331         if(length > cereal.bytesLeft) {
332             alias E = ElementType!(typeof(__traits(getMember, val, member)));
333             throw new CerealException(text("@LengthInBytes of ", length, " bytes ",
334                                            "larger than remaining byte array (",
335                                            cereal.bytesLeft, " bytes)"));
336         }
337 
338         __traits(getMember, val, member).length = 0;
339 
340         while(cereal.bytesLeft) {
341             __traits(getMember, val, member).length++;
342             cereal.grain(__traits(getMember, val, member)[$ - 1]);
343         }
344     }
345 }
346 
347 private void checkArrayAttrType(string member, C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C) {
348     alias M = typeof(__traits(getMember, val, member));
349     static assert(is(M == E[], E),
350                   text("@ArrayLength and @LengthInBytes not valid for ", member,
351                        ": they can only be used on slices"));
352 }
353 
354 
355 private int lengthOfArray(string member, string lengthMember, C, T)(auto ref C cereal, ref T val)
356     @safe if(isCereal!C) {
357     int _tmpLen;
358     mixin(q{with(val) _tmpLen = cast(int)(} ~ lengthMember ~ q{);});
359 
360     if(_tmpLen < 0)
361         throw new CerealException(text("@LengthInBytes resulted in negative length ", _tmpLen));
362 
363     return _tmpLen;
364 }
365 
366 void grainRawArray(C, T)(auto ref C cereal, ref T[] val) @trusted if(isCereal!C) {
367     //can't use virtual functions due to template parameter
368     static if(isDecerealiser!C) {
369         val.length = 0;
370         while(cereal.bytesLeft()) {
371             val.length++;
372             cereal.grain(val[$ - 1]);
373         }
374     } else {
375         foreach(ref t; val) cereal.grain(t);
376     }
377 }
378 
379 
380 /**
381  * To be used when the length of the array is known at run-time based on the value
382  * of a part of byte stream.
383  */
384 void grainLengthedArray(C, T)(auto ref C cereal, ref T[] val, long length) {
385     val.length = cast(typeof(val.length))length;
386     foreach(ref t; val) cereal.grain(t);
387 }
388 
389 
390 package void grainClassImpl(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == class)) {
391     //do base classes first or else the order is wrong
392     cereal.grainBaseClasses(val);
393     cereal.grainAllMembersImpl!T(val);
394 }
395 
396 private void grainBitsT(C, T)(auto ref C cereal, ref T val, int bits) @safe if(isCereal!C) {
397     uint realVal = val;
398     cereal.grainBits(realVal, bits);
399     val = cast(T)realVal;
400 }
401 
402 private void grainReinterpret(C, T)(auto ref C cereal, ref T val) @trusted if(isCereal!C) {
403     auto ptr = cast(CerealPtrType!T)(&val);
404     cereal.grain(*ptr);
405 }
406 
407 private void grainBaseClasses(C, T)(auto ref C cereal, ref T val) @safe if(isCereal!C && is(T == class)) {
408     foreach(base; BaseTypeTuple!T) {
409         cereal.grainAllMembersImpl!base(val);
410     }
411 }
412 
413 
414 private void grainAllMembersImpl(ActualType, C, ValType)(auto ref C cereal, ref ValType val) @trusted
415 if(isCereal!C) {
416     foreach(member; __traits(derivedMembers, ActualType)) {
417         //makes sure to only serialise members that make sense, i.e. data
418         enum isMemberVariable = is(typeof(() {
419                                            __traits(getMember, val, member) = __traits(getMember, val, member).init;
420                                        }));
421         static if(isMemberVariable) {
422             cereal.grainMemberWithAttr!member(val);
423         }
424     }
425 }
426 
427 private template CerealPtrType(T) {
428     static if(is(T == bool) || is(T == char)) {
429         alias ubyte* CerealPtrType;
430     } else static if(is(T == float)) {
431         alias uint* CerealPtrType;
432     } else static if(is(T == double)) {
433         alias ulong* CerealPtrType;
434     } else {
435        alias Unsigned!T* CerealPtrType;
436     }
437 }