Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/Decoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class DecodeError extends Error {
}
}

export class Decoder<ContextType> {
export class Decoder<ContextType = undefined> {
private totalPos = 0;
private pos = 0;

Expand All @@ -99,6 +99,9 @@ export class Decoder<ContextType> {
private reinitializeState() {
this.totalPos = 0;
this.headByte = HEAD_BYTE_REQUIRED;
this.stack.length = 0;

// view, bytes, and pos will be re-initialized in setBuffer()
}

private setBuffer(buffer: ArrayLike<number> | BufferSource): void {
Expand Down
2 changes: 1 addition & 1 deletion src/Encoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { ExtData } from "./ExtData";
export const DEFAULT_MAX_DEPTH = 100;
export const DEFAULT_INITIAL_BUFFER_SIZE = 2048;

export class Encoder<ContextType> {
export class Encoder<ContextType = undefined> {
private pos = 0;
private view = new DataView(new ArrayBuffer(this.initialBufferSize));
private bytes = new Uint8Array(this.view.buffer);
Expand Down
64 changes: 52 additions & 12 deletions test/edge-cases.test.ts
Original file line number Diff line number Diff line change
@@ -1,110 +1,150 @@
// kind of hand-written fuzzing data
// any errors should not break Encoder/Decoder instance states
import assert from "assert";
import { encode, decode, decodeAsync } from "../src";
import { encode, decode, decodeAsync, Encoder, Decoder } from "../src";
import { DataViewIndexOutOfBoundsError } from "../src/Decoder";

function testEncoder(encoder: Encoder): void {
const object = {
foo: 1,
bar: 2,
baz: ["one", "two", "three"],
};
assert.deepStrictEqual(decode(encoder.encode(object)), object);
}

function testDecoder(decoder: Decoder): void {
const object = {
foo: 1,
bar: 2,
baz: ["one", "two", "three"],
};
assert.deepStrictEqual(decoder.decode(encode(object)), object);
}

describe("edge cases", () => {
context("try to encode cyclic refs", () => {
it("throws errors on arrays", () => {
const encoder = new Encoder();
const cyclicRefs: Array<any> = [];
cyclicRefs.push(cyclicRefs);
assert.throws(() => {
encode(cyclicRefs);
encoder.encode(cyclicRefs);
}, /too deep/i);
testEncoder(encoder);
});

it("throws errors on objects", () => {
const encoder = new Encoder();
const cyclicRefs: Record<string, any> = {};
cyclicRefs["foo"] = cyclicRefs;
assert.throws(() => {
encode(cyclicRefs);
encoder.encode(cyclicRefs);
}, /too deep/i);
testEncoder(encoder);
});
});

context("try to encode non-encodable objects", () => {
context("try to encode unrecognized objects", () => {
it("throws errors", () => {
const encoder = new Encoder();
assert.throws(() => {
encode(() => {});
}, /unrecognized object/i);
testEncoder(encoder);
});
});

context("try to decode a map with non-string keys (asynchronous)", () => {
it("throws errors", async () => {
const decoder = new Decoder();
const createStream = async function* () {
yield [0x81]; // fixmap size=1
yield encode(null);
yield encode(null);
};

await assert.rejects(async () => {
await decodeAsync(createStream());
await decoder.decodeAsync(createStream());
}, /The type of key must be string/i);
testDecoder(decoder);
});
});

context("try to decode invlid MessagePack binary", () => {
context("try to decode invalid MessagePack binary", () => {
it("throws errors", () => {
const decoder = new Decoder();
const TYPE_NEVER_USED = 0xc1;

assert.throws(() => {
decode([TYPE_NEVER_USED]);
decoder.decode([TYPE_NEVER_USED]);
}, /unrecognized type byte/i);
testDecoder(decoder);
});
});

context("try to decode insufficient data", () => {
it("throws errors (synchronous)", () => {
const decoder = new Decoder();
assert.throws(() => {
decode([
decoder.decode([
0x92, // fixarray size=2
0xc0, // nil
]);
// [IE11] A raw error thrown by DataView
}, DataViewIndexOutOfBoundsError);
testDecoder(decoder);
});

it("throws errors (asynchronous)", async () => {
const decoder = new Decoder();
const createStream = async function* () {
yield [0x92]; // fixarray size=2
yield encode(null);
};

await assert.rejects(async () => {
await decodeAsync(createStream());
await decoder.decodeAsync(createStream());
}, RangeError);
testDecoder(decoder);
});
});

context("try to decode data with extra bytes", () => {
it("throws errors (synchronous)", () => {
const decoder = new Decoder();
assert.throws(() => {
decode([
decoder.decode([
0x90, // fixarray size=0
...encode(null),
]);
}, RangeError);
testDecoder(decoder);
});

it("throws errors (asynchronous)", async () => {
const decoder = new Decoder();
const createStream = async function* () {
yield [0x90]; // fixarray size=0
yield encode(null);
};

await assert.rejects(async () => {
await decodeAsync(createStream());
await decoder.decodeAsync(createStream());
}, RangeError);
testDecoder(decoder);
});

it("throws errors (asynchronous)", async () => {
const decoder = new Decoder();
const createStream = async function* () {
yield [0x90, ...encode(null)]; // fixarray size=0 + nil
};

await assert.rejects(async () => {
await decodeAsync(createStream());
await decoder.decodeAsync(createStream());
}, RangeError);
testDecoder(decoder);
});
});
});