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 }