msgpack.js (9858B)
1 // Copyright © 2019, Yves Goergen, https://unclassified.software/source/msgpack-js 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 4 // associated documentation files (the “Software”), to deal in the Software without restriction, 5 // including without limitation the rights to use, copy, modify, merge, publish, distribute, 6 // sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 7 // furnished to do so, subject to the following conditions: 8 // 9 // The above copyright notice and this permission notice shall be included in all copies or 10 // substantial portions of the Software. 11 // 12 // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 13 // NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 15 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 18 "use strict"; 19 20 // Deserializes a MessagePack byte array to a value. 21 // 22 // array: The MessagePack byte array to deserialize. This must be an Array or Uint8Array containing bytes, not a string. 23 function deserialize(array) { 24 const pow32 = 0x100000000; // 2^32 25 let pos = 0; 26 if (array instanceof ArrayBuffer) { 27 array = new Uint8Array(array); 28 } 29 if (typeof array !== "object" || typeof array.length === "undefined") { 30 throw new Error( 31 "Invalid argument type: Expected a byte array (Array or Uint8Array) to deserialize." 32 ); 33 } 34 if (!array.length) { 35 throw new Error( 36 "Invalid argument: The byte array to deserialize is empty." 37 ); 38 } 39 if (!(array instanceof Uint8Array)) { 40 array = new Uint8Array(array); 41 } 42 const data = read(); 43 if (pos < array.length) { 44 // Junk data at the end 45 } 46 return data; 47 48 // eslint-disable-next-line complexity 49 function read() { 50 const byte = array[pos++]; 51 if (byte >= 0x00 && byte <= 0x7f) { 52 return byte; 53 } // positive fixint 54 if (byte >= 0x80 && byte <= 0x8f) { 55 return readMap(byte - 0x80); 56 } // fixmap 57 if (byte >= 0x90 && byte <= 0x9f) { 58 return readArray(byte - 0x90); 59 } // fixarray 60 if (byte >= 0xa0 && byte <= 0xbf) { 61 return readStr(byte - 0xa0); 62 } // fixstr 63 if (byte === 0xc0) { 64 return null; 65 } // nil 66 if (byte === 0xc1) { 67 throw new Error("Invalid byte code 0xc1 found."); 68 } // never used 69 if (byte === 0xc2) { 70 return false; 71 } // false 72 if (byte === 0xc3) { 73 return true; 74 } // true 75 if (byte === 0xc4) { 76 return readBin(-1, 1); 77 } // bin 8 78 if (byte === 0xc5) { 79 return readBin(-1, 2); 80 } // bin 16 81 if (byte === 0xc6) { 82 return readBin(-1, 4); 83 } // bin 32 84 if (byte === 0xc7) { 85 return readExt(-1, 1); 86 } // ext 8 87 if (byte === 0xc8) { 88 return readExt(-1, 2); 89 } // ext 16 90 if (byte === 0xc9) { 91 return readExt(-1, 4); 92 } // ext 32 93 if (byte === 0xca) { 94 return readFloat(4); 95 } // float 32 96 if (byte === 0xcb) { 97 return readFloat(8); 98 } // float 64 99 if (byte === 0xcc) { 100 return readUInt(1); 101 } // uint 8 102 if (byte === 0xcd) { 103 return readUInt(2); 104 } // uint 16 105 if (byte === 0xce) { 106 return readUInt(4); 107 } // uint 32 108 if (byte === 0xcf) { 109 return readUInt(8); 110 } // uint 64 111 if (byte === 0xd0) { 112 return readInt(1); 113 } // int 8 114 if (byte === 0xd1) { 115 return readInt(2); 116 } // int 16 117 if (byte === 0xd2) { 118 return readInt(4); 119 } // int 32 120 if (byte === 0xd3) { 121 return readInt(8); 122 } // int 64 123 if (byte === 0xd4) { 124 return readExt(1); 125 } // fixext 1 126 if (byte === 0xd5) { 127 return readExt(2); 128 } // fixext 2 129 if (byte === 0xd6) { 130 return readExt(4); 131 } // fixext 4 132 if (byte === 0xd7) { 133 return readExt(8); 134 } // fixext 8 135 if (byte === 0xd8) { 136 return readExt(16); 137 } // fixext 16 138 if (byte === 0xd9) { 139 return readStr(-1, 1); 140 } // str 8 141 if (byte === 0xda) { 142 return readStr(-1, 2); 143 } // str 16 144 if (byte === 0xdb) { 145 return readStr(-1, 4); 146 } // str 32 147 if (byte === 0xdc) { 148 return readArray(-1, 2); 149 } // array 16 150 if (byte === 0xdd) { 151 return readArray(-1, 4); 152 } // array 32 153 if (byte === 0xde) { 154 return readMap(-1, 2); 155 } // map 16 156 if (byte === 0xdf) { 157 return readMap(-1, 4); 158 } // map 32 159 if (byte >= 0xe0 && byte <= 0xff) { 160 return byte - 256; 161 } // negative fixint 162 console.debug("msgpack array:", array); 163 throw new Error( 164 "Invalid byte value '" + 165 byte + 166 "' at index " + 167 (pos - 1) + 168 " in the MessagePack binary data (length " + 169 array.length + 170 "): Expecting a range of 0 to 255. This is not a byte array." 171 ); 172 } 173 174 function readInt(size) { 175 let value = 0; 176 let first = true; 177 while (size-- > 0) { 178 if (first) { 179 const byte = array[pos++]; 180 value += byte & 0x7f; 181 if (byte & 0x80) { 182 value -= 0x80; // Treat most-significant bit as -2^i instead of 2^i 183 } 184 first = false; 185 } else { 186 value *= 256; 187 value += array[pos++]; 188 } 189 } 190 return value; 191 } 192 193 function readUInt(size) { 194 let value = 0; 195 while (size-- > 0) { 196 value *= 256; 197 value += array[pos++]; 198 } 199 return value; 200 } 201 202 function readFloat(size) { 203 const view = new DataView(array.buffer, pos, size); 204 pos += size; 205 if (size === 4) { 206 return view.getFloat32(0, false); 207 } 208 if (size === 8) { 209 return view.getFloat64(0, false); 210 } 211 throw new Error("Invalid size for readFloat."); 212 } 213 214 function readBin(size, lengthSize) { 215 if (size < 0) { 216 size = readUInt(lengthSize); 217 } 218 const readData = array.subarray(pos, pos + size); 219 pos += size; 220 return readData; 221 } 222 223 function readMap(size, lengthSize) { 224 if (size < 0) { 225 size = readUInt(lengthSize); 226 } 227 const readData = {}; 228 while (size-- > 0) { 229 const key = read(); 230 readData[key] = read(); 231 } 232 return readData; 233 } 234 235 function readArray(size, lengthSize) { 236 if (size < 0) { 237 size = readUInt(lengthSize); 238 } 239 const readData = []; 240 while (size-- > 0) { 241 readData.push(read()); 242 } 243 return readData; 244 } 245 246 function readStr(size, lengthSize) { 247 if (size < 0) { 248 size = readUInt(lengthSize); 249 } 250 const start = pos; 251 pos += size; 252 return decodeUtf8(array, start, size); 253 } 254 255 function readExt(size, lengthSize) { 256 if (size < 0) { 257 size = readUInt(lengthSize); 258 } 259 const type = readUInt(1); 260 const readData = readBin(size); 261 switch (type) { 262 case 255: 263 return readExtDate(readData); 264 } 265 return { type, data: readData }; 266 } 267 268 function readExtDate(givenData) { 269 if (givenData.length === 4) { 270 const sec = 271 ((givenData[0] << 24) >>> 0) + 272 ((givenData[1] << 16) >>> 0) + 273 ((givenData[2] << 8) >>> 0) + 274 givenData[3]; 275 return new Date(sec * 1000); 276 } 277 if (givenData.length === 8) { 278 const ns = 279 ((givenData[0] << 22) >>> 0) + 280 ((givenData[1] << 14) >>> 0) + 281 ((givenData[2] << 6) >>> 0) + 282 (givenData[3] >>> 2); 283 const sec = 284 (givenData[3] & 0x3) * pow32 + 285 ((givenData[4] << 24) >>> 0) + 286 ((givenData[5] << 16) >>> 0) + 287 ((givenData[6] << 8) >>> 0) + 288 givenData[7]; 289 return new Date(sec * 1000 + ns / 1000000); 290 } 291 if (givenData.length === 12) { 292 const ns = 293 ((givenData[0] << 24) >>> 0) + 294 ((givenData[1] << 16) >>> 0) + 295 ((givenData[2] << 8) >>> 0) + 296 givenData[3]; 297 pos -= 8; 298 const sec = readInt(8); 299 return new Date(sec * 1000 + ns / 1000000); 300 } 301 throw new Error("Invalid givenData length for a date value."); 302 } 303 } 304 305 // Decodes a string from UTF-8 bytes. 306 function decodeUtf8(bytes, start, length) { 307 // Based on: https://gist.github.com/pascaldekloe/62546103a1576803dade9269ccf76330 308 let i = start, 309 str = ""; 310 length += start; 311 while (i < length) { 312 let c = bytes[i++]; 313 if (c > 127) { 314 if (c > 191 && c < 224) { 315 if (i >= length) { 316 throw new Error("UTF-8 decode: incomplete 2-byte sequence"); 317 } 318 c = ((c & 31) << 6) | (bytes[i++] & 63); 319 } else if (c > 223 && c < 240) { 320 if (i + 1 >= length) { 321 throw new Error("UTF-8 decode: incomplete 3-byte sequence"); 322 } 323 c = ((c & 15) << 12) | ((bytes[i++] & 63) << 6) | (bytes[i++] & 63); 324 } else if (c > 239 && c < 248) { 325 if (i + 2 >= length) { 326 throw new Error("UTF-8 decode: incomplete 4-byte sequence"); 327 } 328 c = 329 ((c & 7) << 18) | 330 ((bytes[i++] & 63) << 12) | 331 ((bytes[i++] & 63) << 6) | 332 (bytes[i++] & 63); 333 } else { 334 throw new Error( 335 "UTF-8 decode: unknown multibyte start 0x" + 336 c.toString(16) + 337 " at index " + 338 (i - 1) 339 ); 340 } 341 } 342 if (c <= 0xffff) { 343 str += String.fromCharCode(c); 344 } else if (c <= 0x10ffff) { 345 c -= 0x10000; 346 str += String.fromCharCode((c >> 10) | 0xd800); 347 str += String.fromCharCode((c & 0x3ff) | 0xdc00); 348 } else { 349 throw new Error( 350 "UTF-8 decode: code point 0x" + c.toString(16) + " exceeds UTF-16 reach" 351 ); 352 } 353 } 354 return str; 355 } 356 357 // The exported functions 358 const msgpack = { 359 deserialize, 360 361 // Compatibility with other libraries 362 decode: deserialize, 363 }; 364 365 module.exports = msgpack;