From a737d05d8cd8b22568ff489d07c9e4c824cb4f40 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 31 Jan 2024 11:22:07 +0100 Subject: [PATCH] feat: runtime size limits for arrays and maps (#128) It's possible to limit the size of arrays and maps at compile time: ```protobuf message MyMessage { repeated uint32 repeatedField = 1 [(protons.options).limit = 10]; map stringMap = 2 [(protons.options).limit = 10]; } ``` This PR adds the ability to do it at runtime too: ```TypeScript const message = MyMessage.decode(buf, { limits: { repeatedField: 10, stringMap: 10 } }) ``` --- packages/protons-runtime/src/codec.ts | 9 +- .../protons-runtime/src/codecs/message.ts | 5 +- packages/protons-runtime/src/decode.ts | 6 +- packages/protons-runtime/src/index.ts | 2 +- packages/protons/README.md | 56 ++++++ packages/protons/bin/protons.ts | 1 - packages/protons/package.json | 2 +- packages/protons/src/index.ts | 31 ++-- packages/protons/test/fixtures/basic.ts | 14 +- packages/protons/test/fixtures/bitswap.ts | 48 +++-- packages/protons/test/fixtures/circuit.ts | 18 +- .../test/fixtures/custom-option-jstype.ts | 14 +- packages/protons/test/fixtures/daemon.ts | 154 ++++++++++------ packages/protons/test/fixtures/dht.ts | 32 +++- packages/protons/test/fixtures/maps.ts | 54 ++++-- packages/protons/test/fixtures/noise.ts | 14 +- packages/protons/test/fixtures/optional.ts | 14 +- packages/protons/test/fixtures/peer.ts | 32 +++- packages/protons/test/fixtures/proto2.ts | 8 +- .../protons/test/fixtures/protons-options.ts | 28 ++- packages/protons/test/fixtures/repeated.proto | 11 ++ packages/protons/test/fixtures/repeated.ts | 172 ++++++++++++++++++ packages/protons/test/fixtures/singular.ts | 14 +- packages/protons/test/fixtures/test.ts | 18 +- packages/protons/test/maps.spec.ts | 16 ++ packages/protons/test/repeated.spec.ts | 43 +++++ packages/protons/tsconfig.json | 4 +- 27 files changed, 625 insertions(+), 195 deletions(-) create mode 100644 packages/protons/test/fixtures/repeated.proto create mode 100644 packages/protons/test/fixtures/repeated.ts create mode 100644 packages/protons/test/repeated.spec.ts diff --git a/packages/protons-runtime/src/codec.ts b/packages/protons-runtime/src/codec.ts index c73fe80..74ff90a 100644 --- a/packages/protons-runtime/src/codec.ts +++ b/packages/protons-runtime/src/codec.ts @@ -19,8 +19,15 @@ export interface EncodeFunction { (value: Partial, writer: Writer, opts?: EncodeOptions): void } +export interface DecodeOptions { + /** + * Runtime-specified limits for lengths of repeated/map fields + */ + limits?: Partial> +} + export interface DecodeFunction { - (reader: Reader, length?: number): T + (reader: Reader, length?: number, opts?: DecodeOptions): T } export interface Codec { diff --git a/packages/protons-runtime/src/codecs/message.ts b/packages/protons-runtime/src/codecs/message.ts index a96f8e7..e076ba3 100644 --- a/packages/protons-runtime/src/codecs/message.ts +++ b/packages/protons-runtime/src/codecs/message.ts @@ -1,10 +1,9 @@ -import { createCodec, CODEC_TYPES, type EncodeOptions, type Codec } from '../codec.js' -import type { Reader, Writer } from '../index.js' +import { createCodec, CODEC_TYPES, type EncodeFunction, type DecodeFunction, type Codec } from '../codec.js' export interface Factory { new (obj: A): T } -export function message (encode: (obj: Partial, writer: Writer, opts?: EncodeOptions) => void, decode: (reader: Reader, length?: number) => T): Codec { +export function message (encode: EncodeFunction, decode: DecodeFunction): Codec { return createCodec('message', CODEC_TYPES.LENGTH_DELIMITED, encode, decode) } diff --git a/packages/protons-runtime/src/decode.ts b/packages/protons-runtime/src/decode.ts index 19360c2..6a05d92 100644 --- a/packages/protons-runtime/src/decode.ts +++ b/packages/protons-runtime/src/decode.ts @@ -1,9 +1,9 @@ import { createReader } from './utils/reader.js' -import type { Codec } from './codec.js' +import type { Codec, DecodeOptions } from './codec.js' import type { Uint8ArrayList } from 'uint8arraylist' -export function decodeMessage (buf: Uint8Array | Uint8ArrayList, codec: Codec): T { +export function decodeMessage (buf: Uint8Array | Uint8ArrayList, codec: Codec, opts?: DecodeOptions): T { const reader = createReader(buf) - return codec.decode(reader) + return codec.decode(reader, undefined, opts) } diff --git a/packages/protons-runtime/src/index.ts b/packages/protons-runtime/src/index.ts index e0e53d5..d8c6de2 100644 --- a/packages/protons-runtime/src/index.ts +++ b/packages/protons-runtime/src/index.ts @@ -20,7 +20,7 @@ export { enumeration } from './codecs/enum.js' export { message } from './codecs/message.js' export { createReader as reader } from './utils/reader.js' export { createWriter as writer } from './utils/writer.js' -export type { Codec, EncodeOptions } from './codec.js' +export type { Codec, EncodeOptions, DecodeOptions } from './codec.js' export interface Writer { /** diff --git a/packages/protons/README.md b/packages/protons/README.md index 4d1a1db..d2b5683 100644 --- a/packages/protons/README.md +++ b/packages/protons/README.md @@ -12,6 +12,9 @@ - [Install](#install) - [Usage](#usage) - [Differences from protobuf.js](#differences-from-protobufjs) +- [Extra features](#extra-features) + - [Limiting the size of repeated/map elements](#limiting-the-size-of-repeatedmap-elements) + - [Overriding 64 bit types](#overriding-64-bit-types) - [Missing features](#missing-features) - [API Docs](#api-docs) - [License](#license) @@ -73,6 +76,59 @@ It does have one or two differences: 5. `map` fields can have keys of any type - protobufs.js [only supports strings](https://github.com/protobufjs/protobuf.js/issues/1203#issuecomment-488637338) 6. `map` fields are deserialized as ES6 `Map`s - protobuf.js uses `Object`s +## Extra features + +### Limiting the size of repeated/map elements + +To protect decoders from malicious payloads, it's possible to limit the maximum size of repeated/map elements. + +You can either do this at compile time by using the [protons.options](https://github.com/protocolbuffers/protobuf/blob/6f1d88107f268b8ebdad6690d116e74c403e366e/docs/options.md?plain=1#L490-L493) extension: + +```protobuf +message MyMessage { + // repeatedField cannot have more than 10 entries + repeated uint32 repeatedField = 1 [(protons.options).limit = 10]; + + // stringMap cannot have more than 10 keys + map stringMap = 2 [(protons.options).limit = 10]; +} +``` + +Or at runtime by passing objects to the `.decode` function of your message: + +```TypeScript +const message = MyMessage.decode(buf, { + limits: { + repeatedField: 10, + stringMap: 10 + } +}) +``` + +### Overriding 64 bit types + +By default 64 bit types are implemented as [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)s. + +Sometimes this is undesirable due to [performance issues](https://betterprogramming.pub/the-downsides-of-bigints-in-javascript-6350fd807d) or code legibility. + +It's possible to override the JavaScript type 64 bit fields will deserialize to: + +```protobuf +message MyMessage { + repeated int64 bigintField = 1; + repeated int64 numberField = 2 [jstype = JS_NUMBER]; + repeated int64 stringField = 3 [jstype = JS_STRING]; +} +``` + +```TypeScript +const message = MyMessage.decode(buf) + +console.info(typeof message.bigintField) // bigint +console.info(typeof message.numberField) // number +console.info(typeof message.stringField) // string +``` + ## Missing features Some features are missing `OneOf`s, etc due to them not being needed so far in ipfs/libp2p. If these features are important to you, please open PRs implementing them along with tests comparing the generated bytes to `protobuf.js` and `pbjs`. diff --git a/packages/protons/bin/protons.ts b/packages/protons/bin/protons.ts index e92e270..4215170 100644 --- a/packages/protons/bin/protons.ts +++ b/packages/protons/bin/protons.ts @@ -16,7 +16,6 @@ async function main (): Promise { Examples $ protons ./path/to/file.proto ./path/to/other/file.proto `, { - // @ts-expect-error importMeta is missing from the types importMeta: import.meta, flags: { output: { diff --git a/packages/protons/package.json b/packages/protons/package.json index 2a54261..40758c6 100644 --- a/packages/protons/package.json +++ b/packages/protons/package.json @@ -130,7 +130,7 @@ "release": "aegir release" }, "dependencies": { - "meow": "^13.0.0", + "meow": "^13.1.0", "protobufjs-cli": "^1.0.0" }, "devDependencies": { diff --git a/packages/protons/src/index.ts b/packages/protons/src/index.ts index aff1816..5b073ab 100644 --- a/packages/protons/src/index.ts +++ b/packages/protons/src/index.ts @@ -518,6 +518,7 @@ export namespace ${messageDef.name} { moduleDef.addImport('protons-runtime', 'decodeMessage') moduleDef.addImport('protons-runtime', 'message') moduleDef.addTypeImport('protons-runtime', 'Codec') + moduleDef.addTypeImport('protons-runtime', 'DecodeOptions') moduleDef.addTypeImport('uint8arraylist', 'Uint8ArrayList') const interfaceFields = defineFields(fields, messageDef, moduleDef) @@ -691,12 +692,16 @@ export interface ${messageDef.name} { const parseValue = `${decoderGenerators[type] == null ? `${codec}.decode(reader${type === 'message' ? ', reader.uint32()' : ''})` : decoderGenerators[type](jsTypeOverride)}` if (fieldDef.map) { - let limit = '' + moduleDef.addImport('protons-runtime', 'CodeError') - if (fieldDef.lengthLimit != null) { - moduleDef.addImport('protons-runtime', 'CodeError') + let limit = ` + if (opts.limits?.${fieldName} != null && obj.${fieldName}.size === opts.limits.${fieldName}) { + throw new CodeError('decode error - map field "${fieldName}" had too many elements', 'ERR_MAX_SIZE') + } +` - limit = ` + if (fieldDef.lengthLimit != null) { + limit += ` if (obj.${fieldName}.size === ${fieldDef.lengthLimit}) { throw new CodeError('decode error - map field "${fieldName}" had too many elements', 'ERR_MAX_SIZE') } @@ -709,12 +714,16 @@ export interface ${messageDef.name} { break }` } else if (fieldDef.repeated) { - let limit = '' + moduleDef.addImport('protons-runtime', 'CodeError') - if (fieldDef.lengthLimit != null) { - moduleDef.addImport('protons-runtime', 'CodeError') + let limit = ` + if (opts.limits?.${fieldName} != null && obj.${fieldName}.length === opts.limits.${fieldName}) { + throw new CodeError('decode error - map field "${fieldName}" had too many elements', 'ERR_MAX_LENGTH') + } +` - limit = ` + if (fieldDef.lengthLimit != null) { + limit += ` if (obj.${fieldName}.length === ${fieldDef.lengthLimit}) { throw new CodeError('decode error - repeated field "${fieldName}" had too many elements', 'ERR_MAX_LENGTH') } @@ -750,7 +759,7 @@ ${encodeFields === '' ? '' : `${encodeFields}\n`} if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {${createDefaultObject(fields, messageDef, moduleDef)}} const end = length == null ? reader.len : reader.pos + length @@ -777,8 +786,8 @@ ${encodeFields === '' ? '' : `${encodeFields}\n`} return encodeMessage(obj, ${messageDef.name}.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ${messageDef.name} => { - return decodeMessage(buf, ${messageDef.name}.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<${messageDef.name}>): ${messageDef.name} => { + return decodeMessage(buf, ${messageDef.name}.codec(), opts) }` return ` diff --git a/packages/protons/test/fixtures/basic.ts b/packages/protons/test/fixtures/basic.ts index 53a7731..ef82dfa 100644 --- a/packages/protons/test/fixtures/basic.ts +++ b/packages/protons/test/fixtures/basic.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, message } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface Basic { @@ -35,7 +35,7 @@ export namespace Basic { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { num: 0 } @@ -72,8 +72,8 @@ export namespace Basic { return encodeMessage(obj, Basic.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Basic => { - return decodeMessage(buf, Basic.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Basic => { + return decodeMessage(buf, Basic.codec(), opts) } } @@ -92,7 +92,7 @@ export namespace Empty { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -119,7 +119,7 @@ export namespace Empty { return encodeMessage(obj, Empty.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Empty => { - return decodeMessage(buf, Empty.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Empty => { + return decodeMessage(buf, Empty.codec(), opts) } } diff --git a/packages/protons/test/fixtures/bitswap.ts b/packages/protons/test/fixtures/bitswap.ts index 60f4bfe..8253723 100644 --- a/packages/protons/test/fixtures/bitswap.ts +++ b/packages/protons/test/fixtures/bitswap.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, enumeration, message } from 'protons-runtime' +import { type Codec, CodeError, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime' import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Uint8ArrayList } from 'uint8arraylist' @@ -85,7 +85,7 @@ export namespace Message { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { block: uint8ArrayAlloc(0), priority: 0, @@ -137,8 +137,8 @@ export namespace Message { return encodeMessage(obj, Entry.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Entry => { - return decodeMessage(buf, Entry.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Entry => { + return decodeMessage(buf, Entry.codec(), opts) } } @@ -166,7 +166,7 @@ export namespace Message { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { entries: [], full: false @@ -179,6 +179,10 @@ export namespace Message { switch (tag >>> 3) { case 1: { + if (opts.limits?.entries != null && obj.entries.length === opts.limits.entries) { + throw new CodeError('decode error - map field "entries" had too many elements', 'ERR_MAX_LENGTH') + } + obj.entries.push(Message.Wantlist.Entry.codec().decode(reader, reader.uint32())) break } @@ -204,8 +208,8 @@ export namespace Message { return encodeMessage(obj, Wantlist.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Wantlist => { - return decodeMessage(buf, Wantlist.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Wantlist => { + return decodeMessage(buf, Wantlist.codec(), opts) } } @@ -237,7 +241,7 @@ export namespace Message { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { prefix: uint8ArrayAlloc(0), data: uint8ArrayAlloc(0) @@ -275,8 +279,8 @@ export namespace Message { return encodeMessage(obj, Block.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Block => { - return decodeMessage(buf, Block.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Block => { + return decodeMessage(buf, Block.codec(), opts) } } @@ -324,7 +328,7 @@ export namespace Message { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { cid: uint8ArrayAlloc(0), type: BlockPresenceType.Have @@ -362,8 +366,8 @@ export namespace Message { return encodeMessage(obj, BlockPresence.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): BlockPresence => { - return decodeMessage(buf, BlockPresence.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): BlockPresence => { + return decodeMessage(buf, BlockPresence.codec(), opts) } } @@ -410,7 +414,7 @@ export namespace Message { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { blocks: [], payload: [], @@ -429,14 +433,26 @@ export namespace Message { break } case 2: { + if (opts.limits?.blocks != null && obj.blocks.length === opts.limits.blocks) { + throw new CodeError('decode error - map field "blocks" had too many elements', 'ERR_MAX_LENGTH') + } + obj.blocks.push(reader.bytes()) break } case 3: { + if (opts.limits?.payload != null && obj.payload.length === opts.limits.payload) { + throw new CodeError('decode error - map field "payload" had too many elements', 'ERR_MAX_LENGTH') + } + obj.payload.push(Message.Block.codec().decode(reader, reader.uint32())) break } case 4: { + if (opts.limits?.blockPresences != null && obj.blockPresences.length === opts.limits.blockPresences) { + throw new CodeError('decode error - map field "blockPresences" had too many elements', 'ERR_MAX_LENGTH') + } + obj.blockPresences.push(Message.BlockPresence.codec().decode(reader, reader.uint32())) break } @@ -462,7 +478,7 @@ export namespace Message { return encodeMessage(obj, Message.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Message => { - return decodeMessage(buf, Message.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Message => { + return decodeMessage(buf, Message.codec(), opts) } } diff --git a/packages/protons/test/fixtures/circuit.ts b/packages/protons/test/fixtures/circuit.ts index 3bce53f..f854a2c 100644 --- a/packages/protons/test/fixtures/circuit.ts +++ b/packages/protons/test/fixtures/circuit.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, enumeration, message } from 'protons-runtime' +import { type Codec, CodeError, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime' import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Uint8ArrayList } from 'uint8arraylist' @@ -110,7 +110,7 @@ export namespace CircuitRelay { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { id: uint8ArrayAlloc(0), addrs: [] @@ -127,6 +127,10 @@ export namespace CircuitRelay { break } case 2: { + if (opts.limits?.addrs != null && obj.addrs.length === opts.limits.addrs) { + throw new CodeError('decode error - map field "addrs" had too many elements', 'ERR_MAX_LENGTH') + } + obj.addrs.push(reader.bytes()) break } @@ -148,8 +152,8 @@ export namespace CircuitRelay { return encodeMessage(obj, Peer.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Peer => { - return decodeMessage(buf, Peer.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Peer => { + return decodeMessage(buf, Peer.codec(), opts) } } @@ -185,7 +189,7 @@ export namespace CircuitRelay { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -228,7 +232,7 @@ export namespace CircuitRelay { return encodeMessage(obj, CircuitRelay.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): CircuitRelay => { - return decodeMessage(buf, CircuitRelay.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): CircuitRelay => { + return decodeMessage(buf, CircuitRelay.codec(), opts) } } diff --git a/packages/protons/test/fixtures/custom-option-jstype.ts b/packages/protons/test/fixtures/custom-option-jstype.ts index 1370674..d75deda 100644 --- a/packages/protons/test/fixtures/custom-option-jstype.ts +++ b/packages/protons/test/fixtures/custom-option-jstype.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, message } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface CustomOptionNumber { @@ -59,7 +59,7 @@ export namespace CustomOptionNumber { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { num: 0, i64: 0, @@ -117,8 +117,8 @@ export namespace CustomOptionNumber { return encodeMessage(obj, CustomOptionNumber.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): CustomOptionNumber => { - return decodeMessage(buf, CustomOptionNumber.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): CustomOptionNumber => { + return decodeMessage(buf, CustomOptionNumber.codec(), opts) } } @@ -174,7 +174,7 @@ export namespace CustomOptionString { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { num: 0, i64: '', @@ -232,7 +232,7 @@ export namespace CustomOptionString { return encodeMessage(obj, CustomOptionString.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): CustomOptionString => { - return decodeMessage(buf, CustomOptionString.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): CustomOptionString => { + return decodeMessage(buf, CustomOptionString.codec(), opts) } } diff --git a/packages/protons/test/fixtures/daemon.ts b/packages/protons/test/fixtures/daemon.ts index 37c9d26..e12e220 100644 --- a/packages/protons/test/fixtures/daemon.ts +++ b/packages/protons/test/fixtures/daemon.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, enumeration, message } from 'protons-runtime' +import { type Codec, CodeError, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime' import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Uint8ArrayList } from 'uint8arraylist' @@ -110,7 +110,7 @@ export namespace Request { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { type: Type.IDENTIFY } @@ -175,8 +175,8 @@ export namespace Request { return encodeMessage(obj, Request.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Request => { - return decodeMessage(buf, Request.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Request => { + return decodeMessage(buf, Request.codec(), opts) } } @@ -262,7 +262,7 @@ export namespace Response { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { type: Type.OK, peers: [] @@ -295,6 +295,10 @@ export namespace Response { break } case 6: { + if (opts.limits?.peers != null && obj.peers.length === opts.limits.peers) { + throw new CodeError('decode error - map field "peers" had too many elements', 'ERR_MAX_LENGTH') + } + obj.peers.push(PeerInfo.codec().decode(reader, reader.uint32())) break } @@ -324,8 +328,8 @@ export namespace Response { return encodeMessage(obj, Response.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Response => { - return decodeMessage(buf, Response.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Response => { + return decodeMessage(buf, Response.codec(), opts) } } @@ -359,7 +363,7 @@ export namespace IdentifyResponse { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { id: uint8ArrayAlloc(0), addrs: [] @@ -376,6 +380,10 @@ export namespace IdentifyResponse { break } case 2: { + if (opts.limits?.addrs != null && obj.addrs.length === opts.limits.addrs) { + throw new CodeError('decode error - map field "addrs" had too many elements', 'ERR_MAX_LENGTH') + } + obj.addrs.push(reader.bytes()) break } @@ -397,8 +405,8 @@ export namespace IdentifyResponse { return encodeMessage(obj, IdentifyResponse.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): IdentifyResponse => { - return decodeMessage(buf, IdentifyResponse.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): IdentifyResponse => { + return decodeMessage(buf, IdentifyResponse.codec(), opts) } } @@ -438,7 +446,7 @@ export namespace ConnectRequest { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { peer: uint8ArrayAlloc(0), addrs: [] @@ -455,6 +463,10 @@ export namespace ConnectRequest { break } case 2: { + if (opts.limits?.addrs != null && obj.addrs.length === opts.limits.addrs) { + throw new CodeError('decode error - map field "addrs" had too many elements', 'ERR_MAX_LENGTH') + } + obj.addrs.push(reader.bytes()) break } @@ -480,8 +492,8 @@ export namespace ConnectRequest { return encodeMessage(obj, ConnectRequest.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ConnectRequest => { - return decodeMessage(buf, ConnectRequest.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ConnectRequest => { + return decodeMessage(buf, ConnectRequest.codec(), opts) } } @@ -521,7 +533,7 @@ export namespace StreamOpenRequest { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { peer: uint8ArrayAlloc(0), proto: [] @@ -538,6 +550,10 @@ export namespace StreamOpenRequest { break } case 2: { + if (opts.limits?.proto != null && obj.proto.length === opts.limits.proto) { + throw new CodeError('decode error - map field "proto" had too many elements', 'ERR_MAX_LENGTH') + } + obj.proto.push(reader.string()) break } @@ -563,8 +579,8 @@ export namespace StreamOpenRequest { return encodeMessage(obj, StreamOpenRequest.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): StreamOpenRequest => { - return decodeMessage(buf, StreamOpenRequest.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): StreamOpenRequest => { + return decodeMessage(buf, StreamOpenRequest.codec(), opts) } } @@ -598,7 +614,7 @@ export namespace StreamHandlerRequest { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { addr: uint8ArrayAlloc(0), proto: [] @@ -615,6 +631,10 @@ export namespace StreamHandlerRequest { break } case 2: { + if (opts.limits?.proto != null && obj.proto.length === opts.limits.proto) { + throw new CodeError('decode error - map field "proto" had too many elements', 'ERR_MAX_LENGTH') + } + obj.proto.push(reader.string()) break } @@ -636,8 +656,8 @@ export namespace StreamHandlerRequest { return encodeMessage(obj, StreamHandlerRequest.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): StreamHandlerRequest => { - return decodeMessage(buf, StreamHandlerRequest.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): StreamHandlerRequest => { + return decodeMessage(buf, StreamHandlerRequest.codec(), opts) } } @@ -663,7 +683,7 @@ export namespace ErrorResponse { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { msg: '' } @@ -696,8 +716,8 @@ export namespace ErrorResponse { return encodeMessage(obj, ErrorResponse.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ErrorResponse => { - return decodeMessage(buf, ErrorResponse.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ErrorResponse => { + return decodeMessage(buf, ErrorResponse.codec(), opts) } } @@ -735,7 +755,7 @@ export namespace StreamInfo { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { peer: uint8ArrayAlloc(0), addr: uint8ArrayAlloc(0), @@ -778,8 +798,8 @@ export namespace StreamInfo { return encodeMessage(obj, StreamInfo.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): StreamInfo => { - return decodeMessage(buf, StreamInfo.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): StreamInfo => { + return decodeMessage(buf, StreamInfo.codec(), opts) } } @@ -871,7 +891,7 @@ export namespace DHTRequest { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { type: Type.FIND_PEER } @@ -928,8 +948,8 @@ export namespace DHTRequest { return encodeMessage(obj, DHTRequest.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): DHTRequest => { - return decodeMessage(buf, DHTRequest.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): DHTRequest => { + return decodeMessage(buf, DHTRequest.codec(), opts) } } @@ -985,7 +1005,7 @@ export namespace DHTResponse { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { type: Type.BEGIN } @@ -1026,8 +1046,8 @@ export namespace DHTResponse { return encodeMessage(obj, DHTResponse.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): DHTResponse => { - return decodeMessage(buf, DHTResponse.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): DHTResponse => { + return decodeMessage(buf, DHTResponse.codec(), opts) } } @@ -1061,7 +1081,7 @@ export namespace PeerInfo { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { id: uint8ArrayAlloc(0), addrs: [] @@ -1078,6 +1098,10 @@ export namespace PeerInfo { break } case 2: { + if (opts.limits?.addrs != null && obj.addrs.length === opts.limits.addrs) { + throw new CodeError('decode error - map field "addrs" had too many elements', 'ERR_MAX_LENGTH') + } + obj.addrs.push(reader.bytes()) break } @@ -1099,8 +1123,8 @@ export namespace PeerInfo { return encodeMessage(obj, PeerInfo.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PeerInfo => { - return decodeMessage(buf, PeerInfo.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PeerInfo => { + return decodeMessage(buf, PeerInfo.codec(), opts) } } @@ -1162,7 +1186,7 @@ export namespace ConnManagerRequest { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { type: Type.TAG_PEER } @@ -1207,8 +1231,8 @@ export namespace ConnManagerRequest { return encodeMessage(obj, ConnManagerRequest.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): ConnManagerRequest => { - return decodeMessage(buf, ConnManagerRequest.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): ConnManagerRequest => { + return decodeMessage(buf, ConnManagerRequest.codec(), opts) } } @@ -1234,7 +1258,7 @@ export namespace DisconnectRequest { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { peer: uint8ArrayAlloc(0) } @@ -1267,8 +1291,8 @@ export namespace DisconnectRequest { return encodeMessage(obj, DisconnectRequest.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): DisconnectRequest => { - return decodeMessage(buf, DisconnectRequest.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): DisconnectRequest => { + return decodeMessage(buf, DisconnectRequest.codec(), opts) } } @@ -1326,7 +1350,7 @@ export namespace PSRequest { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { type: Type.GET_TOPICS } @@ -1367,8 +1391,8 @@ export namespace PSRequest { return encodeMessage(obj, PSRequest.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PSRequest => { - return decodeMessage(buf, PSRequest.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PSRequest => { + return decodeMessage(buf, PSRequest.codec(), opts) } } @@ -1426,7 +1450,7 @@ export namespace PSMessage { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { topicIDs: [] } @@ -1450,6 +1474,10 @@ export namespace PSMessage { break } case 4: { + if (opts.limits?.topicIDs != null && obj.topicIDs.length === opts.limits.topicIDs) { + throw new CodeError('decode error - map field "topicIDs" had too many elements', 'ERR_MAX_LENGTH') + } + obj.topicIDs.push(reader.string()) break } @@ -1479,8 +1507,8 @@ export namespace PSMessage { return encodeMessage(obj, PSMessage.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PSMessage => { - return decodeMessage(buf, PSMessage.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PSMessage => { + return decodeMessage(buf, PSMessage.codec(), opts) } } @@ -1516,7 +1544,7 @@ export namespace PSResponse { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { topics: [], peerIDs: [] @@ -1529,10 +1557,18 @@ export namespace PSResponse { switch (tag >>> 3) { case 1: { + if (opts.limits?.topics != null && obj.topics.length === opts.limits.topics) { + throw new CodeError('decode error - map field "topics" had too many elements', 'ERR_MAX_LENGTH') + } + obj.topics.push(reader.string()) break } case 2: { + if (opts.limits?.peerIDs != null && obj.peerIDs.length === opts.limits.peerIDs) { + throw new CodeError('decode error - map field "peerIDs" had too many elements', 'ERR_MAX_LENGTH') + } + obj.peerIDs.push(reader.bytes()) break } @@ -1554,8 +1590,8 @@ export namespace PSResponse { return encodeMessage(obj, PSResponse.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PSResponse => { - return decodeMessage(buf, PSResponse.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PSResponse => { + return decodeMessage(buf, PSResponse.codec(), opts) } } @@ -1613,7 +1649,7 @@ export namespace PeerstoreRequest { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { type: Type.INVALID, protos: [] @@ -1634,6 +1670,10 @@ export namespace PeerstoreRequest { break } case 3: { + if (opts.limits?.protos != null && obj.protos.length === opts.limits.protos) { + throw new CodeError('decode error - map field "protos" had too many elements', 'ERR_MAX_LENGTH') + } + obj.protos.push(reader.string()) break } @@ -1655,8 +1695,8 @@ export namespace PeerstoreRequest { return encodeMessage(obj, PeerstoreRequest.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PeerstoreRequest => { - return decodeMessage(buf, PeerstoreRequest.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PeerstoreRequest => { + return decodeMessage(buf, PeerstoreRequest.codec(), opts) } } @@ -1690,7 +1730,7 @@ export namespace PeerstoreResponse { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { protos: [] } @@ -1706,6 +1746,10 @@ export namespace PeerstoreResponse { break } case 2: { + if (opts.limits?.protos != null && obj.protos.length === opts.limits.protos) { + throw new CodeError('decode error - map field "protos" had too many elements', 'ERR_MAX_LENGTH') + } + obj.protos.push(reader.string()) break } @@ -1727,7 +1771,7 @@ export namespace PeerstoreResponse { return encodeMessage(obj, PeerstoreResponse.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): PeerstoreResponse => { - return decodeMessage(buf, PeerstoreResponse.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): PeerstoreResponse => { + return decodeMessage(buf, PeerstoreResponse.codec(), opts) } } diff --git a/packages/protons/test/fixtures/dht.ts b/packages/protons/test/fixtures/dht.ts index e12e068..d13a028 100644 --- a/packages/protons/test/fixtures/dht.ts +++ b/packages/protons/test/fixtures/dht.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, enumeration, message } from 'protons-runtime' +import { type Codec, CodeError, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface Record { @@ -53,7 +53,7 @@ export namespace Record { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -100,8 +100,8 @@ export namespace Record { return encodeMessage(obj, Record.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Record => { - return decodeMessage(buf, Record.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Record => { + return decodeMessage(buf, Record.codec(), opts) } } @@ -195,7 +195,7 @@ export namespace Message { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { addrs: [] } @@ -211,6 +211,10 @@ export namespace Message { break } case 2: { + if (opts.limits?.addrs != null && obj.addrs.length === opts.limits.addrs) { + throw new CodeError('decode error - map field "addrs" had too many elements', 'ERR_MAX_LENGTH') + } + obj.addrs.push(reader.bytes()) break } @@ -236,8 +240,8 @@ export namespace Message { return encodeMessage(obj, Peer.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Peer => { - return decodeMessage(buf, Peer.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Peer => { + return decodeMessage(buf, Peer.codec(), opts) } } @@ -287,7 +291,7 @@ export namespace Message { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { closerPeers: [], providerPeers: [] @@ -316,10 +320,18 @@ export namespace Message { break } case 8: { + if (opts.limits?.closerPeers != null && obj.closerPeers.length === opts.limits.closerPeers) { + throw new CodeError('decode error - map field "closerPeers" had too many elements', 'ERR_MAX_LENGTH') + } + obj.closerPeers.push(Message.Peer.codec().decode(reader, reader.uint32())) break } case 9: { + if (opts.limits?.providerPeers != null && obj.providerPeers.length === opts.limits.providerPeers) { + throw new CodeError('decode error - map field "providerPeers" had too many elements', 'ERR_MAX_LENGTH') + } + obj.providerPeers.push(Message.Peer.codec().decode(reader, reader.uint32())) break } @@ -341,7 +353,7 @@ export namespace Message { return encodeMessage(obj, Message.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Message => { - return decodeMessage(buf, Message.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Message => { + return decodeMessage(buf, Message.codec(), opts) } } diff --git a/packages/protons/test/fixtures/maps.ts b/packages/protons/test/fixtures/maps.ts index f65d46f..b0a6833 100644 --- a/packages/protons/test/fixtures/maps.ts +++ b/packages/protons/test/fixtures/maps.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, message } from 'protons-runtime' +import { type Codec, CodeError, decodeMessage, type DecodeOptions, encodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface SubMessage { @@ -29,7 +29,7 @@ export namespace SubMessage { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { foo: '' } @@ -62,8 +62,8 @@ export namespace SubMessage { return encodeMessage(obj, SubMessage.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): SubMessage => { - return decodeMessage(buf, SubMessage.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): SubMessage => { + return decodeMessage(buf, SubMessage.codec(), opts) } } @@ -103,7 +103,7 @@ export namespace MapTypes { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { key: '', value: '' @@ -141,8 +141,8 @@ export namespace MapTypes { return encodeMessage(obj, MapTypes$stringMapEntry.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): MapTypes$stringMapEntry => { - return decodeMessage(buf, MapTypes$stringMapEntry.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): MapTypes$stringMapEntry => { + return decodeMessage(buf, MapTypes$stringMapEntry.codec(), opts) } } @@ -174,7 +174,7 @@ export namespace MapTypes { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { key: 0, value: 0 @@ -212,8 +212,8 @@ export namespace MapTypes { return encodeMessage(obj, MapTypes$intMapEntry.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): MapTypes$intMapEntry => { - return decodeMessage(buf, MapTypes$intMapEntry.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): MapTypes$intMapEntry => { + return decodeMessage(buf, MapTypes$intMapEntry.codec(), opts) } } @@ -245,7 +245,7 @@ export namespace MapTypes { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { key: false, value: false @@ -283,8 +283,8 @@ export namespace MapTypes { return encodeMessage(obj, MapTypes$boolMapEntry.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): MapTypes$boolMapEntry => { - return decodeMessage(buf, MapTypes$boolMapEntry.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): MapTypes$boolMapEntry => { + return decodeMessage(buf, MapTypes$boolMapEntry.codec(), opts) } } @@ -316,7 +316,7 @@ export namespace MapTypes { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { key: '' } @@ -353,8 +353,8 @@ export namespace MapTypes { return encodeMessage(obj, MapTypes$messageMapEntry.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): MapTypes$messageMapEntry => { - return decodeMessage(buf, MapTypes$messageMapEntry.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): MapTypes$messageMapEntry => { + return decodeMessage(buf, MapTypes$messageMapEntry.codec(), opts) } } @@ -398,7 +398,7 @@ export namespace MapTypes { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { stringMap: new Map(), intMap: new Map(), @@ -413,21 +413,37 @@ export namespace MapTypes { switch (tag >>> 3) { case 1: { + if (opts.limits?.stringMap != null && obj.stringMap.size === opts.limits.stringMap) { + throw new CodeError('decode error - map field "stringMap" had too many elements', 'ERR_MAX_SIZE') + } + const entry = MapTypes.MapTypes$stringMapEntry.codec().decode(reader, reader.uint32()) obj.stringMap.set(entry.key, entry.value) break } case 2: { + if (opts.limits?.intMap != null && obj.intMap.size === opts.limits.intMap) { + throw new CodeError('decode error - map field "intMap" had too many elements', 'ERR_MAX_SIZE') + } + const entry = MapTypes.MapTypes$intMapEntry.codec().decode(reader, reader.uint32()) obj.intMap.set(entry.key, entry.value) break } case 3: { + if (opts.limits?.boolMap != null && obj.boolMap.size === opts.limits.boolMap) { + throw new CodeError('decode error - map field "boolMap" had too many elements', 'ERR_MAX_SIZE') + } + const entry = MapTypes.MapTypes$boolMapEntry.codec().decode(reader, reader.uint32()) obj.boolMap.set(entry.key, entry.value) break } case 4: { + if (opts.limits?.messageMap != null && obj.messageMap.size === opts.limits.messageMap) { + throw new CodeError('decode error - map field "messageMap" had too many elements', 'ERR_MAX_SIZE') + } + const entry = MapTypes.MapTypes$messageMapEntry.codec().decode(reader, reader.uint32()) obj.messageMap.set(entry.key, entry.value) break @@ -450,7 +466,7 @@ export namespace MapTypes { return encodeMessage(obj, MapTypes.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): MapTypes => { - return decodeMessage(buf, MapTypes.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): MapTypes => { + return decodeMessage(buf, MapTypes.codec(), opts) } } diff --git a/packages/protons/test/fixtures/noise.ts b/packages/protons/test/fixtures/noise.ts index 252b03a..611c7d0 100644 --- a/packages/protons/test/fixtures/noise.ts +++ b/packages/protons/test/fixtures/noise.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, message } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, message } from 'protons-runtime' import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Uint8ArrayList } from 'uint8arraylist' @@ -45,7 +45,7 @@ export namespace pb { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { identityKey: uint8ArrayAlloc(0), identitySig: uint8ArrayAlloc(0), @@ -88,8 +88,8 @@ export namespace pb { return encodeMessage(obj, NoiseHandshakePayload.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): NoiseHandshakePayload => { - return decodeMessage(buf, NoiseHandshakePayload.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): NoiseHandshakePayload => { + return decodeMessage(buf, NoiseHandshakePayload.codec(), opts) } } @@ -105,7 +105,7 @@ export namespace pb { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -132,7 +132,7 @@ export namespace pb { return encodeMessage(obj, pb.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): pb => { - return decodeMessage(buf, pb.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): pb => { + return decodeMessage(buf, pb.codec(), opts) } } diff --git a/packages/protons/test/fixtures/optional.ts b/packages/protons/test/fixtures/optional.ts index aca5239..53dc250 100644 --- a/packages/protons/test/fixtures/optional.ts +++ b/packages/protons/test/fixtures/optional.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, enumeration, message } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export enum OptionalEnum { @@ -52,7 +52,7 @@ export namespace OptionalSubMessage { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -87,8 +87,8 @@ export namespace OptionalSubMessage { return encodeMessage(obj, OptionalSubMessage.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): OptionalSubMessage => { - return decodeMessage(buf, OptionalSubMessage.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): OptionalSubMessage => { + return decodeMessage(buf, OptionalSubMessage.codec(), opts) } } @@ -210,7 +210,7 @@ export namespace Optional { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = {} const end = length == null ? reader.len : reader.pos + length @@ -305,7 +305,7 @@ export namespace Optional { return encodeMessage(obj, Optional.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Optional => { - return decodeMessage(buf, Optional.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Optional => { + return decodeMessage(buf, Optional.codec(), opts) } } diff --git a/packages/protons/test/fixtures/peer.ts b/packages/protons/test/fixtures/peer.ts index ad27fa4..86e62e1 100644 --- a/packages/protons/test/fixtures/peer.ts +++ b/packages/protons/test/fixtures/peer.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, message } from 'protons-runtime' +import { type Codec, CodeError, decodeMessage, type DecodeOptions, encodeMessage, message } from 'protons-runtime' import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Uint8ArrayList } from 'uint8arraylist' @@ -60,7 +60,7 @@ export namespace Peer { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { addresses: [], protocols: [], @@ -74,14 +74,26 @@ export namespace Peer { switch (tag >>> 3) { case 1: { + if (opts.limits?.addresses != null && obj.addresses.length === opts.limits.addresses) { + throw new CodeError('decode error - map field "addresses" had too many elements', 'ERR_MAX_LENGTH') + } + obj.addresses.push(Address.codec().decode(reader, reader.uint32())) break } case 2: { + if (opts.limits?.protocols != null && obj.protocols.length === opts.limits.protocols) { + throw new CodeError('decode error - map field "protocols" had too many elements', 'ERR_MAX_LENGTH') + } + obj.protocols.push(reader.string()) break } case 3: { + if (opts.limits?.metadata != null && obj.metadata.length === opts.limits.metadata) { + throw new CodeError('decode error - map field "metadata" had too many elements', 'ERR_MAX_LENGTH') + } + obj.metadata.push(Metadata.codec().decode(reader, reader.uint32())) break } @@ -111,8 +123,8 @@ export namespace Peer { return encodeMessage(obj, Peer.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Peer => { - return decodeMessage(buf, Peer.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Peer => { + return decodeMessage(buf, Peer.codec(), opts) } } @@ -144,7 +156,7 @@ export namespace Address { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { multiaddr: uint8ArrayAlloc(0) } @@ -181,8 +193,8 @@ export namespace Address { return encodeMessage(obj, Address.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Address => { - return decodeMessage(buf, Address.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions
): Address => { + return decodeMessage(buf, Address.codec(), opts) } } @@ -214,7 +226,7 @@ export namespace Metadata { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { key: '', value: uint8ArrayAlloc(0) @@ -252,7 +264,7 @@ export namespace Metadata { return encodeMessage(obj, Metadata.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Metadata => { - return decodeMessage(buf, Metadata.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Metadata => { + return decodeMessage(buf, Metadata.codec(), opts) } } diff --git a/packages/protons/test/fixtures/proto2.ts b/packages/protons/test/fixtures/proto2.ts index f3e44b7..774a714 100644 --- a/packages/protons/test/fixtures/proto2.ts +++ b/packages/protons/test/fixtures/proto2.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, message } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface MessageWithRequired { @@ -29,7 +29,7 @@ export namespace MessageWithRequired { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { scalarField: 0 } @@ -62,7 +62,7 @@ export namespace MessageWithRequired { return encodeMessage(obj, MessageWithRequired.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): MessageWithRequired => { - return decodeMessage(buf, MessageWithRequired.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): MessageWithRequired => { + return decodeMessage(buf, MessageWithRequired.codec(), opts) } } diff --git a/packages/protons/test/fixtures/protons-options.ts b/packages/protons/test/fixtures/protons-options.ts index bd580f7..5601fe5 100644 --- a/packages/protons/test/fixtures/protons-options.ts +++ b/packages/protons/test/fixtures/protons-options.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, CodeError, decodeMessage, encodeMessage, message } from 'protons-runtime' +import { type Codec, CodeError, decodeMessage, type DecodeOptions, encodeMessage, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export interface MessageWithSizeLimitedRepeatedField { @@ -31,7 +31,7 @@ export namespace MessageWithSizeLimitedRepeatedField { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { repeatedField: [] } @@ -43,6 +43,10 @@ export namespace MessageWithSizeLimitedRepeatedField { switch (tag >>> 3) { case 1: { + if (opts.limits?.repeatedField != null && obj.repeatedField.length === opts.limits.repeatedField) { + throw new CodeError('decode error - map field "repeatedField" had too many elements', 'ERR_MAX_LENGTH') + } + if (obj.repeatedField.length === 1) { throw new CodeError('decode error - repeated field "repeatedField" had too many elements', 'ERR_MAX_LENGTH') } @@ -68,8 +72,8 @@ export namespace MessageWithSizeLimitedRepeatedField { return encodeMessage(obj, MessageWithSizeLimitedRepeatedField.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): MessageWithSizeLimitedRepeatedField => { - return decodeMessage(buf, MessageWithSizeLimitedRepeatedField.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): MessageWithSizeLimitedRepeatedField => { + return decodeMessage(buf, MessageWithSizeLimitedRepeatedField.codec(), opts) } } @@ -106,7 +110,7 @@ export namespace MessageWithSizeLimitedMap { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { key: '', value: '' @@ -144,8 +148,8 @@ export namespace MessageWithSizeLimitedMap { return encodeMessage(obj, MessageWithSizeLimitedMap$mapFieldEntry.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): MessageWithSizeLimitedMap$mapFieldEntry => { - return decodeMessage(buf, MessageWithSizeLimitedMap$mapFieldEntry.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): MessageWithSizeLimitedMap$mapFieldEntry => { + return decodeMessage(buf, MessageWithSizeLimitedMap$mapFieldEntry.codec(), opts) } } @@ -168,7 +172,7 @@ export namespace MessageWithSizeLimitedMap { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { mapField: new Map() } @@ -180,6 +184,10 @@ export namespace MessageWithSizeLimitedMap { switch (tag >>> 3) { case 1: { + if (opts.limits?.mapField != null && obj.mapField.size === opts.limits.mapField) { + throw new CodeError('decode error - map field "mapField" had too many elements', 'ERR_MAX_SIZE') + } + if (obj.mapField.size === 1) { throw new CodeError('decode error - map field "mapField" had too many elements', 'ERR_MAX_SIZE') } @@ -206,7 +214,7 @@ export namespace MessageWithSizeLimitedMap { return encodeMessage(obj, MessageWithSizeLimitedMap.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): MessageWithSizeLimitedMap => { - return decodeMessage(buf, MessageWithSizeLimitedMap.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): MessageWithSizeLimitedMap => { + return decodeMessage(buf, MessageWithSizeLimitedMap.codec(), opts) } } diff --git a/packages/protons/test/fixtures/repeated.proto b/packages/protons/test/fixtures/repeated.proto new file mode 100644 index 0000000..79c1d54 --- /dev/null +++ b/packages/protons/test/fixtures/repeated.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +message SubMessage { + string foo = 1; +} + +message RepeatedTypes { + repeated uint32 number = 1; + repeated uint32 limitedNumber = 2 [(protons.options).limit = 1]; + repeated SubMessage message = 3; +} diff --git a/packages/protons/test/fixtures/repeated.ts b/packages/protons/test/fixtures/repeated.ts new file mode 100644 index 0000000..3e29034 --- /dev/null +++ b/packages/protons/test/fixtures/repeated.ts @@ -0,0 +1,172 @@ +/* eslint-disable import/export */ +/* eslint-disable complexity */ +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ +/* eslint-disable @typescript-eslint/no-empty-interface */ + +import { type Codec, CodeError, decodeMessage, type DecodeOptions, encodeMessage, message } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' + +export interface SubMessage { + foo: string +} + +export namespace SubMessage { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if ((obj.foo != null && obj.foo !== '')) { + w.uint32(10) + w.string(obj.foo) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length, opts = {}) => { + const obj: any = { + foo: '' + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: { + obj.foo = reader.string() + break + } + default: { + reader.skipType(tag & 7) + break + } + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, SubMessage.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): SubMessage => { + return decodeMessage(buf, SubMessage.codec(), opts) + } +} + +export interface RepeatedTypes { + number: number[] + limitedNumber: number[] + message: SubMessage[] +} + +export namespace RepeatedTypes { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.number != null) { + for (const value of obj.number) { + w.uint32(8) + w.uint32(value) + } + } + + if (obj.limitedNumber != null) { + for (const value of obj.limitedNumber) { + w.uint32(16) + w.uint32(value) + } + } + + if (obj.message != null) { + for (const value of obj.message) { + w.uint32(26) + SubMessage.codec().encode(value, w) + } + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (reader, length, opts = {}) => { + const obj: any = { + number: [], + limitedNumber: [], + message: [] + } + + const end = length == null ? reader.len : reader.pos + length + + while (reader.pos < end) { + const tag = reader.uint32() + + switch (tag >>> 3) { + case 1: { + if (opts.limits?.number != null && obj.number.length === opts.limits.number) { + throw new CodeError('decode error - map field "number" had too many elements', 'ERR_MAX_LENGTH') + } + + obj.number.push(reader.uint32()) + break + } + case 2: { + if (opts.limits?.limitedNumber != null && obj.limitedNumber.length === opts.limits.limitedNumber) { + throw new CodeError('decode error - map field "limitedNumber" had too many elements', 'ERR_MAX_LENGTH') + } + + if (obj.limitedNumber.length === 1) { + throw new CodeError('decode error - repeated field "limitedNumber" had too many elements', 'ERR_MAX_LENGTH') + } + + obj.limitedNumber.push(reader.uint32()) + break + } + case 3: { + if (opts.limits?.message != null && obj.message.length === opts.limits.message) { + throw new CodeError('decode error - map field "message" had too many elements', 'ERR_MAX_LENGTH') + } + + obj.message.push(SubMessage.codec().decode(reader, reader.uint32())) + break + } + default: { + reader.skipType(tag & 7) + break + } + } + } + + return obj + }) + } + + return _codec + } + + export const encode = (obj: Partial): Uint8Array => { + return encodeMessage(obj, RepeatedTypes.codec()) + } + + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): RepeatedTypes => { + return decodeMessage(buf, RepeatedTypes.codec(), opts) + } +} diff --git a/packages/protons/test/fixtures/singular.ts b/packages/protons/test/fixtures/singular.ts index 406c03e..91bdfb8 100644 --- a/packages/protons/test/fixtures/singular.ts +++ b/packages/protons/test/fixtures/singular.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, enumeration, message } from 'protons-runtime' +import { type Codec, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime' import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Uint8ArrayList } from 'uint8arraylist' @@ -53,7 +53,7 @@ export namespace SingularSubMessage { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { foo: '', bar: 0 @@ -91,8 +91,8 @@ export namespace SingularSubMessage { return encodeMessage(obj, SingularSubMessage.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): SingularSubMessage => { - return decodeMessage(buf, SingularSubMessage.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): SingularSubMessage => { + return decodeMessage(buf, SingularSubMessage.codec(), opts) } } @@ -214,7 +214,7 @@ export namespace Singular { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { double: 0, float: 0, @@ -326,7 +326,7 @@ export namespace Singular { return encodeMessage(obj, Singular.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): Singular => { - return decodeMessage(buf, Singular.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Singular => { + return decodeMessage(buf, Singular.codec(), opts) } } diff --git a/packages/protons/test/fixtures/test.ts b/packages/protons/test/fixtures/test.ts index c91c6c3..6cbee45 100644 --- a/packages/protons/test/fixtures/test.ts +++ b/packages/protons/test/fixtures/test.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */ /* eslint-disable @typescript-eslint/no-empty-interface */ -import { type Codec, decodeMessage, encodeMessage, enumeration, message } from 'protons-runtime' +import { type Codec, CodeError, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' export enum AnEnum { @@ -44,7 +44,7 @@ export namespace SubMessage { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { foo: '' } @@ -77,8 +77,8 @@ export namespace SubMessage { return encodeMessage(obj, SubMessage.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): SubMessage => { - return decodeMessage(buf, SubMessage.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): SubMessage => { + return decodeMessage(buf, SubMessage.codec(), opts) } } @@ -208,7 +208,7 @@ export namespace AllTheTypes { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (reader, length) => { + }, (reader, length, opts = {}) => { const obj: any = { field14: [] } @@ -272,6 +272,10 @@ export namespace AllTheTypes { break } case 14: { + if (opts.limits?.field14 != null && obj.field14.length === opts.limits.field14) { + throw new CodeError('decode error - map field "field14" had too many elements', 'ERR_MAX_LENGTH') + } + obj.field14.push(reader.string()) break } @@ -309,7 +313,7 @@ export namespace AllTheTypes { return encodeMessage(obj, AllTheTypes.codec()) } - export const decode = (buf: Uint8Array | Uint8ArrayList): AllTheTypes => { - return decodeMessage(buf, AllTheTypes.codec()) + export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): AllTheTypes => { + return decodeMessage(buf, AllTheTypes.codec(), opts) } } diff --git a/packages/protons/test/maps.spec.ts b/packages/protons/test/maps.spec.ts index b8c1181..996a33f 100644 --- a/packages/protons/test/maps.spec.ts +++ b/packages/protons/test/maps.spec.ts @@ -144,4 +144,20 @@ describe('maps', () => { testEncodings(obj, MapTypes, './test/fixtures/maps.proto', 'MapTypes') }) + + it('should limit map sizes using runtime options', () => { + const obj: MapTypes = { + stringMap: new Map([['key', 'value'], ['foo', 'bar']]), + intMap: new Map(), + boolMap: new Map(), + messageMap: new Map() + } + + const buf = MapTypes.encode(obj) + expect(() => MapTypes.decode(buf, { + limits: { + stringMap: 1 + } + })).to.throw(/too many elements/) + }) }) diff --git a/packages/protons/test/repeated.spec.ts b/packages/protons/test/repeated.spec.ts new file mode 100644 index 0000000..49bc43b --- /dev/null +++ b/packages/protons/test/repeated.spec.ts @@ -0,0 +1,43 @@ +/* eslint-env mocha */ + +import { expect } from 'aegir/chai' +import { RepeatedTypes } from './fixtures/repeated.js' + +describe('repeated', () => { + it('should encode repeated fields', () => { + const obj: RepeatedTypes = { + number: [], + limitedNumber: [], + message: [] + } + + const buf = RepeatedTypes.encode(obj) + expect(RepeatedTypes.decode(buf)).to.deep.equal(obj) + }) + + it('should limit repeated fields', () => { + const obj: RepeatedTypes = { + number: [], + limitedNumber: [1, 2], + message: [] + } + + const buf = RepeatedTypes.encode(obj) + expect(() => RepeatedTypes.decode(buf)).to.throw(/too many elements/) + }) + + it('should limit repeated fields using runtime options', () => { + const obj: RepeatedTypes = { + number: [1, 2], + limitedNumber: [], + message: [] + } + + const buf = RepeatedTypes.encode(obj) + expect(() => RepeatedTypes.decode(buf, { + limits: { + number: 1 + } + })).to.throw(/too many elements/) + }) +}) diff --git a/packages/protons/tsconfig.json b/packages/protons/tsconfig.json index 196cb1e..315999b 100644 --- a/packages/protons/tsconfig.json +++ b/packages/protons/tsconfig.json @@ -1,7 +1,9 @@ { "extends": "aegir/src/config/tsconfig.aegir.json", "compilerOptions": { - "outDir": "dist" + "outDir": "dist", + "moduleResolution": "Node16", + "module": "Node16" }, "include": [ "bin",