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 }