Skip to content

Incorporating algo msgpack changes to see diff against latest version #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
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