1 /*******************************************************************************
2 
3     Format Explained
4     ----------------
5 
6     This package implements a binary serialization format useful for efficient
7     struct representation in monomorphic environment. All servers are expected to
8     have similar enough h/w architecture and software to have identical in-memory
9     representation of D structures. It doesn't work as a generic cross-platform
10     serialization format.
11 
12     Essential idea here is storing all struct instance data (including all data
13     transitively accessible via arrays / pointers) in a single contiguous memory
14     buffer. Which is exactly the reason why the package is named like that. That
15     way deserialization is very fast and doesn't need any memory allocation for
16     simple cases - all the deserializer needs to do is to iterate through the
17     memory chunk and update internal pointers.
18 
19     ``contiguous.Deserializer`` returns a memory buffer wrapped in
20     ``Contiguous!(S)`` struct. Such wrapper is guaranteed to conform to the
21     contiguity expectation explained above. It is recommended to use it in your
22     application instead of plain ``void[]`` for added type safety.
23 
24     There are certain practical complications with it that are explained as part of
25     ``contiguous.Serializer`` and ``contiguous.Deserializer`` API docs. Those should
26     not concern most applications and won't be mentioned in the overview.
27 
28     Available Decorators
29     --------------------
30 
31     ``contiguous.VersionDecorator`` adds struct versioning information to the basic
32     binary serialization format. It expects struct definitions with additional
33     meta-information available at compile-time and prepends a version number byte
34     before the actual data buffer. Upon loading the serialized data, the stored
35     version number is compared against the expected one and automatic struct
36     conversion is done if needed. It only allows conversion through one version
37     increment/decrement at a time.
38 
39     ``contiguous.MultiVersionDecorator`` is almost identical to
40     plain ``VersionDecorator`` but allows the version increment range to be defined
41     in the constructor. Distinct classes are used so that, if incoming data
42     accidentally is too old, performance-critical applications will emit an error
43     rather than wasting CPU cycles converting through multiple versions.
44     For other aplications multi-version implementation should be more convenient.
45 
46     API
47     ---
48 
49     All methods that do deserialization of data (``Deserializer.deserialize``,
50     ``VersionDecorator.load``, ``VersionDecorator.loadCopy``) return
51     ``Contiguous!(S)`` struct. Lifetime of such struct is identical to lifetime
52     of buffer used for deserialization. For 1-argument methods it is that argument,
53     for 2-argument ones it is the destination argument.
54 
55     To get a detailed overview of serializer API check the modules:
56 
57     ``ocean.util.serialize.contiguous.Serializer``
58     ``ocean.util.serialize.contiguous.Deserializer``
59 
60     To get a detailed overview of decorator API check the mixins used for its
61     generation:
62 
63     ``ocean.util.serialize.model.VersionDecoratorMixins``
64     ``ocean.util.serialize.contiguous.model.LoadCopyMixin``
65 
66     Serializer methods are static because they work only with argument state.
67     Decorators need to be created as persistent objects because they need an
68     intermediate state for version conversions.
69 
70     If a method refers to ``DeserializerReturnType!(Deserializer, S)`` as return
71     type, you can substitute it with ``Contiguous!(S)`` as it is the return type
72     used by existing contiguous deserializer.
73 
74     There is also the ``ocean.util.serialize.contiguous.Util`` module which provides
75     higher level ``copy`` functions for optimized deep copying of data between
76     contiguous structs as well as from normal structs to contiguous ones.
77 
78     Recommended Usage
79     -----------------
80 
81     The contiguous serialization format and the version decorator are primarily
82     designed as a way to interchange D structs between different applications
83     that may expect different versions of those struct layout.
84     It is recommended to completely strip the version information with the help
85     of the decorator upon initially reading a record, and to use the resulting
86     raw contiguous buffer internally in the application (i.e. in a cache). That
87     way you can use any of the serialization/deserialization utilities in the
88     application without thinking about the version meta-data
89 
90     Typical code pattern for a cache:
91 
92         1. Define a ``Cache`` of ``Contiguous!(S)`` elements.
93 
94         2. When receiving external data we do
95         ``version_decorator.loadCopy!(S)(dht_data, cache_element)``
96 
97         3. Use ``contiguous.Util.copy(cache_element, contiguous_instance)`` for
98         copying the struct instance if needed
99 
100         4. Use ``contiguous_instance.ptr`` to work with deserialized data as if
101         it was ``S*``
102 
103     It is likely that you will need to change the code to use strongly-typed
104     ``Contiguous!(S)`` persistent buffer instead of raw ``void[]`` buffer.
105     Possibly multiple such buffers if the old one was reused for different
106     struct types. This may result in small memory footprint increase at the
107     benefit of better type safety and optimized copy operations.
108 
109     You can completely abandon the resulting the ``Contiguous!(S)`` instance and
110     use/store the plain ``S*`` instead (retrieved via .ptr getter) which will
111     work as long as underlying buffer persists. This is however slightly
112     discouraged because if you will need to copy the data later, it
113     will be impossible to use optimized version without unsafe explicit casts.
114 
115     License:
116         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
117         Alternatively, this file may be distributed under the terms of the Tango
118         3-Clause BSD License (see LICENSE_BSD.txt for details).
119 
120     Copyright:
121         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
122         All rights reserved.
123 
124 *******************************************************************************/
125 
126 module ocean.util.serialize.contiguous;
127 
128 /******************************************************************************
129 
130     Public imports
131 
132 ******************************************************************************/
133 
134 public import ocean.util.serialize.contiguous.Contiguous,
135               ocean.util.serialize.contiguous.Deserializer,
136               ocean.util.serialize.contiguous.Serializer,
137               ocean.util.serialize.contiguous.Util;
138 
139 version (unittest):
140 
141 /******************************************************************************
142 
143     Test imports
144 
145 ******************************************************************************/
146 
147 import ocean.meta.types.Qualifiers;
148 import ocean.core.Test;
149 import ocean.core.Verify;
150 import ocean.core.StructConverter;
151 import ocean.core.DeepCompare;
152 import ocean.core.Buffer;
153 
154 /******************************************************************************
155 
156     Complex data structure used in most tests
157 
158     Some test define more specialized structures as nested types for debugging
159     simplicity
160 
161 ******************************************************************************/
162 
163 struct S
164 {
165     struct S_1
166     {
167         int a;
168         double b;
169     }
170 
171     struct S_2
172     {
173         int[]   a;
174         int[][] b;
175     }
176 
177     struct S_3
178     {
179         float[][2] a;
180     }
181 
182     struct S_4
183     {
184         char[][] a;
185     }
186 
187     S_1 s1;
188 
189     S_2 s2;
190     S_2[1] s2_static_array;
191 
192     S_3 s3;
193 
194     S_4[] s4_dynamic_array;
195 
196     char[][3] static_of_dynamic;
197 
198     char[][2][3][] dynamic_of_static_of_static_of_dynamic;
199 
200     union
201     {
202         int union_a;
203         int union_b;
204     }
205 
206     alias typeof(this) This;
207 
208     /***************************************************************************
209 
210         Ensure all dynamic array references in this instance are `null`, which
211         they should be if this instance references the output data of
212         `Serializer.serialize`.
213 
214     ***************************************************************************/
215 
216     void testNullReferences ( ) const
217     {
218         foreach (s2_static_array_element; this.s2_static_array)
219         {
220             testArray!("is")(s2_static_array_element.a, null);
221             testArray!("is")(s2_static_array_element.b, null);
222         }
223 
224         foreach (s3_a_element; this.s3.a)
225             testArray!("is")(s3_a_element, null);
226 
227         testArray!("is")(this.s4_dynamic_array, null);
228 
229         foreach (static_of_dynamic_element; this.static_of_dynamic)
230             testArray!("is")(static_of_dynamic_element, null);
231     }
232 
233     /***************************************************************************
234 
235         Convenience alias, this template instantiation is used a lot in tests.
236 
237     ***************************************************************************/
238 
239     alias .trivialDeserialize!(This) trivialDeserialize;
240 
241     /***************************************************************************
242 
243         Returns the number of bytes the `Serializer` should use to serialise
244         this instance.
245 
246     ***************************************************************************/
247 
248     size_t serialized_length ( ) const
249     {
250         static size_t s2_length ( ref const(S_2) s2 )
251         {
252             return serialArrayLength(s2.a) + serialArrayLength(s2.b);
253         }
254 
255         size_t n = This.sizeof;
256 
257         n += s2_length(this.s2);
258 
259         foreach (s2_static_array_element; this.s2_static_array)
260             n += s2_length(s2_static_array_element);
261 
262         foreach (s3_a_element; this.s3.a)
263             n += serialArrayLength(s3_a_element);
264 
265         n += serialArrayLength(this.s4_dynamic_array);
266         foreach (s4_dynamic_array_element; this.s4_dynamic_array)
267             n += serialArrayLength(s4_dynamic_array_element.a);
268 
269         foreach (static_of_dynamic_element; this.static_of_dynamic)
270             n += serialArrayLength(static_of_dynamic_element);
271 
272         n += serialArrayLength(this.dynamic_of_static_of_static_of_dynamic);
273         foreach (dynamic_element; this.dynamic_of_static_of_static_of_dynamic)
274             foreach (static_element; dynamic_element)
275                 foreach (static_element2; static_element)
276                     n += serialArrayLength(static_element2);
277 
278         return n;
279     }
280 }
281 
282 /******************************************************************************
283 
284     Returns:
285         S instance with fields set to some meaningful values
286 
287 ******************************************************************************/
288 
289 S defaultS()
290 {
291     S s;
292 
293     s.s1.a = 42;
294     s.s1.b = 42.42;
295 
296     s.s2.a = [ 1, 2, 3, 4 ];
297     s.s2.b = [ [ 0 ], [ 20, 21 ], [ 22 ] ];
298 
299     structConvert(s.s2, s.s2_static_array[0]);
300 
301     s.s3.a[0] = [ 1.0, 2.0 ];
302     s.s3.a[1] = [ 100.1, 200.2 ];
303 
304     s.s4_dynamic_array = [
305         S.S_4([ "aaa".dup, "bbb".dup, "ccc".dup ]),
306         S.S_4([ "a".dup, "bb".dup, "ccc".dup, "dddd".dup ]),
307         S.S_4([ "".dup ])
308     ];
309 
310     s.static_of_dynamic[] = [ "a".dup, "b".dup, "c".dup ];
311 
312     s.dynamic_of_static_of_static_of_dynamic = [
313         [["Die".dup, "Katze".dup], ["tritt".dup, "die".dup], ["Treppe".dup, "krumm.".dup]],
314         [["abc".dup, "def".dup], ["ghi".dup, "jkl".dup], ["mno".dup, "pqr".dup]]
315     ];
316 
317     s.union_a = 42;
318 
319     return s;
320 }
321 
322 /******************************************************************************
323 
324     Does series of tests on `checked` to verify that it is equal to struct
325     returned by `defaultS()`
326 
327 
328     Params:
329         checked = S instance to check for equality
330         t = test to check
331 
332 ******************************************************************************/
333 
334 void testS(NamedTest t, ref S checked)
335 {
336     with (t)
337     {
338         test!("==")(checked.s1, defaultS().s1);
339         test!("==")(checked.s2.a, defaultS().s2.a);
340         test!("==")(checked.s2.b, defaultS().s2.b);
341 
342         foreach (index, elem; checked.s2_static_array)
343         {
344             test!("==")(elem.a, defaultS().s2_static_array[index].a);
345         }
346 
347         foreach (index, elem; checked.s3.a)
348         {
349             test!("==")(elem, defaultS().s3.a[index]);
350         }
351 
352         foreach (index, elem; checked.s4_dynamic_array)
353         {
354             test!("==")(elem.a, defaultS().s4_dynamic_array[index].a);
355         }
356 
357         test!("==")(checked.static_of_dynamic[],
358                     defaultS().static_of_dynamic[]);
359 
360         test!("==")(checked.dynamic_of_static_of_static_of_dynamic,
361                     defaultS().dynamic_of_static_of_static_of_dynamic);
362 
363         test!("==")(checked.union_a, defaultS().union_b);
364     }
365 }
366 
367 /******************************************************************************
368 
369     Sanity test for helper functions
370 
371 ******************************************************************************/
372 
373 unittest
374 {
375     auto t = new NamedTest("Sanity");
376     auto s = defaultS();
377     testS(t, s);
378 }
379 
380 /******************************************************************************
381 
382     Standard workflow
383 
384 ******************************************************************************/
385 
386 unittest
387 {
388     auto t = new NamedTest("Basic");
389     auto s = defaultS();
390     void[] buffer;
391 
392     Serializer.serialize(s, buffer);
393     test!("==")(buffer.length, s.serialized_length);
394     S.trivialDeserialize(buffer).testNullReferences();
395     auto cont_S = Deserializer.deserialize!(S)(buffer);
396     cont_S.enforceIntegrity();
397     testS(t, *cont_S.ptr);
398 }
399 
400 /******************************************************************************
401 
402     Standard workflow, copy version
403 
404 ******************************************************************************/
405 
406 unittest
407 {
408     auto t = new NamedTest("Basic + Copy");
409     auto s = defaultS();
410     void[] buffer;
411 
412     Serializer.serialize(s, buffer);
413     test!("==")(buffer.length, s.serialized_length);
414     S.trivialDeserialize(buffer).testNullReferences();
415     Contiguous!(S) destination;
416     auto cont_S = Deserializer.deserialize(buffer, destination);
417     cont_S.enforceIntegrity();
418 
419     t.test(cont_S.ptr is destination.ptr);
420     testS(t, *cont_S.ptr);
421 }
422 
423 /******************************************************************************
424 
425     Serialize in-place
426 
427 ******************************************************************************/
428 
429 unittest
430 {
431     auto t = new NamedTest("In-place serialization");
432     auto s = defaultS();
433     void[] buffer;
434 
435     // create Contiguous!(S) instance first
436     Serializer.serialize(s, buffer);
437     test!("==")(buffer.length, s.serialized_length);
438     S.trivialDeserialize(buffer).testNullReferences();
439     auto cont_S = Deserializer.deserialize!(S)(buffer);
440 
441     // check that serializations nulls pointers
442     auto serialized = Serializer.serialize(cont_S);
443     test!("is")(serialized.ptr, cont_S.ptr);
444     test!("is")(cont_S.ptr.s4_dynamic_array.ptr, null);
445     test!("is")(cont_S.ptr.s2.a.ptr, null);
446     test!("is")(cont_S.ptr.s2.b.ptr, null);
447 }
448 
449 /******************************************************************************
450 
451     Extra unused bytes in source
452 
453 ******************************************************************************/
454 
455 unittest
456 {
457     auto t = new NamedTest("Basic + Copy");
458     auto s = defaultS();
459     void[] buffer;
460 
461     Serializer.serialize(s, buffer);
462     test!("==")(buffer.length, s.serialized_length);
463     S.trivialDeserialize(buffer).testNullReferences();
464 
465     // emulate left-over bytes from previous deserializations
466     buffer.length = buffer.length * 2;
467 
468     Contiguous!(S) destination;
469     auto cont_S = Deserializer.deserialize(buffer, destination);
470     cont_S.enforceIntegrity();
471 
472     t.test(cont_S.ptr is destination.ptr);
473     testS(t, *cont_S.ptr);
474 }
475 
476 /******************************************************************************
477 
478     Some arrays set to null
479 
480 ******************************************************************************/
481 
482 unittest
483 {
484     auto t = new NamedTest("Null Arrays");
485     auto s = defaultS();
486     s.s2.a = null;
487     void[] buffer;
488 
489     Serializer.serialize(s, buffer);
490     test!("==")(buffer.length, s.serialized_length);
491     S.trivialDeserialize(buffer).testNullReferences();
492     auto cont_S = Deserializer.deserialize!(S)(buffer);
493     cont_S.enforceIntegrity();
494 
495     t.test!("==")(cont_S.ptr.s2.a.length, 0);
496     auto s_ = cont_S.ptr;      // hijack the invariant
497     s_.s2.a = defaultS().s2.a; // revert the difference
498     testS(t, *s_);             // check the rest
499 }
500 
501 /******************************************************************************
502 
503     Nested arrays set to null
504 
505 ******************************************************************************/
506 
507 unittest
508 {
509     auto t = new NamedTest("Nested Null Arrays");
510     auto s = defaultS();
511     s.s2.b[0] = null;
512     void[] buffer;
513 
514     Serializer.serialize(s, buffer);
515     test!("==")(buffer.length, s.serialized_length);
516     S.trivialDeserialize(buffer).testNullReferences();
517     auto cont_S = Deserializer.deserialize!(S)(buffer);
518     cont_S.enforceIntegrity();
519 
520     t.test!("==")(cont_S.ptr.s2.b[0].length, 0);
521     auto s_ = cont_S.ptr;            // hijack the invariant
522     s_.s2.b[0] = defaultS().s2.b[0]; // revert the difference
523     testS(t, *s_);                   // check the rest
524 }
525 
526 /******************************************************************************
527 
528     Recursie static arrays
529 
530 ******************************************************************************/
531 
532 unittest
533 {
534     auto t = new NamedTest("Recursive static");
535 
536     struct Outer
537     {
538         struct Inner
539         {
540             char[][] a;
541         }
542 
543         Inner[2][1][1] a;
544     }
545 
546     Outer s;
547     s.a[0][0][0].a = [ "1".dup, "2".dup, "3".dup ];
548     s.a[0][0][1].a = [ "1".dup, "2".dup ];
549 
550     void[] buffer;
551     Serializer.serialize(s, buffer);
552 
553     size_t expected_length = s.sizeof;
554     foreach (a1; s.a)
555         foreach (a2; a1)
556             foreach (a3; a2)
557                 expected_length += serialArrayLength(a3.a);
558     test!("==")(buffer.length, expected_length);
559 
560     with (*trivialDeserialize!(Outer)(buffer))
561         foreach (a1; a)
562             foreach (a2; a1)
563                 foreach (a3; a2)
564                     testArray!("is")(a3.a, null);
565 
566     auto cont = Deserializer.deserialize!(Outer)(buffer);
567 
568     test!("==")(cont.ptr.a[0][0][0].a, s.a[0][0][0].a);
569     test!("==")(cont.ptr.a[0][0][1].a, s.a[0][0][1].a);
570 }
571 
572 /******************************************************************************
573 
574     Partial loading of extended struct
575 
576     Ensures that if struct definition has been extended incrementaly one can
577     still load old definition from the serialized buffer
578 
579 ******************************************************************************/
580 
581 unittest
582 {
583     struct Old
584     {
585         int one;
586     }
587 
588     struct New
589     {
590         int one;
591         int two;
592     }
593 
594     auto input = New(32, 42);
595     void[] buffer;
596     Serializer.serialize(input, buffer);
597     auto output = Deserializer.deserialize!(Old)(buffer);
598 
599     test!("==")(input.one, output.ptr.one);
600 }
601 
602 /******************************************************************************
603 
604     Serialization of unions of structs with no dynamic arrays
605 
606 ******************************************************************************/
607 
608 unittest
609 {
610     struct A { int x; }
611     struct B { int[3] arr; }
612 
613     struct S
614     {
615         union
616         {
617             A a;
618             B b;
619         };
620     }
621 
622     void[] buffer;
623     auto input = S(A(42));
624     Serializer.serialize(input, buffer);
625     auto output = Deserializer.deserialize!(S)(buffer);
626 
627     test!("==")(output.ptr.a, A(42));
628 
629     input.b.arr[] = [0, 1, 2];
630     Serializer.serialize(input, buffer);
631     output = Deserializer.deserialize!(S)(buffer);
632 
633     test!("==")(output.ptr.b.arr[], [0, 1, 2][]);
634 }
635 
636 /******************************************************************************
637 
638     Serialization of unions of structs with dynamic arrays (fails)
639 
640 ******************************************************************************/
641 
642 unittest
643 {
644     struct A { int x; }
645     struct B { int[] arr; }
646 
647     struct S
648     {
649         union XX
650         {
651             A a;
652             B b;
653         };
654 
655         XX field;
656     }
657 
658     void[] buffer;
659     S input;
660 
661     static assert (!is(typeof(Serializer.serialize(input, buffer))));
662 }
663 
664 /******************************************************************************
665 
666     Allocation Control
667 
668 ******************************************************************************/
669 
670 unittest
671 {
672     auto t = new NamedTest("Memory Usage");
673     auto s = defaultS();
674     void[] buffer;
675 
676     Serializer.serialize(s, buffer);
677     S.trivialDeserialize(buffer).testNullReferences();
678     testNoAlloc(Serializer.serialize(s, buffer));
679     auto cont_s = Deserializer.deserialize!(S)(buffer);
680     testNoAlloc(Deserializer.deserialize!(S)(buffer));
681     buffer = buffer.dup;
682     testNoAlloc(Deserializer.deserialize(buffer, cont_s));
683 }
684 
685 
686 /******************************************************************************
687 
688     Array of const elements
689 
690 ******************************************************************************/
691 
692 unittest
693 {
694     static struct CS
695     {
696         cstring s;
697     }
698 
699     auto cs = CS("Hello world");
700     void[] buffer;
701 
702     Serializer.serialize(cs, buffer);
703     test!("==")(buffer.length, cs.sizeof + serialArrayLength(cs.s));
704     with (*trivialDeserialize!(CS)(buffer))
705         testArray!("is")(s, null);
706     auto new_s = Deserializer.deserialize!(CS)(buffer);
707     test!("==")(cs.s, new_s.ptr.s);
708 }
709 
710 
711 /******************************************************************************
712 
713     Ensure that immutable elements are rejected
714 
715 ******************************************************************************/
716 
717 unittest
718 {
719     static struct IS
720     {
721         istring s;
722     }
723 
724     static struct II
725     {
726         immutable(int) s;
727     }
728 
729     IS s1 = IS("Hello world");
730     II s2 = II(42);
731     void[] buffer1, buffer2;
732 
733     /*
734      * There is no check for the serializer because it is "okay" to
735      * serialize immutable data.
736      * Obviously they won't be deserializable but that is where
737      * we could break the type system.
738      */
739 
740     // Uncomment to check error message
741     //Deserializer.deserialize!(IS)(buffer1);
742     //Deserializer.deserialize!(II)(buffer2);
743 
744     static assert(!is(typeof({Deserializer.deserialize!(IS)(buffer1);})),
745         "Serializer should reject a struct with 'istring'");
746     static assert(!is(typeof({Deserializer.deserialize!(II)(buffer2);})),
747         "Deserializer should reject a struct with 'immutable' element");
748 }
749 
750 /******************************************************************************
751 
752     Ensure that full-const struct can be serialized
753 
754 ******************************************************************************/
755 
756 unittest
757 {
758     static struct S1
759     {
760         mstring s;
761     }
762 
763     static struct S2
764     {
765         S1[] nested;
766     }
767 
768     auto s = const(S2)([ const(S1)("Hello world") ]);
769     void[] buffer;
770 
771     Serializer.serialize(s, buffer);
772 
773     size_t expected_length = s.sizeof + serialArrayLength(s.nested);
774     foreach (nested_element; s.nested)
775         expected_length += serialArrayLength(nested_element.s);
776     test!("==")(buffer.length, expected_length);
777 
778     with (*trivialDeserialize!(S2)(buffer))
779         testArray!("is")(nested, null);
780 
781     auto d = Deserializer.deserialize!(S2)(buffer);
782     test(deepEquals(*d.ptr, s));
783 }
784 
785 /******************************************************************************
786 
787     Const arrays of arrays
788 
789 ******************************************************************************/
790 
791 struct ConstS
792 {
793     const(int) n = 3;
794     const(char[][]) a = ["Hello", "World"];
795     const(char[])[2][3] b = [
796         ["Die", "Katze"], ["tritt", "die"], ["Treppe", "krumm."]
797     ];
798 }
799 
800 unittest
801 {
802     void[] buffer;
803 
804     ConstS s;
805 
806     Serializer.serialize(s, buffer);
807 
808     size_t expected_length = s.sizeof + serialArrayLength(s.a);
809     foreach (b1; s.b)
810         foreach (b2; b1)
811             expected_length += serialArrayLength(b2);
812     test!("==")(buffer.length, expected_length);
813 
814     with (*trivialDeserialize!(ConstS)(buffer))
815     {
816         testArray!("is")(a, null);
817         foreach (b1; b)
818             foreach (b2; b1)
819                 testArray!("is")(b2, null);
820     }
821 
822     auto cont_S = Deserializer.deserialize!(ConstS)(buffer);
823     cont_S.enforceIntegrity();
824 
825     test!("==")(cont_S.ptr.n, 3);
826     test!("==")(cont_S.ptr.a, ["Hello", "World"]);
827 
828     cstring[2][3] b = [
829         ["Die", "Katze"], ["tritt", "die"], ["Treppe", "krumm."]
830     ];
831     test!("==")(cont_S.ptr.b, b);
832 }
833 
834 unittest
835 {
836     static struct Inner
837     {
838         mstring s;
839     }
840 
841     static struct Outer
842     {
843         Contiguous!(Inner) inner;
844     }
845 
846     auto s1 = Inner("abcd".dup);
847     Outer s2;
848     copy(s1, s2.inner);
849 
850     Contiguous!(Outer) s3;
851     copy(s2, s3);
852 
853     s3.ptr.inner.enforceIntegrity();
854     test!("==")(s3.ptr.inner.ptr.s, "abcd");
855 }
856 
857 unittest
858 {
859     static struct S
860     {
861         mstring s;
862     }
863 
864     auto s = S("abcd".dup);
865 
866     Buffer!(void) src;
867     Contiguous!(S) dst;
868 
869     Serializer.serialize(s, src);
870     Deserializer.deserialize(src[], dst);
871 
872     test!("==")(dst.ptr.s, "abcd");
873 }
874 
875 /*******************************************************************************
876 
877     Deserialise `serializer_output` in the trivial way to verify all dynamic
878     array slices are `null`.
879 
880 *******************************************************************************/
881 
882 static const(Struct)* trivialDeserialize ( Struct )
883     ( const(void)[] serializer_output )
884 {
885     verify(serializer_output.length >= Struct.sizeof);
886     return cast(const(Struct)*)serializer_output.ptr;
887 }
888 
889 /*******************************************************************************
890 
891     Returns the number of bytes used to serialise `array`, recursing into the
892     elements of `array` if `Element` is a dynamic array type. No recursion is
893     done if `Element` is a value type containing dynamic arrays.
894 
895 *******************************************************************************/
896 
897 static size_t serialArrayLength ( T : Element[], Element ) ( T array )
898 {
899     size_t n = array.length.sizeof;
900 
901     static if (is(Unqual!(Element) Sub == Sub[]))
902     {
903         foreach (element; array)
904             n += serialArrayLength(element);
905     }
906     else
907         n += (array.length * array[0].sizeof);
908 
909     return n;
910 }
911 
912 /*******************************************************************************
913 
914     `testArray!(op)(a, b)` is equivalent to
915     `testArray!(op)(cast(const(void)[])a, cast(const(void)[])b)`, which allows
916     for `testArray!(op)(a, null)` avoiding D2 trouble with `typeof(null)`.
917 
918 *******************************************************************************/
919 
920 template testArray ( istring op )
921 {
922     alias test!(op, const(void)[], const(void)[]) testArray;
923 }