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 }