diff --git a/implementations/javascript/packages/core/src/bytes.ts b/implementations/javascript/packages/core/src/bytes.ts index c53c8e4..89fd2a1 100644 --- a/implementations/javascript/packages/core/src/bytes.ts +++ b/implementations/javascript/packages/core/src/bytes.ts @@ -172,11 +172,11 @@ export function hexDigit(n: number): string { return '0123456789abcdef'[n]; } -export function unhexDigit(asciiCode: number) { +export function unhexDigit(asciiCode: number, errorClass: {new(msg: string): Error} = Error) { if (asciiCode >= 48 && asciiCode <= 57) return asciiCode - 48; if (asciiCode >= 97 && asciiCode <= 102) return asciiCode - 97 + 10; if (asciiCode >= 65 && asciiCode <= 70) return asciiCode - 65 + 10; - throw new Error("Invalid hex digit: " + String.fromCharCode(asciiCode)); + throw new errorClass("Invalid hex digit: " + String.fromCharCode(asciiCode)); } export function underlying(b: Bytes | Uint8Array): Uint8Array { diff --git a/implementations/javascript/packages/core/src/reader.ts b/implementations/javascript/packages/core/src/reader.ts index 4c914d7..3eaa823 100644 --- a/implementations/javascript/packages/core/src/reader.ts +++ b/implementations/javascript/packages/core/src/reader.ts @@ -4,7 +4,7 @@ import type { Value } from './values'; import { DecodeError, ShortPacket } from './codec'; import { Dictionary, Set } from './dictionary'; import { strip } from './strip'; -import { Bytes, underlying, unhexDigit } from './bytes'; +import { Bytes, unhexDigit } from './bytes'; import { Decoder, DecoderState, neverEmbeddedTypeDecode } from './decoder'; import { Record } from './record'; import { Annotated, newPosition, Position, updatePosition } from './annotated'; @@ -106,16 +106,16 @@ export class ReaderState { } readHex2(): number { - const x1 = unhexDigit(this.nextcharcode()); - const x2 = unhexDigit(this.nextcharcode()); + const x1 = unhexDigit(this.nextcharcode(), DecodeError); + const x2 = unhexDigit(this.nextcharcode(), DecodeError); return (x1 << 4) | x2; } readHex4(): number { - const x1 = unhexDigit(this.nextcharcode()); - const x2 = unhexDigit(this.nextcharcode()); - const x3 = unhexDigit(this.nextcharcode()); - const x4 = unhexDigit(this.nextcharcode()); + const x1 = unhexDigit(this.nextcharcode(), DecodeError); + const x2 = unhexDigit(this.nextcharcode(), DecodeError); + const x3 = unhexDigit(this.nextcharcode(), DecodeError); + const x4 = unhexDigit(this.nextcharcode(), DecodeError); return (x1 << 12) | (x2 << 8) | (x3 << 4) | x4; } @@ -346,7 +346,7 @@ export class Reader { switch (c) { case 'f': return false; case 't': return true; - case '{': return this.seq(new Set(), (v, s) => s.add(v), '}'); + case '{': return this.readSet(); case '"': return this.state.readLiteralBinary(); case 'x': switch (this.state.nextchar()) { case '"': return this.state.readHexBinary(); @@ -411,6 +411,16 @@ export class Reader { }, '}'); } + + readSet(): Set { + return this.seq(new Set(), + (v, acc) => { + if (acc.has(v)) this.state.error( + `Duplicate value in set: ${stringify(v)}`, this.state.pos); + acc.add(v); + }, + '}'); + } } const BASE64: {[key: string]: number} = {}; diff --git a/implementations/javascript/packages/core/test/codec.test.ts b/implementations/javascript/packages/core/test/codec.test.ts index bbf5130..046eced 100644 --- a/implementations/javascript/packages/core/test/codec.test.ts +++ b/implementations/javascript/packages/core/test/codec.test.ts @@ -19,6 +19,7 @@ import { embed, genericEmbeddedTypeDecode, genericEmbeddedTypeEncode, + parse, } from '../src/index'; const { Tag } = Constants; import './test-utils'; @@ -321,9 +322,23 @@ describe('common test suite', () => { }); break; case Symbol.for('ParseError'): + describe(tName, () => { + it('should fail with DecodeError', () => { + expect(() => parse(strip(t[0]) as string)) + .toThrowFilter(e => + DecodeError.isDecodeError(e) && + !ShortPacket.isShortPacket(e)); + }); + }); + break; case Symbol.for('ParseEOF'): case Symbol.for('ParseShort'): - /* Skipped for now, until we have an implementation of text syntax */ + describe(tName, () => { + it('should fail with ShortPacket', () => { + expect(() => parse(strip(t[0]) as string)) + .toThrowFilter(e => ShortPacket.isShortPacket(e)); + }); + }); break; default:{ const e = new Error(preserves`Unsupported test kind ${t}`);