Skip to content
485 changes: 284 additions & 201 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "@msgpack/msgpack",
"version": "2.1.0",
"description": "MessagePack for JavaScript/ECMA-262",
"author": "The MessagePack community",
"name": "algo-msgpack-with-bigint",
"version": "2.1.1",
"description": "MessagePack for JavaScript/ECMA-262, with changed behavior for BigInts",
"author": "The MessagePack community orginally; forked by @EvanJRichard for Algorand",
"license": "ISC",
"main": "./dist/index.js",
"browser": "./dist.es5/msgpack.min.js",
Expand Down Expand Up @@ -35,7 +35,7 @@
"homepage": "https://msgpack.org/",
"repository": {
"type": "git",
"url": "https://github.com/msgpack/msgpack-javascript.git"
"url": "https://github.com/EvanJRichard/msgpack-javascript.git"
},
"bugs": {
"url": "https://github.com/msgpack/msgpack-javascript/issues"
Expand Down Expand Up @@ -78,7 +78,7 @@
"prettier": "latest",
"rimraf": "latest",
"ts-loader": "latest",
"ts-node": "latest",
"ts-node": "^9.0.0",
"tsconfig-paths": "latest",
"typescript": "latest",
"web-streams-polyfill": "latest",
Expand Down
4 changes: 2 additions & 2 deletions src/Decoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -582,13 +582,13 @@ export class Decoder<ContextType> {
return value;
}

private readU64(): number {
private readU64(): bigint | number {
const value = getUint64(this.view, this.pos);
this.pos += 8;
return value;
}

private readI64(): number {
private readI64(): bigint | number {
const value = getInt64(this.view, this.pos);
this.pos += 8;
return value;
Expand Down
57 changes: 56 additions & 1 deletion src/Encoder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { utf8EncodeJs, utf8Count, TEXT_ENCODING_AVAILABLE, TEXT_ENCODER_THRESHOLD, utf8EncodeTE } from "./utils/utf8";
import { ExtensionCodec, ExtensionCodecType } from "./ExtensionCodec";
import { setInt64, setUint64 } from "./utils/int";
import { setInt64, setUint64, setBigInt64 } from "./utils/int";
import { ensureUint8Array } from "./utils/typedArrays";
import { ExtData } from "./ExtData";

Expand Down Expand Up @@ -50,6 +50,8 @@ export class Encoder<ContextType> {
this.encodeNumber(object);
} else if (typeof object === "string") {
this.encodeString(object);
} else if (typeof object === "bigint") {
this.encodebigint(object);
} else {
this.encodeObject(object, depth);
}
Expand Down Expand Up @@ -144,6 +146,52 @@ export class Encoder<ContextType> {
}
}

private encodebigint(object: bigint) {
if (object >= BigInt(0)) {
if (object < BigInt(0x80)) {
// positive fixint
this.writeU8(Number(object));
} else if (object < BigInt(0x100)) {
// uint 8
this.writeU8(0xcc);
this.writeU8(Number(object));
} else if (object < BigInt(0x10000)) {
// uint 16
this.writeU8(0xcd);
this.writeU16(Number(object));
} else if (object < BigInt(0x100000000)) {
// uint 32
this.writeU8(0xce);
this.writeU32(Number(object));
} else {
// uint 64
this.writeU8(0xcf);
this.writeBig64(object);
}
} else {
if (object >= BigInt(-0x20)) {
// nagative fixint
this.writeU8(0xe0 | (Number(object) + 0x20));
} else if (object >= BigInt(-0x80)) {
// int 8
this.writeU8(0xd0);
this.writeI8(Number(object));
} else if (object >= BigInt(-0x8000)) {
// int 16
this.writeU8(0xd1);
this.writeI16(Number(object));
} else if (object >= BigInt(-0x80000000)) {
// int 32
this.writeU8(0xd2);
this.writeI32(Number(object));
} else {
// int 64
this.writeU8(0xd3);
this.writeBig64(object);
}
}
}

private writeStringHeader(byteLength: number) {
if (byteLength < 32) {
// fixstr
Expand Down Expand Up @@ -399,4 +447,11 @@ export class Encoder<ContextType> {
setInt64(this.view, this.pos, value);
this.pos += 8;
}

private writeBig64(value: bigint) {
this.ensureBufferSizeToWrite(8);

setBigInt64(this.view, this.pos, value);
this.pos += 8;
}
}
2 changes: 1 addition & 1 deletion src/timestamp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export function decodeTimestampToTimeSpec(data: Uint8Array): TimeSpec {
case 12: {
// timestamp 96 = { nsec32 (unsigned), sec64 (signed) }

const sec = getInt64(view, 4);
const sec = getInt64(view, 4) as number;
const nsec = view.getUint32(0);
return { sec, nsec };
}
Expand Down
27 changes: 27 additions & 0 deletions src/utils/int.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,41 @@ export function setInt64(view: DataView, offset: number, value: number): void {
view.setUint32(offset + 4, low);
}

export function setBigInt64(view: DataView, offset: number, value: bigint): void {
let high = Number(value / BigInt(0x1_0000_0000));
const low = Number(value % BigInt(0x1_0000_0000));
if (high < 0 && low !== 0) {
// simulate Math.floor for negative high
high -= 1;
}
view.setUint32(offset, high);
view.setUint32(offset + 4, low);
}

export function getInt64(view: DataView, offset: number) {
const high = view.getInt32(offset);
const low = view.getUint32(offset + 4);

const exceeds_min_safe_int =
high < Math.floor(Number.MIN_SAFE_INTEGER / 0x1_0000_0000) ||
(high === Math.floor(Number.MIN_SAFE_INTEGER / 0x1_0000_0000) && low === 0);

const exceeds_max_safe_int = high > Math.floor(Number.MAX_SAFE_INTEGER / 0x1_0000_0000);

if (exceeds_min_safe_int || exceeds_max_safe_int) {
return BigInt(high) * BigInt(0x1_0000_0000) + BigInt(low);
}
return high * 0x1_0000_0000 + low;
}

export function getUint64(view: DataView, offset: number) {
const high = view.getUint32(offset);
const low = view.getUint32(offset + 4);

const exceeds_max_safe_int = high > Math.floor(Number.MAX_SAFE_INTEGER / 0x1_0000_0000);

if (exceeds_max_safe_int) {
return BigInt(high) * BigInt(0x1_0000_0000) + BigInt(low);
}
return high * 0x1_0000_0000 + low;
}
93 changes: 80 additions & 13 deletions test/codec-bigint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,88 @@ describe("codec BigInt", () => {
}
});

it("encodes and decodes 0n", () => {
const value = BigInt(0);
const encoded = encode(value, { extensionCodec });
assert.deepStrictEqual(decode(encoded, { extensionCodec }), value);
});
context("extension", () => {
it("encodes and decodes 0n", () => {
return; //this fork does not need to support "string-style" bigint encode/decode
const value = BigInt(0);
const encoded = encode(value, { extensionCodec });
assert.deepStrictEqual(decode(encoded, { extensionCodec }), value);
});

it("encodes and decodes MAX_SAFE_INTEGER+1", () => {
const value = BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1);
const encoded = encode(value, { extensionCodec });
assert.deepStrictEqual(decode(encoded, { extensionCodec }), value);
});

it("encodes and decodes MAX_SAFE_INTEGER+1", () => {
const value = BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1);
const encoded = encode(value, { extensionCodec });
assert.deepStrictEqual(decode(encoded, { extensionCodec }), value);
it("encodes and decodes MIN_SAFE_INTEGER-1", () => {
const value = BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1);
const encoded = encode(value, { extensionCodec });
assert.deepStrictEqual(decode(encoded, { extensionCodec }), value);
});
});

it("encodes and decodes MIN_SAFE_INTEGER-1", () => {
const value = BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1);
const encoded = encode(value, { extensionCodec });
assert.deepStrictEqual(decode(encoded, { extensionCodec }), value);
context("native", () => {
const UINT64_TYPE = 0xcf;
const INT64_TYPE = 0xd3;

const BIGINTSPECS = {
ZERO: {
value: BigInt(0),
expectedEncoding: encode(0),
expectedDecoding: 0,
},
POSITIVE_VALUE: {
value: BigInt(100),
expectedEncoding: encode(100),
expectedDecoding: 100,
},
NEGATIVE_VALUE: {
value: BigInt(-117),
expectedEncoding: encode(-117),
expectedDecoding: -117,
},
MAX_SAFE_INTEGER: {
value: BigInt(Number.MAX_SAFE_INTEGER),
expectedEncoding: encode(Number.MAX_SAFE_INTEGER),
expectedDecoding: Number.MAX_SAFE_INTEGER,
},
MIN_SAFE_INTEGER: {
value: BigInt(Number.MIN_SAFE_INTEGER),
expectedEncoding: encode(Number.MIN_SAFE_INTEGER),
expectedDecoding: Number.MIN_SAFE_INTEGER,
},
MAX_SAFE_INTEGER_PLUS_ONE: {
value: BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1),
expectedEncoding: Uint8Array.from([UINT64_TYPE, 0, 32, 0, 0, 0, 0, 0, 0]), // 2^53
expectedDecoding: BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1),
},
MIN_SAFE_INTEGER_MINUS_ONE: {
value: BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1),
expectedEncoding: Uint8Array.from([INT64_TYPE, 255, 224, 0, 0, 0, 0, 0, 0]), // -(2^53)
expectedDecoding: BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1),
},
MAX_UINT64: {
value: BigInt("0xffffffffffffffff"),
expectedEncoding: Uint8Array.from([UINT64_TYPE, 255, 255, 255, 255, 255, 255, 255, 255]), // 2^64 - 1
expectedDecoding: BigInt("0xffffffffffffffff"),
},
MIN_INT64: {
value: -BigInt("0x8000000000000000"),
expectedEncoding: Uint8Array.from([INT64_TYPE, 128, 0, 0, 0, 0, 0, 0, 0]), // -(2^63)
expectedDecoding: -BigInt("0x8000000000000000"),
},
} as Record<string, { value: bigint; expectedEncoding: Uint8Array; expectedDecoding: any }>;

for (const name of Object.keys(BIGINTSPECS)) {
const { value, expectedEncoding, expectedDecoding } = BIGINTSPECS[name];
const sign = value < BigInt(0) ? BigInt(-1) : BigInt(1);

it(`encodes and decodes ${name} (${sign === BigInt(-1) ? "-" : ""}0x${(sign * value).toString(16)})`, () => {
const encoded = encode(value);
assert.deepStrictEqual(encoded, expectedEncoding);
assert.deepStrictEqual(decode(encoded), expectedDecoding);
});
}
});
});
10 changes: 8 additions & 2 deletions test/msgpack-test-suite.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import assert from "assert";
import util from "util";
import { Exam } from "msgpack-test-js";
import { Exam, Type } from "msgpack-test-js";
import { MsgTimestamp } from "msg-timestamp";
import { encode, decode, ExtensionCodec, EXT_TIMESTAMP, encodeTimeSpecToTimestamp } from "@msgpack/msgpack";

if (typeof BigInt !== "undefined") {
Type.getType("bignum").parse = function parseBigInt(str: string) {
return BigInt(str);
} as any;
}

const extensionCodec = new ExtensionCodec();
extensionCodec.register({
type: EXT_TIMESTAMP,
Expand All @@ -24,7 +30,7 @@ extensionCodec.register({

const TEST_TYPES = {
array: 1,
bignum: 0, // TODO
bignum: typeof BigInt !== "undefined",
binary: 1,
bool: 1,
map: 1,
Expand Down