├── .travis.yml ├── README.md ├── component.json ├── index.html ├── msgpack.js ├── package.json └── test.js /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 0.8 5 | script: phantomjs test.js 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # msgpack for the browser 2 | 3 | [![Build Status](https://secure.travis-ci.org/creationix/msgpack-js-browser.png)](http://travis-ci.org/creationix/msgpack-js-browser) 4 | 5 | A handwritten msgpack encoder and decoder for Browsers 6 | 7 | This is a browser port of https://github.com/creationix/msgpack-js 8 | 9 | The original format can be found at 10 | 11 | ## Extension 12 | 13 | I've extended the format a little to allow for encoding and decoding of `undefined`. 14 | 15 | This required a new type code that is marked as "ext format". 16 | This change means that using these new types will render your serialized data 17 | incompatible with other messagepack implementations that don't have the same 18 | extension. 19 | 20 | I've added a type for `undefined` that works just like the `null` type. 21 | 22 | undefined 11010100 0xd4 23 | 24 | ## Usage 25 | 26 | ``` javascript 27 | require(['msgpack'], function (msgpack) { 28 | 29 | var initial = {Hello: "World"}; 30 | var encoded = msgpack.encode(initial); 31 | var decoded = msgpack.decode(encoded); 32 | 33 | }); 34 | ``` 35 | 36 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "msgpack-js", 3 | "version": "1.0.0", 4 | "description": "A msgpack encoder and decoder using ArrayBuffer and DataView", 5 | "main": "msgpack.js", 6 | "scripts": ["msgpack.js"] 7 | } 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Msgpack 6 | 30 | 31 | 32 |

Msgpack

33 |

- running...

34 |

- succeeded

35 |

- FAILED

