1 /******************************************************************************
2 
3     Home for binary contiguous Serializer. Check the `Serializer` struct
4     documentation for more details.
5 
6     Copyright:
7         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
8         All rights reserved.
9 
10     License:
11         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
12         Alternatively, this file may be distributed under the terms of the Tango
13         3-Clause BSD License (see LICENSE_BSD.txt for details).
14 
15 *******************************************************************************/
16 
17 module ocean.util.serialize.contiguous.Serializer;
18 
19 
20 import ocean.meta.types.Qualifiers;
21 
22 import ocean.util.serialize.contiguous.Contiguous;
23 import ocean.meta.traits.Indirections;
24 import ocean.core.Test;
25 
26 import ocean.core.Buffer;
27 
28 import core.memory;
29 
30 debug(SerializationTrace) import ocean.io.Stdout;
31 
32 /******************************************************************************
33 
34     Binary serializer that generates contiguous structs. It recursively
35     iterates over struct fields copying any array contents into the same
36     byte buffer and clear the array pointer field. Latter is done to avoid
37     accidental access via dangling pointer once that data is read from external
38     source.
39 
40     Arrays of arrays are stored with small optimization, keeping only length
41     part of the slice (as .ptr will be always null)
42 
43     Deserializer later does similar iteration updating all internal pointers.
44 
45 *******************************************************************************/
46 
47 struct Serializer
48 {
49     /**************************************************************************
50 
51         Convenience shortcut
52 
53     **************************************************************************/
54 
55     alias typeof(this) This;
56 
57     /***************************************************************************
58 
59         Serializes the data in src.
60 
61         Note that the serialization process may expand the dst buffer more than
62         ultimately necessary, but it always returns a slice into the buffer
63         that covers bytes of the struct instance itself, not including any of
64         indirections (that are placed after).
65         Therefore calling applications should be careful to use the returned
66         buffer rather than the dst buffer directly.
67 
68         Params:
69             S = type of the struct to dump
70             src = struct to serialize
71             dst = buffer to write to. It is only extended if needed and
72                 never shrunk
73 
74         Returns:
75             the slice to the serialize data
76 
77     ***************************************************************************/
78 
79     public static void[] serialize ( S ) ( ref S src, ref Buffer!(void) dst )
80     out (data)
81     {
82         debug (SerializationTrace)
83         {
84             Stdout.formatln("< serialize!({})(<src>, {}) : {}", S.stringof,
85                 dst[].ptr, data.ptr);
86         }
87     }
88     do
89     {
90         debug (SerializationTrace)
91         {
92             Stdout.formatln("> serialize!({})(<src>, {})",
93                 S.stringof, dst[].ptr);
94         }
95 
96         auto data = This.resize(dst, This.countRequiredSize(src));
97 
98         data[0 .. S.sizeof] = (cast(void*) &src)[0 .. S.sizeof];
99         auto s_root = cast(Unqual!(S)*) data.ptr;
100 
101         static if (containsDynamicArray!(S))
102         {
103             void[] remaining = This.dumpAllArrays(*s_root, data[S.sizeof .. $]);
104 
105             return data[0 .. $ - remaining.length];
106         }
107         else
108         {
109             foreach (i, T; typeof(S.tupleof))
110                 alias ensureValueTypeMember!(S, i) evt;
111 
112             return data[0 .. src.sizeof];
113         }
114     }
115 
116     /// ditto
117     public static void[] serialize ( S, D ) ( ref S src, ref D[] dst )
118     {
119         static assert (D.sizeof == 1,
120             "dst buffer can't be interpreted as void[]");
121         return serialize!(S)(src, *cast(Buffer!(void)*) &dst);
122     }
123 
124     /***************************************************************************
125 
126         In-place serialization that takes advantage of the fact Contiguous
127         instances already have required data layout. All arrays within
128         `src` will be reset to null (and their length to 0) making their data
129         unreachable from original struct. This is done to minimize risk of
130         dangling array pointers.
131 
132         Params:
133             src = contiguous struct instance to serialize
134 
135         Returns:
136             slice of internal `src` byte array after setting all array pointers
137             to null
138 
139     ***************************************************************************/
140 
141     public static void[] serialize ( S ) ( ref Contiguous!(S) src )
142     {
143         This.resetReferences(*src.ptr);
144         return src.data[];
145     }
146 
147     /***************************************************************************
148 
149         Return the serialized length of input
150 
151         Params:
152             S = type of the struct
153             input = struct to get the serialized length of
154 
155         Returns:
156             serialized length of input
157 
158     ***************************************************************************/
159 
160     public static size_t countRequiredSize ( S ) ( ref S input )
161     out (size)
162     {
163         debug (SerializationTrace)
164         {
165             Stdout.formatln("< countRequiredSize!({})(<input>) : {}", S.stringof, cast(size_t)size);
166         }
167     }
168     do
169     {
170         debug (SerializationTrace)
171         {
172             Stdout.formatln("> countRequiredSize!({})(<input>)", S.stringof);
173         }
174 
175         static if (containsDynamicArray!(S))
176         {
177             return input.sizeof + This.countAllArraySize(input);
178         }
179         else
180         {
181             foreach (i, T; typeof(S.tupleof))
182                 alias ensureValueTypeMember!(S, i) evt;
183 
184             return input.sizeof;
185         }
186     }
187 
188     /***************************************************************************
189 
190         Resizes the passed buffer reference in case it is not large enough to
191         store len bytes. If resized, new memory is allocated as ubyte[] chunk so
192         that GC ignores it.
193 
194         Params:
195             buffer = buffer to resize
196             len    = length to resize to
197 
198         Returns:
199             slice to the potentially resized buffer
200 
201     ***************************************************************************/
202 
203     private static void[] resize ( ref Buffer!(void) buffer, size_t len )
204     out (buffer_out)
205     {
206         assert (buffer_out.ptr is buffer[].ptr);
207         assert (buffer_out.length == buffer.length);
208 
209         debug (SerializationTrace)
210         {
211             Stdout.formatln("< resize({}, {}) : ", buffer[].ptr, len,
212                 buffer_out.ptr);
213         }
214     }
215     do
216     {
217         debug (SerializationTrace)
218         {
219             Stdout.formatln("> resize({}, {})", buffer[].ptr, len);
220         }
221 
222         if (len > buffer.length)
223         {
224             if (buffer[].ptr is null)
225             {
226                 buffer.length = len;
227                 GC.setAttr(buffer[].ptr, GC.BlkAttr.NO_SCAN);
228             }
229             else
230                 buffer.length = len;
231         }
232 
233         return buffer[];
234     }
235 
236     /**************************************************************************
237 
238         Calculates the length of ALL serialized dynamic arrays in s.
239 
240         Params:
241             s = S instance to calculate the length of the serialized dynamic
242                 arrays for
243 
244         Returns:
245             the length of the serialized dynamic arrays of s.
246 
247      **************************************************************************/
248 
249     private static size_t countAllArraySize ( S ) ( S s )
250     out (size)
251     {
252         debug (SerializationTrace)
253         {
254             Stdout.formatln("< countAllArraySize!({})(<s>) : {}",
255                 S.stringof, cast(size_t)size);
256         }
257     }
258     do
259     {
260         debug (SerializationTrace)
261         {
262             Stdout.formatln("> countAllArraySize!({})(<s>)", S.stringof);
263         }
264 
265         size_t len = 0;
266 
267         static if (containsDynamicArray!(S))
268         {
269             foreach (i, ref field; s.tupleof)
270             {
271                 alias typeof (field) Field;
272 
273                 static if (is (Field == struct))
274                 {
275                     // Recurse into struct field.
276                     static if (containsDynamicArray!(Field))
277                     {
278                         len += This.countAllArraySize(field);
279                     }
280                 }
281                 else static if (is (Field Element == Element[]))
282                 {
283                     // Dump dynamic array.
284 
285                     len += This.countArraySize!(S, i)(field);
286                 }
287                 else static if (is (Field Element : Element[]))
288                 {
289                     // Static array
290 
291                     static if (containsDynamicArray!(Element))
292                     {
293                         // Recurse into static array elements which contain a
294                         // dynamic array.
295                         len += This.countElementSize!(S, i)(field);
296                     }
297                     else
298                     {
299                         alias ensureValueTypeMember!(S, i, Element) evt;
300                     }
301                 }
302                 else
303                 {
304                     alias ensureValueTypeMember!(S, i) evt;
305                 }
306             }
307         }
308         else
309         {
310             foreach (i, T; typeof(S.tupleof))
311                 alias ensureValueTypeMember!(S, i) evt;
312         }
313 
314         return len;
315     }
316 
317     /**************************************************************************
318 
319         Calculates the length of the serialized dynamic arrays in all elements
320         of array.
321 
322         Params:
323             array = array to calculate the length of the serialized dynamic
324                     arrays in all elements
325 
326         Returns:
327              the length of the serialized dynamic arrays in all elements of
328              array.
329 
330     ***************************************************************************/
331 
332     private static size_t countArraySize ( S, size_t i, T ) ( T[] array )
333     out (size)
334     {
335         debug (SerializationTrace)
336         {
337             Stdout.formatln("< countArraySize!({})({} @{}) : {}",
338                 T.stringof, array.length, array.ptr, cast(size_t)size);
339         }
340     }
341     do
342     {
343         debug (SerializationTrace)
344         {
345             Stdout.formatln("> countArraySize!({})({} @{})", T.stringof, array.length, array.ptr);
346         }
347 
348         size_t len = size_t.sizeof;
349 
350         static if (is (Unqual!(T) Element == Element[]))
351         {
352             // array is a dynamic array of dynamic arrays.
353 
354             foreach (element; array)
355             {
356                 len += This.countArraySize!(S, i)(element);
357             }
358         }
359         else
360         {
361             // array is a dynamic array of values.
362 
363             len += array.length * T.sizeof;
364 
365             static if (containsDynamicArray!(T))
366             {
367                 foreach (element; array)
368                 {
369                     len += This.countElementSize!(S, i)(element);
370                 }
371             }
372             else
373             {
374                 alias ensureValueTypeMember!(S, i, T) evt;
375             }
376         }
377 
378         return len;
379     }
380 
381     /**************************************************************************
382 
383         Calculates the length of the serialized dynamic arrays in element.
384 
385         Params:
386             element = element to calculate the length of the serialized dynamic
387                       arrays
388 
389         Returns:
390              the length of the serialized dynamic arrays in element.
391 
392     ***************************************************************************/
393 
394     private static size_t countElementSize ( S, size_t i, T ) ( T element )
395     out (size)
396     {
397         debug (SerializationTrace)
398         {
399             Stdout.formatln("< countElementSize!({})(<element>) : {}",
400                 T.stringof, cast(size_t)size);
401         }
402     }
403     do
404     {
405         static assert (containsDynamicArray!(T), T.stringof ~
406                        " contains no dynamic array - nothing to do");
407 
408         debug (SerializationTrace)
409         {
410             Stdout.formatln("> countElementSize!({})(<element>)", T.stringof);
411         }
412 
413         static if (is (T == struct))
414         {
415             static if (containsDynamicArray!(T))
416             {
417                 return This.countAllArraySize(element);
418             }
419         }
420         else static if (is (T Element : Element[]))
421         {
422             static assert (!is (Element[] == T),
423                            "expected a static, not a dynamic array of " ~ T.stringof);
424 
425             size_t len = 0;
426 
427             foreach (subelement; element)
428             {
429                 static if (is(Unqual!(Element) Sub == Sub[]))
430                 {
431                     // subelement is a dynamic array
432                     len += This.countArraySize!(S, i)(subelement);
433                 }
434                 else
435                 {
436                     // subelement is a value containing dynamic arrays
437                     len += This.countElementSize!(S, i)(subelement);
438                 }
439             }
440 
441             return len;
442         }
443         else
444         {
445             static assert (false,
446                            "struct or static array expected, not " ~ T.stringof);
447         }
448     }
449 
450     /**************************************************************************
451 
452         Serializes the dynamic array data in s and sets the dynamic arrays to
453         null.
454 
455         Params:
456             s    = instance of S to serialize and reset the dynamic arrays
457             data = destination buffer
458 
459         Returns:
460             the tail of data, starting with the next after the last byte that
461             was populated.
462 
463     ***************************************************************************/
464 
465     private static void[] dumpAllArrays ( S ) ( ref S s, void[] data )
466     out (result)
467     {
468         debug (SerializationTrace)
469         {
470             Stdout.formatln("< dumpAllArrays!({})({}, {}) : {} @{}",
471                 S.stringof, &s, data.ptr, result.length, result.ptr);
472         }
473     }
474     do
475     {
476         debug (SerializationTrace)
477         {
478             Stdout.formatln("> dumpAllArrays!({})({}, {})",
479                 S.stringof, &s, data.ptr);
480         }
481 
482         static if (containsDynamicArray!(S))
483         {
484             foreach (i, ref field; s.tupleof)
485             {
486                 alias typeof(field) Field;
487 
488                 static if (is (Field == struct))
489                 {
490                     // Recurse into struct field.
491 
492                     auto ptr = cast(Unqual!(Field)*) &field;
493                     data = This.dumpAllArrays(*ptr, data);
494                 }
495                 else static if (is (Field Element == Element[]))
496                 {
497                     // Dump dynamic array.
498 
499                     data = This.dumpArray!(S, i)(field, data);
500                     *(cast(Unqual!(Field)*)&field) = null;
501                 }
502                 else static if (is (Field Element : Element[]))
503                 {
504                     // Dump static array
505 
506                     static if (containsDynamicArray!(Element))
507                     {
508                         // Recurse into static array elements which contain a
509                         // dynamic array.
510 
511                         debug (SerializationTrace)
512                         {
513                             Stdout.formatln("  iterating static array of length {}",
514                                 field.length);
515                         }
516 
517                         data = This.dumpStaticArray!(S, i)(field[], data);
518                     }
519                     else
520                     {
521                         // The field is a static array not containing dynamic
522                         // arrays so the array elements should be values.
523                         alias ensureValueTypeMember!(S, i, Element) evt;
524                     }
525                 }
526                 else
527                 {
528                     alias ensureValueTypeMember!(S, i) evt;
529                 }
530             }
531         }
532         else
533         {
534             foreach (i, T; typeof(S.tupleof))
535                 alias ensureValueTypeMember!(S, i) evt;
536         }
537 
538         return data;
539     }
540 
541     /**************************************************************************
542 
543         Serializes array and the dynamic arrays in all of its elements and sets
544         the dynamic arrays, including array itself, to null.
545 
546         Params:
547             array = this array and all dynamic subarrays will be serialized and
548                     reset
549             data  = destination buffer
550 
551         Returns:
552             the tail of data, starting with the next after the last byte that
553             was populated.
554 
555     ***************************************************************************/
556 
557     private static void[] dumpArray ( S, size_t i, T ) ( T[] array, void[] data )
558     out (result)
559     {
560         debug (SerializationTrace)
561         {
562             Stdout.formatln("< dumpArray!({})({} @{}, {} @{}) : {} @{}",
563                 T.stringof, array.length, array.ptr, data.length, data.ptr, result.length, result.ptr);
564         }
565     }
566     do
567     {
568         debug (SerializationTrace)
569         {
570             Stdout.formatln("> dumpArray!({})({} @{}, {} @{})",
571                 T.stringof, array.length, array.ptr, data.length, data.ptr);
572         }
573 
574         *cast (size_t*) data[0 .. size_t.sizeof] = array.length;
575 
576         data = data[size_t.sizeof .. $];
577 
578         if (array.length)
579         {
580             static if (is (Unqual!(T) Element == Element[]))
581             {
582                 foreach (ref element; array)
583                 {
584                     // array is a dynamic array of dynamic arrays:
585                     // Recurse into subarrays.
586 
587                     data = This.dumpArray!(S, i)(element, data);
588                 }
589             }
590             else
591             {
592                 // array is a dynamic array of values: Dump array.
593 
594                 size_t n = array.length * T.sizeof;
595 
596                 debug (SerializationTrace)
597                 {
598                     Stdout.formatln("  dumping dynamic array ({}), {} bytes",
599                         (T[]).stringof, n);
600                 }
601 
602                 auto dst = (cast (Unqual!(T)[]) (data[0 .. n]));
603 
604                 data = data[n .. $];
605 
606                 dst[] = array[];
607 
608                 static if (containsDynamicArray!(T))
609                 {
610                     // array is an array of structs or static arrays which
611                     // contain dynamic arrays: Recurse into array elements.
612 
613                     data = This.dumpArrayElements!(S, i)(dst, data);
614                 }
615                 else
616                 {
617                     alias ensureValueTypeMember!(S, i, T) evt;
618                 }
619             }
620         }
621 
622         return data;
623     }
624 
625     /**************************************************************************
626 
627         Serializes the static array, also handles static arrays of static
628         arrays and similar recursive cases.
629 
630         Params:
631             T = array element type
632             array = slice of static array to serialize
633             data = destination buffer
634 
635         Returns:
636             the tail of data, starting with the next after the last byte that
637             was populated.
638 
639     **************************************************************************/
640 
641     private static void[] dumpStaticArray ( S, size_t i, T ) ( T[] array, void[] data )
642     {
643         foreach (ref element; array)
644         {
645             alias Unqual!(typeof(element)) U;
646 
647             static if (is(T Element == Element[]))
648             {
649                 // element is a dynamic array
650                 data = This.dumpArray!(S, i)(element, data);
651                 *(cast(U*)&element) = null;
652             }
653             else static if (is(T Element : Element[]))
654             {
655                 // element is a static array
656                 data = This.dumpStaticArray!(S, i)(element, data);
657             }
658             else
659             {
660                 // T is expected to contain indirections and is not
661                 // an array so it must be a struct.
662                 static assert (
663                     is(T == struct),
664                     "static array elements expected to have indirections which " ~
665                         T.stringof ~ " doesn't have"
666                 );
667 
668                 auto ptr = cast(U*) &element;
669                 data = This.dumpAllArrays(*ptr, data);
670             }
671         }
672 
673         return data;
674     }
675 
676     /**************************************************************************
677 
678         Serializes the dynamic arrays in all elements of array and sets them to
679         null.
680 
681         Params:
682             array = the dynamic arrays of all members of this array will be
683                     serialized and reset
684             data  = destination buffer
685 
686         Returns:
687             the tail of data, starting with the next after the last byte that
688             was populated.
689 
690     ***************************************************************************/
691 
692     private static void[] dumpArrayElements ( S, size_t i, T ) ( T[] array,
693         void[] data )
694     out (result)
695     {
696         debug (SerializationTrace)
697         {
698             Stdout.formatln("< dumpArrayElements!({})({}, {}) : {}",
699                 T.stringof, array.ptr, data.ptr, result.ptr);
700         }
701     }
702     do
703     {
704         debug (SerializationTrace)
705         {
706             Stdout.formatln("> dumpArrayElements!({})({}, {})",
707                 T.stringof, array.ptr, data.ptr);
708         }
709 
710         // array is a dynamic array of structs or static arrays which
711         // contain dynamic arrays.
712 
713         static assert (containsDynamicArray!(T), "nothing to do for " ~ T.stringof);
714 
715         static if (is (T == struct))
716         {
717             foreach (ref element; cast(Unqual!(T)[])array)
718             {
719                 data = This.dumpAllArrays(element, data);
720                 This.resetReferences(element);
721             }
722         }
723         else static if (is (T Element : Element[]))
724         {
725             static assert (!is (Element[] == Unqual!(T)),
726                "expected static, not dynamic array of " ~ T.stringof);
727 
728             debug (SerializationTrace)
729             {
730                 Stdout.formatln("  dumping static array of type {}", T.stringof);
731             }
732 
733             foreach (ref element; array)
734             {
735                 data = This.dumpStaticArray!(S, i)(element[], data);
736                 This.resetArrayReferences(element);
737             }
738         }
739         else
740         {
741             static assert (false);
742         }
743 
744         return data;
745     }
746 
747     /**************************************************************************
748 
749         Resets all dynamic arrays in s to null.
750 
751         Params:
752             s = struct instance to resets all dynamic arrays
753 
754         Returns:
755             a pointer to s
756 
757     ***************************************************************************/
758 
759     private static S* resetReferences ( S ) ( ref S s )
760     out (result)
761     {
762         debug (SerializationTrace)
763         {
764             Stdout.formatln("< resetReferences!({})({}) : {}",
765                 S.stringof, &s, result);
766         }
767     }
768     do
769     {
770         static assert (is (S == struct), "struct expected, not " ~ S.stringof);
771         static assert (containsDynamicArray!(S), "nothing to do for " ~ S.stringof);
772 
773         debug (SerializationTrace)
774         {
775             Stdout.formatln("> resetReferences!({})({})", S.stringof, &s);
776         }
777 
778         foreach (i, ref field; s.tupleof)
779         {
780             alias typeof(field) Field;
781 
782             static if (is (Field == struct))
783             {
784                 // Recurse into field of struct type if it contains
785                 // a dynamic array.
786                 static if (containsDynamicArray!(Field))
787                 {
788                     This.resetReferences(field);
789                 }
790             }
791             else static if (is (Unqual!(Field) Element == Element[]))
792             {
793                 // Reset field of dynamic array type.
794 
795                 field = null;
796             }
797             else static if (is (Field Element : Element[]))
798             {
799                 // Static array
800 
801                 static if (containsDynamicArray!(Element))
802                 {
803                     // Field of static array that contains a dynamic array:
804                     // Recurse into field array elements.
805 
806                     resetArrayReferences(cast(Unqual!(Element)[])field);
807                 }
808             }
809             else
810             {
811                 alias ensureValueTypeMember!(S, i) evt;
812             }
813         }
814 
815         return &s;
816     }
817 
818     /**************************************************************************
819 
820         Resets all dynamic arrays in all elements of array to null.
821 
822         Params:
823             array = all dynamic arrays in all elements of this array will be
824                     reset to to null.
825 
826         Returns:
827             array
828 
829     ***************************************************************************/
830 
831     static T[] resetArrayReferences ( T ) ( T[] array )
832     out (arr)
833     {
834         debug (SerializationTrace)
835         {
836             Stdout.formatln("< resetArrayReferences!({})({}) : {}",
837                 T.stringof, array.ptr, arr.ptr);
838         }
839     }
840     do
841     {
842         debug (SerializationTrace)
843         {
844             Stdout.formatln("> resetArrayReferences!({})({})", T.stringof, array.ptr);
845         }
846 
847         static if (is (T Element : Element[]))
848         {
849             static if (is (Element[] == T))
850             {
851                 // Reset elements of dynamic array type.
852 
853                 array[] = null;
854             }
855             else foreach (ref element; array)
856             {
857                 // Recurse into static array elements.
858 
859                 This.resetArrayReferences(element);
860             }
861         }
862         else foreach (ref element; array)
863         {
864             static assert (is (T == struct), "struct expected, not " ~ T.stringof);
865 
866             // Recurse into struct elements.
867 
868             This.resetReferences(element);
869         }
870 
871         return array;
872     }
873 }
874 
875 unittest
876 {
877     struct Dummy
878     {
879         int a, b;
880         int[] c;
881         char[][] d;
882     }
883 
884     Dummy d; d.a = 42; d.b = 43;
885     d.c = [1, 2, 3];
886     d.d = ["aaa".dup, "bbb".dup, "ccc".dup];
887 
888     void[] target;
889     Serializer.serialize(d, target);
890     auto ptr = cast(Dummy*) target.ptr;
891 
892     test!("==")(ptr.a, 42);
893     test!("==")(ptr.b, 43);
894     test!("is")(ptr.c.ptr, null);
895 }
896 
897 // non-void[] dst
898 unittest
899 {
900     struct Dummy
901     {
902         int a, b;
903     }
904 
905     Dummy d; d.a = 1; d.b = 3;
906 
907     ubyte[] target;
908     Serializer.serialize(d, target);
909     auto ptr = cast(Dummy*) target.ptr;
910 
911     test!("==")(ptr.a, 1);
912     test!("==")(ptr.b, 3);
913 }
914 
915 // Allocation test
916 unittest
917 {
918     static struct Dummy
919     {
920         size_t v;
921     }
922 
923     ubyte[] buffer;
924     Dummy d;
925 
926     Serializer.serialize(d, buffer);
927     test!("==")(buffer.length, d.sizeof);
928     buffer.length = 0;
929     testNoAlloc(Serializer.serialize(d, buffer));
930 }