1 // Written in the D programming language.
2 
3 /**
4  * MessagePack serializer and deserializer implementation.
5  *
6  * MessagePack is a binary-based serialization specification.
7  *
8  * Example:
9  * -----
10  * auto data = tuple("MessagePack!", [1, 2], true);
11  *
12  * auto serialized = pack(data);
13  *
14  * // ...
15  *
16  * typeof(data) deserialized;
17  *
18  * unpack(serialized, deserialized);
19  *
20  * assert(data == deserialized);
21  * -----
22  *
23  * See_Also:
24  *  $(LINK2 http://msgpack.org/, The MessagePack Project)$(BR)
25  *  $(LINK2 https://github.com/msgpack/msgpack/blob/master/spec.md, MessagePack data format)
26  *
27  * Copyright: Copyright Masahiro Nakagawa 2010-.
28  * License:   <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
29  * Authors:   Masahiro Nakagawa
30  */
31 module msgpack;
32 
33 public:
34 
35 import msgpack.common;
36 import msgpack.attribute;
37 import msgpack.buffer;
38 import msgpack.exception;
39 import msgpack.packer;
40 import msgpack.unpacker;
41 import msgpack.streaming_unpacker;
42 import msgpack.register;
43 import msgpack.value;
44 
45 pragma(lib, "WS2_32");
46 
47 @trusted:
48 
49 
50 /**
51  * Serializes $(D_PARAM args).
52  *
53  * Assumes single object if the length of $(D_PARAM args) == 1,
54  * otherwise array object.
55  *
56  * Params:
57  *  args = the contents to serialize.
58  *
59  * Returns:
60  *  a serialized data.
61  */
62 ubyte[] pack(bool withFieldName = false, Args...)(in Args args)
63 {
64     auto packer = Packer(withFieldName);
65 
66     static if (Args.length == 1)
67         packer.pack(args[0]);
68     else
69         packer.packArray(args);
70 
71     return packer.stream.data;
72 }
73 
74 
75 /**
76  * Deserializes $(D_PARAM buffer) using stream deserializer.
77  *
78  * Params:
79  *  buffer = the buffer to deserialize.
80  *
81  * Returns:
82  *  a $(D Unpacked) contains deserialized object.
83  *
84  * Throws:
85  *  UnpackException if deserialization doesn't succeed.
86  */
87 Unpacked unpack(in ubyte[] buffer)
88 {
89     auto unpacker = StreamingUnpacker(buffer);
90 
91     if (!unpacker.execute())
92         throw new UnpackException("Deserialization failure");
93 
94     return unpacker.unpacked;
95 }
96 
97 
98 /**
99  * Deserializes $(D_PARAM buffer) using direct-conversion deserializer.
100  *
101  * Assumes single object if the length of $(D_PARAM args) == 1,
102  * otherwise array object.
103  *
104  * Params:
105  *  buffer = the buffer to deserialize.
106  *  args   = the references of values to assign.
107  */
108 void unpack(bool withFieldName = false, Args...)(in ubyte[] buffer, ref Args args)
109 {
110     auto unpacker = Unpacker(buffer, buffer.length, withFieldName);
111 
112     static if (Args.length == 1)
113         unpacker.unpack(args[0]);
114     else
115         unpacker.unpackArray(args);
116 }
117 
118 
119 /**
120  * Return value version
121  */
122 Type unpack(Type, bool withFieldName = false)(in ubyte[] buffer)
123 {
124     auto unpacker = Unpacker(buffer, buffer.length, withFieldName);
125 
126     Type result;
127     unpacker.unpack(result);
128     return result;
129 }
130 
131 
132 unittest
133 {
134     auto serialized = pack(false);
135 
136     assert(serialized[0] == Format.FALSE);
137 
138     auto deserialized = unpack(pack(1, true, "Foo"));
139 
140     assert(deserialized.type == Value.Type.array);
141     assert(deserialized.via.array[0].type == Value.Type.unsigned);
142     assert(deserialized.via.array[1].type == Value.Type.boolean);
143     assert(deserialized.via.array[2].type == Value.Type.raw);
144 }
145 
146 
147 unittest
148 {
149     import std.typecons;
150 
151     { // stream
152         auto result = unpack(pack(false));
153 
154         assert(result.via.boolean == false);
155     }
156     { // direct conversion
157         Tuple!(uint, string) result;
158         Tuple!(uint, string) test = tuple(1, "Hi!");
159 
160         unpack(pack(test), result);
161         assert(result == test);
162 
163         test.field[0] = 2;
164         test.field[1] = "Hey!";
165         unpack(pack(test.field[0], test.field[1]), result.field[0], result.field[1]);
166         assert(result == test);
167     }
168     { // return value direct conversion
169         Tuple!(uint, string) test = tuple(1, "Hi!");
170 
171         auto data = pack(test);
172         assert(data.unpack!(Tuple!(uint, string)) == test);
173     }
174     { // serialize object as a Map
175         static class C
176         {
177             int num;
178 
179             this(int num) { this.num = num; }
180         }
181 
182         auto test = new C(10);
183         auto result = new C(100);
184 
185         unpack!(true)(pack!(true)(test), result);
186         assert(result.num == 10, "Unpacking with field names failed");
187     }
188 }
189 
190 
191 unittest
192 {
193     import std.typetuple;
194 
195     // unittest for https://github.com/msgpack/msgpack-d/issues/8
196     foreach (Type; TypeTuple!(byte, short, int, long)) {
197         foreach (i; [-33, -20, -1, 0, 1, 20, 33]) {
198             Type a = cast(Type)i;
199             Type b;
200             unpack(pack(a), b);
201             assert(a == b);
202         }
203     }
204 }
205 
206 
207 unittest
208 {
209     import std.typetuple;
210 
211     // char types
212     foreach (Type; TypeTuple!(char, wchar, dchar)) {
213         foreach (i; [Type.init, Type.min, Type.max, cast(Type)'j']) {
214             Type a = i;
215             Type b;
216             unpack(pack(a), b);
217             assert(a == b);
218         }
219     }
220 }
221 
222 unittest
223 {
224     // ext type
225     auto result = unpack(pack(ExtValue(7, [1,2,3,4])));
226     assert(result == ExtValue(7, [1,2,3,4]));
227 }
228 
229 unittest {
230     import std.exception: assertThrown;
231 
232     struct Version {
233         int major= -1;
234         int minor = -1;
235     }
236 
237     struct SubscriptionTopic {
238         string[] topicComponents;
239     }
240 
241     struct SubscriptionSender
242     {
243         string hostName;
244         string biosName;
245     }
246 
247     struct PubSubMessage {
248 
249         enum Type {
250             publication,
251             subscribe,
252             unsubscribe,
253         }
254 
255         Version version_;
256         Type type;
257         SubscriptionSender sender;
258         SubscriptionTopic topic;
259         string value;
260     }
261 
262     ubyte[] bytes = [149, 146, 255, 255,   0, 146, 164, 104,
263                      111, 115, 116, 164,  98, 105, 111, 115,
264                      145, 221, 171, 105, 110, 116, 101, 114,
265                      101, 115, 116, 105, 110, 103, 165, 116,
266                      111, 112, 105,  99, 167, 112,  97, 121,
267                      108, 111,  97, 100, 158, 142, 210,  31,
268                      127,  81, 149, 125, 183, 108,  86,  17,
269                      100,  35, 168];
270 
271     // should not throw OutOfMemoryError
272     assertThrown!MessagePackException(unpack!PubSubMessage(bytes));
273 }
274 
275 
276 /**
277  * Handy helper for creating MessagePackable object.
278  *
279  * toMsgpack / fromMsgpack are special methods for serialization / deserialization.
280  * This template provides those methods to struct/class.
281  *
282  * Example:
283  * -----
284  * struct S
285  * {
286  *     int num; string str;
287  *
288  *     // http://d.puremagic.com/issues/show_bug.cgi?id = 1099
289  *     mixin MessagePackable;  // all members
290  *     // mixin MessagePackable!("num");  // num only
291  * }
292  * -----
293  *
294  * Defines those methods manually if you treat complex data-structure.
295  */
296 mixin template MessagePackable(Members...)
297 {
298     static if (Members.length == 0) {
299         /**
300          * Serializes members using $(D_PARAM packer).
301          *
302          * Params:
303          *  packer = the serializer to pack.
304          */
305         void toMsgpack(Packer)(ref Packer packer, bool withFieldName = false) const
306         {
307             if (withFieldName) {
308                 packer.beginMap(this.tupleof.length);
309                 foreach (i, member; this.tupleof) {
310                     packer.pack(getFieldName!(typeof(this), i));
311                     packer.pack(member);
312                 }
313             } else {
314                 packer.beginArray(this.tupleof.length);
315                 foreach (member; this.tupleof)
316                     packer.pack(member);
317             }
318         }
319 
320 
321         /**
322          * Deserializes $(D MessagePack) object to members using Value.
323          *
324          * Params:
325          *  value = the MessagePack value to unpack.
326          *
327          * Throws:
328          *  MessagePackException if $(D_PARAM value) is not an Array type.
329          */
330         void fromMsgpack(Value value)
331         {
332             // enables if std.contracts.enforce is moved to object_.d
333             // enforceEx!MessagePackException(value.type == Value.Type.array, "Value must be Array type");
334             if (value.type != Value.Type.array)
335                 throw new MessagePackException("Value must be an Array type");
336             if (value.via.array.length != this.tupleof.length)
337                 throw new MessagePackException("The size of deserialized value is mismatched");
338 
339             foreach (i, member; this.tupleof)
340                 this.tupleof[i] = value.via.array[i].as!(typeof(member));
341         }
342 
343 
344         /**
345          * Deserializes $(D MessagePack) object to members using direct-conversion deserializer.
346          *
347          * Params:
348          *  value = the reference to direct-conversion deserializer.
349          *
350          * Throws:
351          *  MessagePackException if the size of deserialized value is mismatched.
352          */
353         void fromMsgpack(ref Unpacker unpacker)
354         {
355             auto length = unpacker.beginArray();
356             if (length != this.tupleof.length)
357                 throw new MessagePackException("The size of deserialized value is mismatched");
358 
359             foreach (i, member; this.tupleof)
360                 unpacker.unpack(this.tupleof[i]);
361         }
362     } else {
363         /**
364          * Member selecting version of toMsgpack.
365          */
366         void toMsgpack(Packer)(ref Packer packer, bool withFieldName = false) const
367         {
368             if (withFieldName) {
369                 packer.beginMap(Members.length);
370                 foreach (member; Members) {
371                     packer.pack(member);
372                     packer.pack(mixin(member));
373                 }
374             } else {
375                 packer.beginArray(Members.length);
376                 foreach (member; Members)
377                     packer.pack(mixin(member));
378             }
379         }
380 
381 
382         /**
383          * Member selecting version of fromMsgpack for Value.
384          */
385         void fromMsgpack(Value value)
386         {
387             if (value.type != Value.Type.array)
388                 throw new MessagePackException("Value must be an Array type");
389             if (value.via.array.length != Members.length)
390                 throw new MessagePackException("The size of deserialized value is mismatched");
391 
392             foreach (i, member; Members)
393                 mixin(member ~ "= value.via.array[i].as!(typeof(" ~ member ~ "));");
394         }
395 
396 
397         /**
398          * Member selecting version of fromMsgpack for direct-converion deserializer.
399          */
400         void fromMsgpack(ref Unpacker unpacker)
401         {
402             auto length = unpacker.beginArray();
403             if (length != Members.length)
404                 throw new MessagePackException("The size of deserialized value is mismatched");
405 
406             foreach (member; Members)
407                 unpacker.unpack(mixin(member));
408         }
409     }
410 }
411 
412 
413 unittest
414 {
415     { // all members
416         /*
417          * Comment out because "src/msgpack.d(4048): Error: struct msgpack.__unittest16.S no size yet for forward reference" occurs
418          */
419         static struct S
420         {
421             uint num; string str;
422             mixin MessagePackable;
423         }
424 
425         mixin DefinePacker;
426 
427         S orig = S(10, "Hi!"); orig.toMsgpack(packer);
428 
429         { // stream
430             auto unpacker = StreamingUnpacker(packer.stream.data); unpacker.execute();
431 
432             S result; result.fromMsgpack(unpacker.unpacked);
433 
434             assert(result.num == 10);
435             assert(result.str == "Hi!");
436         }
437         { // direct conversion
438             auto unpacker = Unpacker(packer.stream.data);
439 
440             S result; unpacker.unpack(result);
441 
442             assert(result.num == 10);
443             assert(result.str == "Hi!");
444         }
445     }
446     { // member select
447         static class C
448         {
449             uint num; string str;
450 
451             this() {}
452             this(uint n, string s) { num = n; str = s; }
453 
454             mixin MessagePackable!("num");
455         }
456 
457         mixin DefinePacker;
458 
459         C orig = new C(10, "Hi!"); orig.toMsgpack(packer);
460 
461         { // stream
462             auto unpacker = StreamingUnpacker(packer.stream.data); unpacker.execute();
463 
464             C result = new C; result.fromMsgpack(unpacker.unpacked);
465 
466             assert(result.num == 10);
467         }
468         { // direct conversion
469             auto unpacker = Unpacker(packer.stream.data);
470 
471             C result; unpacker.unpack(result);
472 
473             assert(result.num == 10);
474         }
475     }
476 }
477 
478 
479 unittest
480 {
481     import std.datetime: Clock, SysTime;
482     import msgpack.packer, msgpack.unpacker;
483 
484     static struct SysTimePackProxy
485     {
486         static void serialize(ref Packer p, ref in SysTime tim)
487         {
488             p.pack(tim.toISOExtString());
489         }
490 
491         static void deserialize(ref Unpacker u, ref SysTime tim)
492         {
493             string tmp;
494             u.unpack(tmp);
495             tim = SysTime.fromISOExtString(tmp);
496         }
497     }
498     static struct LogData
499     {
500         string msg;
501         string file;
502         ulong  line;
503         @serializedAs!SysTimePackProxy SysTime timestamp;
504 
505         this(string message, string file = __FILE__, ulong line = __LINE__)
506         {
507             this.msg = message;
508             this.file = file;
509             this.line = line;
510             this.timestamp = Clock.currTime();
511         }
512     }
513 
514     /// Now we can serialize/deserialize LogData
515     LogData[] logs;
516     logs ~= LogData("MessagePack is nice!");
517     auto data = pack(logs);
518     LogData[] datas = unpack!(LogData[])(data);
519     assert(datas[0].timestamp.toString() == datas[0].timestamp.toString());
520 }