diff --git a/src/Encoder.ts b/src/Encoder.ts index b047c1d..76051fe 100644 --- a/src/Encoder.ts +++ b/src/Encoder.ts @@ -72,6 +72,10 @@ export type EncoderOptions = Partial< > & ContextOf; +// Bounds of the int64/uint64 range msgpack can represent, precomputed once for encodeBigInt64. +const INT64_MIN = -(BigInt(2) ** BigInt(63)); +const UINT64_MAX = BigInt(2) ** BigInt(64) - BigInt(1); + export class Encoder { private readonly extensionCodec: ExtensionCodecType; private readonly context: ContextType; @@ -291,6 +295,9 @@ export class Encoder { } private encodeBigInt64(object: bigint): void { + if (object < INT64_MIN || object > UINT64_MAX) { + throw new Error(`Cannot encode BigInt as int64/uint64 because it is out of range: ${object}`); + } if (object >= BigInt(0)) { // uint 64 this.writeU8(0xcf); diff --git a/test/bigint64.test.ts b/test/bigint64.test.ts index e2bf08f..f6f5c96 100644 --- a/test/bigint64.test.ts +++ b/test/bigint64.test.ts @@ -35,4 +35,30 @@ describe("useBigInt64: true", () => { const encoded = encode(value, { useBigInt64: true }); assert.deepStrictEqual(decode(encoded, { useBigInt64: true }), value); }); + + it("round-trips the boundary values of int64/uint64", () => { + const values = [ + BigInt(0), + BigInt(42), + BigInt(2) ** BigInt(63) - BigInt(1), // max int64 + -(BigInt(2) ** BigInt(63)), // min int64 + BigInt(2) ** BigInt(64) - BigInt(1), // max uint64 + ]; + for (const value of values) { + const encoded = encode(value, { useBigInt64: true }); + assert.deepStrictEqual(decode(encoded, { useBigInt64: true }), value); + } + }); + + it("throws when a bigint is out of the int64/uint64 range", () => { + const values = [ + BigInt(2) ** BigInt(64), // uint64 max + 1 + BigInt(2) ** BigInt(64) + BigInt(1), + -(BigInt(2) ** BigInt(63)) - BigInt(1), // int64 min - 1 + -(BigInt(2) ** BigInt(100)), + ]; + for (const value of values) { + assert.throws(() => encode(value, { useBigInt64: true }), /out of range/); + } + }); });