1 /******************************************************************************
2 
3     Provides basic utilities to work with version information in structures.
4 
5     Most common idiom used in application code is checking for
6     version support:
7 
8     ---
9     static if (Version.Info!(S).exists)
10     {
11     }
12     ---
13 
14     It is also possible to manually figure out whole version increment
15     chain based on provided metadata (though use cases for that
16     are supposed to be rare)
17 
18     Copyright:
19         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
20         All rights reserved.
21 
22     License:
23         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
24         Alternatively, this file may be distributed under the terms of the Tango
25         3-Clause BSD License (see LICENSE_BSD.txt for details).
26 
27 *******************************************************************************/
28 
29 module ocean.util.serialize.Version;
30 
31 
32 import ocean.meta.types.Qualifiers;
33 import ocean.core.Verify;
34 import ocean.core.Buffer;
35 
36 version (unittest) import ocean.core.Test;
37 
38 /*******************************************************************************
39 
40     Tag type that denotes missing version type for either next or previous
41     version of a struct.
42 
43 *******************************************************************************/
44 
45 public struct MissingVersion
46 {
47     enum exists = false;
48     enum ubyte number = 0;
49 }
50 
51 /*******************************************************************************
52 
53     Namespace struct is desired because most of symbols have very common and
54     generic names.
55 
56 *******************************************************************************/
57 
58 struct Version
59 {
60     /***************************************************************************
61 
62         The type of the version number tag that is prepended to serialised data.
63 
64     ***************************************************************************/
65 
66     public alias ubyte Type;
67 
68     /***************************************************************************
69 
70         Evaluates to version information of S if S is a versioned struct:
71 
72             - exists: true if S is a versioned struct or false if S is a struct
73               without version or not a struct. If false the other constants are
74               not defined.
75             - number: The value of the struct version (S.Info), expected to
76               be of type Type.
77             - next/prev: Info for S.StructNext or S.StructPrev, respectively
78             - type : type of struct this info belongs to
79 
80         next/prev are recursive instances of this template so they can be
81         checked for existence by Info!(S).next.exists
82         If that's true the next version number is Info!(S).next.number.
83         The next version itself can contain a next version,
84         use Info!(S).next.next.exists to check it out.
85 
86     ***************************************************************************/
87 
88     template Info ( S )
89     {
90         static if (is(S == struct) && is(typeof(S.StructVersion) V))
91         {
92             static assert (
93                 S.StructVersion <= Version.Type.max,
94                 S.stringof ~ ".StructVersion == " ~
95                 S.StructVersion.stringof ~
96                 ", but it must be lower than Version.Type.max"
97             );
98 
99             enum exists = true;
100             enum number = S.StructVersion;
101 
102             alias S type;
103 
104             static if (is(S.StructNext))
105             {
106                 alias Info!(S.StructNext) next;
107             }
108             else
109             {
110                 alias MissingVersion next; // dummy
111             }
112 
113             static if (is(S.StructPrevious))
114             {
115                 alias Info!(S.StructPrevious) prev;
116             }
117             else
118             {
119                 alias MissingVersion prev; // dummy
120             }
121         }
122         else
123         {
124             alias MissingVersion Info;
125         }
126     }
127 
128     unittest
129     {
130         struct S { }
131         static assert (is(Info!(S) == MissingVersion));
132         static assert (!  Info!(S).exists);
133     }
134 
135     unittest
136     {
137         struct S1 { enum StructVersion = 1; }
138         alias Info!(S1) Ver;
139         static assert (Ver.exists);
140         static assert (Ver.number == 1);
141         static assert (is(Ver.next == MissingVersion));
142         static assert (is(Ver.prev == MissingVersion));
143 
144         struct S2 { enum StructVersion = Version.Type.max + 1; }
145         static assert (!is(typeof(Info!(S2))));
146     }
147 
148     unittest
149     {
150         struct S
151         {
152             enum StructVersion = 1;
153             alias S StructPrevious;
154             alias S StructNext;
155         }
156 
157         alias Info!(S) Ver;
158 
159         static assert (Ver.exists);
160         static assert (is(Ver.next.type == S));
161         static assert (is(Ver.prev.type == S));
162     }
163 
164     /***************************************************************************
165 
166         Assumes that input is versioned struct chunk and extracts version number
167         from it. Otherwise will return garbage
168 
169         Params:
170             data = serialized struct data, untouched
171             ver  = out parameter to store version number
172 
173         Returns:
174             data slices after the version bytes
175 
176     ***************************************************************************/
177 
178     static void[] extract ( void[] data, ref Version.Type ver )
179     {
180         verify (data.length > Version.Type.sizeof);
181 
182         ver = *(cast(Version.Type*) data.ptr);
183         return data[Version.Type.sizeof .. $];
184     }
185 
186     unittest
187     {
188         Version.Type V = 42;
189         void[] data = [ V, cast(Version.Type) 1, cast(Version.Type) 1 ];
190         Version.Type ver;
191         auto data_unver = extract(data, ver);
192         test!("==")(ver, V);
193         test!("==")(data_unver.length, 2);
194     }
195 
196     static const(void)[] extract ( in void[] data, ref Version.Type ver )
197     {
198         return extract(cast(void[]) data, ver);
199     }
200 
201     unittest
202     {
203         Version.Type V = 42;
204         void[] data = [ V, cast(Version.Type) 1, cast(Version.Type) 1 ];
205         const(void[]) cdata = data;
206         Version.Type ver;
207         const(void)[] data_unver = extract(cdata, ver);
208         test!("==")(ver, V);
209         test!("==")(data_unver.length, 2);
210     }
211 
212 
213     /***************************************************************************
214 
215         Writes version data in the beginning of provided data buffer. Grows buffer
216         if it is too small. Call this function before actually writing any useful
217         payload to the buffer or it will be overwritten.
218 
219         Params:
220             data = any byte buffer, will be modified to start with version info
221             ver  = version number to inject
222 
223         Returns:
224             slice of data after the version data. Use that slice to add actual
225             payload
226 
227     ***************************************************************************/
228 
229     static void[] inject ( ref Buffer!(void) data, Version.Type ver )
230     {
231         if (data.length < Version.Type.sizeof)
232         {
233             data.length = Version.Type.sizeof;
234         }
235 
236         *(cast(Version.Type*) data[].ptr) = ver;
237 
238         return data[Version.Type.sizeof .. data.length];
239     }
240 
241     /// ditto
242     static void[] inject ( ref void[] data, Version.Type ver )
243     {
244         return inject(*cast(Buffer!(void)*) &data, ver);
245     }
246 
247     unittest
248     {
249         Version.Type V = 42;
250         void[] data = [ cast(ubyte) 1, 2, 3 ];
251         auto result = inject(data, V);
252         test!("is")(data.ptr + V.sizeof, result.ptr);
253         test!("==")(data, [ V, 2, 3 ][]);
254     }
255 }