diff --git a/pack.js b/pack.js index 9fc4a66..dca72c7 100644 --- a/pack.js +++ b/pack.js @@ -532,8 +532,26 @@ export class Packr extends Unpackr { if (this.largeBigIntToFloat) { target[position++] = 0xcb targetView.setFloat64(position, Number(value)) + } else if (this.useBigIntExtension && value < 2n**(1023n) && value > -(2n**(1023n))) { + target[position++] = 0xc7 + position++; + target[position++] = 0x42 // "B" for BigInt + let bytes = []; + let alignedSign; + do { + let byte = value & 0xffn; + alignedSign = (byte & 0x80n) === (value < 0n ? 0x80n : 0n); + bytes.push(byte); + value >>= 8n; + } while (!((value === 0n || value === -1n) && alignedSign)); + target[position-2] = bytes.length; + for (let i = bytes.length; i > 0;) { + target[position++] = Number(bytes[--i]); + } + return } else { - throw new RangeError(value + ' was too large to fit in MessagePack 64-bit integer format, set largeBigIntToFloat to convert to float-64') + throw new RangeError(value + ' was too large to fit in MessagePack 64-bit integer format, use' + + ' useBigIntExtension or set largeBigIntToFloat to convert to float-64') } } position += 8 diff --git a/tests/test.js b/tests/test.js index ef193c8..f9c278f 100644 --- a/tests/test.js +++ b/tests/test.js @@ -374,6 +374,22 @@ suite('msgpackr basic tests', function() { assert.equal(pack(123).length, 1) }) + test('BigInt', function() { + let packr = new Packr({useBigIntExtension: true}) + let data = { + a: 3333333333333333333333333333n, + b: 1234567890123456789012345678901234567890n, + c: -3333333333333333333333333333n, + d: -352523523642364364364264264264264264262642642n, + e: 0xffffffffffffffffffffffffffn, + f: -0xffffffffffffffffffffffffffn, + } + let serialized = packr.pack(data) + let deserialized = packr.unpack(serialized) + assert.deepEqual(data, deserialized) + }) + + test('extended class pack/unpack', function(){ function Extended() { diff --git a/unpack.js b/unpack.js index 3e9e616..b019a02 100644 --- a/unpack.js +++ b/unpack.js @@ -988,6 +988,17 @@ const recordDefinition = (id, highByte) => { currentExtensions[0] = () => {} // notepack defines extension 0 to mean undefined, so use that as the default here currentExtensions[0].noBuffer = true +currentExtensions[0x42] = (data) => { + // decode bigint + let length = data.length; + let value = BigInt(data[0] & 0x80 ? data[0] - 0x100 : data[0]); + for (let i = 1; i < length; i++) { + value <<= 8n; + value += BigInt(data[i]); + } + return value; +} + let errors = { Error, TypeError, ReferenceError }; currentExtensions[0x65] = () => { let data = read()