36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
InputEncodedDecoded
46 | 47 | 48 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /msgpack.js: -------------------------------------------------------------------------------- 1 | /* jshint browser: true */ 2 | /* global define, module */ 3 | ( // Module boilerplate to support browser globals and browserify and AMD. 4 | typeof define === "function" ? function (m) { define("msgpack-js", m); } : 5 | typeof exports === "object" ? function (m) { module.exports = m(); } : 6 | function(m){ this.msgpack = m(); } 7 | )(function () { 8 | "use strict"; 9 | 10 | var exports = {}; 11 | 12 | exports.inspect = inspect; 13 | function inspect(buffer) { 14 | if (buffer === undefined) return "undefined"; 15 | var view; 16 | var type; 17 | if (buffer instanceof ArrayBuffer) { 18 | type = "ArrayBuffer"; 19 | view = new DataView(buffer); 20 | } 21 | else if (buffer instanceof DataView) { 22 | type = "DataView"; 23 | view = buffer; 24 | } 25 | if (!view) return JSON.stringify(buffer); 26 | var bytes = []; 27 | for (var i = 0; i < buffer.byteLength; i++) { 28 | if (i > 20) { 29 | bytes.push("..."); 30 | break; 31 | } 32 | var byte = view.getUint8(i).toString(16); 33 | if (byte.length === 1) byte = "0" + byte; 34 | bytes.push(byte); 35 | } 36 | return "<" + type + " " + bytes.join(" ") + ">"; 37 | } 38 | 39 | // Encode string as utf8 into dataview at offset 40 | exports.utf8Write = utf8Write; 41 | function utf8Write(view, offset, string) { 42 | var byteLength = view.byteLength; 43 | for(var i = 0, l = string.length; i < l; i++) { 44 | var codePoint = string.charCodeAt(i); 45 | 46 | // One byte of UTF-8 47 | if (codePoint < 0x80) { 48 | view.setUint8(offset++, codePoint >>> 0 & 0x7f | 0x00); 49 | continue; 50 | } 51 | 52 | // Two bytes of UTF-8 53 | if (codePoint < 0x800) { 54 | view.setUint8(offset++, codePoint >>> 6 & 0x1f | 0xc0); 55 | view.setUint8(offset++, codePoint >>> 0 & 0x3f | 0x80); 56 | continue; 57 | } 58 | 59 | // Three bytes of UTF-8. 60 | if (codePoint < 0x10000) { 61 | view.setUint8(offset++, codePoint >>> 12 & 0x0f | 0xe0); 62 | view.setUint8(offset++, codePoint >>> 6 & 0x3f | 0x80); 63 | view.setUint8(offset++, codePoint >>> 0 & 0x3f | 0x80); 64 | continue; 65 | } 66 | 67 | // Four bytes of UTF-8 68 | if (codePoint < 0x110000) { 69 | view.setUint8(offset++, codePoint >>> 18 & 0x07 | 0xf0); 70 | view.setUint8(offset++, codePoint >>> 12 & 0x3f | 0x80); 71 | view.setUint8(offset++, codePoint >>> 6 & 0x3f | 0x80); 72 | view.setUint8(offset++, codePoint >>> 0 & 0x3f | 0x80); 73 | continue; 74 | } 75 | throw new Error("bad codepoint " + codePoint); 76 | } 77 | } 78 | 79 | exports.utf8Read = utf8Read; 80 | function utf8Read(view, offset, length) { 81 | var string = ""; 82 | for (var i = offset, end = offset + length; i < end; i++) { 83 | var byte = view.getUint8(i); 84 | // One byte character 85 | if ((byte & 0x80) === 0x00) { 86 | string += String.fromCharCode(byte); 87 | continue; 88 | } 89 | // Two byte character 90 | if ((byte & 0xe0) === 0xc0) { 91 | string += String.fromCharCode( 92 | ((byte & 0x1f) << 6) | 93 | (view.getUint8(++i) & 0x3f) 94 | ); 95 | continue; 96 | } 97 | // Three byte character 98 | if ((byte & 0xf0) === 0xe0) { 99 | string += String.fromCharCode( 100 | ((byte & 0x0f) << 12) | 101 | ((view.getUint8(++i) & 0x3f) << 6) | 102 | ((view.getUint8(++i) & 0x3f) << 0) 103 | ); 104 | continue; 105 | } 106 | // Four byte character 107 | if ((byte & 0xf8) === 0xf0) { 108 | string += String.fromCharCode( 109 | ((byte & 0x07) << 18) | 110 | ((view.getUint8(++i) & 0x3f) << 12) | 111 | ((view.getUint8(++i) & 0x3f) << 6) | 112 | ((view.getUint8(++i) & 0x3f) << 0) 113 | ); 114 | continue; 115 | } 116 | throw new Error("Invalid byte " + byte.toString(16)); 117 | } 118 | return string; 119 | } 120 | 121 | exports.utf8ByteCount = utf8ByteCount; 122 | function utf8ByteCount(string) { 123 | var count = 0; 124 | for(var i = 0, l = string.length; i < l; i++) { 125 | var codePoint = string.charCodeAt(i); 126 | if (codePoint < 0x80) { 127 | count += 1; 128 | continue; 129 | } 130 | if (codePoint < 0x800) { 131 | count += 2; 132 | continue; 133 | } 134 | if (codePoint < 0x10000) { 135 | count += 3; 136 | continue; 137 | } 138 | if (codePoint < 0x110000) { 139 | count += 4; 140 | continue; 141 | } 142 | throw new Error("bad codepoint " + codePoint); 143 | } 144 | return count; 145 | } 146 | 147 | exports.encode = function (value) { 148 | var buffer = new ArrayBuffer(encodedSize(value)); 149 | var view = new DataView(buffer); 150 | encode(value, view, 0); 151 | return buffer; 152 | }; 153 | 154 | exports.decode = decode; 155 | 156 | // https://github.com/msgpack/msgpack/blob/master/spec.md 157 | // we reserve extension type 0x00 to encode javascript 'undefined' 158 | 159 | function Decoder(view, offset) { 160 | this.offset = offset || 0; 161 | this.view = view; 162 | } 163 | Decoder.prototype.map = function (length) { 164 | var value = {}; 165 | for (var i = 0; i < length; i++) { 166 | var key = this.parse(); 167 | value[key] = this.parse(); 168 | } 169 | return value; 170 | }; 171 | Decoder.prototype.bin = function (length) { 172 | var value = new ArrayBuffer(length); 173 | (new Uint8Array(value)).set(new Uint8Array(this.view.buffer, this.offset, length), 0); 174 | this.offset += length; 175 | return value; 176 | }; 177 | Decoder.prototype.str = function (length) { 178 | var value = utf8Read(this.view, this.offset, length); 179 | this.offset += length; 180 | return value; 181 | }; 182 | Decoder.prototype.array = function (length) { 183 | var value = new Array(length); 184 | for (var i = 0; i < length; i++) { 185 | value[i] = this.parse(); 186 | } 187 | return value; 188 | }; 189 | Decoder.prototype.parse = function () { 190 | var type = this.view.getUint8(this.offset); 191 | var value, length; 192 | // FixStr 193 | if ((type & 0xe0) === 0xa0) { 194 | length = type & 0x1f; 195 | this.offset++; 196 | return this.str(length); 197 | } 198 | // FixMap 199 | if ((type & 0xf0) === 0x80) { 200 | length = type & 0x0f; 201 | this.offset++; 202 | return this.map(length); 203 | } 204 | // FixArray 205 | if ((type & 0xf0) === 0x90) { 206 | length = type & 0x0f; 207 | this.offset++; 208 | return this.array(length); 209 | } 210 | // Positive FixNum 211 | if ((type & 0x80) === 0x00) { 212 | this.offset++; 213 | return type; 214 | } 215 | // Negative Fixnum 216 | if ((type & 0xe0) === 0xe0) { 217 | value = this.view.getInt8(this.offset); 218 | this.offset++; 219 | return value; 220 | } 221 | // Undefined as FixExt1 222 | if (type === 0xd4 && this.view.getUint8(this.offset + 1) === 0x00) { 223 | this.offset += 3; 224 | return undefined; 225 | } 226 | switch (type) { 227 | // str 8 228 | case 0xd9: 229 | length = this.view.getUint8(this.offset + 1); 230 | this.offset += 2; 231 | return this.str(length); 232 | // str 16 233 | case 0xda: 234 | length = this.view.getUint16(this.offset + 1); 235 | this.offset += 3; 236 | return this.str(length); 237 | // str 32 238 | case 0xdb: 239 | length = this.view.getUint32(this.offset + 1); 240 | this.offset += 5; 241 | return this.str(length); 242 | // bin 8 243 | case 0xc4: 244 | length = this.view.getUint8(this.offset + 1); 245 | this.offset += 2; 246 | return this.bin(length); 247 | // bin 16 248 | case 0xc5: 249 | length = this.view.getUint16(this.offset + 1); 250 | this.offset += 3; 251 | return this.bin(length); 252 | // bin 32 253 | case 0xc6: 254 | length = this.view.getUint32(this.offset + 1); 255 | this.offset += 5; 256 | return this.bin(length); 257 | // nil 258 | case 0xc0: 259 | this.offset++; 260 | return null; 261 | // false 262 | case 0xc2: 263 | this.offset++; 264 | return false; 265 | // true 266 | case 0xc3: 267 | this.offset++; 268 | return true; 269 | // uint8 270 | case 0xcc: 271 | value = this.view.getUint8(this.offset + 1); 272 | this.offset += 2; 273 | return value; 274 | // uint 16 275 | case 0xcd: 276 | value = this.view.getUint16(this.offset + 1); 277 | this.offset += 3; 278 | return value; 279 | // uint 32 280 | case 0xce: 281 | value = this.view.getUint32(this.offset + 1); 282 | this.offset += 5; 283 | return value; 284 | // uint 64 285 | case 0xcf: 286 | var high = this.view.getUint32(this.offset + 1); 287 | var low = this.view.getUint32(this.offset + 5); 288 | value = high*0x100000000 + low; 289 | this.offset += 9; 290 | return value; 291 | // int 8 292 | case 0xd0: 293 | value = this.view.getInt8(this.offset + 1); 294 | this.offset += 2; 295 | return value; 296 | // int 16 297 | case 0xd1: 298 | value = this.view.getInt16(this.offset + 1); 299 | this.offset += 3; 300 | return value; 301 | // int 32 302 | case 0xd2: 303 | value = this.view.getInt32(this.offset + 1); 304 | this.offset += 5; 305 | return value; 306 | // int 64 307 | case 0xd3: 308 | var high = this.view.getInt32(this.offset + 1); 309 | var low = this.view.getUint32(this.offset + 5); 310 | value = high*0x100000000 + low; 311 | this.offset += 9; 312 | return value; 313 | // map 16 314 | case 0xde: 315 | length = this.view.getUint16(this.offset + 1); 316 | this.offset += 3; 317 | return this.map(length); 318 | // map 32 319 | case 0xdf: 320 | length = this.view.getUint32(this.offset + 1); 321 | this.offset += 5; 322 | return this.map(length); 323 | // array 16 324 | case 0xdc: 325 | length = this.view.getUint16(this.offset + 1); 326 | this.offset += 3; 327 | return this.array(length); 328 | // array 32 329 | case 0xdd: 330 | length = this.view.getUint32(this.offset + 1); 331 | this.offset += 5; 332 | return this.array(length); 333 | // float 334 | case 0xca: 335 | value = this.view.getFloat32(this.offset + 1); 336 | this.offset += 5; 337 | return value; 338 | // double 339 | case 0xcb: 340 | value = this.view.getFloat64(this.offset + 1); 341 | this.offset += 9; 342 | return value; 343 | } 344 | throw new Error("Unknown type 0x" + type.toString(16)); 345 | }; 346 | function decode(buffer) { 347 | var view = new DataView(buffer); 348 | var decoder = new Decoder(view); 349 | var value = decoder.parse(); 350 | if (decoder.offset !== buffer.byteLength) throw new Error((buffer.byteLength - decoder.offset) + " trailing bytes"); 351 | return value; 352 | } 353 | 354 | function encode(value, view, offset) { 355 | var type = typeof value; 356 | 357 | // Strings Bytes 358 | if (type === "string") { 359 | var length = utf8ByteCount(value); 360 | // fix str 361 | if (length < 0x20) { 362 | view.setUint8(offset, length | 0xa0); 363 | utf8Write(view, offset + 1, value); 364 | return 1 + length; 365 | } 366 | // str 8 367 | if (length < 0x100) { 368 | view.setUint8(offset, 0xd9); 369 | view.setUint8(offset + 1, length); 370 | utf8Write(view, offset + 2, value); 371 | return 2 + length; 372 | } 373 | // str 16 374 | if (length < 0x10000) { 375 | view.setUint8(offset, 0xda); 376 | view.setUint16(offset + 1, length); 377 | utf8Write(view, offset + 3, value); 378 | return 3 + length; 379 | } 380 | // str 32 381 | if (length < 0x100000000) { 382 | view.setUint8(offset, 0xdb); 383 | view.setUint32(offset + 1, length); 384 | utf8Write(view, offset + 5, value); 385 | return 5 + length; 386 | } 387 | } 388 | 389 | if (value instanceof ArrayBuffer) { 390 | var length = value.byteLength; 391 | // bin 8 392 | if (length < 0x100) { 393 | view.setUint8(offset, 0xc4); 394 | view.setUint8(offset + 1, length); 395 | (new Uint8Array(view.buffer)).set(new Uint8Array(value), offset + 2); 396 | return 2 + length; 397 | } 398 | // bin 16 399 | if (length < 0x10000) { 400 | view.setUint8(offset, 0xc5); 401 | view.setUint16(offset + 1, length); 402 | (new Uint8Array(view.buffer)).set(new Uint8Array(value), offset + 3); 403 | return 3 + length; 404 | } 405 | // bin 32 406 | if (length < 0x100000000) { 407 | view.setUint8(offset, 0xc6); 408 | view.setUint32(offset + 1, length); 409 | (new Uint8Array(view.buffer)).set(new Uint8Array(value), offset + 5); 410 | return 5 + length; 411 | } 412 | } 413 | 414 | if (type === "number") { 415 | // Floating Point 416 | if ((value << 0) !== value) { 417 | view.setUint8(offset, 0xcb); 418 | view.setFloat64(offset + 1, value); 419 | return 9; 420 | } 421 | 422 | // Integers 423 | if (value >=0) { 424 | // positive fixnum 425 | if (value < 0x80) { 426 | view.setUint8(offset, value); 427 | return 1; 428 | } 429 | // uint 8 430 | if (value < 0x100) { 431 | view.setUint8(offset, 0xcc); 432 | view.setUint8(offset + 1, value); 433 | return 2; 434 | } 435 | // uint 16 436 | if (value < 0x10000) { 437 | view.setUint8(offset, 0xcd); 438 | view.setUint16(offset + 1, value); 439 | return 3; 440 | } 441 | // uint 32 442 | if (value < 0x100000000) { 443 | view.setUint8(offset, 0xce); 444 | view.setUint32(offset + 1, value); 445 | return 5; 446 | } 447 | throw new Error("Number too big 0x" + value.toString(16)); 448 | } 449 | // negative fixnum 450 | if (value >= -0x20) { 451 | view.setInt8(offset, value); 452 | return 1; 453 | } 454 | // int 8 455 | if (value >= -0x80) { 456 | view.setUint8(offset, 0xd0); 457 | view.setInt8(offset + 1, value); 458 | return 2; 459 | } 460 | // int 16 461 | if (value >= -0x8000) { 462 | view.setUint8(offset, 0xd1); 463 | view.setInt16(offset + 1, value); 464 | return 3; 465 | } 466 | // int 32 467 | if (value >= -0x80000000) { 468 | view.setUint8(offset, 0xd2); 469 | view.setInt32(offset + 1, value); 470 | return 5; 471 | } 472 | throw new Error("Number too small -0x" + (-value).toString(16).substr(1)); 473 | } 474 | 475 | // undefined 476 | if (type === "undefined") { 477 | view.setUint8(offset, 0xd4); // fixext 1 478 | view.setUint8(offset + 1, 0); // type (undefined) 479 | view.setUint8(offset + 2, 0); // data (ignored) 480 | return 3; 481 | } 482 | 483 | // null 484 | if (value === null) { 485 | view.setUint8(offset, 0xc0); 486 | return 1; 487 | } 488 | 489 | // Boolean 490 | if (type === "boolean") { 491 | view.setUint8(offset, value ? 0xc3 : 0xc2); 492 | return 1; 493 | } 494 | 495 | // Container Types 496 | if (type === "object") { 497 | var length, size = 0; 498 | var isArray = Array.isArray(value); 499 | 500 | if (isArray) { 501 | length = value.length; 502 | } 503 | else { 504 | var keys = Object.keys(value); 505 | length = keys.length; 506 | } 507 | 508 | var size; 509 | if (length < 0x10) { 510 | view.setUint8(offset, length | (isArray ? 0x90 : 0x80)); 511 | size = 1; 512 | } 513 | else if (length < 0x10000) { 514 | view.setUint8(offset, isArray ? 0xdc : 0xde); 515 | view.setUint16(offset + 1, length); 516 | size = 3; 517 | } 518 | else if (length < 0x100000000) { 519 | view.setUint8(offset, isArray ? 0xdd : 0xdf); 520 | view.setUint32(offset + 1, length); 521 | size = 5; 522 | } 523 | 524 | if (isArray) { 525 | for (var i = 0; i < length; i++) { 526 | size += encode(value[i], view, offset + size); 527 | } 528 | } 529 | else { 530 | for (var i = 0; i < length; i++) { 531 | var key = keys[i]; 532 | size += encode(key, view, offset + size); 533 | size += encode(value[key], view, offset + size); 534 | } 535 | } 536 | 537 | return size; 538 | } 539 | throw new Error("Unknown type " + type); 540 | } 541 | 542 | function encodedSize(value) { 543 | var type = typeof value; 544 | 545 | // Raw Bytes 546 | if (type === "string") { 547 | var length = utf8ByteCount(value); 548 | if (length < 0x20) { 549 | return 1 + length; 550 | } 551 | if (length < 0x100) { 552 | return 2 + length; 553 | } 554 | if (length < 0x10000) { 555 | return 3 + length; 556 | } 557 | if (length < 0x100000000) { 558 | return 5 + length; 559 | } 560 | } 561 | 562 | if (value instanceof ArrayBuffer) { 563 | var length = value.byteLength; 564 | if (length < 0x100) { 565 | return 2 + length; 566 | } 567 | if (length < 0x10000) { 568 | return 3 + length; 569 | } 570 | if (length < 0x100000000) { 571 | return 5 + length; 572 | } 573 | } 574 | 575 | if (type === "number") { 576 | // Floating Point 577 | // double 578 | if (value << 0 !== value) return 9; 579 | 580 | // Integers 581 | if (value >=0) { 582 | // positive fixnum 583 | if (value < 0x80) return 1; 584 | // uint 8 585 | if (value < 0x100) return 2; 586 | // uint 16 587 | if (value < 0x10000) return 3; 588 | // uint 32 589 | if (value < 0x100000000) return 5; 590 | // uint 64 591 | if (value < 0x10000000000000000) return 9; 592 | throw new Error("Number too big 0x" + value.toString(16)); 593 | } 594 | // negative fixnum 595 | if (value >= -0x20) return 1; 596 | // int 8 597 | if (value >= -0x80) return 2; 598 | // int 16 599 | if (value >= -0x8000) return 3; 600 | // int 32 601 | if (value >= -0x80000000) return 5; 602 | // int 64 603 | if (value >= -0x8000000000000000) return 9; 604 | throw new Error("Number too small -0x" + value.toString(16).substr(1)); 605 | } 606 | 607 | // undefined 608 | if (type === "undefined") return 3; 609 | 610 | // Boolean, null 611 | if (type === "boolean" || value === null) return 1; 612 | 613 | // Container Types 614 | if (type === "object") { 615 | var length, size = 0; 616 | if (Array.isArray(value)) { 617 | length = value.length; 618 | for (var i = 0; i < length; i++) { 619 | size += encodedSize(value[i]); 620 | } 621 | } 622 | else { 623 | var keys = Object.keys(value); 624 | length = keys.length; 625 | for (var i = 0; i < length; i++) { 626 | var key = keys[i]; 627 | size += encodedSize(key) + encodedSize(value[key]); 628 | } 629 | } 630 | if (length < 0x10) { 631 | return 1 + size; 632 | } 633 | if (length < 0x10000) { 634 | return 3 + size; 635 | } 636 | if (length < 0x100000000) { 637 | return 5 + size; 638 | } 639 | throw new Error("Array or object too long 0x" + length.toString(16)); 640 | } 641 | throw new Error("Unknown type " + type); 642 | } 643 | 644 | return exports; 645 | 646 | }); 647 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "msgpack-js-browser", 3 | "version": "1.0.0", 4 | "description": "A msgpack encoder and decoder using ArrayBuffer and DataView", 5 | "main": "msgpack.js", 6 | "licenses": [ 7 | { 8 | "type": "MIT", 9 | "url": "http://www.opensource.org/licenses/mit-license.php" 10 | } 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/creationix/msgpack-js-browser.git" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env phantomjs 2 | 3 | var page = require('webpage').create(); 4 | 5 | page.onConsoleMessage = function (msg) { 6 | console.log(msg); 7 | }; 8 | 9 | page.onError = function (msg, trace) { 10 | console.log(msg); 11 | trace.forEach(function(item) { 12 | console.log(' ', item.file, ':', item.line); 13 | }); 14 | phantom.exit(1); 15 | }; 16 | 17 | page.onLoadFinished = function (status) { 18 | if (status !== "success") { 19 | console.log("page.open failed"); 20 | phantom.exit(2); 21 | } 22 | console.log("All tests passed!"); 23 | phantom.exit(); 24 | }; 25 | 26 | console.log('Loading test page'); 27 | page.open("./index.html"); 28 | --------------------------------------------------------------------------------