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