diff --git a/packages/agent/src/cbor.test.ts b/packages/agent/src/cbor.test.ts index b36c8bfa6..035ed1454 100644 --- a/packages/agent/src/cbor.test.ts +++ b/packages/agent/src/cbor.test.ts @@ -60,3 +60,43 @@ function buf2hex(buffer: Uint8Array) { // join the elements. return Array.prototype.map.call(buffer, x => ('00' + x.toString(16)).slice(-2)).join(''); } + +describe('encode + decode numbers', () => { + it('should handle 0', () => { + expect(decode(encode(0))).toBe(0); + }); + it('should handle 1', () => { + expect(decode(encode(1))).toBe(1); + }); + it('should handle -1', () => { + expect(decode(encode(-1))).toBe(-1); + }); + it('should handle 255', () => { + expect(decode(encode(255))).toBe(255); + }); + it('should handle 256', () => { + expect(decode(encode(256))).toBe(256); + }); + it('should handle 65535', () => { + expect(decode(encode(65535))).toBe(65535); + }); + it('should handle 65536', () => { + expect(decode(encode(65536))).toBe(65536); + }); + it('should handle 4294967295', () => { + expect(decode(encode(4294967295))).toBe(4294967295); + }); + it('should handle 4294967296', () => { + expect(decode(encode(4294967296))).toBe(4294967296); + }); + it('should handle 18446744073709551615n', () => { + expect(decode(encode(BigInt('18446744073709551615')))).toBe(BigInt('18446744073709551615')); + }); + it('should encode 0n', () => { + expect(decode(encode(0n))).toBe(0n); + }); + it('should encode 1n', () => { + // Is this valid? + expect(decode(encode(1n))).toBe(1); + }); +}); diff --git a/packages/agent/src/cbor.ts b/packages/agent/src/cbor.ts index fec1a7d66..e59ac4eaf 100644 --- a/packages/agent/src/cbor.ts +++ b/packages/agent/src/cbor.ts @@ -50,7 +50,7 @@ class BufferEncoder implements CborEncoder { } } -class BigIntEncoder implements CborEncoder { +class BigIntEncoder implements CborEncoder { public get name() { return 'BigInt'; } @@ -64,13 +64,30 @@ class BigIntEncoder implements CborEncoder { } public encode(v: bigint): cbor.CborValue { - // Always use a bigint encoding. - if (v > BigInt(0)) { + // Use a bigint encoding for large values + if (v > BigInt(Number.MAX_SAFE_INTEGER)) { return cbor.value.tagged(2, cbor.value.bytes(fromHex(v.toString(16)))); - } else { + } + // If zero, use the zero encoding. + else if (v === BigInt(0)) { + return cbor.value.tagged(2, cbor.value.bytes(new Uint8Array([]))); + } + // Use Number serialization for safe numbers + else if (v >= BigInt(-1 * Number.MAX_SAFE_INTEGER) && v <= BigInt(Number.MAX_SAFE_INTEGER)) { + const serialized = serializer.serialize(Number(v)) as cbor.CborValue; + // Add CBOR tag + Object.defineProperty(serialized, '__brand', { value: 'CBOR' }); + + return serialized; + } else if (v < BigInt(Number.MAX_SAFE_INTEGER)) { return cbor.value.tagged(3, cbor.value.bytes(fromHex((BigInt('-1') * v).toString(16)))); + } else { + this.#never(); } } + #never(): never { + throw new Error('Should never happen'); + } } const serializer = SelfDescribeCborSerializer.withDefaultEncoders(true);