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 }