1 module tests.structs;
2 
3 import unit_threaded;
4 import cerealed;
5 import std.conv;
6 import core.exception;
7 
8 
9 private struct DummyStruct {
10     int i;
11     double d;
12     int[] a;
13     bool b;
14     double[int] aa;
15     string s;
16 
17     void foo() {}
18 }
19 
20 
21 void testDummyStruct() {
22     auto enc = Cerealiser();
23     auto dummy = DummyStruct(5, 6.0, [2, 3], true, [2: 4.0], "dummy!");
24     enc ~= dummy;
25 
26     auto dec = Decerealiser(enc.bytes);
27     dec.value!DummyStruct.shouldEqual(dummy);
28 
29     dec.value!ubyte.shouldThrow!RangeError;
30 }
31 
32 private struct StringStruct {
33     string s;
34 }
35 
36 void testDecodeStringStruct() {
37     auto dec = Decerealiser([0, 3, 'f', 'o', 'o']);
38     auto str = StringStruct();
39     dec.grain(str);
40     str.s.shouldEqual("foo");
41     dec.value!ubyte.shouldThrow!RangeError;
42 }
43 
44 void testEncodeStringStruct() {
45     auto enc = Cerealiser();
46     const str = StringStruct("foo");
47     enc ~= str;
48     enc.bytes.shouldEqual([ 0, 3, 'f', 'o', 'o']);
49 }
50 
51 
52 private struct ProtoHeaderStruct {
53     @Bits!3 ubyte bits3;
54     @Bits!1 ubyte bits1;
55     @Bits!4 uint bits4;
56     ubyte bits8; //no UDA necessary
57 }
58 
59 
60 void testEncDecProtoHeaderStruct() {
61     const hdr = ProtoHeaderStruct(6, 1, 3, 254);
62     auto enc = Cerealiser();
63     enc ~= hdr; //1101 0011, 254
64     enc.bytes.shouldEqual([0xd3, 254]);
65 
66     auto dec = Decerealiser(enc.bytes);
67     dec.value!ProtoHeaderStruct.shouldEqual(hdr);
68 }
69 
70 private struct StructWithNoCereal {
71     @Bits!4 ubyte nibble1;
72     @Bits!4 ubyte nibble2;
73     @NoCereal ushort nocereal1;
74     ushort value;
75     @NoCereal ushort nocereal2;
76 }
77 
78 void testNoCereal() {
79     auto cerealizer = Cerealizer();
80     cerealizer ~= StructWithNoCereal(3, 14, 42, 5, 12);
81     //only nibble1, nibble2 and value should show up in bytes
82     immutable bytes = [0x3e, 0x00, 0x05];
83     cerealizer.bytes.shouldEqual(bytes);
84 
85     auto decerealizer = Decerealizer(bytes);
86     //won't be the same as the serialised struct, since the members
87     //marked with NoCereal will be set to T.init
88     decerealizer.value!StructWithNoCereal.shouldEqual(StructWithNoCereal(3, 14, 0, 5, 0));
89 }
90 
91 private struct CustomStruct {
92     ubyte mybyte;
93     ushort myshort;
94     void accept(Cereal)(ref Cereal cereal) {
95         //can't call grain(this), that would cause an infinite loop
96         cereal.grainAllMembers(this);
97         ubyte otherbyte = 4;
98         cereal.grain(otherbyte);
99     }
100 }
101 
102 void testCustomCereal() {
103     auto cerealiser = Cerealiser();
104     cerealiser ~= CustomStruct(1, 2);
105     cerealiser.bytes.shouldEqual([ 1, 0, 2, 4]);
106 
107     //because of the custom serialisation, passing in just [1, 0, 2] would throw
108     auto decerealiser = Decerealiser([1, 0, 2, 4]);
109     decerealiser.value!CustomStruct.shouldEqual(CustomStruct(1, 2));
110 }
111 
112 
113 void testAttrMember() {
114     //test that attributes work when calling grain member by member
115     auto cereal = Cerealizer();
116     auto str = StructWithNoCereal(3, 14, 42, 5, 12);
117     cereal.grainMemberWithAttr!"nibble1"(str);
118     cereal.grainMemberWithAttr!"nibble2"(str);
119     cereal.grainMemberWithAttr!"nocereal1"(str);
120     cereal.grainMemberWithAttr!"value"(str);
121     cereal.grainMemberWithAttr!"nocereal2"(str);
122 
123     //only nibble1, nibble2 and value should show up in bytes
124     cereal.bytes.shouldEqual([0x3e, 0x00, 0x05]);
125 }
126 
127 struct EnumStruct {
128     enum Enum:byte {
129         Foo,
130         Bar,
131         Baz
132     }
133 
134     ubyte foo;
135     Enum bar;
136 }
137 
138 void testEnum() {
139     auto cerealiser = Cerealiser();
140     const e = EnumStruct(1, EnumStruct.Enum.Baz);
141     cerealiser ~= e;
142     const bytes = [1, 2];
143     cerealiser.bytes.shouldEqual(bytes);
144 
145     auto decerealiser = Decerealiser(bytes);
146     decerealiser.value!EnumStruct.shouldEqual(e);
147 }
148 
149 struct PostBlitStruct {
150     ubyte foo;
151     @NoCereal ubyte bar;
152     ubyte baz;
153     void postBlit(Cereal)(ref Cereal cereal) {
154         ushort foo = 4;
155         cereal.grain(foo);
156     }
157 }
158 
159 void testPostBlit() {
160     auto enc = Cerealiser();
161     enc ~= PostBlitStruct(3, 5, 8);
162     const bytes = [ 3, 8, 0, 4];
163     enc.bytes.shouldEqual(bytes);
164 
165     auto dec = Decerealiser(bytes);
166     dec.value!PostBlitStruct.shouldEqual(PostBlitStruct(3, 0, 8));
167 }
168 
169 private struct StringsStruct {
170     ubyte mybyte;
171     @RawArray string[] strings;
172 }
173 
174 void testRawArray() {
175     auto enc = Cerealiser();
176     auto strs = StringsStruct(5, ["foo", "foobar", "ohwell"]);
177     enc ~= strs;
178     //no length encoding for the array, but strings still get a length each
179     const bytes = [ 5, 0, 3, 'f', 'o', 'o', 0, 6, 'f', 'o', 'o', 'b', 'a', 'r',
180                     0, 6, 'o', 'h', 'w', 'e', 'l', 'l'];
181     enc.bytes.shouldEqual(bytes);
182 
183     auto dec = Decerealiser(bytes);
184     dec.value!StringsStruct.shouldEqual(strs);
185 }
186 
187 void testReadmeCode() {
188     struct MyStruct {
189         ubyte mybyte1;
190         @NoCereal uint nocereal1; //won't be serialised
191         //the next 3 members will all take up one byte
192         @Bits!4 ubyte nibble; //gets packed into 4 bits
193         @Bits!1 ubyte bit; //gets packed into 1 bit
194         @Bits!3 ubyte bits3; //gets packed into 3 bits
195         ubyte mybyte2;
196     }
197 
198     auto enc = Cerealiser();
199     enc ~= MyStruct(3, 123, 14, 1, 2, 42);
200     import std.conv;
201     assert(enc.bytes == [ 3, 0xea /*1110 1 010*/, 42], text("bytes were ", enc.bytes));
202 
203     auto dec = Decerealizer([ 3, 0xea, 42]); //US spelling works too
204     //the 2nd value is 0 and not 123 since that value
205     //doesn't get serialised/deserialised
206     auto val = dec.value!MyStruct;
207     assert(val == MyStruct(3, 0, 14, 1, 2, 42), text("struct was ", val));
208 }
209 
210 
211 private enum MqttType {
212     RESERVED1 = 0, CONNECT = 1, CONNACK = 2, PUBLISH = 3,
213     PUBACK = 4, PUBREC = 5, PUBREL = 6, PUBCOMP = 7,
214     SUBSCRIBE = 8, SUBACK = 9, UNSUBSCRIBE = 10, UNSUBACK = 11,
215     PINGREQ = 12, PINGRESP = 13, DISCONNECT = 14, RESERVED2 = 15
216 }
217 
218 private struct MqttFixedHeader {
219 public:
220     enum SIZE = 2;
221 
222     @Bits!4 MqttType type;
223     @Bits!1 bool dup;
224     @Bits!2 ubyte qos;
225     @Bits!1 bool retain;
226     @NoCereal uint remaining;
227 
228     void postBlit(Cereal)(ref Cereal cereal) if(isCerealiser!Cereal) {
229         setRemainingSize(cereal);
230     }
231 
232     void postBlit(Cereal)(ref Cereal cereal) if(isDecerealiser!Cereal) {
233         remaining = getRemainingSize(cereal);
234     }
235 
236 private:
237 
238     uint getRemainingSize(Cereal)(ref Cereal cereal) {
239         //algorithm straight from the MQTT spec
240         int multiplier = 1;
241         uint value = 0;
242         ubyte digit;
243         do {
244             cereal.grain(digit);
245             value += (digit & 127) * multiplier;
246             multiplier *= 128;
247         } while((digit & 128) != 0);
248 
249         return value;
250     }
251 
252     void setRemainingSize(Cereal)(ref Cereal cereal) const {
253         //algorithm straight from the MQTT spec
254         ubyte[] digits;
255         uint x = remaining;
256         do {
257             ubyte digit = x % 128;
258             x /= 128;
259             if(x > 0) {
260                 digit = digit | 0x80;
261             }
262             digits ~= digit;
263         } while(x > 0);
264 
265         foreach(b; digits) cereal.grain(b);
266     }
267 }
268 
269 void testAcceptPostBlitAttrs() {
270     import cerealed.traits;
271     static assert(hasPostBlit!MqttFixedHeader);
272     static assert(hasAccept!CustomStruct);
273     mixin assertHasPostBlit!MqttFixedHeader;
274     mixin assertHasAccept!CustomStruct;
275 
276 }
277 
278 void testCerealiseMqttHeader() {
279     auto cereal = Cerealiser();
280     cereal ~= MqttFixedHeader(MqttType.PUBLISH, true, 2, false, 5);
281     cereal.bytes.shouldEqual([0x3c, 0x5]);
282 }
283 
284 void testDecerealiseMqttHeader() {
285     auto cereal = Decerealiser([0x3c, 0x5]);
286     cereal.value!MqttFixedHeader.shouldEqual(MqttFixedHeader(MqttType.PUBLISH, true, 2, false, 5));
287 }
288 
289 
290 class CustomException: Exception {
291     this(string msg) {
292         super(msg);
293     }
294 }
295 
296 struct StructWithPreBlit {
297     static struct Header {
298         uint i;
299     }
300 
301     alias header this;
302     enum headerSize = unalignedSizeof!Header;
303 
304     Header header;
305     ubyte ub1;
306     ubyte ub2;
307 
308     void preBlit(C)(auto ref C cereal) {
309         static if(isDecerealiser!C) {
310             if(cereal.bytesLeft < headerSize)
311                 throw new CustomException(
312                     text("Cannot decerealise into header of size ", headerSize,
313                          " when there are only ", cereal.bytesLeft, " bytes left"));
314         }
315     }
316 
317     mixin assertHasPreBlit!StructWithPreBlit;
318 }
319 
320 void testPreBlit() {
321     immutable ubyte[] bytesOk = [0, 0, 0, 3, 1, 2];
322     bytesOk.decerealise!StructWithPreBlit;
323 
324     immutable ubyte[] bytesOops = [0, 0, 0];
325     bytesOops.decerealise!StructWithPreBlit.shouldThrow!CustomException;
326 
327     immutable ubyte[] bytesMegaOops = [0, 0, 0, 3];
328     bytesMegaOops.decerealise!StructWithPreBlit.shouldThrow!RangeError;
329 }