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 }