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 }