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