/*!{id:msgpack.js,ver:1.05,license:"MIT",author:"uupaa.js@gmail.com"}*/ // === msgpack === // MessagePack -> http://msgpack.sourceforge.net/ this.msgpack || (function(globalScope) { globalScope.msgpack = { pack: msgpackpack, // msgpack.pack(data:Mix, // toString:Boolean = false):ByteArray/ByteString/false // [1][mix to String] msgpack.pack({}, true) -> "..." // [2][mix to ByteArray] msgpack.pack({}) -> [...] unpack: msgpackunpack, // msgpack.unpack(data:BinaryString/ByteArray):Mix // [1][String to mix] msgpack.unpack("...") -> {} // [2][ByteArray to mix] msgpack.unpack([...]) -> {} worker: "msgpack.js", // msgpack.worker - WebWorkers script filename upload: msgpackupload, // msgpack.upload(url:String, option:Hash, callback:Function) download: msgpackdownload // msgpack.download(url:String, option:Hash, callback:Function) }; var _ie = /MSIE/.test(navigator.userAgent), _bin2num = {}, // BinaryStringToNumber { "\00": 0, ... "\ff": 255 } _num2bin = {}, // NumberToBinaryString { 0: "\00", ... 255: "\ff" } _num2b64 = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz0123456789+/").split(""), _buf = [], // decode buffer _idx = 0, // decode buffer[index] _error = 0, // msgpack.pack() error code. 1 = CYCLIC_REFERENCE_ERROR _isArray = Array.isArray || (function(mix) { return Object.prototype.toString.call(mix) === "[object Array]"; }), _toString = String.fromCharCode, // CharCode/ByteArray to String _MAX_DEPTH = 512; // for WebWorkers Code Block self.importScripts && (onmessage = function(event) { if (event.data.method === "pack") { postMessage(base64encode(msgpackpack(event.data.data))); } else { postMessage(msgpackunpack(event.data.data)); } }); // msgpack.pack function msgpackpack(data, // @param Mix: toString) { // @param Boolean(= false): // @return ByteArray/BinaryString/false: // false is error return // [1][mix to String] msgpack.pack({}, true) -> "..." // [2][mix to ByteArray] msgpack.pack({}) -> [...] _error = 0; var byteArray = encode([], data, 0); return _error ? false : toString ? byteArrayToByteString(byteArray) : byteArray; } // msgpack.unpack function msgpackunpack(data) { // @param BinaryString/ByteArray: // @return Mix/undefined: // undefined is error return // [1][String to mix] msgpack.unpack("...") -> {} // [2][ByteArray to mix] msgpack.unpack([...]) -> {} _buf = typeof data === "string" ? toByteArray(data) : data; _idx = -1; return decode(); // mix or undefined } // inner - encoder function encode(rv, // @param ByteArray: result mix, // @param Mix: source data depth) { // @param Number: depth var size, i, iz, c, pos, // for UTF8.encode, Array.encode, Hash.encode high, low, sign, exp, frac; // for IEEE754 if (mix == null) { // null or undefined -> 0xc0 ( null ) rv.push(0xc0); } else if (mix === false) { // false -> 0xc2 ( false ) rv.push(0xc2); } else if (mix === true) { // true -> 0xc3 ( true ) rv.push(0xc3); } else { switch (typeof mix) { case "number": if (mix !== mix) { // isNaN rv.push(0xcb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff); // quiet NaN } else if (mix === Infinity) { rv.push(0xcb, 0x7f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); // positive infinity } else if (Math.floor(mix) === mix) { // int or uint if (mix < 0) { // int if (mix >= -32) { // negative fixnum rv.push(0xe0 + mix + 32); } else if (mix > -0x80) { rv.push(0xd0, mix + 0x100); } else if (mix > -0x8000) { mix += 0x10000; rv.push(0xd1, mix >> 8, mix & 0xff); } else if (mix > -0x80000000) { mix += 0x100000000; rv.push(0xd2, mix >>> 24, (mix >> 16) & 0xff, (mix >> 8) & 0xff, mix & 0xff); } else { high = Math.floor(mix / 0x100000000); low = mix & 0xffffffff; rv.push(0xd3, (high >> 24) & 0xff, (high >> 16) & 0xff, (high >> 8) & 0xff, high & 0xff, (low >> 24) & 0xff, (low >> 16) & 0xff, (low >> 8) & 0xff, low & 0xff); } } else { // uint if (mix < 0x80) { rv.push(mix); // positive fixnum } else if (mix < 0x100) { // uint 8 rv.push(0xcc, mix); } else if (mix < 0x10000) { // uint 16 rv.push(0xcd, mix >> 8, mix & 0xff); } else if (mix < 0x100000000) { // uint 32 rv.push(0xce, mix >>> 24, (mix >> 16) & 0xff, (mix >> 8) & 0xff, mix & 0xff); } else { high = Math.floor(mix / 0x100000000); low = mix & 0xffffffff; rv.push(0xcf, (high >> 24) & 0xff, (high >> 16) & 0xff, (high >> 8) & 0xff, high & 0xff, (low >> 24) & 0xff, (low >> 16) & 0xff, (low >> 8) & 0xff, low & 0xff); } } } else { // double // THX!! @edvakf // http://javascript.g.hatena.ne.jp/edvakf/20101128/1291000731 sign = mix < 0; sign && (mix *= -1); // add offset 1023 to ensure positive // 0.6931471805599453 = Math.LN2; exp = ((Math.log(mix) / 0.6931471805599453) + 1023) | 0; // shift 52 - (exp - 1023) bits to make integer part exactly 53 bits, // then throw away trash less than decimal point frac = mix * Math.pow(2, 52 + 1023 - exp); // S+-Exp(11)--++-----------------Fraction(52bits)-----------------------+ // || || | // v+----------++--------------------------------------------------------+ // 00000000|00000000|00000000|00000000|00000000|00000000|00000000|00000000 // 6 5 55 4 4 3 2 1 8 0 // 3 6 21 8 0 2 4 6 // // +----------high(32bits)-----------+ +----------low(32bits)------------+ // | | | | // +---------------------------------+ +---------------------------------+ // 3 2 21 1 8 0 // 1 4 09 6 low = frac & 0xffffffff; sign && (exp |= 0x800); high = ((frac / 0x100000000) & 0xfffff) | (exp << 20); rv.push(0xcb, (high >> 24) & 0xff, (high >> 16) & 0xff, (high >> 8) & 0xff, high & 0xff, (low >> 24) & 0xff, (low >> 16) & 0xff, (low >> 8) & 0xff, low & 0xff); } break; case "string": // http://d.hatena.ne.jp/uupaa/20101128 iz = mix.length; pos = rv.length; // keep rewrite position rv.push(0); // placeholder // utf8.encode for (i = 0; i < iz; ++i) { c = mix.charCodeAt(i); if (c < 0x80) { // ASCII(0x00 ~ 0x7f) rv.push(c & 0x7f); } else if (c < 0x0800) { rv.push(((c >>> 6) & 0x1f) | 0xc0, (c & 0x3f) | 0x80); } else if (c < 0x10000) { rv.push(((c >>> 12) & 0x0f) | 0xe0, ((c >>> 6) & 0x3f) | 0x80, (c & 0x3f) | 0x80); } } size = rv.length - pos - 1; if (size < 32) { rv[pos] = 0xa0 + size; // rewrite } else if (size < 0x10000) { // 16 rv.splice(pos, 1, 0xda, size >> 8, size & 0xff); } else if (size < 0x100000000) { // 32 rv.splice(pos, 1, 0xdb, size >>> 24, (size >> 16) & 0xff, (size >> 8) & 0xff, size & 0xff); } break; default: // array or hash if (++depth >= _MAX_DEPTH) { _error = 1; // CYCLIC_REFERENCE_ERROR return rv = []; // clear } if (_isArray(mix)) { size = mix.length; if (size < 16) { rv.push(0x90 + size); } else if (size < 0x10000) { // 16 rv.push(0xdc, size >> 8, size & 0xff); } else if (size < 0x100000000) { // 32 rv.push(0xdd, size >>> 24, (size >> 16) & 0xff, (size >> 8) & 0xff, size & 0xff); } for (i = 0; i < size; ++i) { encode(rv, mix[i], depth); } } else { // hash // http://d.hatena.ne.jp/uupaa/20101129 pos = rv.length; // keep rewrite position rv.push(0); // placeholder size = 0; for (i in mix) { ++size; encode(rv, i, depth); encode(rv, mix[i], depth); } if (size < 16) { rv[pos] = 0x80 + size; // rewrite } else if (size < 0x10000) { // 16 rv.splice(pos, 1, 0xde, size >> 8, size & 0xff); } else if (size < 0x100000000) { // 32 rv.splice(pos, 1, 0xdf, size >>> 24, (size >> 16) & 0xff, (size >> 8) & 0xff, size & 0xff); } } } } return rv; } // inner - decoder function decode() { // @return Mix: var size, i, iz, c, num = 0, sign, exp, frac, ary, hash, buf = _buf, type = buf[++_idx]; if (type >= 0xe0) { // Negative FixNum (111x xxxx) (-32 ~ -1) return type - 0x100; } if (type < 0xc0) { if (type < 0x80) { // Positive FixNum (0xxx xxxx) (0 ~ 127) return type; } if (type < 0x90) { // FixMap (1000 xxxx) num = type - 0x80; type = 0x80; } else if (type < 0xa0) { // FixArray (1001 xxxx) num = type - 0x90; type = 0x90; } else { // if (type < 0xc0) { // FixRaw (101x xxxx) num = type - 0xa0; type = 0xa0; } } switch (type) { case 0xc0: return null; case 0xc2: return false; case 0xc3: return true; case 0xca: // float num = buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) + (buf[++_idx] << 8) + buf[++_idx]; sign = num & 0x80000000; // 1bit exp = (num >> 23) & 0xff; // 8bits frac = num & 0x7fffff; // 23bits if (!num || num === 0x80000000) { // 0.0 or -0.0 return 0; } if (exp === 0xff) { // NaN or Infinity return frac ? NaN : Infinity; } return (sign ? -1 : 1) * (frac | 0x800000) * Math.pow(2, exp - 127 - 23); // 127: bias case 0xcb: // double num = buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) + (buf[++_idx] << 8) + buf[++_idx]; sign = num & 0x80000000; // 1bit exp = (num >> 20) & 0x7ff; // 11bits frac = num & 0xfffff; // 52bits - 32bits (high word) if (!num || num === 0x80000000) { // 0.0 or -0.0 _idx += 4; return 0; } if (exp === 0x7ff) { // NaN or Infinity _idx += 4; return frac ? NaN : Infinity; } num = buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) + (buf[++_idx] << 8) + buf[++_idx]; return (sign ? -1 : 1) * ((frac | 0x100000) * Math.pow(2, exp - 1023 - 20) // 1023: bias + num * Math.pow(2, exp - 1023 - 52)); // 0xcf: uint64, 0xce: uint32, 0xcd: uint16 case 0xcf: num = buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) + (buf[++_idx] << 8) + buf[++_idx]; return num * 0x100000000 + buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) + (buf[++_idx] << 8) + buf[++_idx]; case 0xce: num += buf[++_idx] * 0x1000000 + (buf[++_idx] << 16); case 0xcd: num += buf[++_idx] << 8; case 0xcc: return num + buf[++_idx]; // 0xd3: int64, 0xd2: int32, 0xd1: int16, 0xd0: int8 case 0xd3: num = buf[++_idx]; if (num & 0x80) { // sign -> avoid overflow return ((num ^ 0xff) * 0x100000000000000 + (buf[++_idx] ^ 0xff) * 0x1000000000000 + (buf[++_idx] ^ 0xff) * 0x10000000000 + (buf[++_idx] ^ 0xff) * 0x100000000 + (buf[++_idx] ^ 0xff) * 0x1000000 + (buf[++_idx] ^ 0xff) * 0x10000 + (buf[++_idx] ^ 0xff) * 0x100 + (buf[++_idx] ^ 0xff) + 1) * -1; } return num * 0x100000000000000 + buf[++_idx] * 0x1000000000000 + buf[++_idx] * 0x10000000000 + buf[++_idx] * 0x100000000 + buf[++_idx] * 0x1000000 + buf[++_idx] * 0x10000 + buf[++_idx] * 0x100 + buf[++_idx]; case 0xd2: num = buf[++_idx] * 0x1000000 + (buf[++_idx] << 16) + (buf[++_idx] << 8) + buf[++_idx]; return num < 0x80000000 ? num : num - 0x100000000; // 0x80000000 * 2 case 0xd1: num = (buf[++_idx] << 8) + buf[++_idx]; return num < 0x8000 ? num : num - 0x10000; // 0x8000 * 2 case 0xd0: num = buf[++_idx]; return num < 0x80 ? num : num - 0x100; // 0x80 * 2 // 0xdb: raw32, 0xda: raw16, 0xa0: raw ( string ) case 0xdb: num += buf[++_idx] * 0x1000000 + (buf[++_idx] << 16); case 0xda: num += (buf[++_idx] << 8) + buf[++_idx]; case 0xa0: // utf8.decode for (ary = [], i = _idx, iz = i + num; i < iz; ) { c = buf[++i]; // lead byte ary.push(c); } _idx = i; return ary.length < 10240 ? _toString.apply(null, ary) : byteArrayToByteString(ary); // 0xdf: map32, 0xde: map16, 0x80: map case 0xdf: num += buf[++_idx] * 0x1000000 + (buf[++_idx] << 16); case 0xde: num += (buf[++_idx] << 8) + buf[++_idx]; case 0x80: hash = {}; while (num--) { // make key/value pair size = buf[++_idx] - 0xa0; for (ary = [], i = _idx, iz = i + size; i < iz; ) { c = buf[++i]; // lead byte ary.push(c < 0x80 ? c : // ASCII(0x00 ~ 0x7f) c < 0xe0 ? ((c & 0x1f) << 6 | (buf[++i] & 0x3f)) : ((c & 0x0f) << 12 | (buf[++i] & 0x3f) << 6 | (buf[++i] & 0x3f))); } _idx = i; hash[_toString.apply(null, ary)] = decode(); } return hash; // 0xdd: array32, 0xdc: array16, 0x90: array case 0xdd: num += buf[++_idx] * 0x1000000 + (buf[++_idx] << 16); case 0xdc: num += (buf[++_idx] << 8) + buf[++_idx]; case 0x90: ary = []; while (num--) { ary.push(decode()); } return ary; } return; } // inner - byteArray To ByteString function byteArrayToByteString(byteArray) { // @param ByteArray // @return String // http://d.hatena.ne.jp/uupaa/20101128 try { return _toString.apply(this, byteArray); // toString } catch(err) { ; // avoid "Maximum call stack size exceeded" } var rv = [], i = 0, iz = byteArray.length, num2bin = _num2bin; for (; i < iz; ++i) { rv[i] = num2bin[byteArray[i]]; } return rv.join(""); } // msgpack.download - load from server function msgpackdownload(url, // @param String: option, // @param Hash: { worker, timeout, before, after } // option.worker - Boolean(= false): true is use WebWorkers // option.timeout - Number(= 10): timeout sec // option.before - Function: before(xhr, option) // option.after - Function: after(xhr, option, { status, ok }) callback) { // @param Function: callback(data, option, { status, ok }) // data - Mix/null: // option - Hash: // status - Number: HTTP status code // ok - Boolean: option.method = "GET"; option.binary = true; ajax(url, option, callback); } // msgpack.upload - save to server function msgpackupload(url, // @param String: option, // @param Hash: { data, worker, timeout, before, after } // option.data - Mix: // option.worker - Boolean(= false): true is use WebWorkers // option.timeout - Number(= 10): timeout sec // option.before - Function: before(xhr, option) // option.after - Function: after(xhr, option, { status, ok }) callback) { // @param Function: callback(data, option, { status, ok }) // data - String: responseText // option - Hash: // status - Number: HTTP status code // ok - Boolean: option.method = "PUT"; option.binary = true; if (option.worker && globalScope.Worker) { var worker = new Worker(msgpack.worker); worker.onmessage = function(event) { option.data = event.data; ajax(url, option, callback); }; worker.postMessage({ method: "pack", data: option.data }); } else { // pack and base64 encode option.data = base64encode(msgpackpack(option.data)); ajax(url, option, callback); } } // inner - function ajax(url, // @param String: option, // @param Hash: { data, ifmod, method, timeout, // header, binary, before, after, worker } // option.data - Mix: upload data // option.ifmod - Boolean: true is "If-Modified-Since" header // option.method - String: "GET", "POST", "PUT" // option.timeout - Number(= 10): timeout sec // option.header - Hash(= {}): { key: "value", ... } // option.binary - Boolean(= false): true is binary data // option.before - Function: before(xhr, option) // option.after - Function: after(xhr, option, { status, ok }) // option.worker - Boolean(= false): true is use WebWorkers callback) { // @param Function: callback(data, option, { status, ok }) // data - String/Mix/null: // option - Hash: // status - Number: HTTP status code // ok - Boolean: function readyStateChange() { if (xhr.readyState === 4) { var data, status = xhr.status, worker, byteArray, rv = { status: status, ok: status >= 200 && status < 300 }; if (!run++) { if (method === "PUT") { data = rv.ok ? xhr.responseText : ""; } else { if (rv.ok) { if (option.worker && globalScope.Worker) { worker = new Worker(msgpack.worker); worker.onmessage = function(event) { callback(event.data, option, rv); }; worker.postMessage({ method: "unpack", data: xhr.responseText }); gc(); return; } else { byteArray = _ie ? toByteArrayIE(xhr) : toByteArray(xhr.responseText); data = msgpackunpack(byteArray); } } } after && after(xhr, option, rv); callback(data, option, rv); gc(); } } } function ng(abort, status) { if (!run++) { var rv = { status: status || 400, ok: false }; after && after(xhr, option, rv); callback(null, option, rv); gc(abort); } } function gc(abort) { abort && xhr && xhr.abort && xhr.abort(); watchdog && (clearTimeout(watchdog), watchdog = 0); xhr = null; globalScope.addEventListener && globalScope.removeEventListener("beforeunload", ng, false); } var watchdog = 0, method = option.method || "GET", header = option.header || {}, before = option.before, after = option.after, data = option.data || null, xhr = globalScope.XMLHttpRequest ? new XMLHttpRequest() : globalScope.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : null, run = 0, i, overrideMimeType = "overrideMimeType", setRequestHeader = "setRequestHeader", getbinary = method === "GET" && option.binary; try { xhr.onreadystatechange = readyStateChange; xhr.open(method, url, true); // ASync before && before(xhr, option); getbinary && xhr[overrideMimeType] && xhr[overrideMimeType]("text/plain; charset=x-user-defined"); data && xhr[setRequestHeader]("Content-Type", "application/x-www-form-urlencoded"); for (i in header) { xhr[setRequestHeader](i, header[i]); } globalScope.addEventListener && globalScope.addEventListener("beforeunload", ng, false); // 400: Bad Request xhr.send(data); watchdog = setTimeout(function() { ng(1, 408); // 408: Request Time-out }, (option.timeout || 10) * 1000); } catch (err) { ng(0, 400); // 400: Bad Request } } // inner - BinaryString To ByteArray function toByteArray(data) { // @param BinaryString: "\00\01" // @return ByteArray: [0x00, 0x01] var rv = [], bin2num = _bin2num, remain, ary = data.split(""), i = -1, iz; iz = ary.length; remain = iz % 8; while (remain--) { ++i; rv[i] = bin2num[ary[i]]; } remain = iz >> 3; while (remain--) { rv.push(bin2num[ary[++i]], bin2num[ary[++i]], bin2num[ary[++i]], bin2num[ary[++i]], bin2num[ary[++i]], bin2num[ary[++i]], bin2num[ary[++i]], bin2num[ary[++i]]); } return rv; } // inner - BinaryString to ByteArray function toByteArrayIE(xhr) { var rv = [], data, remain, charCodeAt = "charCodeAt", loop, v0, v1, v2, v3, v4, v5, v6, v7, i = -1, iz; iz = vblen(xhr); data = vbstr(xhr); loop = Math.ceil(iz / 2); remain = loop % 8; while (remain--) { v0 = data[charCodeAt](++i); // 0x00,0x01 -> 0x0100 rv.push(v0 & 0xff, v0 >> 8); } remain = loop >> 3; while (remain--) { v0 = data[charCodeAt](++i); v1 = data[charCodeAt](++i); v2 = data[charCodeAt](++i); v3 = data[charCodeAt](++i); v4 = data[charCodeAt](++i); v5 = data[charCodeAt](++i); v6 = data[charCodeAt](++i); v7 = data[charCodeAt](++i); rv.push(v0 & 0xff, v0 >> 8, v1 & 0xff, v1 >> 8, v2 & 0xff, v2 >> 8, v3 & 0xff, v3 >> 8, v4 & 0xff, v4 >> 8, v5 & 0xff, v5 >> 8, v6 & 0xff, v6 >> 8, v7 & 0xff, v7 >> 8); } iz % 2 && rv.pop(); return rv; } // inner - base64.encode function base64encode(data) { // @param ByteArray: // @return Base64String: var rv = [], c = 0, i = -1, iz = data.length, pad = [0, 2, 1][data.length % 3], num2bin = _num2bin, num2b64 = _num2b64; if (globalScope.btoa) { while (i < iz) { rv.push(num2bin[data[++i]]); } return btoa(rv.join("")); } --iz; while (i < iz) { c = (data[++i] << 16) | (data[++i] << 8) | (data[++i]); // 24bit rv.push(num2b64[(c >> 18) & 0x3f], num2b64[(c >> 12) & 0x3f], num2b64[(c >> 6) & 0x3f], num2b64[ c & 0x3f]); } pad > 1 && (rv[rv.length - 2] = "="); pad > 0 && (rv[rv.length - 1] = "="); return rv.join(""); } // --- init --- (function() { var i = 0, v; for (; i < 0x100; ++i) { v = _toString(i); _bin2num[v] = i; // "\00" -> 0x00 _num2bin[i] = v; // 0 -> "\00" } // http://twitter.com/edvakf/statuses/15576483807 for (i = 0x80; i < 0x100; ++i) { // [Webkit][Gecko] _bin2num[_toString(0xf700 + i)] = i; // "\f780" -> 0x80 } })(); _ie && document.write('