From 0de8190f2956fab081fa8b9b7736ec9a3d2ec221 Mon Sep 17 00:00:00 2001 From: Cayman Date: Thu, 8 Oct 2026 11:50:59 -0400 Subject: [PATCH] feat: add aggregate decode limits --- packages/protons-runtime/src/codec.ts | 7 + .../protons-runtime/src/codecs/message.ts | 26 +- .../protons-runtime/src/decode-context.ts | 53 + packages/protons-runtime/src/index.ts | 8 + packages/protons-runtime/src/stream.ts | 4 +- packages/protons/src/fields/array-field.ts | 2 + packages/protons/src/fields/map-field.ts | 3 + packages/protons/src/index.ts | 19 + packages/protons/src/types/message.ts | 55 +- packages/protons/test/fixtures/basic.ts | 4 +- packages/protons/test/fixtures/bitswap.ts | 80 +- packages/protons/test/fixtures/circuit.ts | 40 +- .../test/fixtures/custom-option-jstype.ts | 50 +- packages/protons/test/fixtures/daemon.ts | 322 +++--- packages/protons/test/fixtures/dht.ts | 46 +- packages/protons/test/fixtures/maps.ts | 118 ++- packages/protons/test/fixtures/noise.ts | 4 +- packages/protons/test/fixtures/oneof.ts | 4 +- packages/protons/test/fixtures/optional.ts | 22 +- .../protons/test/fixtures/packed-global.ts | 16 +- .../protons/test/fixtures/packed-proto2.ts | 92 +- packages/protons/test/fixtures/packed.ts | 98 +- packages/protons/test/fixtures/peer.ts | 44 +- packages/protons/test/fixtures/proto2.ts | 2 +- .../protons/test/fixtures/protons-options.ts | 30 +- packages/protons/test/fixtures/repeated.ts | 82 +- packages/protons/test/fixtures/singular.ts | 22 +- packages/protons/test/fixtures/streaming.ts | 132 ++- packages/protons/test/fixtures/test.ts | 24 +- .../protons/test/fixtures/total-limits.proto | 15 + .../protons/test/fixtures/total-limits.ts | 978 ++++++++++++++++++ packages/protons/test/total-limits.spec.ts | 147 +++ 32 files changed, 2135 insertions(+), 414 deletions(-) create mode 100644 packages/protons-runtime/src/decode-context.ts create mode 100644 packages/protons/test/fixtures/total-limits.proto create mode 100644 packages/protons/test/fixtures/total-limits.ts create mode 100644 packages/protons/test/total-limits.spec.ts diff --git a/packages/protons-runtime/src/codec.ts b/packages/protons-runtime/src/codec.ts index ebff2dc..3a63f79 100644 --- a/packages/protons-runtime/src/codec.ts +++ b/packages/protons-runtime/src/codec.ts @@ -65,6 +65,13 @@ export interface DecodeOptions { * Runtime-specified limits for lengths of repeated/map fields */ limits?: Limits + + /** + * Maximum wire elements at each repeated/map field path across the entire + * decode or stream operation. Uses the same shape as `limits`; map entries + * count even when duplicate keys overwrite earlier entries. + */ + totalLimits?: Limits } export interface DecodeFunction { diff --git a/packages/protons-runtime/src/codecs/message.ts b/packages/protons-runtime/src/codecs/message.ts index fb44162..840fc8b 100644 --- a/packages/protons-runtime/src/codecs/message.ts +++ b/packages/protons-runtime/src/codecs/message.ts @@ -1,10 +1,32 @@ import { createCodec, CODEC_TYPES } from '../codec.ts' +import { UnsupportedDecodeLimitsError } from '../index.ts' import type { EncodeFunction, DecodeFunction, Codec, StreamFunction } from '../codec.ts' export interface Factory { new (obj: A): T } -export function message (encode: EncodeFunction, decode: DecodeFunction, stream: StreamFunction): Codec { - return createCodec('message', CODEC_TYPES.LENGTH_DELIMITED, encode, decode, stream) +export interface MessageCodecOptions { + totalLimits?: boolean +} + +export function message (encode: EncodeFunction, decode: DecodeFunction, stream: StreamFunction, options: MessageCodecOptions = {}): Codec { + const codec = createCodec('message', CODEC_TYPES.LENGTH_DELIMITED, encode, decode, stream) + + if (options.totalLimits !== true) { + codec.decode = (r, length, opts) => { + if (opts?.totalLimits != null) { + throw new UnsupportedDecodeLimitsError('Regenerate this codec to use totalLimits') + } + return decode(r, length, opts) + } + codec.stream = function * (r, length, prefix, opts) { + if (opts?.totalLimits != null) { + throw new UnsupportedDecodeLimitsError('Regenerate this codec to use totalLimits') + } + yield * stream(r, length, prefix, opts) + } + } + + return codec } diff --git a/packages/protons-runtime/src/decode-context.ts b/packages/protons-runtime/src/decode-context.ts new file mode 100644 index 0000000..e763b26 --- /dev/null +++ b/packages/protons-runtime/src/decode-context.ts @@ -0,0 +1,53 @@ +import { MaxLengthError, MaxSizeError } from './index.ts' + +const contextKey = Symbol('decodeContext') + +interface InternalDecodeOptions { + totalLimits?: unknown + [contextKey]?: DecodeContext +} + +/** Shared counters for a field path during one decode or stream operation. */ +export class DecodeContext { + private readonly counts = new Map() + private readonly children = new Map() + + private constructor () {} + + static get (opts: InternalDecodeOptions): DecodeContext | undefined { + if (opts.totalLimits == null) { + return + } + + return opts[contextKey] ?? new DecodeContext() + } + + static child (context: DecodeContext | undefined, field: string, opts: T): T { + if (context == null || opts.totalLimits == null) { + return opts + } + + let child = context.children.get(field) + + if (child == null) { + child = new DecodeContext() + context.children.set(field, child) + } + + return { ...opts, [contextKey]: child } + } + + consume (field: string, limit: number | undefined, ErrorType: typeof MaxLengthError | typeof MaxSizeError = MaxLengthError): void { + if (limit == null) { + return + } + + const count = this.counts.get(field) ?? 0 + + if (count >= limit) { + throw new ErrorType(`Decode error - field "${field}" exceeded its total element limit`) + } + + this.counts.set(field, count + 1) + } +} diff --git a/packages/protons-runtime/src/index.ts b/packages/protons-runtime/src/index.ts index c8bf0bd..636348f 100644 --- a/packages/protons-runtime/src/index.ts +++ b/packages/protons-runtime/src/index.ts @@ -45,6 +45,7 @@ export type { } from './codec.ts' export { enumeration } from './codecs/enum.ts' +export { DecodeContext } from './decode-context.ts' export { message } from './codecs/message.ts' export { createReader as reader } from './utils/reader.ts' export { createWriter as writer } from './utils/writer.ts' @@ -396,6 +397,13 @@ export class MaxSizeError extends Error { public name = 'MaxSizeError' } +/** + * Thrown when aggregate limits are requested from a codec that cannot enforce them + */ +export class UnsupportedDecodeLimitsError extends Error { + public name = 'UnsupportedDecodeLimitsError' +} + export class ParseError extends Error { /** * This will be removed in a future release diff --git a/packages/protons-runtime/src/stream.ts b/packages/protons-runtime/src/stream.ts index aae7e0b..634865f 100644 --- a/packages/protons-runtime/src/stream.ts +++ b/packages/protons-runtime/src/stream.ts @@ -1,8 +1,8 @@ import { createReader } from './utils/reader.ts' -import type { Codec } from './codec.ts' +import type { Codec, DecodeOptions } from './codec.ts' import type { Uint8ArrayList } from 'uint8arraylist' -export function * streamMessage (buf: Uint8Array | Uint8ArrayList, codec: Pick, 'stream'>, opts?: any): Generator { +export function * streamMessage (buf: Uint8Array | Uint8ArrayList, codec: Pick, 'stream'>, opts?: DecodeOptions): Generator { const reader = createReader(buf) yield * codec.stream(reader, undefined, '.', opts) diff --git a/packages/protons/src/fields/array-field.ts b/packages/protons/src/fields/array-field.ts index 41b709f..6fefc74 100644 --- a/packages/protons/src/fields/array-field.ts +++ b/packages/protons/src/fields/array-field.ts @@ -148,6 +148,7 @@ export class ArrayField extends Field { parent.addImport('protons-runtime', 'MaxLengthError') let limit = ` + decodeContext?.consume('${this.name}', opts.totalLimits?.${this.name}) if (opts.limits?.${this.name} != null && obj.${this.name}.length === opts.limits.${this.name}) { throw new MaxLengthError('Decode error - repeated field "${this.name}" had too many elements') } @@ -188,6 +189,7 @@ export class ArrayField extends Field { parent.addImport('protons-runtime', 'MaxLengthError') let limit = ` + decodeContext?.consume('${this.name}', opts.totalLimits?.${this.name}) if (opts.limits?.${this.name} != null && obj.${this.name} === opts.limits.${this.name}) { throw new MaxLengthError('Streaming decode error - repeated field "${this.name}" had too many elements') } diff --git a/packages/protons/src/fields/map-field.ts b/packages/protons/src/fields/map-field.ts index 6befbb8..2121abf 100644 --- a/packages/protons/src/fields/map-field.ts +++ b/packages/protons/src/fields/map-field.ts @@ -77,6 +77,7 @@ export class MapField extends Field { const entryType = parent.findType(this.entryType) let limit = ` + decodeContext?.consume('${this.name}', opts.totalLimits?.${this.name}, MaxSizeError) if (opts.limits?.${this.name} != null && obj.${this.name}.size === opts.limits.${this.name}) { throw new MaxSizeError('Decode error - map field "${this.name}" had too many elements') } @@ -98,9 +99,11 @@ export class MapField extends Field { } getStreamingDecoder (parent: Parent): string { + parent.addImport('protons-runtime', 'MaxSizeError') parent.addImport('protons-runtime', 'MaxLengthError') let limit = ` + decodeContext?.consume('${this.name}', opts.totalLimits?.${this.name}, MaxSizeError) if (opts.limits?.${this.name} != null && obj.${this.name} === opts.limits.${this.name}) { throw new MaxLengthError('Decode error - map field "${this.name}" had too many elements') } diff --git a/packages/protons/src/index.ts b/packages/protons/src/index.ts index c715ad1..d6f0158 100644 --- a/packages/protons/src/index.ts +++ b/packages/protons/src/index.ts @@ -170,6 +170,25 @@ * }) * ``` * + * #### Limiting totals across nested messages + * + * `limits` applies separately to each container. To bound the total number of + * decoded elements at a field path, use `totalLimits` with the same shape: + * + * ```TypeScript + * const message = MyMessage.decode(buf, { + * limits: { messages$value: { repeatedField: 5 } }, + * totalLimits: { messages$value: { repeatedField: 10 } } + * }) + * ``` + * Each nested list may contain up to five elements, and all those lists together + * may contain up to ten. Totals include repeated occurrences of singular parent + * messages and duplicate map entries, even when earlier values are overwritten. + * Limits are checked before decoding the next element. The same options work + * with `.stream()` and can be reused: each operation starts with fresh counters. + * Regenerate existing codecs to use this option. Older generated codecs throw + * `UnsupportedDecodeLimitsError` when passed `totalLimits`. + * * ### 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. diff --git a/packages/protons/src/types/message.ts b/packages/protons/src/types/message.ts index 6abc1fb..f38699a 100644 --- a/packages/protons/src/types/message.ts +++ b/packages/protons/src/types/message.ts @@ -176,19 +176,22 @@ export class Message implements Type { let opts = '' if (field instanceof MessageField) { - opts = `, { -${indent} limits: opts.limits?.${field.name} -${indent} }` + opts = `, DecodeContext.child(decodeContext, '${field.name}', { +${indent} limits: opts.limits?.${field.name}, +${indent} totalLimits: opts.totalLimits?.${field.name} +${indent} })` } else if (field instanceof ArrayField) { - opts = `, { -${indent} limits: opts.limits?.${field.name}$ -${indent} }` + opts = `, DecodeContext.child(decodeContext, '${field.name}', { +${indent} limits: opts.limits?.${field.name}$, +${indent} totalLimits: opts.totalLimits?.${field.name}$ +${indent} })` } else if (field instanceof MapField) { - opts = `, { + opts = `, DecodeContext.child(decodeContext, '${field.name}', { ${indent} limits: { ${indent} value: opts.limits?.${field.name}$value - } -${indent} }` + }, +${indent} totalLimits: opts.totalLimits?.${field.name}$value == null ? undefined : { value: opts.totalLimits.${field.name}$value } +${indent} })` } return `${this.jsType.decode}.codec().decode(${reader}, ${reader}.uint32()${opts})` @@ -198,13 +201,15 @@ ${indent} }` let opts = '' if (field instanceof MessageField) { - opts = `, { -${indent} limits: opts.limits?.${field.name} -${indent} }` + opts = `, DecodeContext.child(decodeContext, '${field.name}', { +${indent} limits: opts.limits?.${field.name}, +${indent} totalLimits: opts.totalLimits?.${field.name} +${indent} })` } else if (field instanceof ArrayField) { - opts = `, { -${indent} limits: opts.limits?.${field.name}$ -${indent} }` + opts = `, DecodeContext.child(decodeContext, '${field.name}', { +${indent} limits: opts.limits?.${field.name}$, +${indent} totalLimits: opts.totalLimits?.${field.name}$ +${indent} })` return `for (const evt of ${this.jsType.decode}.codec().stream(${reader}, ${reader}.uint32(), ${prefix}${opts})) { ${indent} yield { @@ -213,11 +218,12 @@ ${indent} index: obj.${field.name} ${indent} } ${indent} }` } else if (field instanceof MapField) { - opts = `, { + opts = `, DecodeContext.child(decodeContext, '${field.name}', { ${indent} limits: { ${indent} value: opts.limits?.${field.name}$value - } -${indent} }` +${indent} }, +${indent} totalLimits: opts.totalLimits?.${field.name}$value == null ? undefined : { value: opts.totalLimits.${field.name}$value } +${indent} })` } return `yield * ${this.jsType.decode}.codec().stream(${reader}, ${reader}.uint32(), ${prefix}${opts})` @@ -302,6 +308,13 @@ export interface ${this.pbType}Input { const decodeOpts = this.fields.some(field => field instanceof ArrayField || field instanceof MapField || field instanceof MessageField) ? ', opts = {}' : '' + const decodeContext = decodeOpts === '' + ? '' + : '\n const decodeContext = DecodeContext.get(opts)' + + if (decodeContext !== '') { + parent.addImport('protons-runtime', 'DecodeContext') + } interfaceCodecDef = ` let _codec: Codec<${this.pbType}, ${this.pbType}Input> @@ -316,7 +329,7 @@ export interface ${this.pbType}Input { if (opts.lengthDelimited !== false) { w.ldelim() } - }, (r, length${decodeOpts}) => { + }, (r, length${decodeOpts}) => {${decodeContext} const obj: any = {${this.createDefaultObject()}} const end = length == null ? r.len : r.pos + length @@ -333,7 +346,7 @@ export interface ${this.pbType}Input { } ${enforceOneOfDecoding === '' ? '' : `${enforceOneOfDecoding}\n`} return obj - }, function * (r, length, prefix${decodeOpts}) { + }, function * (r, length, prefix${decodeOpts}) {${decodeContext} ${this.createLimitObject()}const end = length == null ? r.len : r.pos + length if (prefix !== '.') { @@ -362,7 +375,7 @@ ${enforceOneOfDecoding === '' ? '' : `${enforceOneOfDecoding}\n`} message: '${this.jsType.decode}' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/basic.ts b/packages/protons/test/fixtures/basic.ts index 8037ca8..22c4f5a 100644 --- a/packages/protons/test/fixtures/basic.ts +++ b/packages/protons/test/fixtures/basic.ts @@ -105,7 +105,7 @@ export namespace Basic { message: 'Basic' } } - }) + }, { totalLimits: true }) } return _codec @@ -197,7 +197,7 @@ export namespace Empty { message: 'Empty' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/bitswap.ts b/packages/protons/test/fixtures/bitswap.ts index f891c22..0629563 100644 --- a/packages/protons/test/fixtures/bitswap.ts +++ b/packages/protons/test/fixtures/bitswap.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, enumeration, MaxLengthError, message, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, enumeration, MaxLengthError, message, streamMessage } from 'protons-runtime' import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -207,7 +207,7 @@ export namespace Message { message: 'Message.Wantlist.Entry' } } - }) + }, { totalLimits: true }) } return _codec @@ -276,6 +276,7 @@ export namespace Message { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { entries: [], full: false @@ -288,13 +289,15 @@ export namespace Message { switch (tag >>> 3) { case 1: { + decodeContext?.consume('entries', opts.totalLimits?.entries) if (opts.limits?.entries != null && obj.entries.length === opts.limits.entries) { throw new MaxLengthError('Decode error - repeated field "entries" had too many elements') } - obj.entries.push(Message.Wantlist.Entry.codec().decode(r, r.uint32(), { - limits: opts.limits?.entries$ - })) + obj.entries.push(Message.Wantlist.Entry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'entries', { + limits: opts.limits?.entries$, + totalLimits: opts.totalLimits?.entries$ + }))) break } case 2: { @@ -310,6 +313,7 @@ export namespace Message { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { entries: 0 } @@ -329,13 +333,15 @@ export namespace Message { switch (tag >>> 3) { case 1: { + decodeContext?.consume('entries', opts.totalLimits?.entries) if (opts.limits?.entries != null && obj.entries === opts.limits.entries) { throw new MaxLengthError('Streaming decode error - repeated field "entries" had too many elements') } - for (const evt of Message.Wantlist.Entry.codec().stream(r, r.uint32(), `${prefix}entries[].`, { - limits: opts.limits?.entries$ - })) { + for (const evt of Message.Wantlist.Entry.codec().stream(r, r.uint32(), `${prefix}entries[].`, DecodeContext.child(decodeContext, 'entries', { + limits: opts.limits?.entries$, + totalLimits: opts.totalLimits?.entries$ + }))) { yield { ...evt, index: obj.entries @@ -367,7 +373,7 @@ export namespace Message { message: 'Message.Wantlist' } } - }) + }, { totalLimits: true }) } return _codec @@ -539,7 +545,7 @@ export namespace Message { message: 'Message.Block' } } - }) + }, { totalLimits: true }) } return _codec @@ -688,7 +694,7 @@ export namespace Message { message: 'Message.BlockPresence' } } - }) + }, { totalLimits: true }) } return _codec @@ -761,6 +767,7 @@ export namespace Message { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { blocks: [], payload: [], @@ -775,12 +782,14 @@ export namespace Message { switch (tag >>> 3) { case 1: { - obj.wantlist = Message.Wantlist.codec().decode(r, r.uint32(), { - limits: opts.limits?.wantlist - }) + obj.wantlist = Message.Wantlist.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'wantlist', { + limits: opts.limits?.wantlist, + totalLimits: opts.totalLimits?.wantlist + })) break } case 2: { + decodeContext?.consume('blocks', opts.totalLimits?.blocks) if (opts.limits?.blocks != null && obj.blocks.length === opts.limits.blocks) { throw new MaxLengthError('Decode error - repeated field "blocks" had too many elements') } @@ -789,23 +798,27 @@ export namespace Message { break } case 3: { + decodeContext?.consume('payload', opts.totalLimits?.payload) if (opts.limits?.payload != null && obj.payload.length === opts.limits.payload) { throw new MaxLengthError('Decode error - repeated field "payload" had too many elements') } - obj.payload.push(Message.Block.codec().decode(r, r.uint32(), { - limits: opts.limits?.payload$ - })) + obj.payload.push(Message.Block.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'payload', { + limits: opts.limits?.payload$, + totalLimits: opts.totalLimits?.payload$ + }))) break } case 4: { + decodeContext?.consume('blockPresences', opts.totalLimits?.blockPresences) if (opts.limits?.blockPresences != null && obj.blockPresences.length === opts.limits.blockPresences) { throw new MaxLengthError('Decode error - repeated field "blockPresences" had too many elements') } - obj.blockPresences.push(Message.BlockPresence.codec().decode(r, r.uint32(), { - limits: opts.limits?.blockPresences$ - })) + obj.blockPresences.push(Message.BlockPresence.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'blockPresences', { + limits: opts.limits?.blockPresences$, + totalLimits: opts.totalLimits?.blockPresences$ + }))) break } case 5: { @@ -821,6 +834,7 @@ export namespace Message { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { blocks: 0, payload: 0, @@ -842,13 +856,15 @@ export namespace Message { switch (tag >>> 3) { case 1: { - yield * Message.Wantlist.codec().stream(r, r.uint32(), `${prefix}wantlist.`, { - limits: opts.limits?.wantlist - }) + yield * Message.Wantlist.codec().stream(r, r.uint32(), `${prefix}wantlist.`, DecodeContext.child(decodeContext, 'wantlist', { + limits: opts.limits?.wantlist, + totalLimits: opts.totalLimits?.wantlist + })) break } case 2: { + decodeContext?.consume('blocks', opts.totalLimits?.blocks) if (opts.limits?.blocks != null && obj.blocks === opts.limits.blocks) { throw new MaxLengthError('Streaming decode error - repeated field "blocks" had too many elements') } @@ -864,13 +880,15 @@ export namespace Message { break } case 3: { + decodeContext?.consume('payload', opts.totalLimits?.payload) if (opts.limits?.payload != null && obj.payload === opts.limits.payload) { throw new MaxLengthError('Streaming decode error - repeated field "payload" had too many elements') } - for (const evt of Message.Block.codec().stream(r, r.uint32(), `${prefix}payload[].`, { - limits: opts.limits?.payload$ - })) { + for (const evt of Message.Block.codec().stream(r, r.uint32(), `${prefix}payload[].`, DecodeContext.child(decodeContext, 'payload', { + limits: opts.limits?.payload$, + totalLimits: opts.totalLimits?.payload$ + }))) { yield { ...evt, index: obj.payload @@ -882,13 +900,15 @@ export namespace Message { break } case 4: { + decodeContext?.consume('blockPresences', opts.totalLimits?.blockPresences) if (opts.limits?.blockPresences != null && obj.blockPresences === opts.limits.blockPresences) { throw new MaxLengthError('Streaming decode error - repeated field "blockPresences" had too many elements') } - for (const evt of Message.BlockPresence.codec().stream(r, r.uint32(), `${prefix}blockPresences[].`, { - limits: opts.limits?.blockPresences$ - })) { + for (const evt of Message.BlockPresence.codec().stream(r, r.uint32(), `${prefix}blockPresences[].`, DecodeContext.child(decodeContext, 'blockPresences', { + limits: opts.limits?.blockPresences$, + totalLimits: opts.totalLimits?.blockPresences$ + }))) { yield { ...evt, index: obj.blockPresences @@ -920,7 +940,7 @@ export namespace Message { message: 'Message' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/circuit.ts b/packages/protons/test/fixtures/circuit.ts index db6e254..cf0da92 100644 --- a/packages/protons/test/fixtures/circuit.ts +++ b/packages/protons/test/fixtures/circuit.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, enumeration, MaxLengthError, message, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, enumeration, MaxLengthError, message, streamMessage } from 'protons-runtime' import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -118,6 +118,7 @@ export namespace CircuitRelay { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { id: uint8ArrayAlloc(0), addrs: [] @@ -134,6 +135,7 @@ export namespace CircuitRelay { break } case 2: { + decodeContext?.consume('addrs', opts.totalLimits?.addrs) if (opts.limits?.addrs != null && obj.addrs.length === opts.limits.addrs) { throw new MaxLengthError('Decode error - repeated field "addrs" had too many elements') } @@ -150,6 +152,7 @@ export namespace CircuitRelay { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { addrs: 0 } @@ -176,6 +179,7 @@ export namespace CircuitRelay { break } case 2: { + decodeContext?.consume('addrs', opts.totalLimits?.addrs) if (opts.limits?.addrs != null && obj.addrs === opts.limits.addrs) { throw new MaxLengthError('Streaming decode error - repeated field "addrs" had too many elements') } @@ -204,7 +208,7 @@ export namespace CircuitRelay { message: 'CircuitRelay.Peer' } } - }) + }, { totalLimits: true }) } return _codec @@ -267,6 +271,7 @@ export namespace CircuitRelay { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = {} const end = length == null ? r.len : r.pos + length @@ -280,15 +285,17 @@ export namespace CircuitRelay { break } case 2: { - obj.srcPeer = CircuitRelay.Peer.codec().decode(r, r.uint32(), { - limits: opts.limits?.srcPeer - }) + obj.srcPeer = CircuitRelay.Peer.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'srcPeer', { + limits: opts.limits?.srcPeer, + totalLimits: opts.totalLimits?.srcPeer + })) break } case 3: { - obj.dstPeer = CircuitRelay.Peer.codec().decode(r, r.uint32(), { - limits: opts.limits?.dstPeer - }) + obj.dstPeer = CircuitRelay.Peer.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'dstPeer', { + limits: opts.limits?.dstPeer, + totalLimits: opts.totalLimits?.dstPeer + })) break } case 4: { @@ -304,6 +311,7 @@ export namespace CircuitRelay { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const end = length == null ? r.len : r.pos + length if (prefix !== '.') { @@ -326,16 +334,18 @@ export namespace CircuitRelay { break } case 2: { - yield * CircuitRelay.Peer.codec().stream(r, r.uint32(), `${prefix}srcPeer.`, { - limits: opts.limits?.srcPeer - }) + yield * CircuitRelay.Peer.codec().stream(r, r.uint32(), `${prefix}srcPeer.`, DecodeContext.child(decodeContext, 'srcPeer', { + limits: opts.limits?.srcPeer, + totalLimits: opts.totalLimits?.srcPeer + })) break } case 3: { - yield * CircuitRelay.Peer.codec().stream(r, r.uint32(), `${prefix}dstPeer.`, { - limits: opts.limits?.dstPeer - }) + yield * CircuitRelay.Peer.codec().stream(r, r.uint32(), `${prefix}dstPeer.`, DecodeContext.child(decodeContext, 'dstPeer', { + limits: opts.limits?.dstPeer, + totalLimits: opts.totalLimits?.dstPeer + })) break } @@ -360,7 +370,7 @@ export namespace CircuitRelay { message: 'CircuitRelay' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/custom-option-jstype.ts b/packages/protons/test/fixtures/custom-option-jstype.ts index 72147ec..5463f81 100644 --- a/packages/protons/test/fixtures/custom-option-jstype.ts +++ b/packages/protons/test/fixtures/custom-option-jstype.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, MaxLengthError, MaxSizeError, message, reader, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, MaxLengthError, MaxSizeError, message, reader, streamMessage } from 'protons-runtime' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -129,7 +129,7 @@ export namespace CustomOptionNumber { message: 'CustomOptionNumber.CustomOptionNumber$i64MapEntry' } } - }) + }, { totalLimits: true }) } return _codec @@ -219,6 +219,7 @@ export namespace CustomOptionNumber { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { num: 0, i64: 0, @@ -265,6 +266,7 @@ export namespace CustomOptionNumber { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('i64Array', opts.totalLimits?.i64Array) if (opts.limits?.i64Array != null && obj.i64Array.length === opts.limits.i64Array) { throw new MaxLengthError('Decode error - repeated field "i64Array" had too many elements') } @@ -275,15 +277,17 @@ export namespace CustomOptionNumber { break } case 8: { + decodeContext?.consume('i64Map', opts.totalLimits?.i64Map, MaxSizeError) if (opts.limits?.i64Map != null && obj.i64Map.size === opts.limits.i64Map) { throw new MaxSizeError('Decode error - map field "i64Map" had too many elements') } - const entry = CustomOptionNumber.CustomOptionNumber$i64MapEntry.codec().decode(r, r.uint32(), { + const entry = CustomOptionNumber.CustomOptionNumber$i64MapEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'i64Map', { limits: { value: opts.limits?.i64Map$value - } - }) + }, + totalLimits: opts.totalLimits?.i64Map$value == null ? undefined : { value: opts.totalLimits.i64Map$value } + })) obj.i64Map.set(entry.key, entry.value) break } @@ -296,6 +300,7 @@ export namespace CustomOptionNumber { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { i64Array: 0, i64Map: 0 @@ -362,6 +367,7 @@ export namespace CustomOptionNumber { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('i64Array', opts.totalLimits?.i64Array) if (opts.limits?.i64Array != null && obj.i64Array === opts.limits.i64Array) { throw new MaxLengthError('Streaming decode error - repeated field "i64Array" had too many elements') } @@ -378,15 +384,17 @@ export namespace CustomOptionNumber { break } case 8: { + decodeContext?.consume('i64Map', opts.totalLimits?.i64Map, MaxSizeError) if (opts.limits?.i64Map != null && obj.i64Map === opts.limits.i64Map) { throw new MaxLengthError('Decode error - map field "i64Map" had too many elements') } - yield * CustomOptionNumber.CustomOptionNumber$i64MapEntry.codec().stream(r, r.uint32(), `${prefix}i64Map{}.`, { + yield * CustomOptionNumber.CustomOptionNumber$i64MapEntry.codec().stream(r, r.uint32(), `${prefix}i64Map{}.`, DecodeContext.child(decodeContext, 'i64Map', { limits: { value: opts.limits?.i64Map$value - } - }) + }, + totalLimits: opts.totalLimits?.i64Map$value == null ? undefined : { value: opts.totalLimits.i64Map$value } + })) obj.i64Map++ @@ -406,7 +414,7 @@ export namespace CustomOptionNumber { message: 'CustomOptionNumber' } } - }) + }, { totalLimits: true }) } return _codec @@ -594,7 +602,7 @@ export namespace CustomOptionString { message: 'CustomOptionString.CustomOptionString$i64MapEntry' } } - }) + }, { totalLimits: true }) } return _codec @@ -684,6 +692,7 @@ export namespace CustomOptionString { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { num: 0, i64: '', @@ -730,6 +739,7 @@ export namespace CustomOptionString { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('i64Array', opts.totalLimits?.i64Array) if (opts.limits?.i64Array != null && obj.i64Array.length === opts.limits.i64Array) { throw new MaxLengthError('Decode error - repeated field "i64Array" had too many elements') } @@ -740,15 +750,17 @@ export namespace CustomOptionString { break } case 8: { + decodeContext?.consume('i64Map', opts.totalLimits?.i64Map, MaxSizeError) if (opts.limits?.i64Map != null && obj.i64Map.size === opts.limits.i64Map) { throw new MaxSizeError('Decode error - map field "i64Map" had too many elements') } - const entry = CustomOptionString.CustomOptionString$i64MapEntry.codec().decode(r, r.uint32(), { + const entry = CustomOptionString.CustomOptionString$i64MapEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'i64Map', { limits: { value: opts.limits?.i64Map$value - } - }) + }, + totalLimits: opts.totalLimits?.i64Map$value == null ? undefined : { value: opts.totalLimits.i64Map$value } + })) obj.i64Map.set(entry.key, entry.value) break } @@ -761,6 +773,7 @@ export namespace CustomOptionString { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { i64Array: 0, i64Map: 0 @@ -827,6 +840,7 @@ export namespace CustomOptionString { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('i64Array', opts.totalLimits?.i64Array) if (opts.limits?.i64Array != null && obj.i64Array === opts.limits.i64Array) { throw new MaxLengthError('Streaming decode error - repeated field "i64Array" had too many elements') } @@ -843,15 +857,17 @@ export namespace CustomOptionString { break } case 8: { + decodeContext?.consume('i64Map', opts.totalLimits?.i64Map, MaxSizeError) if (opts.limits?.i64Map != null && obj.i64Map === opts.limits.i64Map) { throw new MaxLengthError('Decode error - map field "i64Map" had too many elements') } - yield * CustomOptionString.CustomOptionString$i64MapEntry.codec().stream(r, r.uint32(), `${prefix}i64Map{}.`, { + yield * CustomOptionString.CustomOptionString$i64MapEntry.codec().stream(r, r.uint32(), `${prefix}i64Map{}.`, DecodeContext.child(decodeContext, 'i64Map', { limits: { value: opts.limits?.i64Map$value - } - }) + }, + totalLimits: opts.totalLimits?.i64Map$value == null ? undefined : { value: opts.totalLimits.i64Map$value } + })) obj.i64Map++ @@ -871,7 +887,7 @@ export namespace CustomOptionString { message: 'CustomOptionString' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/daemon.ts b/packages/protons/test/fixtures/daemon.ts index 421f75d..bec440f 100644 --- a/packages/protons/test/fixtures/daemon.ts +++ b/packages/protons/test/fixtures/daemon.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, enumeration, MaxLengthError, message, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, enumeration, MaxLengthError, message, streamMessage } from 'protons-runtime' import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -118,6 +118,7 @@ export namespace Request { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { type: Type.IDENTIFY } @@ -133,51 +134,59 @@ export namespace Request { break } case 2: { - obj.connect = ConnectRequest.codec().decode(r, r.uint32(), { - limits: opts.limits?.connect - }) + obj.connect = ConnectRequest.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'connect', { + limits: opts.limits?.connect, + totalLimits: opts.totalLimits?.connect + })) break } case 3: { - obj.streamOpen = StreamOpenRequest.codec().decode(r, r.uint32(), { - limits: opts.limits?.streamOpen - }) + obj.streamOpen = StreamOpenRequest.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'streamOpen', { + limits: opts.limits?.streamOpen, + totalLimits: opts.totalLimits?.streamOpen + })) break } case 4: { - obj.streamHandler = StreamHandlerRequest.codec().decode(r, r.uint32(), { - limits: opts.limits?.streamHandler - }) + obj.streamHandler = StreamHandlerRequest.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'streamHandler', { + limits: opts.limits?.streamHandler, + totalLimits: opts.totalLimits?.streamHandler + })) break } case 5: { - obj.dht = DHTRequest.codec().decode(r, r.uint32(), { - limits: opts.limits?.dht - }) + obj.dht = DHTRequest.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'dht', { + limits: opts.limits?.dht, + totalLimits: opts.totalLimits?.dht + })) break } case 6: { - obj.connManager = ConnManagerRequest.codec().decode(r, r.uint32(), { - limits: opts.limits?.connManager - }) + obj.connManager = ConnManagerRequest.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'connManager', { + limits: opts.limits?.connManager, + totalLimits: opts.totalLimits?.connManager + })) break } case 7: { - obj.disconnect = DisconnectRequest.codec().decode(r, r.uint32(), { - limits: opts.limits?.disconnect - }) + obj.disconnect = DisconnectRequest.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'disconnect', { + limits: opts.limits?.disconnect, + totalLimits: opts.totalLimits?.disconnect + })) break } case 8: { - obj.pubsub = PSRequest.codec().decode(r, r.uint32(), { - limits: opts.limits?.pubsub - }) + obj.pubsub = PSRequest.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'pubsub', { + limits: opts.limits?.pubsub, + totalLimits: opts.totalLimits?.pubsub + })) break } case 9: { - obj.peerStore = PeerstoreRequest.codec().decode(r, r.uint32(), { - limits: opts.limits?.peerStore - }) + obj.peerStore = PeerstoreRequest.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'peerStore', { + limits: opts.limits?.peerStore, + totalLimits: opts.totalLimits?.peerStore + })) break } default: { @@ -189,6 +198,7 @@ export namespace Request { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const end = length == null ? r.len : r.pos + length if (prefix !== '.') { @@ -211,58 +221,66 @@ export namespace Request { break } case 2: { - yield * ConnectRequest.codec().stream(r, r.uint32(), `${prefix}connect.`, { - limits: opts.limits?.connect - }) + yield * ConnectRequest.codec().stream(r, r.uint32(), `${prefix}connect.`, DecodeContext.child(decodeContext, 'connect', { + limits: opts.limits?.connect, + totalLimits: opts.totalLimits?.connect + })) break } case 3: { - yield * StreamOpenRequest.codec().stream(r, r.uint32(), `${prefix}streamOpen.`, { - limits: opts.limits?.streamOpen - }) + yield * StreamOpenRequest.codec().stream(r, r.uint32(), `${prefix}streamOpen.`, DecodeContext.child(decodeContext, 'streamOpen', { + limits: opts.limits?.streamOpen, + totalLimits: opts.totalLimits?.streamOpen + })) break } case 4: { - yield * StreamHandlerRequest.codec().stream(r, r.uint32(), `${prefix}streamHandler.`, { - limits: opts.limits?.streamHandler - }) + yield * StreamHandlerRequest.codec().stream(r, r.uint32(), `${prefix}streamHandler.`, DecodeContext.child(decodeContext, 'streamHandler', { + limits: opts.limits?.streamHandler, + totalLimits: opts.totalLimits?.streamHandler + })) break } case 5: { - yield * DHTRequest.codec().stream(r, r.uint32(), `${prefix}dht.`, { - limits: opts.limits?.dht - }) + yield * DHTRequest.codec().stream(r, r.uint32(), `${prefix}dht.`, DecodeContext.child(decodeContext, 'dht', { + limits: opts.limits?.dht, + totalLimits: opts.totalLimits?.dht + })) break } case 6: { - yield * ConnManagerRequest.codec().stream(r, r.uint32(), `${prefix}connManager.`, { - limits: opts.limits?.connManager - }) + yield * ConnManagerRequest.codec().stream(r, r.uint32(), `${prefix}connManager.`, DecodeContext.child(decodeContext, 'connManager', { + limits: opts.limits?.connManager, + totalLimits: opts.totalLimits?.connManager + })) break } case 7: { - yield * DisconnectRequest.codec().stream(r, r.uint32(), `${prefix}disconnect.`, { - limits: opts.limits?.disconnect - }) + yield * DisconnectRequest.codec().stream(r, r.uint32(), `${prefix}disconnect.`, DecodeContext.child(decodeContext, 'disconnect', { + limits: opts.limits?.disconnect, + totalLimits: opts.totalLimits?.disconnect + })) break } case 8: { - yield * PSRequest.codec().stream(r, r.uint32(), `${prefix}pubsub.`, { - limits: opts.limits?.pubsub - }) + yield * PSRequest.codec().stream(r, r.uint32(), `${prefix}pubsub.`, DecodeContext.child(decodeContext, 'pubsub', { + limits: opts.limits?.pubsub, + totalLimits: opts.totalLimits?.pubsub + })) break } case 9: { - yield * PeerstoreRequest.codec().stream(r, r.uint32(), `${prefix}peerStore.`, { - limits: opts.limits?.peerStore - }) + yield * PeerstoreRequest.codec().stream(r, r.uint32(), `${prefix}peerStore.`, DecodeContext.child(decodeContext, 'peerStore', { + limits: opts.limits?.peerStore, + totalLimits: opts.totalLimits?.peerStore + })) break } @@ -280,7 +298,7 @@ export namespace Request { message: 'Request' } } - }) + }, { totalLimits: true }) } return _codec @@ -612,6 +630,7 @@ export namespace Response { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { type: Type.OK, peers: [] @@ -628,49 +647,57 @@ export namespace Response { break } case 2: { - obj.error = ErrorResponse.codec().decode(r, r.uint32(), { - limits: opts.limits?.error - }) + obj.error = ErrorResponse.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'error', { + limits: opts.limits?.error, + totalLimits: opts.totalLimits?.error + })) break } case 3: { - obj.streamInfo = StreamInfo.codec().decode(r, r.uint32(), { - limits: opts.limits?.streamInfo - }) + obj.streamInfo = StreamInfo.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'streamInfo', { + limits: opts.limits?.streamInfo, + totalLimits: opts.totalLimits?.streamInfo + })) break } case 4: { - obj.identify = IdentifyResponse.codec().decode(r, r.uint32(), { - limits: opts.limits?.identify - }) + obj.identify = IdentifyResponse.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'identify', { + limits: opts.limits?.identify, + totalLimits: opts.totalLimits?.identify + })) break } case 5: { - obj.dht = DHTResponse.codec().decode(r, r.uint32(), { - limits: opts.limits?.dht - }) + obj.dht = DHTResponse.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'dht', { + limits: opts.limits?.dht, + totalLimits: opts.totalLimits?.dht + })) break } case 6: { + decodeContext?.consume('peers', opts.totalLimits?.peers) if (opts.limits?.peers != null && obj.peers.length === opts.limits.peers) { throw new MaxLengthError('Decode error - repeated field "peers" had too many elements') } - obj.peers.push(PeerInfo.codec().decode(r, r.uint32(), { - limits: opts.limits?.peers$ - })) + obj.peers.push(PeerInfo.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'peers', { + limits: opts.limits?.peers$, + totalLimits: opts.totalLimits?.peers$ + }))) break } case 7: { - obj.pubsub = PSResponse.codec().decode(r, r.uint32(), { - limits: opts.limits?.pubsub - }) + obj.pubsub = PSResponse.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'pubsub', { + limits: opts.limits?.pubsub, + totalLimits: opts.totalLimits?.pubsub + })) break } case 8: { - obj.peerStore = PeerstoreResponse.codec().decode(r, r.uint32(), { - limits: opts.limits?.peerStore - }) + obj.peerStore = PeerstoreResponse.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'peerStore', { + limits: opts.limits?.peerStore, + totalLimits: opts.totalLimits?.peerStore + })) break } default: { @@ -682,6 +709,7 @@ export namespace Response { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { peers: 0 } @@ -708,41 +736,47 @@ export namespace Response { break } case 2: { - yield * ErrorResponse.codec().stream(r, r.uint32(), `${prefix}error.`, { - limits: opts.limits?.error - }) + yield * ErrorResponse.codec().stream(r, r.uint32(), `${prefix}error.`, DecodeContext.child(decodeContext, 'error', { + limits: opts.limits?.error, + totalLimits: opts.totalLimits?.error + })) break } case 3: { - yield * StreamInfo.codec().stream(r, r.uint32(), `${prefix}streamInfo.`, { - limits: opts.limits?.streamInfo - }) + yield * StreamInfo.codec().stream(r, r.uint32(), `${prefix}streamInfo.`, DecodeContext.child(decodeContext, 'streamInfo', { + limits: opts.limits?.streamInfo, + totalLimits: opts.totalLimits?.streamInfo + })) break } case 4: { - yield * IdentifyResponse.codec().stream(r, r.uint32(), `${prefix}identify.`, { - limits: opts.limits?.identify - }) + yield * IdentifyResponse.codec().stream(r, r.uint32(), `${prefix}identify.`, DecodeContext.child(decodeContext, 'identify', { + limits: opts.limits?.identify, + totalLimits: opts.totalLimits?.identify + })) break } case 5: { - yield * DHTResponse.codec().stream(r, r.uint32(), `${prefix}dht.`, { - limits: opts.limits?.dht - }) + yield * DHTResponse.codec().stream(r, r.uint32(), `${prefix}dht.`, DecodeContext.child(decodeContext, 'dht', { + limits: opts.limits?.dht, + totalLimits: opts.totalLimits?.dht + })) break } case 6: { + decodeContext?.consume('peers', opts.totalLimits?.peers) if (opts.limits?.peers != null && obj.peers === opts.limits.peers) { throw new MaxLengthError('Streaming decode error - repeated field "peers" had too many elements') } - for (const evt of PeerInfo.codec().stream(r, r.uint32(), `${prefix}peers[].`, { - limits: opts.limits?.peers$ - })) { + for (const evt of PeerInfo.codec().stream(r, r.uint32(), `${prefix}peers[].`, DecodeContext.child(decodeContext, 'peers', { + limits: opts.limits?.peers$, + totalLimits: opts.totalLimits?.peers$ + }))) { yield { ...evt, index: obj.peers @@ -754,16 +788,18 @@ export namespace Response { break } case 7: { - yield * PSResponse.codec().stream(r, r.uint32(), `${prefix}pubsub.`, { - limits: opts.limits?.pubsub - }) + yield * PSResponse.codec().stream(r, r.uint32(), `${prefix}pubsub.`, DecodeContext.child(decodeContext, 'pubsub', { + limits: opts.limits?.pubsub, + totalLimits: opts.totalLimits?.pubsub + })) break } case 8: { - yield * PeerstoreResponse.codec().stream(r, r.uint32(), `${prefix}peerStore.`, { - limits: opts.limits?.peerStore - }) + yield * PeerstoreResponse.codec().stream(r, r.uint32(), `${prefix}peerStore.`, DecodeContext.child(decodeContext, 'peerStore', { + limits: opts.limits?.peerStore, + totalLimits: opts.totalLimits?.peerStore + })) break } @@ -781,7 +817,7 @@ export namespace Response { message: 'Response' } } - }) + }, { totalLimits: true }) } return _codec @@ -1028,6 +1064,7 @@ export namespace IdentifyResponse { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { id: uint8ArrayAlloc(0), addrs: [] @@ -1044,6 +1081,7 @@ export namespace IdentifyResponse { break } case 2: { + decodeContext?.consume('addrs', opts.totalLimits?.addrs) if (opts.limits?.addrs != null && obj.addrs.length === opts.limits.addrs) { throw new MaxLengthError('Decode error - repeated field "addrs" had too many elements') } @@ -1060,6 +1098,7 @@ export namespace IdentifyResponse { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { addrs: 0 } @@ -1086,6 +1125,7 @@ export namespace IdentifyResponse { break } case 2: { + decodeContext?.consume('addrs', opts.totalLimits?.addrs) if (opts.limits?.addrs != null && obj.addrs === opts.limits.addrs) { throw new MaxLengthError('Streaming decode error - repeated field "addrs" had too many elements') } @@ -1114,7 +1154,7 @@ export namespace IdentifyResponse { message: 'IdentifyResponse' } } - }) + }, { totalLimits: true }) } return _codec @@ -1187,6 +1227,7 @@ export namespace ConnectRequest { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { peer: uint8ArrayAlloc(0), addrs: [] @@ -1203,6 +1244,7 @@ export namespace ConnectRequest { break } case 2: { + decodeContext?.consume('addrs', opts.totalLimits?.addrs) if (opts.limits?.addrs != null && obj.addrs.length === opts.limits.addrs) { throw new MaxLengthError('Decode error - repeated field "addrs" had too many elements') } @@ -1223,6 +1265,7 @@ export namespace ConnectRequest { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { addrs: 0 } @@ -1249,6 +1292,7 @@ export namespace ConnectRequest { break } case 2: { + decodeContext?.consume('addrs', opts.totalLimits?.addrs) if (opts.limits?.addrs != null && obj.addrs === opts.limits.addrs) { throw new MaxLengthError('Streaming decode error - repeated field "addrs" had too many elements') } @@ -1284,7 +1328,7 @@ export namespace ConnectRequest { message: 'ConnectRequest' } } - }) + }, { totalLimits: true }) } return _codec @@ -1362,6 +1406,7 @@ export namespace StreamOpenRequest { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { peer: uint8ArrayAlloc(0), proto: [] @@ -1378,6 +1423,7 @@ export namespace StreamOpenRequest { break } case 2: { + decodeContext?.consume('proto', opts.totalLimits?.proto) if (opts.limits?.proto != null && obj.proto.length === opts.limits.proto) { throw new MaxLengthError('Decode error - repeated field "proto" had too many elements') } @@ -1398,6 +1444,7 @@ export namespace StreamOpenRequest { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { proto: 0 } @@ -1424,6 +1471,7 @@ export namespace StreamOpenRequest { break } case 2: { + decodeContext?.consume('proto', opts.totalLimits?.proto) if (opts.limits?.proto != null && obj.proto === opts.limits.proto) { throw new MaxLengthError('Streaming decode error - repeated field "proto" had too many elements') } @@ -1459,7 +1507,7 @@ export namespace StreamOpenRequest { message: 'StreamOpenRequest' } } - }) + }, { totalLimits: true }) } return _codec @@ -1530,6 +1578,7 @@ export namespace StreamHandlerRequest { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { addr: uint8ArrayAlloc(0), proto: [] @@ -1546,6 +1595,7 @@ export namespace StreamHandlerRequest { break } case 2: { + decodeContext?.consume('proto', opts.totalLimits?.proto) if (opts.limits?.proto != null && obj.proto.length === opts.limits.proto) { throw new MaxLengthError('Decode error - repeated field "proto" had too many elements') } @@ -1562,6 +1612,7 @@ export namespace StreamHandlerRequest { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { proto: 0 } @@ -1588,6 +1639,7 @@ export namespace StreamHandlerRequest { break } case 2: { + decodeContext?.consume('proto', opts.totalLimits?.proto) if (opts.limits?.proto != null && obj.proto === opts.limits.proto) { throw new MaxLengthError('Streaming decode error - repeated field "proto" had too many elements') } @@ -1616,7 +1668,7 @@ export namespace StreamHandlerRequest { message: 'StreamHandlerRequest' } } - }) + }, { totalLimits: true }) } return _codec @@ -1731,7 +1783,7 @@ export namespace ErrorResponse { message: 'ErrorResponse' } } - }) + }, { totalLimits: true }) } return _codec @@ -1878,7 +1930,7 @@ export namespace StreamInfo { message: 'StreamInfo' } } - }) + }, { totalLimits: true }) } return _codec @@ -2135,7 +2187,7 @@ export namespace DHTRequest { message: 'DHTRequest' } } - }) + }, { totalLimits: true }) } return _codec @@ -2248,6 +2300,7 @@ export namespace DHTResponse { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { type: Type.BEGIN } @@ -2263,9 +2316,10 @@ export namespace DHTResponse { break } case 2: { - obj.peer = PeerInfo.codec().decode(r, r.uint32(), { - limits: opts.limits?.peer - }) + obj.peer = PeerInfo.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'peer', { + limits: opts.limits?.peer, + totalLimits: opts.totalLimits?.peer + })) break } case 3: { @@ -2281,6 +2335,7 @@ export namespace DHTResponse { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const end = length == null ? r.len : r.pos + length if (prefix !== '.') { @@ -2303,9 +2358,10 @@ export namespace DHTResponse { break } case 2: { - yield * PeerInfo.codec().stream(r, r.uint32(), `${prefix}peer.`, { - limits: opts.limits?.peer - }) + yield * PeerInfo.codec().stream(r, r.uint32(), `${prefix}peer.`, DecodeContext.child(decodeContext, 'peer', { + limits: opts.limits?.peer, + totalLimits: opts.totalLimits?.peer + })) break } @@ -2330,7 +2386,7 @@ export namespace DHTResponse { message: 'DHTResponse' } } - }) + }, { totalLimits: true }) } return _codec @@ -2416,6 +2472,7 @@ export namespace PeerInfo { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { id: uint8ArrayAlloc(0), addrs: [] @@ -2432,6 +2489,7 @@ export namespace PeerInfo { break } case 2: { + decodeContext?.consume('addrs', opts.totalLimits?.addrs) if (opts.limits?.addrs != null && obj.addrs.length === opts.limits.addrs) { throw new MaxLengthError('Decode error - repeated field "addrs" had too many elements') } @@ -2448,6 +2506,7 @@ export namespace PeerInfo { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { addrs: 0 } @@ -2474,6 +2533,7 @@ export namespace PeerInfo { break } case 2: { + decodeContext?.consume('addrs', opts.totalLimits?.addrs) if (opts.limits?.addrs != null && obj.addrs === opts.limits.addrs) { throw new MaxLengthError('Streaming decode error - repeated field "addrs" had too many elements') } @@ -2502,7 +2562,7 @@ export namespace PeerInfo { message: 'PeerInfo' } } - }) + }, { totalLimits: true }) } return _codec @@ -2689,7 +2749,7 @@ export namespace ConnManagerRequest { message: 'ConnManagerRequest' } } - }) + }, { totalLimits: true }) } return _codec @@ -2813,7 +2873,7 @@ export namespace DisconnectRequest { message: 'DisconnectRequest' } } - }) + }, { totalLimits: true }) } return _codec @@ -2978,7 +3038,7 @@ export namespace PSRequest { message: 'PSRequest' } } - }) + }, { totalLimits: true }) } return _codec @@ -3076,6 +3136,7 @@ export namespace PSMessage { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { topicIDs: [] } @@ -3099,6 +3160,7 @@ export namespace PSMessage { break } case 4: { + decodeContext?.consume('topicIDs', opts.totalLimits?.topicIDs) if (opts.limits?.topicIDs != null && obj.topicIDs.length === opts.limits.topicIDs) { throw new MaxLengthError('Decode error - repeated field "topicIDs" had too many elements') } @@ -3123,6 +3185,7 @@ export namespace PSMessage { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { topicIDs: 0 } @@ -3163,6 +3226,7 @@ export namespace PSMessage { break } case 4: { + decodeContext?.consume('topicIDs', opts.totalLimits?.topicIDs) if (opts.limits?.topicIDs != null && obj.topicIDs === opts.limits.topicIDs) { throw new MaxLengthError('Streaming decode error - repeated field "topicIDs" had too many elements') } @@ -3205,7 +3269,7 @@ export namespace PSMessage { message: 'PSMessage' } } - }) + }, { totalLimits: true }) } return _codec @@ -3293,6 +3357,7 @@ export namespace PSResponse { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { topics: [], peerIDs: [] @@ -3305,6 +3370,7 @@ export namespace PSResponse { switch (tag >>> 3) { case 1: { + decodeContext?.consume('topics', opts.totalLimits?.topics) if (opts.limits?.topics != null && obj.topics.length === opts.limits.topics) { throw new MaxLengthError('Decode error - repeated field "topics" had too many elements') } @@ -3313,6 +3379,7 @@ export namespace PSResponse { break } case 2: { + decodeContext?.consume('peerIDs', opts.totalLimits?.peerIDs) if (opts.limits?.peerIDs != null && obj.peerIDs.length === opts.limits.peerIDs) { throw new MaxLengthError('Decode error - repeated field "peerIDs" had too many elements') } @@ -3329,6 +3396,7 @@ export namespace PSResponse { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { topics: 0, peerIDs: 0 @@ -3349,6 +3417,7 @@ export namespace PSResponse { switch (tag >>> 3) { case 1: { + decodeContext?.consume('topics', opts.totalLimits?.topics) if (opts.limits?.topics != null && obj.topics === opts.limits.topics) { throw new MaxLengthError('Streaming decode error - repeated field "topics" had too many elements') } @@ -3364,6 +3433,7 @@ export namespace PSResponse { break } case 2: { + decodeContext?.consume('peerIDs', opts.totalLimits?.peerIDs) if (opts.limits?.peerIDs != null && obj.peerIDs === opts.limits.peerIDs) { throw new MaxLengthError('Streaming decode error - repeated field "peerIDs" had too many elements') } @@ -3392,7 +3462,7 @@ export namespace PSResponse { message: 'PSResponse' } } - }) + }, { totalLimits: true }) } return _codec @@ -3484,6 +3554,7 @@ export namespace PeerstoreRequest { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { type: Type.INVALID, protos: [] @@ -3504,6 +3575,7 @@ export namespace PeerstoreRequest { break } case 3: { + decodeContext?.consume('protos', opts.totalLimits?.protos) if (opts.limits?.protos != null && obj.protos.length === opts.limits.protos) { throw new MaxLengthError('Decode error - repeated field "protos" had too many elements') } @@ -3520,6 +3592,7 @@ export namespace PeerstoreRequest { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { protos: 0 } @@ -3553,6 +3626,7 @@ export namespace PeerstoreRequest { break } case 3: { + decodeContext?.consume('protos', opts.totalLimits?.protos) if (opts.limits?.protos != null && obj.protos === opts.limits.protos) { throw new MaxLengthError('Streaming decode error - repeated field "protos" had too many elements') } @@ -3581,7 +3655,7 @@ export namespace PeerstoreRequest { message: 'PeerstoreRequest' } } - }) + }, { totalLimits: true }) } return _codec @@ -3652,6 +3726,7 @@ export namespace PeerstoreResponse { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { protos: [] } @@ -3663,12 +3738,14 @@ export namespace PeerstoreResponse { switch (tag >>> 3) { case 1: { - obj.peer = PeerInfo.codec().decode(r, r.uint32(), { - limits: opts.limits?.peer - }) + obj.peer = PeerInfo.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'peer', { + limits: opts.limits?.peer, + totalLimits: opts.totalLimits?.peer + })) break } case 2: { + decodeContext?.consume('protos', opts.totalLimits?.protos) if (opts.limits?.protos != null && obj.protos.length === opts.limits.protos) { throw new MaxLengthError('Decode error - repeated field "protos" had too many elements') } @@ -3685,6 +3762,7 @@ export namespace PeerstoreResponse { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { protos: 0 } @@ -3704,13 +3782,15 @@ export namespace PeerstoreResponse { switch (tag >>> 3) { case 1: { - yield * PeerInfo.codec().stream(r, r.uint32(), `${prefix}peer.`, { - limits: opts.limits?.peer - }) + yield * PeerInfo.codec().stream(r, r.uint32(), `${prefix}peer.`, DecodeContext.child(decodeContext, 'peer', { + limits: opts.limits?.peer, + totalLimits: opts.totalLimits?.peer + })) break } case 2: { + decodeContext?.consume('protos', opts.totalLimits?.protos) if (opts.limits?.protos != null && obj.protos === opts.limits.protos) { throw new MaxLengthError('Streaming decode error - repeated field "protos" had too many elements') } @@ -3739,7 +3819,7 @@ export namespace PeerstoreResponse { message: 'PeerstoreResponse' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/dht.ts b/packages/protons/test/fixtures/dht.ts index f90b65d..c3d4aa2 100644 --- a/packages/protons/test/fixtures/dht.ts +++ b/packages/protons/test/fixtures/dht.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, enumeration, MaxLengthError, message, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, enumeration, MaxLengthError, message, streamMessage } from 'protons-runtime' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -157,7 +157,7 @@ export namespace Record { message: 'Record' } } - }) + }, { totalLimits: true }) } return _codec @@ -307,6 +307,7 @@ export namespace Message { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { addrs: [] } @@ -322,6 +323,7 @@ export namespace Message { break } case 2: { + decodeContext?.consume('addrs', opts.totalLimits?.addrs) if (opts.limits?.addrs != null && obj.addrs.length === opts.limits.addrs) { throw new MaxLengthError('Decode error - repeated field "addrs" had too many elements') } @@ -342,6 +344,7 @@ export namespace Message { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { addrs: 0 } @@ -368,6 +371,7 @@ export namespace Message { break } case 2: { + decodeContext?.consume('addrs', opts.totalLimits?.addrs) if (opts.limits?.addrs != null && obj.addrs === opts.limits.addrs) { throw new MaxLengthError('Streaming decode error - repeated field "addrs" had too many elements') } @@ -403,7 +407,7 @@ export namespace Message { message: 'Message.Peer' } } - }) + }, { totalLimits: true }) } return _codec @@ -485,6 +489,7 @@ export namespace Message { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { closerPeers: [], providerPeers: [] @@ -513,23 +518,27 @@ export namespace Message { break } case 8: { + decodeContext?.consume('closerPeers', opts.totalLimits?.closerPeers) if (opts.limits?.closerPeers != null && obj.closerPeers.length === opts.limits.closerPeers) { throw new MaxLengthError('Decode error - repeated field "closerPeers" had too many elements') } - obj.closerPeers.push(Message.Peer.codec().decode(r, r.uint32(), { - limits: opts.limits?.closerPeers$ - })) + obj.closerPeers.push(Message.Peer.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'closerPeers', { + limits: opts.limits?.closerPeers$, + totalLimits: opts.totalLimits?.closerPeers$ + }))) break } case 9: { + decodeContext?.consume('providerPeers', opts.totalLimits?.providerPeers) if (opts.limits?.providerPeers != null && obj.providerPeers.length === opts.limits.providerPeers) { throw new MaxLengthError('Decode error - repeated field "providerPeers" had too many elements') } - obj.providerPeers.push(Message.Peer.codec().decode(r, r.uint32(), { - limits: opts.limits?.providerPeers$ - })) + obj.providerPeers.push(Message.Peer.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'providerPeers', { + limits: opts.limits?.providerPeers$, + totalLimits: opts.totalLimits?.providerPeers$ + }))) break } default: { @@ -541,6 +550,7 @@ export namespace Message { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { closerPeers: 0, providerPeers: 0 @@ -589,13 +599,15 @@ export namespace Message { break } case 8: { + decodeContext?.consume('closerPeers', opts.totalLimits?.closerPeers) if (opts.limits?.closerPeers != null && obj.closerPeers === opts.limits.closerPeers) { throw new MaxLengthError('Streaming decode error - repeated field "closerPeers" had too many elements') } - for (const evt of Message.Peer.codec().stream(r, r.uint32(), `${prefix}closerPeers[].`, { - limits: opts.limits?.closerPeers$ - })) { + for (const evt of Message.Peer.codec().stream(r, r.uint32(), `${prefix}closerPeers[].`, DecodeContext.child(decodeContext, 'closerPeers', { + limits: opts.limits?.closerPeers$, + totalLimits: opts.totalLimits?.closerPeers$ + }))) { yield { ...evt, index: obj.closerPeers @@ -607,13 +619,15 @@ export namespace Message { break } case 9: { + decodeContext?.consume('providerPeers', opts.totalLimits?.providerPeers) if (opts.limits?.providerPeers != null && obj.providerPeers === opts.limits.providerPeers) { throw new MaxLengthError('Streaming decode error - repeated field "providerPeers" had too many elements') } - for (const evt of Message.Peer.codec().stream(r, r.uint32(), `${prefix}providerPeers[].`, { - limits: opts.limits?.providerPeers$ - })) { + for (const evt of Message.Peer.codec().stream(r, r.uint32(), `${prefix}providerPeers[].`, DecodeContext.child(decodeContext, 'providerPeers', { + limits: opts.limits?.providerPeers$, + totalLimits: opts.totalLimits?.providerPeers$ + }))) { yield { ...evt, index: obj.providerPeers @@ -638,7 +652,7 @@ export namespace Message { message: 'Message' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/maps.ts b/packages/protons/test/fixtures/maps.ts index ea4215b..01e788a 100644 --- a/packages/protons/test/fixtures/maps.ts +++ b/packages/protons/test/fixtures/maps.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, enumeration, MaxLengthError, MaxSizeError, message, reader, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, enumeration, MaxLengthError, MaxSizeError, message, reader, streamMessage } from 'protons-runtime' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -60,6 +60,7 @@ export namespace SubMessage { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { foo: '', bar: [] @@ -80,6 +81,7 @@ export namespace SubMessage { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('bar', opts.totalLimits?.bar) if (opts.limits?.bar != null && obj.bar.length === opts.limits.bar) { throw new MaxLengthError('Decode error - repeated field "bar" had too many elements') } @@ -98,6 +100,7 @@ export namespace SubMessage { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { bar: 0 } @@ -128,6 +131,7 @@ export namespace SubMessage { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('bar', opts.totalLimits?.bar) if (opts.limits?.bar != null && obj.bar === opts.limits.bar) { throw new MaxLengthError('Streaming decode error - repeated field "bar" had too many elements') } @@ -157,7 +161,7 @@ export namespace SubMessage { message: 'SubMessage' } } - }) + }, { totalLimits: true }) } return _codec @@ -308,7 +312,7 @@ export namespace MapTypes { message: 'MapTypes.MapTypes$stringMapEntry' } } - }) + }, { totalLimits: true }) } return _codec @@ -441,7 +445,7 @@ export namespace MapTypes { message: 'MapTypes.MapTypes$intMapEntry' } } - }) + }, { totalLimits: true }) } return _codec @@ -574,7 +578,7 @@ export namespace MapTypes { message: 'MapTypes.MapTypes$boolMapEntry' } } - }) + }, { totalLimits: true }) } return _codec @@ -637,6 +641,7 @@ export namespace MapTypes { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { key: '' } @@ -652,9 +657,10 @@ export namespace MapTypes { break } case 2: { - obj.value = SubMessage.codec().decode(r, r.uint32(), { - limits: opts.limits?.value - }) + obj.value = SubMessage.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'value', { + limits: opts.limits?.value, + totalLimits: opts.totalLimits?.value + })) break } default: { @@ -666,6 +672,7 @@ export namespace MapTypes { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const end = length == null ? r.len : r.pos + length if (prefix !== '.') { @@ -688,9 +695,10 @@ export namespace MapTypes { break } case 2: { - yield * SubMessage.codec().stream(r, r.uint32(), `${prefix}value.`, { - limits: opts.limits?.value - }) + yield * SubMessage.codec().stream(r, r.uint32(), `${prefix}value.`, DecodeContext.child(decodeContext, 'value', { + limits: opts.limits?.value, + totalLimits: opts.totalLimits?.value + })) break } @@ -708,7 +716,7 @@ export namespace MapTypes { message: 'MapTypes.MapTypes$messageMapEntry' } } - }) + }, { totalLimits: true }) } return _codec @@ -857,7 +865,7 @@ export namespace MapTypes { message: 'MapTypes.MapTypes$enumMapEntry' } } - }) + }, { totalLimits: true }) } return _codec @@ -934,6 +942,7 @@ export namespace MapTypes { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { stringMap: new Map(), intMap: new Map(), @@ -949,67 +958,77 @@ export namespace MapTypes { switch (tag >>> 3) { case 1: { + decodeContext?.consume('stringMap', opts.totalLimits?.stringMap, MaxSizeError) if (opts.limits?.stringMap != null && obj.stringMap.size === opts.limits.stringMap) { throw new MaxSizeError('Decode error - map field "stringMap" had too many elements') } - const entry = MapTypes.MapTypes$stringMapEntry.codec().decode(r, r.uint32(), { + const entry = MapTypes.MapTypes$stringMapEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'stringMap', { limits: { value: opts.limits?.stringMap$value - } - }) + }, + totalLimits: opts.totalLimits?.stringMap$value == null ? undefined : { value: opts.totalLimits.stringMap$value } + })) obj.stringMap.set(entry.key, entry.value) break } case 2: { + decodeContext?.consume('intMap', opts.totalLimits?.intMap, MaxSizeError) if (opts.limits?.intMap != null && obj.intMap.size === opts.limits.intMap) { throw new MaxSizeError('Decode error - map field "intMap" had too many elements') } - const entry = MapTypes.MapTypes$intMapEntry.codec().decode(r, r.uint32(), { + const entry = MapTypes.MapTypes$intMapEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'intMap', { limits: { value: opts.limits?.intMap$value - } - }) + }, + totalLimits: opts.totalLimits?.intMap$value == null ? undefined : { value: opts.totalLimits.intMap$value } + })) obj.intMap.set(entry.key, entry.value) break } case 3: { + decodeContext?.consume('boolMap', opts.totalLimits?.boolMap, MaxSizeError) if (opts.limits?.boolMap != null && obj.boolMap.size === opts.limits.boolMap) { throw new MaxSizeError('Decode error - map field "boolMap" had too many elements') } - const entry = MapTypes.MapTypes$boolMapEntry.codec().decode(r, r.uint32(), { + const entry = MapTypes.MapTypes$boolMapEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'boolMap', { limits: { value: opts.limits?.boolMap$value - } - }) + }, + totalLimits: opts.totalLimits?.boolMap$value == null ? undefined : { value: opts.totalLimits.boolMap$value } + })) obj.boolMap.set(entry.key, entry.value) break } case 4: { + decodeContext?.consume('messageMap', opts.totalLimits?.messageMap, MaxSizeError) if (opts.limits?.messageMap != null && obj.messageMap.size === opts.limits.messageMap) { throw new MaxSizeError('Decode error - map field "messageMap" had too many elements') } - const entry = MapTypes.MapTypes$messageMapEntry.codec().decode(r, r.uint32(), { + const entry = MapTypes.MapTypes$messageMapEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'messageMap', { limits: { value: opts.limits?.messageMap$value - } - }) + }, + totalLimits: opts.totalLimits?.messageMap$value == null ? undefined : { value: opts.totalLimits.messageMap$value } + })) obj.messageMap.set(entry.key, entry.value) break } case 5: { + decodeContext?.consume('enumMap', opts.totalLimits?.enumMap, MaxSizeError) if (opts.limits?.enumMap != null && obj.enumMap.size === opts.limits.enumMap) { throw new MaxSizeError('Decode error - map field "enumMap" had too many elements') } - const entry = MapTypes.MapTypes$enumMapEntry.codec().decode(r, r.uint32(), { + const entry = MapTypes.MapTypes$enumMapEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'enumMap', { limits: { value: opts.limits?.enumMap$value - } - }) + }, + totalLimits: opts.totalLimits?.enumMap$value == null ? undefined : { value: opts.totalLimits.enumMap$value } + })) obj.enumMap.set(entry.key, entry.value) break } @@ -1022,6 +1041,7 @@ export namespace MapTypes { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { stringMap: 0, intMap: 0, @@ -1045,75 +1065,85 @@ export namespace MapTypes { switch (tag >>> 3) { case 1: { + decodeContext?.consume('stringMap', opts.totalLimits?.stringMap, MaxSizeError) if (opts.limits?.stringMap != null && obj.stringMap === opts.limits.stringMap) { throw new MaxLengthError('Decode error - map field "stringMap" had too many elements') } - yield * MapTypes.MapTypes$stringMapEntry.codec().stream(r, r.uint32(), `${prefix}stringMap{}.`, { + yield * MapTypes.MapTypes$stringMapEntry.codec().stream(r, r.uint32(), `${prefix}stringMap{}.`, DecodeContext.child(decodeContext, 'stringMap', { limits: { value: opts.limits?.stringMap$value - } - }) + }, + totalLimits: opts.totalLimits?.stringMap$value == null ? undefined : { value: opts.totalLimits.stringMap$value } + })) obj.stringMap++ break } case 2: { + decodeContext?.consume('intMap', opts.totalLimits?.intMap, MaxSizeError) if (opts.limits?.intMap != null && obj.intMap === opts.limits.intMap) { throw new MaxLengthError('Decode error - map field "intMap" had too many elements') } - yield * MapTypes.MapTypes$intMapEntry.codec().stream(r, r.uint32(), `${prefix}intMap{}.`, { + yield * MapTypes.MapTypes$intMapEntry.codec().stream(r, r.uint32(), `${prefix}intMap{}.`, DecodeContext.child(decodeContext, 'intMap', { limits: { value: opts.limits?.intMap$value - } - }) + }, + totalLimits: opts.totalLimits?.intMap$value == null ? undefined : { value: opts.totalLimits.intMap$value } + })) obj.intMap++ break } case 3: { + decodeContext?.consume('boolMap', opts.totalLimits?.boolMap, MaxSizeError) if (opts.limits?.boolMap != null && obj.boolMap === opts.limits.boolMap) { throw new MaxLengthError('Decode error - map field "boolMap" had too many elements') } - yield * MapTypes.MapTypes$boolMapEntry.codec().stream(r, r.uint32(), `${prefix}boolMap{}.`, { + yield * MapTypes.MapTypes$boolMapEntry.codec().stream(r, r.uint32(), `${prefix}boolMap{}.`, DecodeContext.child(decodeContext, 'boolMap', { limits: { value: opts.limits?.boolMap$value - } - }) + }, + totalLimits: opts.totalLimits?.boolMap$value == null ? undefined : { value: opts.totalLimits.boolMap$value } + })) obj.boolMap++ break } case 4: { + decodeContext?.consume('messageMap', opts.totalLimits?.messageMap, MaxSizeError) if (opts.limits?.messageMap != null && obj.messageMap === opts.limits.messageMap) { throw new MaxLengthError('Decode error - map field "messageMap" had too many elements') } - yield * MapTypes.MapTypes$messageMapEntry.codec().stream(r, r.uint32(), `${prefix}messageMap{}.`, { + yield * MapTypes.MapTypes$messageMapEntry.codec().stream(r, r.uint32(), `${prefix}messageMap{}.`, DecodeContext.child(decodeContext, 'messageMap', { limits: { value: opts.limits?.messageMap$value - } - }) + }, + totalLimits: opts.totalLimits?.messageMap$value == null ? undefined : { value: opts.totalLimits.messageMap$value } + })) obj.messageMap++ break } case 5: { + decodeContext?.consume('enumMap', opts.totalLimits?.enumMap, MaxSizeError) if (opts.limits?.enumMap != null && obj.enumMap === opts.limits.enumMap) { throw new MaxLengthError('Decode error - map field "enumMap" had too many elements') } - yield * MapTypes.MapTypes$enumMapEntry.codec().stream(r, r.uint32(), `${prefix}enumMap{}.`, { + yield * MapTypes.MapTypes$enumMapEntry.codec().stream(r, r.uint32(), `${prefix}enumMap{}.`, DecodeContext.child(decodeContext, 'enumMap', { limits: { value: opts.limits?.enumMap$value - } - }) + }, + totalLimits: opts.totalLimits?.enumMap$value == null ? undefined : { value: opts.totalLimits.enumMap$value } + })) obj.enumMap++ @@ -1133,7 +1163,7 @@ export namespace MapTypes { message: 'MapTypes' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/noise.ts b/packages/protons/test/fixtures/noise.ts index c7ba031..f36eaa0 100644 --- a/packages/protons/test/fixtures/noise.ts +++ b/packages/protons/test/fixtures/noise.ts @@ -131,7 +131,7 @@ export namespace pb { message: 'pb.NoiseHandshakePayload' } } - }) + }, { totalLimits: true }) } return _codec @@ -223,7 +223,7 @@ export namespace pb { message: 'pb' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/oneof.ts b/packages/protons/test/fixtures/oneof.ts index 6368580..6e55426 100644 --- a/packages/protons/test/fixtures/oneof.ts +++ b/packages/protons/test/fixtures/oneof.ts @@ -209,7 +209,7 @@ export namespace OneOfMessage { message: 'OneOfMessage' } } - }) + }, { totalLimits: true }) } return _codec @@ -414,7 +414,7 @@ export namespace MessageWithoutOneOfs { message: 'MessageWithoutOneOfs' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/optional.ts b/packages/protons/test/fixtures/optional.ts index 555354c..479e6b6 100644 --- a/packages/protons/test/fixtures/optional.ts +++ b/packages/protons/test/fixtures/optional.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, enumeration, message, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, enumeration, message, streamMessage } from 'protons-runtime' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -121,7 +121,7 @@ export namespace OptionalSubMessage { message: 'OptionalSubMessage' } } - }) + }, { totalLimits: true }) } return _codec @@ -289,6 +289,7 @@ export namespace Optional { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = {} const end = length == null ? r.len : r.pos + length @@ -362,9 +363,10 @@ export namespace Optional { break } case 17: { - obj.subMessage = OptionalSubMessage.codec().decode(r, r.uint32(), { - limits: opts.limits?.subMessage - }) + obj.subMessage = OptionalSubMessage.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'subMessage', { + limits: opts.limits?.subMessage, + totalLimits: opts.totalLimits?.subMessage + })) break } default: { @@ -376,6 +378,7 @@ export namespace Optional { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const end = length == null ? r.len : r.pos + length if (prefix !== '.') { @@ -503,9 +506,10 @@ export namespace Optional { break } case 17: { - yield * OptionalSubMessage.codec().stream(r, r.uint32(), `${prefix}subMessage.`, { - limits: opts.limits?.subMessage - }) + yield * OptionalSubMessage.codec().stream(r, r.uint32(), `${prefix}subMessage.`, DecodeContext.child(decodeContext, 'subMessage', { + limits: opts.limits?.subMessage, + totalLimits: opts.totalLimits?.subMessage + })) break } @@ -523,7 +527,7 @@ export namespace Optional { message: 'Optional' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/packed-global.ts b/packages/protons/test/fixtures/packed-global.ts index 8b78a2c..dac666a 100644 --- a/packages/protons/test/fixtures/packed-global.ts +++ b/packages/protons/test/fixtures/packed-global.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, MaxLengthError, message, reader, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, MaxLengthError, message, reader, streamMessage } from 'protons-runtime' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -71,6 +71,7 @@ export namespace GlobalPacked { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { expanded: [], packedOldOption: [], @@ -86,6 +87,7 @@ export namespace GlobalPacked { switch (tag >>> 3) { case 1: { + decodeContext?.consume('expanded', opts.totalLimits?.expanded) if (opts.limits?.expanded != null && obj.expanded.length === opts.limits.expanded) { throw new MaxLengthError('Decode error - repeated field "expanded" had too many elements') } @@ -94,6 +96,7 @@ export namespace GlobalPacked { break } case 2: { + decodeContext?.consume('packedOldOption', opts.totalLimits?.packedOldOption) if (opts.limits?.packedOldOption != null && obj.packedOldOption.length === opts.limits.packedOldOption) { throw new MaxLengthError('Decode error - repeated field "packedOldOption" had too many elements') } @@ -102,6 +105,7 @@ export namespace GlobalPacked { break } case 3: { + decodeContext?.consume('expandedOldOption', opts.totalLimits?.expandedOldOption) if (opts.limits?.expandedOldOption != null && obj.expandedOldOption.length === opts.limits.expandedOldOption) { throw new MaxLengthError('Decode error - repeated field "expandedOldOption" had too many elements') } @@ -114,6 +118,7 @@ export namespace GlobalPacked { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('packedNewOption', opts.totalLimits?.packedNewOption) if (opts.limits?.packedNewOption != null && obj.packedNewOption.length === opts.limits.packedNewOption) { throw new MaxLengthError('Decode error - repeated field "packedNewOption" had too many elements') } @@ -124,6 +129,7 @@ export namespace GlobalPacked { break } case 5: { + decodeContext?.consume('expandedNewOption', opts.totalLimits?.expandedNewOption) if (opts.limits?.expandedNewOption != null && obj.expandedNewOption.length === opts.limits.expandedNewOption) { throw new MaxLengthError('Decode error - repeated field "expandedNewOption" had too many elements') } @@ -140,6 +146,7 @@ export namespace GlobalPacked { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { expanded: 0, packedOldOption: 0, @@ -163,6 +170,7 @@ export namespace GlobalPacked { switch (tag >>> 3) { case 1: { + decodeContext?.consume('expanded', opts.totalLimits?.expanded) if (opts.limits?.expanded != null && obj.expanded === opts.limits.expanded) { throw new MaxLengthError('Streaming decode error - repeated field "expanded" had too many elements') } @@ -178,6 +186,7 @@ export namespace GlobalPacked { break } case 2: { + decodeContext?.consume('packedOldOption', opts.totalLimits?.packedOldOption) if (opts.limits?.packedOldOption != null && obj.packedOldOption === opts.limits.packedOldOption) { throw new MaxLengthError('Streaming decode error - repeated field "packedOldOption" had too many elements') } @@ -193,6 +202,7 @@ export namespace GlobalPacked { break } case 3: { + decodeContext?.consume('expandedOldOption', opts.totalLimits?.expandedOldOption) if (opts.limits?.expandedOldOption != null && obj.expandedOldOption === opts.limits.expandedOldOption) { throw new MaxLengthError('Streaming decode error - repeated field "expandedOldOption" had too many elements') } @@ -212,6 +222,7 @@ export namespace GlobalPacked { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('packedNewOption', opts.totalLimits?.packedNewOption) if (opts.limits?.packedNewOption != null && obj.packedNewOption === opts.limits.packedNewOption) { throw new MaxLengthError('Streaming decode error - repeated field "packedNewOption" had too many elements') } @@ -228,6 +239,7 @@ export namespace GlobalPacked { break } case 5: { + decodeContext?.consume('expandedNewOption', opts.totalLimits?.expandedNewOption) if (opts.limits?.expandedNewOption != null && obj.expandedNewOption === opts.limits.expandedNewOption) { throw new MaxLengthError('Streaming decode error - repeated field "expandedNewOption" had too many elements') } @@ -256,7 +268,7 @@ export namespace GlobalPacked { message: 'GlobalPacked' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/packed-proto2.ts b/packages/protons/test/fixtures/packed-proto2.ts index b31c690..35c2c1d 100644 --- a/packages/protons/test/fixtures/packed-proto2.ts +++ b/packages/protons/test/fixtures/packed-proto2.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, MaxLengthError, message, reader, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, MaxLengthError, message, reader, streamMessage } from 'protons-runtime' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -139,6 +139,7 @@ export namespace Proto2DefaultPackedTypes { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { doubles: [], floats: [], @@ -162,6 +163,7 @@ export namespace Proto2DefaultPackedTypes { switch (tag >>> 3) { case 1: { + decodeContext?.consume('doubles', opts.totalLimits?.doubles) if (opts.limits?.doubles != null && obj.doubles.length === opts.limits.doubles) { throw new MaxLengthError('Decode error - repeated field "doubles" had too many elements') } @@ -170,6 +172,7 @@ export namespace Proto2DefaultPackedTypes { break } case 2: { + decodeContext?.consume('floats', opts.totalLimits?.floats) if (opts.limits?.floats != null && obj.floats.length === opts.limits.floats) { throw new MaxLengthError('Decode error - repeated field "floats" had too many elements') } @@ -178,6 +181,7 @@ export namespace Proto2DefaultPackedTypes { break } case 3: { + decodeContext?.consume('int32s', opts.totalLimits?.int32s) if (opts.limits?.int32s != null && obj.int32s.length === opts.limits.int32s) { throw new MaxLengthError('Decode error - repeated field "int32s" had too many elements') } @@ -186,6 +190,7 @@ export namespace Proto2DefaultPackedTypes { break } case 4: { + decodeContext?.consume('int64s', opts.totalLimits?.int64s) if (opts.limits?.int64s != null && obj.int64s.length === opts.limits.int64s) { throw new MaxLengthError('Decode error - repeated field "int64s" had too many elements') } @@ -194,6 +199,7 @@ export namespace Proto2DefaultPackedTypes { break } case 5: { + decodeContext?.consume('uint32s', opts.totalLimits?.uint32s) if (opts.limits?.uint32s != null && obj.uint32s.length === opts.limits.uint32s) { throw new MaxLengthError('Decode error - repeated field "uint32s" had too many elements') } @@ -202,6 +208,7 @@ export namespace Proto2DefaultPackedTypes { break } case 6: { + decodeContext?.consume('uint64s', opts.totalLimits?.uint64s) if (opts.limits?.uint64s != null && obj.uint64s.length === opts.limits.uint64s) { throw new MaxLengthError('Decode error - repeated field "uint64s" had too many elements') } @@ -210,6 +217,7 @@ export namespace Proto2DefaultPackedTypes { break } case 7: { + decodeContext?.consume('sint32s', opts.totalLimits?.sint32s) if (opts.limits?.sint32s != null && obj.sint32s.length === opts.limits.sint32s) { throw new MaxLengthError('Decode error - repeated field "sint32s" had too many elements') } @@ -218,6 +226,7 @@ export namespace Proto2DefaultPackedTypes { break } case 8: { + decodeContext?.consume('sint64s', opts.totalLimits?.sint64s) if (opts.limits?.sint64s != null && obj.sint64s.length === opts.limits.sint64s) { throw new MaxLengthError('Decode error - repeated field "sint64s" had too many elements') } @@ -226,6 +235,7 @@ export namespace Proto2DefaultPackedTypes { break } case 9: { + decodeContext?.consume('fixed32s', opts.totalLimits?.fixed32s) if (opts.limits?.fixed32s != null && obj.fixed32s.length === opts.limits.fixed32s) { throw new MaxLengthError('Decode error - repeated field "fixed32s" had too many elements') } @@ -234,6 +244,7 @@ export namespace Proto2DefaultPackedTypes { break } case 10: { + decodeContext?.consume('fixed64s', opts.totalLimits?.fixed64s) if (opts.limits?.fixed64s != null && obj.fixed64s.length === opts.limits.fixed64s) { throw new MaxLengthError('Decode error - repeated field "fixed64s" had too many elements') } @@ -242,6 +253,7 @@ export namespace Proto2DefaultPackedTypes { break } case 11: { + decodeContext?.consume('sfixed32s', opts.totalLimits?.sfixed32s) if (opts.limits?.sfixed32s != null && obj.sfixed32s.length === opts.limits.sfixed32s) { throw new MaxLengthError('Decode error - repeated field "sfixed32s" had too many elements') } @@ -250,6 +262,7 @@ export namespace Proto2DefaultPackedTypes { break } case 12: { + decodeContext?.consume('sfixed64s', opts.totalLimits?.sfixed64s) if (opts.limits?.sfixed64s != null && obj.sfixed64s.length === opts.limits.sfixed64s) { throw new MaxLengthError('Decode error - repeated field "sfixed64s" had too many elements') } @@ -258,6 +271,7 @@ export namespace Proto2DefaultPackedTypes { break } case 13: { + decodeContext?.consume('bools', opts.totalLimits?.bools) if (opts.limits?.bools != null && obj.bools.length === opts.limits.bools) { throw new MaxLengthError('Decode error - repeated field "bools" had too many elements') } @@ -274,6 +288,7 @@ export namespace Proto2DefaultPackedTypes { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { doubles: 0, floats: 0, @@ -305,6 +320,7 @@ export namespace Proto2DefaultPackedTypes { switch (tag >>> 3) { case 1: { + decodeContext?.consume('doubles', opts.totalLimits?.doubles) if (opts.limits?.doubles != null && obj.doubles === opts.limits.doubles) { throw new MaxLengthError('Streaming decode error - repeated field "doubles" had too many elements') } @@ -320,6 +336,7 @@ export namespace Proto2DefaultPackedTypes { break } case 2: { + decodeContext?.consume('floats', opts.totalLimits?.floats) if (opts.limits?.floats != null && obj.floats === opts.limits.floats) { throw new MaxLengthError('Streaming decode error - repeated field "floats" had too many elements') } @@ -335,6 +352,7 @@ export namespace Proto2DefaultPackedTypes { break } case 3: { + decodeContext?.consume('int32s', opts.totalLimits?.int32s) if (opts.limits?.int32s != null && obj.int32s === opts.limits.int32s) { throw new MaxLengthError('Streaming decode error - repeated field "int32s" had too many elements') } @@ -350,6 +368,7 @@ export namespace Proto2DefaultPackedTypes { break } case 4: { + decodeContext?.consume('int64s', opts.totalLimits?.int64s) if (opts.limits?.int64s != null && obj.int64s === opts.limits.int64s) { throw new MaxLengthError('Streaming decode error - repeated field "int64s" had too many elements') } @@ -365,6 +384,7 @@ export namespace Proto2DefaultPackedTypes { break } case 5: { + decodeContext?.consume('uint32s', opts.totalLimits?.uint32s) if (opts.limits?.uint32s != null && obj.uint32s === opts.limits.uint32s) { throw new MaxLengthError('Streaming decode error - repeated field "uint32s" had too many elements') } @@ -380,6 +400,7 @@ export namespace Proto2DefaultPackedTypes { break } case 6: { + decodeContext?.consume('uint64s', opts.totalLimits?.uint64s) if (opts.limits?.uint64s != null && obj.uint64s === opts.limits.uint64s) { throw new MaxLengthError('Streaming decode error - repeated field "uint64s" had too many elements') } @@ -395,6 +416,7 @@ export namespace Proto2DefaultPackedTypes { break } case 7: { + decodeContext?.consume('sint32s', opts.totalLimits?.sint32s) if (opts.limits?.sint32s != null && obj.sint32s === opts.limits.sint32s) { throw new MaxLengthError('Streaming decode error - repeated field "sint32s" had too many elements') } @@ -410,6 +432,7 @@ export namespace Proto2DefaultPackedTypes { break } case 8: { + decodeContext?.consume('sint64s', opts.totalLimits?.sint64s) if (opts.limits?.sint64s != null && obj.sint64s === opts.limits.sint64s) { throw new MaxLengthError('Streaming decode error - repeated field "sint64s" had too many elements') } @@ -425,6 +448,7 @@ export namespace Proto2DefaultPackedTypes { break } case 9: { + decodeContext?.consume('fixed32s', opts.totalLimits?.fixed32s) if (opts.limits?.fixed32s != null && obj.fixed32s === opts.limits.fixed32s) { throw new MaxLengthError('Streaming decode error - repeated field "fixed32s" had too many elements') } @@ -440,6 +464,7 @@ export namespace Proto2DefaultPackedTypes { break } case 10: { + decodeContext?.consume('fixed64s', opts.totalLimits?.fixed64s) if (opts.limits?.fixed64s != null && obj.fixed64s === opts.limits.fixed64s) { throw new MaxLengthError('Streaming decode error - repeated field "fixed64s" had too many elements') } @@ -455,6 +480,7 @@ export namespace Proto2DefaultPackedTypes { break } case 11: { + decodeContext?.consume('sfixed32s', opts.totalLimits?.sfixed32s) if (opts.limits?.sfixed32s != null && obj.sfixed32s === opts.limits.sfixed32s) { throw new MaxLengthError('Streaming decode error - repeated field "sfixed32s" had too many elements') } @@ -470,6 +496,7 @@ export namespace Proto2DefaultPackedTypes { break } case 12: { + decodeContext?.consume('sfixed64s', opts.totalLimits?.sfixed64s) if (opts.limits?.sfixed64s != null && obj.sfixed64s === opts.limits.sfixed64s) { throw new MaxLengthError('Streaming decode error - repeated field "sfixed64s" had too many elements') } @@ -485,6 +512,7 @@ export namespace Proto2DefaultPackedTypes { break } case 13: { + decodeContext?.consume('bools', opts.totalLimits?.bools) if (opts.limits?.bools != null && obj.bools === opts.limits.bools) { throw new MaxLengthError('Streaming decode error - repeated field "bools" had too many elements') } @@ -513,7 +541,7 @@ export namespace Proto2DefaultPackedTypes { message: 'Proto2DefaultPackedTypes' } } - }) + }, { totalLimits: true }) } return _codec @@ -799,6 +827,7 @@ export namespace Proto2PackedTypes { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { doubles: [], floats: [], @@ -826,6 +855,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('doubles', opts.totalLimits?.doubles) if (opts.limits?.doubles != null && obj.doubles.length === opts.limits.doubles) { throw new MaxLengthError('Decode error - repeated field "doubles" had too many elements') } @@ -840,6 +870,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('floats', opts.totalLimits?.floats) if (opts.limits?.floats != null && obj.floats.length === opts.limits.floats) { throw new MaxLengthError('Decode error - repeated field "floats" had too many elements') } @@ -854,6 +885,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('int32s', opts.totalLimits?.int32s) if (opts.limits?.int32s != null && obj.int32s.length === opts.limits.int32s) { throw new MaxLengthError('Decode error - repeated field "int32s" had too many elements') } @@ -868,6 +900,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('int64s', opts.totalLimits?.int64s) if (opts.limits?.int64s != null && obj.int64s.length === opts.limits.int64s) { throw new MaxLengthError('Decode error - repeated field "int64s" had too many elements') } @@ -882,6 +915,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('uint32s', opts.totalLimits?.uint32s) if (opts.limits?.uint32s != null && obj.uint32s.length === opts.limits.uint32s) { throw new MaxLengthError('Decode error - repeated field "uint32s" had too many elements') } @@ -896,6 +930,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('uint64s', opts.totalLimits?.uint64s) if (opts.limits?.uint64s != null && obj.uint64s.length === opts.limits.uint64s) { throw new MaxLengthError('Decode error - repeated field "uint64s" had too many elements') } @@ -910,6 +945,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sint32s', opts.totalLimits?.sint32s) if (opts.limits?.sint32s != null && obj.sint32s.length === opts.limits.sint32s) { throw new MaxLengthError('Decode error - repeated field "sint32s" had too many elements') } @@ -924,6 +960,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sint64s', opts.totalLimits?.sint64s) if (opts.limits?.sint64s != null && obj.sint64s.length === opts.limits.sint64s) { throw new MaxLengthError('Decode error - repeated field "sint64s" had too many elements') } @@ -938,6 +975,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('fixed32s', opts.totalLimits?.fixed32s) if (opts.limits?.fixed32s != null && obj.fixed32s.length === opts.limits.fixed32s) { throw new MaxLengthError('Decode error - repeated field "fixed32s" had too many elements') } @@ -952,6 +990,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('fixed64s', opts.totalLimits?.fixed64s) if (opts.limits?.fixed64s != null && obj.fixed64s.length === opts.limits.fixed64s) { throw new MaxLengthError('Decode error - repeated field "fixed64s" had too many elements') } @@ -966,6 +1005,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sfixed32s', opts.totalLimits?.sfixed32s) if (opts.limits?.sfixed32s != null && obj.sfixed32s.length === opts.limits.sfixed32s) { throw new MaxLengthError('Decode error - repeated field "sfixed32s" had too many elements') } @@ -980,6 +1020,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sfixed64s', opts.totalLimits?.sfixed64s) if (opts.limits?.sfixed64s != null && obj.sfixed64s.length === opts.limits.sfixed64s) { throw new MaxLengthError('Decode error - repeated field "sfixed64s" had too many elements') } @@ -994,6 +1035,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('bools', opts.totalLimits?.bools) if (opts.limits?.bools != null && obj.bools.length === opts.limits.bools) { throw new MaxLengthError('Decode error - repeated field "bools" had too many elements') } @@ -1012,6 +1054,7 @@ export namespace Proto2PackedTypes { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { doubles: 0, floats: 0, @@ -1047,6 +1090,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('doubles', opts.totalLimits?.doubles) if (opts.limits?.doubles != null && obj.doubles === opts.limits.doubles) { throw new MaxLengthError('Streaming decode error - repeated field "doubles" had too many elements') } @@ -1067,6 +1111,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('floats', opts.totalLimits?.floats) if (opts.limits?.floats != null && obj.floats === opts.limits.floats) { throw new MaxLengthError('Streaming decode error - repeated field "floats" had too many elements') } @@ -1087,6 +1132,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('int32s', opts.totalLimits?.int32s) if (opts.limits?.int32s != null && obj.int32s === opts.limits.int32s) { throw new MaxLengthError('Streaming decode error - repeated field "int32s" had too many elements') } @@ -1107,6 +1153,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('int64s', opts.totalLimits?.int64s) if (opts.limits?.int64s != null && obj.int64s === opts.limits.int64s) { throw new MaxLengthError('Streaming decode error - repeated field "int64s" had too many elements') } @@ -1127,6 +1174,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('uint32s', opts.totalLimits?.uint32s) if (opts.limits?.uint32s != null && obj.uint32s === opts.limits.uint32s) { throw new MaxLengthError('Streaming decode error - repeated field "uint32s" had too many elements') } @@ -1147,6 +1195,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('uint64s', opts.totalLimits?.uint64s) if (opts.limits?.uint64s != null && obj.uint64s === opts.limits.uint64s) { throw new MaxLengthError('Streaming decode error - repeated field "uint64s" had too many elements') } @@ -1167,6 +1216,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sint32s', opts.totalLimits?.sint32s) if (opts.limits?.sint32s != null && obj.sint32s === opts.limits.sint32s) { throw new MaxLengthError('Streaming decode error - repeated field "sint32s" had too many elements') } @@ -1187,6 +1237,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sint64s', opts.totalLimits?.sint64s) if (opts.limits?.sint64s != null && obj.sint64s === opts.limits.sint64s) { throw new MaxLengthError('Streaming decode error - repeated field "sint64s" had too many elements') } @@ -1207,6 +1258,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('fixed32s', opts.totalLimits?.fixed32s) if (opts.limits?.fixed32s != null && obj.fixed32s === opts.limits.fixed32s) { throw new MaxLengthError('Streaming decode error - repeated field "fixed32s" had too many elements') } @@ -1227,6 +1279,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('fixed64s', opts.totalLimits?.fixed64s) if (opts.limits?.fixed64s != null && obj.fixed64s === opts.limits.fixed64s) { throw new MaxLengthError('Streaming decode error - repeated field "fixed64s" had too many elements') } @@ -1247,6 +1300,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sfixed32s', opts.totalLimits?.sfixed32s) if (opts.limits?.sfixed32s != null && obj.sfixed32s === opts.limits.sfixed32s) { throw new MaxLengthError('Streaming decode error - repeated field "sfixed32s" had too many elements') } @@ -1267,6 +1321,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sfixed64s', opts.totalLimits?.sfixed64s) if (opts.limits?.sfixed64s != null && obj.sfixed64s === opts.limits.sfixed64s) { throw new MaxLengthError('Streaming decode error - repeated field "sfixed64s" had too many elements') } @@ -1287,6 +1342,7 @@ export namespace Proto2PackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('bools', opts.totalLimits?.bools) if (opts.limits?.bools != null && obj.bools === opts.limits.bools) { throw new MaxLengthError('Streaming decode error - repeated field "bools" had too many elements') } @@ -1316,7 +1372,7 @@ export namespace Proto2PackedTypes { message: 'Proto2PackedTypes' } } - }) + }, { totalLimits: true }) } return _codec @@ -1550,6 +1606,7 @@ export namespace Proto2ExpandedTypes { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { doubles: [], floats: [], @@ -1573,6 +1630,7 @@ export namespace Proto2ExpandedTypes { switch (tag >>> 3) { case 1: { + decodeContext?.consume('doubles', opts.totalLimits?.doubles) if (opts.limits?.doubles != null && obj.doubles.length === opts.limits.doubles) { throw new MaxLengthError('Decode error - repeated field "doubles" had too many elements') } @@ -1581,6 +1639,7 @@ export namespace Proto2ExpandedTypes { break } case 2: { + decodeContext?.consume('floats', opts.totalLimits?.floats) if (opts.limits?.floats != null && obj.floats.length === opts.limits.floats) { throw new MaxLengthError('Decode error - repeated field "floats" had too many elements') } @@ -1589,6 +1648,7 @@ export namespace Proto2ExpandedTypes { break } case 3: { + decodeContext?.consume('int32s', opts.totalLimits?.int32s) if (opts.limits?.int32s != null && obj.int32s.length === opts.limits.int32s) { throw new MaxLengthError('Decode error - repeated field "int32s" had too many elements') } @@ -1597,6 +1657,7 @@ export namespace Proto2ExpandedTypes { break } case 4: { + decodeContext?.consume('int64s', opts.totalLimits?.int64s) if (opts.limits?.int64s != null && obj.int64s.length === opts.limits.int64s) { throw new MaxLengthError('Decode error - repeated field "int64s" had too many elements') } @@ -1605,6 +1666,7 @@ export namespace Proto2ExpandedTypes { break } case 5: { + decodeContext?.consume('uint32s', opts.totalLimits?.uint32s) if (opts.limits?.uint32s != null && obj.uint32s.length === opts.limits.uint32s) { throw new MaxLengthError('Decode error - repeated field "uint32s" had too many elements') } @@ -1613,6 +1675,7 @@ export namespace Proto2ExpandedTypes { break } case 6: { + decodeContext?.consume('uint64s', opts.totalLimits?.uint64s) if (opts.limits?.uint64s != null && obj.uint64s.length === opts.limits.uint64s) { throw new MaxLengthError('Decode error - repeated field "uint64s" had too many elements') } @@ -1621,6 +1684,7 @@ export namespace Proto2ExpandedTypes { break } case 7: { + decodeContext?.consume('sint32s', opts.totalLimits?.sint32s) if (opts.limits?.sint32s != null && obj.sint32s.length === opts.limits.sint32s) { throw new MaxLengthError('Decode error - repeated field "sint32s" had too many elements') } @@ -1629,6 +1693,7 @@ export namespace Proto2ExpandedTypes { break } case 8: { + decodeContext?.consume('sint64s', opts.totalLimits?.sint64s) if (opts.limits?.sint64s != null && obj.sint64s.length === opts.limits.sint64s) { throw new MaxLengthError('Decode error - repeated field "sint64s" had too many elements') } @@ -1637,6 +1702,7 @@ export namespace Proto2ExpandedTypes { break } case 9: { + decodeContext?.consume('fixed32s', opts.totalLimits?.fixed32s) if (opts.limits?.fixed32s != null && obj.fixed32s.length === opts.limits.fixed32s) { throw new MaxLengthError('Decode error - repeated field "fixed32s" had too many elements') } @@ -1645,6 +1711,7 @@ export namespace Proto2ExpandedTypes { break } case 10: { + decodeContext?.consume('fixed64s', opts.totalLimits?.fixed64s) if (opts.limits?.fixed64s != null && obj.fixed64s.length === opts.limits.fixed64s) { throw new MaxLengthError('Decode error - repeated field "fixed64s" had too many elements') } @@ -1653,6 +1720,7 @@ export namespace Proto2ExpandedTypes { break } case 11: { + decodeContext?.consume('sfixed32s', opts.totalLimits?.sfixed32s) if (opts.limits?.sfixed32s != null && obj.sfixed32s.length === opts.limits.sfixed32s) { throw new MaxLengthError('Decode error - repeated field "sfixed32s" had too many elements') } @@ -1661,6 +1729,7 @@ export namespace Proto2ExpandedTypes { break } case 12: { + decodeContext?.consume('sfixed64s', opts.totalLimits?.sfixed64s) if (opts.limits?.sfixed64s != null && obj.sfixed64s.length === opts.limits.sfixed64s) { throw new MaxLengthError('Decode error - repeated field "sfixed64s" had too many elements') } @@ -1669,6 +1738,7 @@ export namespace Proto2ExpandedTypes { break } case 13: { + decodeContext?.consume('bools', opts.totalLimits?.bools) if (opts.limits?.bools != null && obj.bools.length === opts.limits.bools) { throw new MaxLengthError('Decode error - repeated field "bools" had too many elements') } @@ -1685,6 +1755,7 @@ export namespace Proto2ExpandedTypes { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { doubles: 0, floats: 0, @@ -1716,6 +1787,7 @@ export namespace Proto2ExpandedTypes { switch (tag >>> 3) { case 1: { + decodeContext?.consume('doubles', opts.totalLimits?.doubles) if (opts.limits?.doubles != null && obj.doubles === opts.limits.doubles) { throw new MaxLengthError('Streaming decode error - repeated field "doubles" had too many elements') } @@ -1731,6 +1803,7 @@ export namespace Proto2ExpandedTypes { break } case 2: { + decodeContext?.consume('floats', opts.totalLimits?.floats) if (opts.limits?.floats != null && obj.floats === opts.limits.floats) { throw new MaxLengthError('Streaming decode error - repeated field "floats" had too many elements') } @@ -1746,6 +1819,7 @@ export namespace Proto2ExpandedTypes { break } case 3: { + decodeContext?.consume('int32s', opts.totalLimits?.int32s) if (opts.limits?.int32s != null && obj.int32s === opts.limits.int32s) { throw new MaxLengthError('Streaming decode error - repeated field "int32s" had too many elements') } @@ -1761,6 +1835,7 @@ export namespace Proto2ExpandedTypes { break } case 4: { + decodeContext?.consume('int64s', opts.totalLimits?.int64s) if (opts.limits?.int64s != null && obj.int64s === opts.limits.int64s) { throw new MaxLengthError('Streaming decode error - repeated field "int64s" had too many elements') } @@ -1776,6 +1851,7 @@ export namespace Proto2ExpandedTypes { break } case 5: { + decodeContext?.consume('uint32s', opts.totalLimits?.uint32s) if (opts.limits?.uint32s != null && obj.uint32s === opts.limits.uint32s) { throw new MaxLengthError('Streaming decode error - repeated field "uint32s" had too many elements') } @@ -1791,6 +1867,7 @@ export namespace Proto2ExpandedTypes { break } case 6: { + decodeContext?.consume('uint64s', opts.totalLimits?.uint64s) if (opts.limits?.uint64s != null && obj.uint64s === opts.limits.uint64s) { throw new MaxLengthError('Streaming decode error - repeated field "uint64s" had too many elements') } @@ -1806,6 +1883,7 @@ export namespace Proto2ExpandedTypes { break } case 7: { + decodeContext?.consume('sint32s', opts.totalLimits?.sint32s) if (opts.limits?.sint32s != null && obj.sint32s === opts.limits.sint32s) { throw new MaxLengthError('Streaming decode error - repeated field "sint32s" had too many elements') } @@ -1821,6 +1899,7 @@ export namespace Proto2ExpandedTypes { break } case 8: { + decodeContext?.consume('sint64s', opts.totalLimits?.sint64s) if (opts.limits?.sint64s != null && obj.sint64s === opts.limits.sint64s) { throw new MaxLengthError('Streaming decode error - repeated field "sint64s" had too many elements') } @@ -1836,6 +1915,7 @@ export namespace Proto2ExpandedTypes { break } case 9: { + decodeContext?.consume('fixed32s', opts.totalLimits?.fixed32s) if (opts.limits?.fixed32s != null && obj.fixed32s === opts.limits.fixed32s) { throw new MaxLengthError('Streaming decode error - repeated field "fixed32s" had too many elements') } @@ -1851,6 +1931,7 @@ export namespace Proto2ExpandedTypes { break } case 10: { + decodeContext?.consume('fixed64s', opts.totalLimits?.fixed64s) if (opts.limits?.fixed64s != null && obj.fixed64s === opts.limits.fixed64s) { throw new MaxLengthError('Streaming decode error - repeated field "fixed64s" had too many elements') } @@ -1866,6 +1947,7 @@ export namespace Proto2ExpandedTypes { break } case 11: { + decodeContext?.consume('sfixed32s', opts.totalLimits?.sfixed32s) if (opts.limits?.sfixed32s != null && obj.sfixed32s === opts.limits.sfixed32s) { throw new MaxLengthError('Streaming decode error - repeated field "sfixed32s" had too many elements') } @@ -1881,6 +1963,7 @@ export namespace Proto2ExpandedTypes { break } case 12: { + decodeContext?.consume('sfixed64s', opts.totalLimits?.sfixed64s) if (opts.limits?.sfixed64s != null && obj.sfixed64s === opts.limits.sfixed64s) { throw new MaxLengthError('Streaming decode error - repeated field "sfixed64s" had too many elements') } @@ -1896,6 +1979,7 @@ export namespace Proto2ExpandedTypes { break } case 13: { + decodeContext?.consume('bools', opts.totalLimits?.bools) if (opts.limits?.bools != null && obj.bools === opts.limits.bools) { throw new MaxLengthError('Streaming decode error - repeated field "bools" had too many elements') } @@ -1924,7 +2008,7 @@ export namespace Proto2ExpandedTypes { message: 'Proto2ExpandedTypes' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/packed.ts b/packages/protons/test/fixtures/packed.ts index b54006c..5bae7c8 100644 --- a/packages/protons/test/fixtures/packed.ts +++ b/packages/protons/test/fixtures/packed.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, MaxLengthError, message, reader, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, MaxLengthError, message, reader, streamMessage } from 'protons-runtime' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -42,6 +42,7 @@ export namespace SpecPacked { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { str: '', packed: [] @@ -62,6 +63,7 @@ export namespace SpecPacked { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('packed', opts.totalLimits?.packed) if (opts.limits?.packed != null && obj.packed.length === opts.limits.packed) { throw new MaxLengthError('Decode error - repeated field "packed" had too many elements') } @@ -80,6 +82,7 @@ export namespace SpecPacked { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { packed: 0 } @@ -110,6 +113,7 @@ export namespace SpecPacked { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('packed', opts.totalLimits?.packed) if (opts.limits?.packed != null && obj.packed === opts.limits.packed) { throw new MaxLengthError('Streaming decode error - repeated field "packed" had too many elements') } @@ -139,7 +143,7 @@ export namespace SpecPacked { message: 'SpecPacked' } } - }) + }, { totalLimits: true }) } return _codec @@ -358,6 +362,7 @@ export namespace DefaultPackedTypes { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { doubles: [], floats: [], @@ -385,6 +390,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('doubles', opts.totalLimits?.doubles) if (opts.limits?.doubles != null && obj.doubles.length === opts.limits.doubles) { throw new MaxLengthError('Decode error - repeated field "doubles" had too many elements') } @@ -399,6 +405,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('floats', opts.totalLimits?.floats) if (opts.limits?.floats != null && obj.floats.length === opts.limits.floats) { throw new MaxLengthError('Decode error - repeated field "floats" had too many elements') } @@ -413,6 +420,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('int32s', opts.totalLimits?.int32s) if (opts.limits?.int32s != null && obj.int32s.length === opts.limits.int32s) { throw new MaxLengthError('Decode error - repeated field "int32s" had too many elements') } @@ -427,6 +435,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('int64s', opts.totalLimits?.int64s) if (opts.limits?.int64s != null && obj.int64s.length === opts.limits.int64s) { throw new MaxLengthError('Decode error - repeated field "int64s" had too many elements') } @@ -441,6 +450,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('uint32s', opts.totalLimits?.uint32s) if (opts.limits?.uint32s != null && obj.uint32s.length === opts.limits.uint32s) { throw new MaxLengthError('Decode error - repeated field "uint32s" had too many elements') } @@ -455,6 +465,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('uint64s', opts.totalLimits?.uint64s) if (opts.limits?.uint64s != null && obj.uint64s.length === opts.limits.uint64s) { throw new MaxLengthError('Decode error - repeated field "uint64s" had too many elements') } @@ -469,6 +480,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sint32s', opts.totalLimits?.sint32s) if (opts.limits?.sint32s != null && obj.sint32s.length === opts.limits.sint32s) { throw new MaxLengthError('Decode error - repeated field "sint32s" had too many elements') } @@ -483,6 +495,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sint64s', opts.totalLimits?.sint64s) if (opts.limits?.sint64s != null && obj.sint64s.length === opts.limits.sint64s) { throw new MaxLengthError('Decode error - repeated field "sint64s" had too many elements') } @@ -497,6 +510,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('fixed32s', opts.totalLimits?.fixed32s) if (opts.limits?.fixed32s != null && obj.fixed32s.length === opts.limits.fixed32s) { throw new MaxLengthError('Decode error - repeated field "fixed32s" had too many elements') } @@ -511,6 +525,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('fixed64s', opts.totalLimits?.fixed64s) if (opts.limits?.fixed64s != null && obj.fixed64s.length === opts.limits.fixed64s) { throw new MaxLengthError('Decode error - repeated field "fixed64s" had too many elements') } @@ -525,6 +540,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sfixed32s', opts.totalLimits?.sfixed32s) if (opts.limits?.sfixed32s != null && obj.sfixed32s.length === opts.limits.sfixed32s) { throw new MaxLengthError('Decode error - repeated field "sfixed32s" had too many elements') } @@ -539,6 +555,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sfixed64s', opts.totalLimits?.sfixed64s) if (opts.limits?.sfixed64s != null && obj.sfixed64s.length === opts.limits.sfixed64s) { throw new MaxLengthError('Decode error - repeated field "sfixed64s" had too many elements') } @@ -553,6 +570,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('bools', opts.totalLimits?.bools) if (opts.limits?.bools != null && obj.bools.length === opts.limits.bools) { throw new MaxLengthError('Decode error - repeated field "bools" had too many elements') } @@ -571,6 +589,7 @@ export namespace DefaultPackedTypes { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { doubles: 0, floats: 0, @@ -606,6 +625,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('doubles', opts.totalLimits?.doubles) if (opts.limits?.doubles != null && obj.doubles === opts.limits.doubles) { throw new MaxLengthError('Streaming decode error - repeated field "doubles" had too many elements') } @@ -626,6 +646,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('floats', opts.totalLimits?.floats) if (opts.limits?.floats != null && obj.floats === opts.limits.floats) { throw new MaxLengthError('Streaming decode error - repeated field "floats" had too many elements') } @@ -646,6 +667,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('int32s', opts.totalLimits?.int32s) if (opts.limits?.int32s != null && obj.int32s === opts.limits.int32s) { throw new MaxLengthError('Streaming decode error - repeated field "int32s" had too many elements') } @@ -666,6 +688,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('int64s', opts.totalLimits?.int64s) if (opts.limits?.int64s != null && obj.int64s === opts.limits.int64s) { throw new MaxLengthError('Streaming decode error - repeated field "int64s" had too many elements') } @@ -686,6 +709,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('uint32s', opts.totalLimits?.uint32s) if (opts.limits?.uint32s != null && obj.uint32s === opts.limits.uint32s) { throw new MaxLengthError('Streaming decode error - repeated field "uint32s" had too many elements') } @@ -706,6 +730,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('uint64s', opts.totalLimits?.uint64s) if (opts.limits?.uint64s != null && obj.uint64s === opts.limits.uint64s) { throw new MaxLengthError('Streaming decode error - repeated field "uint64s" had too many elements') } @@ -726,6 +751,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sint32s', opts.totalLimits?.sint32s) if (opts.limits?.sint32s != null && obj.sint32s === opts.limits.sint32s) { throw new MaxLengthError('Streaming decode error - repeated field "sint32s" had too many elements') } @@ -746,6 +772,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sint64s', opts.totalLimits?.sint64s) if (opts.limits?.sint64s != null && obj.sint64s === opts.limits.sint64s) { throw new MaxLengthError('Streaming decode error - repeated field "sint64s" had too many elements') } @@ -766,6 +793,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('fixed32s', opts.totalLimits?.fixed32s) if (opts.limits?.fixed32s != null && obj.fixed32s === opts.limits.fixed32s) { throw new MaxLengthError('Streaming decode error - repeated field "fixed32s" had too many elements') } @@ -786,6 +814,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('fixed64s', opts.totalLimits?.fixed64s) if (opts.limits?.fixed64s != null && obj.fixed64s === opts.limits.fixed64s) { throw new MaxLengthError('Streaming decode error - repeated field "fixed64s" had too many elements') } @@ -806,6 +835,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sfixed32s', opts.totalLimits?.sfixed32s) if (opts.limits?.sfixed32s != null && obj.sfixed32s === opts.limits.sfixed32s) { throw new MaxLengthError('Streaming decode error - repeated field "sfixed32s" had too many elements') } @@ -826,6 +856,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sfixed64s', opts.totalLimits?.sfixed64s) if (opts.limits?.sfixed64s != null && obj.sfixed64s === opts.limits.sfixed64s) { throw new MaxLengthError('Streaming decode error - repeated field "sfixed64s" had too many elements') } @@ -846,6 +877,7 @@ export namespace DefaultPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('bools', opts.totalLimits?.bools) if (opts.limits?.bools != null && obj.bools === opts.limits.bools) { throw new MaxLengthError('Streaming decode error - repeated field "bools" had too many elements') } @@ -875,7 +907,7 @@ export namespace DefaultPackedTypes { message: 'DefaultPackedTypes' } } - }) + }, { totalLimits: true }) } return _codec @@ -1161,6 +1193,7 @@ export namespace OptionPackedTypes { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { doubles: [], floats: [], @@ -1188,6 +1221,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('doubles', opts.totalLimits?.doubles) if (opts.limits?.doubles != null && obj.doubles.length === opts.limits.doubles) { throw new MaxLengthError('Decode error - repeated field "doubles" had too many elements') } @@ -1202,6 +1236,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('floats', opts.totalLimits?.floats) if (opts.limits?.floats != null && obj.floats.length === opts.limits.floats) { throw new MaxLengthError('Decode error - repeated field "floats" had too many elements') } @@ -1216,6 +1251,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('int32s', opts.totalLimits?.int32s) if (opts.limits?.int32s != null && obj.int32s.length === opts.limits.int32s) { throw new MaxLengthError('Decode error - repeated field "int32s" had too many elements') } @@ -1230,6 +1266,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('int64s', opts.totalLimits?.int64s) if (opts.limits?.int64s != null && obj.int64s.length === opts.limits.int64s) { throw new MaxLengthError('Decode error - repeated field "int64s" had too many elements') } @@ -1244,6 +1281,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('uint32s', opts.totalLimits?.uint32s) if (opts.limits?.uint32s != null && obj.uint32s.length === opts.limits.uint32s) { throw new MaxLengthError('Decode error - repeated field "uint32s" had too many elements') } @@ -1258,6 +1296,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('uint64s', opts.totalLimits?.uint64s) if (opts.limits?.uint64s != null && obj.uint64s.length === opts.limits.uint64s) { throw new MaxLengthError('Decode error - repeated field "uint64s" had too many elements') } @@ -1272,6 +1311,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sint32s', opts.totalLimits?.sint32s) if (opts.limits?.sint32s != null && obj.sint32s.length === opts.limits.sint32s) { throw new MaxLengthError('Decode error - repeated field "sint32s" had too many elements') } @@ -1286,6 +1326,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sint64s', opts.totalLimits?.sint64s) if (opts.limits?.sint64s != null && obj.sint64s.length === opts.limits.sint64s) { throw new MaxLengthError('Decode error - repeated field "sint64s" had too many elements') } @@ -1300,6 +1341,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('fixed32s', opts.totalLimits?.fixed32s) if (opts.limits?.fixed32s != null && obj.fixed32s.length === opts.limits.fixed32s) { throw new MaxLengthError('Decode error - repeated field "fixed32s" had too many elements') } @@ -1314,6 +1356,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('fixed64s', opts.totalLimits?.fixed64s) if (opts.limits?.fixed64s != null && obj.fixed64s.length === opts.limits.fixed64s) { throw new MaxLengthError('Decode error - repeated field "fixed64s" had too many elements') } @@ -1328,6 +1371,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sfixed32s', opts.totalLimits?.sfixed32s) if (opts.limits?.sfixed32s != null && obj.sfixed32s.length === opts.limits.sfixed32s) { throw new MaxLengthError('Decode error - repeated field "sfixed32s" had too many elements') } @@ -1342,6 +1386,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sfixed64s', opts.totalLimits?.sfixed64s) if (opts.limits?.sfixed64s != null && obj.sfixed64s.length === opts.limits.sfixed64s) { throw new MaxLengthError('Decode error - repeated field "sfixed64s" had too many elements') } @@ -1356,6 +1401,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('bools', opts.totalLimits?.bools) if (opts.limits?.bools != null && obj.bools.length === opts.limits.bools) { throw new MaxLengthError('Decode error - repeated field "bools" had too many elements') } @@ -1374,6 +1420,7 @@ export namespace OptionPackedTypes { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { doubles: 0, floats: 0, @@ -1409,6 +1456,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('doubles', opts.totalLimits?.doubles) if (opts.limits?.doubles != null && obj.doubles === opts.limits.doubles) { throw new MaxLengthError('Streaming decode error - repeated field "doubles" had too many elements') } @@ -1429,6 +1477,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('floats', opts.totalLimits?.floats) if (opts.limits?.floats != null && obj.floats === opts.limits.floats) { throw new MaxLengthError('Streaming decode error - repeated field "floats" had too many elements') } @@ -1449,6 +1498,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('int32s', opts.totalLimits?.int32s) if (opts.limits?.int32s != null && obj.int32s === opts.limits.int32s) { throw new MaxLengthError('Streaming decode error - repeated field "int32s" had too many elements') } @@ -1469,6 +1519,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('int64s', opts.totalLimits?.int64s) if (opts.limits?.int64s != null && obj.int64s === opts.limits.int64s) { throw new MaxLengthError('Streaming decode error - repeated field "int64s" had too many elements') } @@ -1489,6 +1540,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('uint32s', opts.totalLimits?.uint32s) if (opts.limits?.uint32s != null && obj.uint32s === opts.limits.uint32s) { throw new MaxLengthError('Streaming decode error - repeated field "uint32s" had too many elements') } @@ -1509,6 +1561,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('uint64s', opts.totalLimits?.uint64s) if (opts.limits?.uint64s != null && obj.uint64s === opts.limits.uint64s) { throw new MaxLengthError('Streaming decode error - repeated field "uint64s" had too many elements') } @@ -1529,6 +1582,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sint32s', opts.totalLimits?.sint32s) if (opts.limits?.sint32s != null && obj.sint32s === opts.limits.sint32s) { throw new MaxLengthError('Streaming decode error - repeated field "sint32s" had too many elements') } @@ -1549,6 +1603,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sint64s', opts.totalLimits?.sint64s) if (opts.limits?.sint64s != null && obj.sint64s === opts.limits.sint64s) { throw new MaxLengthError('Streaming decode error - repeated field "sint64s" had too many elements') } @@ -1569,6 +1624,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('fixed32s', opts.totalLimits?.fixed32s) if (opts.limits?.fixed32s != null && obj.fixed32s === opts.limits.fixed32s) { throw new MaxLengthError('Streaming decode error - repeated field "fixed32s" had too many elements') } @@ -1589,6 +1645,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('fixed64s', opts.totalLimits?.fixed64s) if (opts.limits?.fixed64s != null && obj.fixed64s === opts.limits.fixed64s) { throw new MaxLengthError('Streaming decode error - repeated field "fixed64s" had too many elements') } @@ -1609,6 +1666,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sfixed32s', opts.totalLimits?.sfixed32s) if (opts.limits?.sfixed32s != null && obj.sfixed32s === opts.limits.sfixed32s) { throw new MaxLengthError('Streaming decode error - repeated field "sfixed32s" had too many elements') } @@ -1629,6 +1687,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('sfixed64s', opts.totalLimits?.sfixed64s) if (opts.limits?.sfixed64s != null && obj.sfixed64s === opts.limits.sfixed64s) { throw new MaxLengthError('Streaming decode error - repeated field "sfixed64s" had too many elements') } @@ -1649,6 +1708,7 @@ export namespace OptionPackedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('bools', opts.totalLimits?.bools) if (opts.limits?.bools != null && obj.bools === opts.limits.bools) { throw new MaxLengthError('Streaming decode error - repeated field "bools" had too many elements') } @@ -1678,7 +1738,7 @@ export namespace OptionPackedTypes { message: 'OptionPackedTypes' } } - }) + }, { totalLimits: true }) } return _codec @@ -1912,6 +1972,7 @@ export namespace OptionExpandedTypes { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { doubles: [], floats: [], @@ -1935,6 +1996,7 @@ export namespace OptionExpandedTypes { switch (tag >>> 3) { case 1: { + decodeContext?.consume('doubles', opts.totalLimits?.doubles) if (opts.limits?.doubles != null && obj.doubles.length === opts.limits.doubles) { throw new MaxLengthError('Decode error - repeated field "doubles" had too many elements') } @@ -1943,6 +2005,7 @@ export namespace OptionExpandedTypes { break } case 2: { + decodeContext?.consume('floats', opts.totalLimits?.floats) if (opts.limits?.floats != null && obj.floats.length === opts.limits.floats) { throw new MaxLengthError('Decode error - repeated field "floats" had too many elements') } @@ -1951,6 +2014,7 @@ export namespace OptionExpandedTypes { break } case 3: { + decodeContext?.consume('int32s', opts.totalLimits?.int32s) if (opts.limits?.int32s != null && obj.int32s.length === opts.limits.int32s) { throw new MaxLengthError('Decode error - repeated field "int32s" had too many elements') } @@ -1959,6 +2023,7 @@ export namespace OptionExpandedTypes { break } case 4: { + decodeContext?.consume('int64s', opts.totalLimits?.int64s) if (opts.limits?.int64s != null && obj.int64s.length === opts.limits.int64s) { throw new MaxLengthError('Decode error - repeated field "int64s" had too many elements') } @@ -1967,6 +2032,7 @@ export namespace OptionExpandedTypes { break } case 5: { + decodeContext?.consume('uint32s', opts.totalLimits?.uint32s) if (opts.limits?.uint32s != null && obj.uint32s.length === opts.limits.uint32s) { throw new MaxLengthError('Decode error - repeated field "uint32s" had too many elements') } @@ -1975,6 +2041,7 @@ export namespace OptionExpandedTypes { break } case 6: { + decodeContext?.consume('uint64s', opts.totalLimits?.uint64s) if (opts.limits?.uint64s != null && obj.uint64s.length === opts.limits.uint64s) { throw new MaxLengthError('Decode error - repeated field "uint64s" had too many elements') } @@ -1983,6 +2050,7 @@ export namespace OptionExpandedTypes { break } case 7: { + decodeContext?.consume('sint32s', opts.totalLimits?.sint32s) if (opts.limits?.sint32s != null && obj.sint32s.length === opts.limits.sint32s) { throw new MaxLengthError('Decode error - repeated field "sint32s" had too many elements') } @@ -1991,6 +2059,7 @@ export namespace OptionExpandedTypes { break } case 8: { + decodeContext?.consume('sint64s', opts.totalLimits?.sint64s) if (opts.limits?.sint64s != null && obj.sint64s.length === opts.limits.sint64s) { throw new MaxLengthError('Decode error - repeated field "sint64s" had too many elements') } @@ -1999,6 +2068,7 @@ export namespace OptionExpandedTypes { break } case 9: { + decodeContext?.consume('fixed32s', opts.totalLimits?.fixed32s) if (opts.limits?.fixed32s != null && obj.fixed32s.length === opts.limits.fixed32s) { throw new MaxLengthError('Decode error - repeated field "fixed32s" had too many elements') } @@ -2007,6 +2077,7 @@ export namespace OptionExpandedTypes { break } case 10: { + decodeContext?.consume('fixed64s', opts.totalLimits?.fixed64s) if (opts.limits?.fixed64s != null && obj.fixed64s.length === opts.limits.fixed64s) { throw new MaxLengthError('Decode error - repeated field "fixed64s" had too many elements') } @@ -2015,6 +2086,7 @@ export namespace OptionExpandedTypes { break } case 11: { + decodeContext?.consume('sfixed32s', opts.totalLimits?.sfixed32s) if (opts.limits?.sfixed32s != null && obj.sfixed32s.length === opts.limits.sfixed32s) { throw new MaxLengthError('Decode error - repeated field "sfixed32s" had too many elements') } @@ -2023,6 +2095,7 @@ export namespace OptionExpandedTypes { break } case 12: { + decodeContext?.consume('sfixed64s', opts.totalLimits?.sfixed64s) if (opts.limits?.sfixed64s != null && obj.sfixed64s.length === opts.limits.sfixed64s) { throw new MaxLengthError('Decode error - repeated field "sfixed64s" had too many elements') } @@ -2031,6 +2104,7 @@ export namespace OptionExpandedTypes { break } case 13: { + decodeContext?.consume('bools', opts.totalLimits?.bools) if (opts.limits?.bools != null && obj.bools.length === opts.limits.bools) { throw new MaxLengthError('Decode error - repeated field "bools" had too many elements') } @@ -2047,6 +2121,7 @@ export namespace OptionExpandedTypes { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { doubles: 0, floats: 0, @@ -2078,6 +2153,7 @@ export namespace OptionExpandedTypes { switch (tag >>> 3) { case 1: { + decodeContext?.consume('doubles', opts.totalLimits?.doubles) if (opts.limits?.doubles != null && obj.doubles === opts.limits.doubles) { throw new MaxLengthError('Streaming decode error - repeated field "doubles" had too many elements') } @@ -2093,6 +2169,7 @@ export namespace OptionExpandedTypes { break } case 2: { + decodeContext?.consume('floats', opts.totalLimits?.floats) if (opts.limits?.floats != null && obj.floats === opts.limits.floats) { throw new MaxLengthError('Streaming decode error - repeated field "floats" had too many elements') } @@ -2108,6 +2185,7 @@ export namespace OptionExpandedTypes { break } case 3: { + decodeContext?.consume('int32s', opts.totalLimits?.int32s) if (opts.limits?.int32s != null && obj.int32s === opts.limits.int32s) { throw new MaxLengthError('Streaming decode error - repeated field "int32s" had too many elements') } @@ -2123,6 +2201,7 @@ export namespace OptionExpandedTypes { break } case 4: { + decodeContext?.consume('int64s', opts.totalLimits?.int64s) if (opts.limits?.int64s != null && obj.int64s === opts.limits.int64s) { throw new MaxLengthError('Streaming decode error - repeated field "int64s" had too many elements') } @@ -2138,6 +2217,7 @@ export namespace OptionExpandedTypes { break } case 5: { + decodeContext?.consume('uint32s', opts.totalLimits?.uint32s) if (opts.limits?.uint32s != null && obj.uint32s === opts.limits.uint32s) { throw new MaxLengthError('Streaming decode error - repeated field "uint32s" had too many elements') } @@ -2153,6 +2233,7 @@ export namespace OptionExpandedTypes { break } case 6: { + decodeContext?.consume('uint64s', opts.totalLimits?.uint64s) if (opts.limits?.uint64s != null && obj.uint64s === opts.limits.uint64s) { throw new MaxLengthError('Streaming decode error - repeated field "uint64s" had too many elements') } @@ -2168,6 +2249,7 @@ export namespace OptionExpandedTypes { break } case 7: { + decodeContext?.consume('sint32s', opts.totalLimits?.sint32s) if (opts.limits?.sint32s != null && obj.sint32s === opts.limits.sint32s) { throw new MaxLengthError('Streaming decode error - repeated field "sint32s" had too many elements') } @@ -2183,6 +2265,7 @@ export namespace OptionExpandedTypes { break } case 8: { + decodeContext?.consume('sint64s', opts.totalLimits?.sint64s) if (opts.limits?.sint64s != null && obj.sint64s === opts.limits.sint64s) { throw new MaxLengthError('Streaming decode error - repeated field "sint64s" had too many elements') } @@ -2198,6 +2281,7 @@ export namespace OptionExpandedTypes { break } case 9: { + decodeContext?.consume('fixed32s', opts.totalLimits?.fixed32s) if (opts.limits?.fixed32s != null && obj.fixed32s === opts.limits.fixed32s) { throw new MaxLengthError('Streaming decode error - repeated field "fixed32s" had too many elements') } @@ -2213,6 +2297,7 @@ export namespace OptionExpandedTypes { break } case 10: { + decodeContext?.consume('fixed64s', opts.totalLimits?.fixed64s) if (opts.limits?.fixed64s != null && obj.fixed64s === opts.limits.fixed64s) { throw new MaxLengthError('Streaming decode error - repeated field "fixed64s" had too many elements') } @@ -2228,6 +2313,7 @@ export namespace OptionExpandedTypes { break } case 11: { + decodeContext?.consume('sfixed32s', opts.totalLimits?.sfixed32s) if (opts.limits?.sfixed32s != null && obj.sfixed32s === opts.limits.sfixed32s) { throw new MaxLengthError('Streaming decode error - repeated field "sfixed32s" had too many elements') } @@ -2243,6 +2329,7 @@ export namespace OptionExpandedTypes { break } case 12: { + decodeContext?.consume('sfixed64s', opts.totalLimits?.sfixed64s) if (opts.limits?.sfixed64s != null && obj.sfixed64s === opts.limits.sfixed64s) { throw new MaxLengthError('Streaming decode error - repeated field "sfixed64s" had too many elements') } @@ -2258,6 +2345,7 @@ export namespace OptionExpandedTypes { break } case 13: { + decodeContext?.consume('bools', opts.totalLimits?.bools) if (opts.limits?.bools != null && obj.bools === opts.limits.bools) { throw new MaxLengthError('Streaming decode error - repeated field "bools" had too many elements') } @@ -2286,7 +2374,7 @@ export namespace OptionExpandedTypes { message: 'OptionExpandedTypes' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/peer.ts b/packages/protons/test/fixtures/peer.ts index 92e8060..e23bbb4 100644 --- a/packages/protons/test/fixtures/peer.ts +++ b/packages/protons/test/fixtures/peer.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, MaxLengthError, message, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, MaxLengthError, message, streamMessage } from 'protons-runtime' import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -64,6 +64,7 @@ export namespace Peer { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { addresses: [], protocols: [], @@ -77,16 +78,19 @@ export namespace Peer { switch (tag >>> 3) { case 1: { + decodeContext?.consume('addresses', opts.totalLimits?.addresses) if (opts.limits?.addresses != null && obj.addresses.length === opts.limits.addresses) { throw new MaxLengthError('Decode error - repeated field "addresses" had too many elements') } - obj.addresses.push(Address.codec().decode(r, r.uint32(), { - limits: opts.limits?.addresses$ - })) + obj.addresses.push(Address.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'addresses', { + limits: opts.limits?.addresses$, + totalLimits: opts.totalLimits?.addresses$ + }))) break } case 2: { + decodeContext?.consume('protocols', opts.totalLimits?.protocols) if (opts.limits?.protocols != null && obj.protocols.length === opts.limits.protocols) { throw new MaxLengthError('Decode error - repeated field "protocols" had too many elements') } @@ -95,13 +99,15 @@ export namespace Peer { break } case 3: { + decodeContext?.consume('metadata', opts.totalLimits?.metadata) if (opts.limits?.metadata != null && obj.metadata.length === opts.limits.metadata) { throw new MaxLengthError('Decode error - repeated field "metadata" had too many elements') } - obj.metadata.push(Metadata.codec().decode(r, r.uint32(), { - limits: opts.limits?.metadata$ - })) + obj.metadata.push(Metadata.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'metadata', { + limits: opts.limits?.metadata$, + totalLimits: opts.totalLimits?.metadata$ + }))) break } case 4: { @@ -121,6 +127,7 @@ export namespace Peer { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { addresses: 0, protocols: 0, @@ -142,13 +149,15 @@ export namespace Peer { switch (tag >>> 3) { case 1: { + decodeContext?.consume('addresses', opts.totalLimits?.addresses) if (opts.limits?.addresses != null && obj.addresses === opts.limits.addresses) { throw new MaxLengthError('Streaming decode error - repeated field "addresses" had too many elements') } - for (const evt of Address.codec().stream(r, r.uint32(), `${prefix}addresses[].`, { - limits: opts.limits?.addresses$ - })) { + for (const evt of Address.codec().stream(r, r.uint32(), `${prefix}addresses[].`, DecodeContext.child(decodeContext, 'addresses', { + limits: opts.limits?.addresses$, + totalLimits: opts.totalLimits?.addresses$ + }))) { yield { ...evt, index: obj.addresses @@ -160,6 +169,7 @@ export namespace Peer { break } case 2: { + decodeContext?.consume('protocols', opts.totalLimits?.protocols) if (opts.limits?.protocols != null && obj.protocols === opts.limits.protocols) { throw new MaxLengthError('Streaming decode error - repeated field "protocols" had too many elements') } @@ -175,13 +185,15 @@ export namespace Peer { break } case 3: { + decodeContext?.consume('metadata', opts.totalLimits?.metadata) if (opts.limits?.metadata != null && obj.metadata === opts.limits.metadata) { throw new MaxLengthError('Streaming decode error - repeated field "metadata" had too many elements') } - for (const evt of Metadata.codec().stream(r, r.uint32(), `${prefix}metadata[].`, { - limits: opts.limits?.metadata$ - })) { + for (const evt of Metadata.codec().stream(r, r.uint32(), `${prefix}metadata[].`, DecodeContext.child(decodeContext, 'metadata', { + limits: opts.limits?.metadata$, + totalLimits: opts.totalLimits?.metadata$ + }))) { yield { ...evt, index: obj.metadata @@ -220,7 +232,7 @@ export namespace Peer { message: 'Peer' } } - }) + }, { totalLimits: true }) } return _codec @@ -410,7 +422,7 @@ export namespace Address { message: 'Address' } } - }) + }, { totalLimits: true }) } return _codec @@ -543,7 +555,7 @@ export namespace Metadata { message: 'Metadata' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/proto2.ts b/packages/protons/test/fixtures/proto2.ts index ead2b9d..a10d3d9 100644 --- a/packages/protons/test/fixtures/proto2.ts +++ b/packages/protons/test/fixtures/proto2.ts @@ -85,7 +85,7 @@ export namespace MessageWithRequired { message: 'MessageWithRequired' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/protons-options.ts b/packages/protons/test/fixtures/protons-options.ts index 2143c82..fd761c6 100644 --- a/packages/protons/test/fixtures/protons-options.ts +++ b/packages/protons/test/fixtures/protons-options.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, MaxLengthError, MaxSizeError, message, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, MaxLengthError, MaxSizeError, message, streamMessage } from 'protons-runtime' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -31,6 +31,7 @@ export namespace MessageWithSizeLimitedRepeatedField { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { repeatedField: [] } @@ -42,6 +43,7 @@ export namespace MessageWithSizeLimitedRepeatedField { switch (tag >>> 3) { case 1: { + decodeContext?.consume('repeatedField', opts.totalLimits?.repeatedField) if (opts.limits?.repeatedField != null && obj.repeatedField.length === opts.limits.repeatedField) { throw new MaxLengthError('Decode error - repeated field "repeatedField" had too many elements') } @@ -62,6 +64,7 @@ export namespace MessageWithSizeLimitedRepeatedField { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { repeatedField: 1 } @@ -81,6 +84,7 @@ export namespace MessageWithSizeLimitedRepeatedField { switch (tag >>> 3) { case 1: { + decodeContext?.consume('repeatedField', opts.totalLimits?.repeatedField) if (opts.limits?.repeatedField != null && obj.repeatedField === opts.limits.repeatedField) { throw new MaxLengthError('Streaming decode error - repeated field "repeatedField" had too many elements') } @@ -113,7 +117,7 @@ export namespace MessageWithSizeLimitedRepeatedField { message: 'MessageWithSizeLimitedRepeatedField' } } - }) + }, { totalLimits: true }) } return _codec @@ -251,7 +255,7 @@ export namespace MessageWithSizeLimitedMap { message: 'MessageWithSizeLimitedMap.MessageWithSizeLimitedMap$mapFieldEntry' } } - }) + }, { totalLimits: true }) } return _codec @@ -300,6 +304,7 @@ export namespace MessageWithSizeLimitedMap { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { mapField: new Map() } @@ -311,6 +316,7 @@ export namespace MessageWithSizeLimitedMap { switch (tag >>> 3) { case 1: { + decodeContext?.consume('mapField', opts.totalLimits?.mapField, MaxSizeError) if (opts.limits?.mapField != null && obj.mapField.size === opts.limits.mapField) { throw new MaxSizeError('Decode error - map field "mapField" had too many elements') } @@ -319,11 +325,12 @@ export namespace MessageWithSizeLimitedMap { throw new MaxSizeError('Decode error - map field "mapField" had too many elements') } - const entry = MessageWithSizeLimitedMap.MessageWithSizeLimitedMap$mapFieldEntry.codec().decode(r, r.uint32(), { + const entry = MessageWithSizeLimitedMap.MessageWithSizeLimitedMap$mapFieldEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'mapField', { limits: { value: opts.limits?.mapField$value - } - }) + }, + totalLimits: opts.totalLimits?.mapField$value == null ? undefined : { value: opts.totalLimits.mapField$value } + })) obj.mapField.set(entry.key, entry.value) break } @@ -336,6 +343,7 @@ export namespace MessageWithSizeLimitedMap { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { mapField: 1 } @@ -355,6 +363,7 @@ export namespace MessageWithSizeLimitedMap { switch (tag >>> 3) { case 1: { + decodeContext?.consume('mapField', opts.totalLimits?.mapField, MaxSizeError) if (opts.limits?.mapField != null && obj.mapField === opts.limits.mapField) { throw new MaxLengthError('Decode error - map field "mapField" had too many elements') } @@ -363,11 +372,12 @@ export namespace MessageWithSizeLimitedMap { throw new MaxLengthError('Decode error - repeated field "mapField" had too many elements') } - yield * MessageWithSizeLimitedMap.MessageWithSizeLimitedMap$mapFieldEntry.codec().stream(r, r.uint32(), `${prefix}mapField{}.`, { + yield * MessageWithSizeLimitedMap.MessageWithSizeLimitedMap$mapFieldEntry.codec().stream(r, r.uint32(), `${prefix}mapField{}.`, DecodeContext.child(decodeContext, 'mapField', { limits: { value: opts.limits?.mapField$value - } - }) + }, + totalLimits: opts.totalLimits?.mapField$value == null ? undefined : { value: opts.totalLimits.mapField$value } + })) obj.mapField++ @@ -387,7 +397,7 @@ export namespace MessageWithSizeLimitedMap { message: 'MessageWithSizeLimitedMap' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/repeated.ts b/packages/protons/test/fixtures/repeated.ts index 213ddd7..930bdbb 100644 --- a/packages/protons/test/fixtures/repeated.ts +++ b/packages/protons/test/fixtures/repeated.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, MaxLengthError, message, reader, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, MaxLengthError, message, reader, streamMessage } from 'protons-runtime' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -38,6 +38,7 @@ export namespace SubSubMessage { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { foo: [] } @@ -49,6 +50,7 @@ export namespace SubSubMessage { switch (tag >>> 3) { case 1: { + decodeContext?.consume('foo', opts.totalLimits?.foo) if (opts.limits?.foo != null && obj.foo.length === opts.limits.foo) { throw new MaxLengthError('Decode error - repeated field "foo" had too many elements') } @@ -69,6 +71,7 @@ export namespace SubSubMessage { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { foo: 0 } @@ -88,6 +91,7 @@ export namespace SubSubMessage { switch (tag >>> 3) { case 1: { + decodeContext?.consume('foo', opts.totalLimits?.foo) if (opts.limits?.foo != null && obj.foo === opts.limits.foo) { throw new MaxLengthError('Streaming decode error - repeated field "foo" had too many elements') } @@ -123,7 +127,7 @@ export namespace SubSubMessage { message: 'SubSubMessage' } } - }) + }, { totalLimits: true }) } return _codec @@ -205,6 +209,7 @@ export namespace SubMessage { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { foo: [], messages: [] @@ -217,6 +222,7 @@ export namespace SubMessage { switch (tag >>> 3) { case 1: { + decodeContext?.consume('foo', opts.totalLimits?.foo) if (opts.limits?.foo != null && obj.foo.length === opts.limits.foo) { throw new MaxLengthError('Decode error - repeated field "foo" had too many elements') } @@ -229,19 +235,22 @@ export namespace SubMessage { break } case 3: { - obj.message = SubSubMessage.codec().decode(r, r.uint32(), { - limits: opts.limits?.message - }) + obj.message = SubSubMessage.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'message', { + limits: opts.limits?.message, + totalLimits: opts.totalLimits?.message + })) break } case 4: { + decodeContext?.consume('messages', opts.totalLimits?.messages) if (opts.limits?.messages != null && obj.messages.length === opts.limits.messages) { throw new MaxLengthError('Decode error - repeated field "messages" had too many elements') } - obj.messages.push(SubSubMessage.codec().decode(r, r.uint32(), { - limits: opts.limits?.messages$ - })) + obj.messages.push(SubSubMessage.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'messages', { + limits: opts.limits?.messages$, + totalLimits: opts.totalLimits?.messages$ + }))) break } default: { @@ -253,6 +262,7 @@ export namespace SubMessage { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { foo: 0, messages: 0 @@ -273,6 +283,7 @@ export namespace SubMessage { switch (tag >>> 3) { case 1: { + decodeContext?.consume('foo', opts.totalLimits?.foo) if (opts.limits?.foo != null && obj.foo === opts.limits.foo) { throw new MaxLengthError('Streaming decode error - repeated field "foo" had too many elements') } @@ -295,20 +306,23 @@ export namespace SubMessage { break } case 3: { - yield * SubSubMessage.codec().stream(r, r.uint32(), `${prefix}message.`, { - limits: opts.limits?.message - }) + yield * SubSubMessage.codec().stream(r, r.uint32(), `${prefix}message.`, DecodeContext.child(decodeContext, 'message', { + limits: opts.limits?.message, + totalLimits: opts.totalLimits?.message + })) break } case 4: { + decodeContext?.consume('messages', opts.totalLimits?.messages) if (opts.limits?.messages != null && obj.messages === opts.limits.messages) { throw new MaxLengthError('Streaming decode error - repeated field "messages" had too many elements') } - for (const evt of SubSubMessage.codec().stream(r, r.uint32(), `${prefix}messages[].`, { - limits: opts.limits?.messages$ - })) { + for (const evt of SubSubMessage.codec().stream(r, r.uint32(), `${prefix}messages[].`, DecodeContext.child(decodeContext, 'messages', { + limits: opts.limits?.messages$, + totalLimits: opts.totalLimits?.messages$ + }))) { yield { ...evt, index: obj.messages @@ -333,7 +347,7 @@ export namespace SubMessage { message: 'SubMessage' } } - }) + }, { totalLimits: true }) } return _codec @@ -479,6 +493,7 @@ export namespace RepeatedTypes { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { number: [], limitedNumber: [], @@ -496,6 +511,7 @@ export namespace RepeatedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('number', opts.totalLimits?.number) if (opts.limits?.number != null && obj.number.length === opts.limits.number) { throw new MaxLengthError('Decode error - repeated field "number" had too many elements') } @@ -510,6 +526,7 @@ export namespace RepeatedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('limitedNumber', opts.totalLimits?.limitedNumber) if (opts.limits?.limitedNumber != null && obj.limitedNumber.length === opts.limits.limitedNumber) { throw new MaxLengthError('Decode error - repeated field "limitedNumber" had too many elements') } @@ -524,19 +541,22 @@ export namespace RepeatedTypes { break } case 3: { + decodeContext?.consume('messages', opts.totalLimits?.messages) if (opts.limits?.messages != null && obj.messages.length === opts.limits.messages) { throw new MaxLengthError('Decode error - repeated field "messages" had too many elements') } - obj.messages.push(SubMessage.codec().decode(r, r.uint32(), { - limits: opts.limits?.messages$ - })) + obj.messages.push(SubMessage.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'messages', { + limits: opts.limits?.messages$, + totalLimits: opts.totalLimits?.messages$ + }))) break } case 4: { - obj.message = SubMessage.codec().decode(r, r.uint32(), { - limits: opts.limits?.message - }) + obj.message = SubMessage.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'message', { + limits: opts.limits?.message, + totalLimits: opts.totalLimits?.message + })) break } case 5: { @@ -552,6 +572,7 @@ export namespace RepeatedTypes { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { number: 0, limitedNumber: 1, @@ -577,6 +598,7 @@ export namespace RepeatedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('number', opts.totalLimits?.number) if (opts.limits?.number != null && obj.number === opts.limits.number) { throw new MaxLengthError('Streaming decode error - repeated field "number" had too many elements') } @@ -597,6 +619,7 @@ export namespace RepeatedTypes { const r2 = reader(b) while (r2.pos < r2.len) { + decodeContext?.consume('limitedNumber', opts.totalLimits?.limitedNumber) if (opts.limits?.limitedNumber != null && obj.limitedNumber === opts.limits.limitedNumber) { throw new MaxLengthError('Streaming decode error - repeated field "limitedNumber" had too many elements') } @@ -617,13 +640,15 @@ export namespace RepeatedTypes { break } case 3: { + decodeContext?.consume('messages', opts.totalLimits?.messages) if (opts.limits?.messages != null && obj.messages === opts.limits.messages) { throw new MaxLengthError('Streaming decode error - repeated field "messages" had too many elements') } - for (const evt of SubMessage.codec().stream(r, r.uint32(), `${prefix}messages[].`, { - limits: opts.limits?.messages$ - })) { + for (const evt of SubMessage.codec().stream(r, r.uint32(), `${prefix}messages[].`, DecodeContext.child(decodeContext, 'messages', { + limits: opts.limits?.messages$, + totalLimits: opts.totalLimits?.messages$ + }))) { yield { ...evt, index: obj.messages @@ -635,9 +660,10 @@ export namespace RepeatedTypes { break } case 4: { - yield * SubMessage.codec().stream(r, r.uint32(), `${prefix}message.`, { - limits: opts.limits?.message - }) + yield * SubMessage.codec().stream(r, r.uint32(), `${prefix}message.`, DecodeContext.child(decodeContext, 'message', { + limits: opts.limits?.message, + totalLimits: opts.totalLimits?.message + })) break } @@ -662,7 +688,7 @@ export namespace RepeatedTypes { message: 'RepeatedTypes' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/singular.ts b/packages/protons/test/fixtures/singular.ts index 9d1b391..e62b8af 100644 --- a/packages/protons/test/fixtures/singular.ts +++ b/packages/protons/test/fixtures/singular.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, enumeration, message, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, enumeration, message, streamMessage } from 'protons-runtime' import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -125,7 +125,7 @@ export namespace SingularSubMessage { message: 'SingularSubMessage' } } - }) + }, { totalLimits: true }) } return _codec @@ -293,6 +293,7 @@ export namespace Singular { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { double: 0, float: 0, @@ -383,9 +384,10 @@ export namespace Singular { break } case 17: { - obj.subMessage = SingularSubMessage.codec().decode(r, r.uint32(), { - limits: opts.limits?.subMessage - }) + obj.subMessage = SingularSubMessage.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'subMessage', { + limits: opts.limits?.subMessage, + totalLimits: opts.totalLimits?.subMessage + })) break } default: { @@ -397,6 +399,7 @@ export namespace Singular { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const end = length == null ? r.len : r.pos + length if (prefix !== '.') { @@ -524,9 +527,10 @@ export namespace Singular { break } case 17: { - yield * SingularSubMessage.codec().stream(r, r.uint32(), `${prefix}subMessage.`, { - limits: opts.limits?.subMessage - }) + yield * SingularSubMessage.codec().stream(r, r.uint32(), `${prefix}subMessage.`, DecodeContext.child(decodeContext, 'subMessage', { + limits: opts.limits?.subMessage, + totalLimits: opts.totalLimits?.subMessage + })) break } @@ -544,7 +548,7 @@ export namespace Singular { message: 'Singular' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/streaming.ts b/packages/protons/test/fixtures/streaming.ts index f8fdf9c..9577867 100644 --- a/packages/protons/test/fixtures/streaming.ts +++ b/packages/protons/test/fixtures/streaming.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, enumeration, MaxLengthError, MaxSizeError, message, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, enumeration, MaxLengthError, MaxSizeError, message, streamMessage } from 'protons-runtime' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -45,6 +45,7 @@ export namespace MessageWithArrayField { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { arr: [] } @@ -64,6 +65,7 @@ export namespace MessageWithArrayField { break } case 3: { + decodeContext?.consume('arr', opts.totalLimits?.arr) if (opts.limits?.arr != null && obj.arr.length === opts.limits.arr) { throw new MaxLengthError('Decode error - repeated field "arr" had too many elements') } @@ -80,6 +82,7 @@ export namespace MessageWithArrayField { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { arr: 0 } @@ -113,6 +116,7 @@ export namespace MessageWithArrayField { break } case 3: { + decodeContext?.consume('arr', opts.totalLimits?.arr) if (opts.limits?.arr != null && obj.arr === opts.limits.arr) { throw new MaxLengthError('Streaming decode error - repeated field "arr" had too many elements') } @@ -141,7 +145,7 @@ export namespace MessageWithArrayField { message: 'MessageWithArrayField' } } - }) + }, { totalLimits: true }) } return _codec @@ -261,7 +265,7 @@ export namespace NestedMessage { message: 'NestedMessage' } } - }) + }, { totalLimits: true }) } return _codec @@ -319,6 +323,7 @@ export namespace MessageWithNestedMessage { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { field1: false } @@ -334,9 +339,10 @@ export namespace MessageWithNestedMessage { break } case 2: { - obj.nestedMessage = NestedMessage.codec().decode(r, r.uint32(), { - limits: opts.limits?.nestedMessage - }) + obj.nestedMessage = NestedMessage.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'nestedMessage', { + limits: opts.limits?.nestedMessage, + totalLimits: opts.totalLimits?.nestedMessage + })) break } default: { @@ -348,6 +354,7 @@ export namespace MessageWithNestedMessage { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const end = length == null ? r.len : r.pos + length if (prefix !== '.') { @@ -370,9 +377,10 @@ export namespace MessageWithNestedMessage { break } case 2: { - yield * NestedMessage.codec().stream(r, r.uint32(), `${prefix}nestedMessage.`, { - limits: opts.limits?.nestedMessage - }) + yield * NestedMessage.codec().stream(r, r.uint32(), `${prefix}nestedMessage.`, DecodeContext.child(decodeContext, 'nestedMessage', { + limits: opts.limits?.nestedMessage, + totalLimits: opts.totalLimits?.nestedMessage + })) break } @@ -390,7 +398,7 @@ export namespace MessageWithNestedMessage { message: 'MessageWithNestedMessage' } } - }) + }, { totalLimits: true }) } return _codec @@ -463,6 +471,7 @@ export namespace MessageWithDeeplyNestedMessage { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { field1: false } @@ -478,9 +487,10 @@ export namespace MessageWithDeeplyNestedMessage { break } case 2: { - obj.nestedMessage = MessageWithNestedMessage.codec().decode(r, r.uint32(), { - limits: opts.limits?.nestedMessage - }) + obj.nestedMessage = MessageWithNestedMessage.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'nestedMessage', { + limits: opts.limits?.nestedMessage, + totalLimits: opts.totalLimits?.nestedMessage + })) break } default: { @@ -492,6 +502,7 @@ export namespace MessageWithDeeplyNestedMessage { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const end = length == null ? r.len : r.pos + length if (prefix !== '.') { @@ -514,9 +525,10 @@ export namespace MessageWithDeeplyNestedMessage { break } case 2: { - yield * MessageWithNestedMessage.codec().stream(r, r.uint32(), `${prefix}nestedMessage.`, { - limits: opts.limits?.nestedMessage - }) + yield * MessageWithNestedMessage.codec().stream(r, r.uint32(), `${prefix}nestedMessage.`, DecodeContext.child(decodeContext, 'nestedMessage', { + limits: opts.limits?.nestedMessage, + totalLimits: opts.totalLimits?.nestedMessage + })) break } @@ -534,7 +546,7 @@ export namespace MessageWithDeeplyNestedMessage { message: 'MessageWithDeeplyNestedMessage' } } - }) + }, { totalLimits: true }) } return _codec @@ -624,6 +636,7 @@ export namespace MessageWithRepeatedMessage { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { field1: false, nestedMessages: [] @@ -640,13 +653,15 @@ export namespace MessageWithRepeatedMessage { break } case 2: { + decodeContext?.consume('nestedMessages', opts.totalLimits?.nestedMessages) if (opts.limits?.nestedMessages != null && obj.nestedMessages.length === opts.limits.nestedMessages) { throw new MaxLengthError('Decode error - repeated field "nestedMessages" had too many elements') } - obj.nestedMessages.push(NestedMessage.codec().decode(r, r.uint32(), { - limits: opts.limits?.nestedMessages$ - })) + obj.nestedMessages.push(NestedMessage.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'nestedMessages', { + limits: opts.limits?.nestedMessages$, + totalLimits: opts.totalLimits?.nestedMessages$ + }))) break } default: { @@ -658,6 +673,7 @@ export namespace MessageWithRepeatedMessage { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { nestedMessages: 0 } @@ -684,13 +700,15 @@ export namespace MessageWithRepeatedMessage { break } case 2: { + decodeContext?.consume('nestedMessages', opts.totalLimits?.nestedMessages) if (opts.limits?.nestedMessages != null && obj.nestedMessages === opts.limits.nestedMessages) { throw new MaxLengthError('Streaming decode error - repeated field "nestedMessages" had too many elements') } - for (const evt of NestedMessage.codec().stream(r, r.uint32(), `${prefix}nestedMessages[].`, { - limits: opts.limits?.nestedMessages$ - })) { + for (const evt of NestedMessage.codec().stream(r, r.uint32(), `${prefix}nestedMessages[].`, DecodeContext.child(decodeContext, 'nestedMessages', { + limits: opts.limits?.nestedMessages$, + totalLimits: opts.totalLimits?.nestedMessages$ + }))) { yield { ...evt, index: obj.nestedMessages @@ -715,7 +733,7 @@ export namespace MessageWithRepeatedMessage { message: 'MessageWithRepeatedMessage' } } - }) + }, { totalLimits: true }) } return _codec @@ -804,6 +822,7 @@ export namespace MessageWithMapMessage { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { key: '' } @@ -819,9 +838,10 @@ export namespace MessageWithMapMessage { break } case 2: { - obj.value = NestedMessage.codec().decode(r, r.uint32(), { - limits: opts.limits?.value - }) + obj.value = NestedMessage.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'value', { + limits: opts.limits?.value, + totalLimits: opts.totalLimits?.value + })) break } default: { @@ -833,6 +853,7 @@ export namespace MessageWithMapMessage { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const end = length == null ? r.len : r.pos + length if (prefix !== '.') { @@ -855,9 +876,10 @@ export namespace MessageWithMapMessage { break } case 2: { - yield * NestedMessage.codec().stream(r, r.uint32(), `${prefix}value.`, { - limits: opts.limits?.value - }) + yield * NestedMessage.codec().stream(r, r.uint32(), `${prefix}value.`, DecodeContext.child(decodeContext, 'value', { + limits: opts.limits?.value, + totalLimits: opts.totalLimits?.value + })) break } @@ -875,7 +897,7 @@ export namespace MessageWithMapMessage { message: 'MessageWithMapMessage.MessageWithMapMessage$nestedMessagesEntry' } } - }) + }, { totalLimits: true }) } return _codec @@ -939,6 +961,7 @@ export namespace MessageWithMapMessage { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { field1: false, nestedMessages: new Map() @@ -955,15 +978,17 @@ export namespace MessageWithMapMessage { break } case 2: { + decodeContext?.consume('nestedMessages', opts.totalLimits?.nestedMessages, MaxSizeError) if (opts.limits?.nestedMessages != null && obj.nestedMessages.size === opts.limits.nestedMessages) { throw new MaxSizeError('Decode error - map field "nestedMessages" had too many elements') } - const entry = MessageWithMapMessage.MessageWithMapMessage$nestedMessagesEntry.codec().decode(r, r.uint32(), { + const entry = MessageWithMapMessage.MessageWithMapMessage$nestedMessagesEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'nestedMessages', { limits: { value: opts.limits?.nestedMessages$value - } - }) + }, + totalLimits: opts.totalLimits?.nestedMessages$value == null ? undefined : { value: opts.totalLimits.nestedMessages$value } + })) obj.nestedMessages.set(entry.key, entry.value) break } @@ -976,6 +1001,7 @@ export namespace MessageWithMapMessage { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { nestedMessages: 0 } @@ -1002,15 +1028,17 @@ export namespace MessageWithMapMessage { break } case 2: { + decodeContext?.consume('nestedMessages', opts.totalLimits?.nestedMessages, MaxSizeError) if (opts.limits?.nestedMessages != null && obj.nestedMessages === opts.limits.nestedMessages) { throw new MaxLengthError('Decode error - map field "nestedMessages" had too many elements') } - yield * MessageWithMapMessage.MessageWithMapMessage$nestedMessagesEntry.codec().stream(r, r.uint32(), `${prefix}nestedMessages{}.`, { + yield * MessageWithMapMessage.MessageWithMapMessage$nestedMessagesEntry.codec().stream(r, r.uint32(), `${prefix}nestedMessages{}.`, DecodeContext.child(decodeContext, 'nestedMessages', { limits: { value: opts.limits?.nestedMessages$value - } - }) + }, + totalLimits: opts.totalLimits?.nestedMessages$value == null ? undefined : { value: opts.totalLimits.nestedMessages$value } + })) obj.nestedMessages++ @@ -1030,7 +1058,7 @@ export namespace MessageWithMapMessage { message: 'MessageWithMapMessage' } } - }) + }, { totalLimits: true }) } return _codec @@ -1189,7 +1217,7 @@ export namespace MessageWithPrimitiveMap { message: 'MessageWithPrimitiveMap.MessageWithPrimitiveMap$nestedStringsEntry' } } - }) + }, { totalLimits: true }) } return _codec @@ -1243,6 +1271,7 @@ export namespace MessageWithPrimitiveMap { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { field1: false, nestedStrings: new Map() @@ -1259,15 +1288,17 @@ export namespace MessageWithPrimitiveMap { break } case 2: { + decodeContext?.consume('nestedStrings', opts.totalLimits?.nestedStrings, MaxSizeError) if (opts.limits?.nestedStrings != null && obj.nestedStrings.size === opts.limits.nestedStrings) { throw new MaxSizeError('Decode error - map field "nestedStrings" had too many elements') } - const entry = MessageWithPrimitiveMap.MessageWithPrimitiveMap$nestedStringsEntry.codec().decode(r, r.uint32(), { + const entry = MessageWithPrimitiveMap.MessageWithPrimitiveMap$nestedStringsEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'nestedStrings', { limits: { value: opts.limits?.nestedStrings$value - } - }) + }, + totalLimits: opts.totalLimits?.nestedStrings$value == null ? undefined : { value: opts.totalLimits.nestedStrings$value } + })) obj.nestedStrings.set(entry.key, entry.value) break } @@ -1280,6 +1311,7 @@ export namespace MessageWithPrimitiveMap { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { nestedStrings: 0 } @@ -1306,15 +1338,17 @@ export namespace MessageWithPrimitiveMap { break } case 2: { + decodeContext?.consume('nestedStrings', opts.totalLimits?.nestedStrings, MaxSizeError) if (opts.limits?.nestedStrings != null && obj.nestedStrings === opts.limits.nestedStrings) { throw new MaxLengthError('Decode error - map field "nestedStrings" had too many elements') } - yield * MessageWithPrimitiveMap.MessageWithPrimitiveMap$nestedStringsEntry.codec().stream(r, r.uint32(), `${prefix}nestedStrings{}.`, { + yield * MessageWithPrimitiveMap.MessageWithPrimitiveMap$nestedStringsEntry.codec().stream(r, r.uint32(), `${prefix}nestedStrings{}.`, DecodeContext.child(decodeContext, 'nestedStrings', { limits: { value: opts.limits?.nestedStrings$value - } - }) + }, + totalLimits: opts.totalLimits?.nestedStrings$value == null ? undefined : { value: opts.totalLimits.nestedStrings$value } + })) obj.nestedStrings++ @@ -1334,7 +1368,7 @@ export namespace MessageWithPrimitiveMap { message: 'MessageWithPrimitiveMap' } } - }) + }, { totalLimits: true }) } return _codec @@ -1418,6 +1452,7 @@ export namespace MessageWithRepeatedEnums { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { field1: false, enums: [] @@ -1434,6 +1469,7 @@ export namespace MessageWithRepeatedEnums { break } case 2: { + decodeContext?.consume('enums', opts.totalLimits?.enums) if (opts.limits?.enums != null && obj.enums.length === opts.limits.enums) { throw new MaxLengthError('Decode error - repeated field "enums" had too many elements') } @@ -1450,6 +1486,7 @@ export namespace MessageWithRepeatedEnums { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { enums: 0 } @@ -1476,6 +1513,7 @@ export namespace MessageWithRepeatedEnums { break } case 2: { + decodeContext?.consume('enums', opts.totalLimits?.enums) if (opts.limits?.enums != null && obj.enums === opts.limits.enums) { throw new MaxLengthError('Streaming decode error - repeated field "enums" had too many elements') } @@ -1504,7 +1542,7 @@ export namespace MessageWithRepeatedEnums { message: 'MessageWithRepeatedEnums' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/test.ts b/packages/protons/test/fixtures/test.ts index ab44201..27e3f50 100644 --- a/packages/protons/test/fixtures/test.ts +++ b/packages/protons/test/fixtures/test.ts @@ -1,4 +1,4 @@ -import { decodeMessage, encodeMessage, enumeration, MaxLengthError, message, streamMessage } from 'protons-runtime' +import { DecodeContext, decodeMessage, encodeMessage, enumeration, MaxLengthError, message, streamMessage } from 'protons-runtime' import type { Codec, DecodeOptions } from 'protons-runtime' import type { Uint8ArrayList } from 'uint8arraylist' @@ -103,7 +103,7 @@ export namespace SubMessage { message: 'SubMessage' } } - }) + }, { totalLimits: true }) } return _codec @@ -275,6 +275,7 @@ export namespace AllTheTypes { w.ldelim() } }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) const obj: any = { field14: [] } @@ -334,12 +335,14 @@ export namespace AllTheTypes { break } case 13: { - obj.field13 = SubMessage.codec().decode(r, r.uint32(), { - limits: opts.limits?.field13 - }) + obj.field13 = SubMessage.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'field13', { + limits: opts.limits?.field13, + totalLimits: opts.totalLimits?.field13 + })) break } case 14: { + decodeContext?.consume('field14', opts.totalLimits?.field14) if (opts.limits?.field14 != null && obj.field14.length === opts.limits.field14) { throw new MaxLengthError('Decode error - repeated field "field14" had too many elements') } @@ -372,6 +375,7 @@ export namespace AllTheTypes { return obj }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) const obj = { field14: 0 } @@ -475,13 +479,15 @@ export namespace AllTheTypes { break } case 13: { - yield * SubMessage.codec().stream(r, r.uint32(), `${prefix}field13.`, { - limits: opts.limits?.field13 - }) + yield * SubMessage.codec().stream(r, r.uint32(), `${prefix}field13.`, DecodeContext.child(decodeContext, 'field13', { + limits: opts.limits?.field13, + totalLimits: opts.totalLimits?.field13 + })) break } case 14: { + decodeContext?.consume('field14', opts.totalLimits?.field14) if (opts.limits?.field14 != null && obj.field14 === opts.limits.field14) { throw new MaxLengthError('Streaming decode error - repeated field "field14" had too many elements') } @@ -538,7 +544,7 @@ export namespace AllTheTypes { message: 'AllTheTypes' } } - }) + }, { totalLimits: true }) } return _codec diff --git a/packages/protons/test/fixtures/total-limits.proto b/packages/protons/test/fixtures/total-limits.proto new file mode 100644 index 0000000..cd30e7b --- /dev/null +++ b/packages/protons/test/fixtures/total-limits.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +message LimitedBatch { + repeated LimitedEntry entries = 1; + LimitedEntry single = 2; + map byKey = 3; + repeated LimitedEntry others = 4; +} + +message LimitedEntry { + repeated bytes ids = 1; + repeated uint32 numbers = 2; + map values = 3; + repeated uint32 expanded = 4 [packed = false]; +} diff --git a/packages/protons/test/fixtures/total-limits.ts b/packages/protons/test/fixtures/total-limits.ts new file mode 100644 index 0000000..50412fa --- /dev/null +++ b/packages/protons/test/fixtures/total-limits.ts @@ -0,0 +1,978 @@ +import { DecodeContext, decodeMessage, encodeMessage, MaxLengthError, MaxSizeError, message, reader, streamMessage } from 'protons-runtime' +import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' +import type { Codec, DecodeOptions } from 'protons-runtime' +import type { Uint8ArrayList } from 'uint8arraylist' + +export interface LimitedBatch { + entries: LimitedEntry[] + single?: LimitedEntry + byKey: Map + others: LimitedEntry[] +} + +export interface LimitedBatchInput { + entries?: LimitedEntryInput[] + single?: LimitedEntryInput + byKey?: Map + others?: LimitedEntryInput[] +} + +export namespace LimitedBatch { + export interface LimitedBatch$byKeyEntry { + key: string + value?: LimitedEntry + } + + export interface LimitedBatch$byKeyEntryInput { + key?: string + value?: LimitedEntryInput + } + + export namespace LimitedBatch$byKeyEntry { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if ((obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key) + } + + if (obj.value != null) { + w.uint32(18) + LimitedEntry.codec().encode(obj.value, w) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) + const obj: any = { + key: '' + } + + const end = length == null ? r.len : r.pos + length + + while (r.pos < end) { + const tag = r.uint32() + + switch (tag >>> 3) { + case 1: { + obj.key = r.string() + break + } + case 2: { + obj.value = LimitedEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'value', { + limits: opts.limits?.value, + totalLimits: opts.totalLimits?.value + })) + break + } + default: { + r.skipType(tag & 7) + break + } + } + } + + return obj + }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) + const end = length == null ? r.len : r.pos + length + + if (prefix !== '.') { + yield { + field: prefix.endsWith('.') ? prefix.substring(0, prefix.length - 1) : prefix, + type: 'start', + message: 'LimitedBatch.LimitedBatch$byKeyEntry' + } + } + + while (r.pos < end) { + const tag = r.uint32() + + switch (tag >>> 3) { + case 1: { + yield { + field: `${prefix}key`, + value: r.string() + } + break + } + case 2: { + yield * LimitedEntry.codec().stream(r, r.uint32(), `${prefix}value.`, DecodeContext.child(decodeContext, 'value', { + limits: opts.limits?.value, + totalLimits: opts.totalLimits?.value + })) + + break + } + default: { + r.skipType(tag & 7) + break + } + } + } + + if (prefix !== '.') { + yield { + field: prefix.endsWith('.') ? prefix.substring(0, prefix.length - 1) : prefix, + type: 'end', + message: 'LimitedBatch.LimitedBatch$byKeyEntry' + } + } + }, { totalLimits: true }) + } + + return _codec + } + + export interface LimitedBatch$byKeyEntryKeyFieldEvent { + field: '.key' + value: string + } + + export interface LimitedBatch$byKeyEntryValueMessageStart { + field: '.value' + type: 'start' + } + + export interface LimitedBatch$byKeyEntryValueMessageEnd { + field: '.value' + type: 'end' + } + + export interface LimitedBatch$byKeyEntryValueIdsFieldEvent { + field: '.value.ids[]' + index: number + value: Uint8Array + } + + export interface LimitedBatch$byKeyEntryValueNumbersFieldEvent { + field: '.value.numbers[]' + index: number + value: number + } + + export interface LimitedBatch$byKeyEntryValueValuesFieldEvent { + field: '.value.values{}' + key: string + value: Uint8Array + } + + export interface LimitedBatch$byKeyEntryValueExpandedFieldEvent { + field: '.value.expanded[]' + index: number + value: number + } + + export function encode (obj: LimitedBatch$byKeyEntryInput): Uint8Array { + return encodeMessage(obj, LimitedBatch$byKeyEntry.codec()) + } + + export function decode (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): LimitedBatch$byKeyEntry { + return decodeMessage(buf, LimitedBatch$byKeyEntry.codec(), opts) + } + + export function stream (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Generator { + return streamMessage(buf, LimitedBatch$byKeyEntry.codec(), opts) + } + } + + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.entries != null && obj.entries.length > 0) { + for (const value of obj.entries) { + w.uint32(10) + LimitedEntry.codec().encode(value, w) + } + } + + if (obj.single != null) { + w.uint32(18) + LimitedEntry.codec().encode(obj.single, w) + } + + if (obj.byKey != null && obj.byKey.size > 0) { + for (const [key, value] of obj.byKey.entries()) { + w.uint32(26) + LimitedBatch.LimitedBatch$byKeyEntry.codec().encode({ key, value }, w) + } + } + + if (obj.others != null && obj.others.length > 0) { + for (const value of obj.others) { + w.uint32(34) + LimitedEntry.codec().encode(value, w) + } + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) + const obj: any = { + entries: [], + byKey: new Map(), + others: [] + } + + const end = length == null ? r.len : r.pos + length + + while (r.pos < end) { + const tag = r.uint32() + + switch (tag >>> 3) { + case 1: { + decodeContext?.consume('entries', opts.totalLimits?.entries) + if (opts.limits?.entries != null && obj.entries.length === opts.limits.entries) { + throw new MaxLengthError('Decode error - repeated field "entries" had too many elements') + } + + obj.entries.push(LimitedEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'entries', { + limits: opts.limits?.entries$, + totalLimits: opts.totalLimits?.entries$ + }))) + break + } + case 2: { + obj.single = LimitedEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'single', { + limits: opts.limits?.single, + totalLimits: opts.totalLimits?.single + })) + break + } + case 3: { + decodeContext?.consume('byKey', opts.totalLimits?.byKey, MaxSizeError) + if (opts.limits?.byKey != null && obj.byKey.size === opts.limits.byKey) { + throw new MaxSizeError('Decode error - map field "byKey" had too many elements') + } + + const entry = LimitedBatch.LimitedBatch$byKeyEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'byKey', { + limits: { + value: opts.limits?.byKey$value + }, + totalLimits: opts.totalLimits?.byKey$value == null ? undefined : { value: opts.totalLimits.byKey$value } + })) + obj.byKey.set(entry.key, entry.value) + break + } + case 4: { + decodeContext?.consume('others', opts.totalLimits?.others) + if (opts.limits?.others != null && obj.others.length === opts.limits.others) { + throw new MaxLengthError('Decode error - repeated field "others" had too many elements') + } + + obj.others.push(LimitedEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'others', { + limits: opts.limits?.others$, + totalLimits: opts.totalLimits?.others$ + }))) + break + } + default: { + r.skipType(tag & 7) + break + } + } + } + + return obj + }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) + const obj = { + entries: 0, + byKey: 0, + others: 0 + } + + const end = length == null ? r.len : r.pos + length + + if (prefix !== '.') { + yield { + field: prefix.endsWith('.') ? prefix.substring(0, prefix.length - 1) : prefix, + type: 'start', + message: 'LimitedBatch' + } + } + + while (r.pos < end) { + const tag = r.uint32() + + switch (tag >>> 3) { + case 1: { + decodeContext?.consume('entries', opts.totalLimits?.entries) + if (opts.limits?.entries != null && obj.entries === opts.limits.entries) { + throw new MaxLengthError('Streaming decode error - repeated field "entries" had too many elements') + } + + for (const evt of LimitedEntry.codec().stream(r, r.uint32(), `${prefix}entries[].`, DecodeContext.child(decodeContext, 'entries', { + limits: opts.limits?.entries$, + totalLimits: opts.totalLimits?.entries$ + }))) { + yield { + ...evt, + index: obj.entries + } + } + + obj.entries++ + + break + } + case 2: { + yield * LimitedEntry.codec().stream(r, r.uint32(), `${prefix}single.`, DecodeContext.child(decodeContext, 'single', { + limits: opts.limits?.single, + totalLimits: opts.totalLimits?.single + })) + + break + } + case 3: { + decodeContext?.consume('byKey', opts.totalLimits?.byKey, MaxSizeError) + if (opts.limits?.byKey != null && obj.byKey === opts.limits.byKey) { + throw new MaxLengthError('Decode error - map field "byKey" had too many elements') + } + + yield * LimitedBatch.LimitedBatch$byKeyEntry.codec().stream(r, r.uint32(), `${prefix}byKey{}.`, DecodeContext.child(decodeContext, 'byKey', { + limits: { + value: opts.limits?.byKey$value + }, + totalLimits: opts.totalLimits?.byKey$value == null ? undefined : { value: opts.totalLimits.byKey$value } + })) + + obj.byKey++ + + break + } + case 4: { + decodeContext?.consume('others', opts.totalLimits?.others) + if (opts.limits?.others != null && obj.others === opts.limits.others) { + throw new MaxLengthError('Streaming decode error - repeated field "others" had too many elements') + } + + for (const evt of LimitedEntry.codec().stream(r, r.uint32(), `${prefix}others[].`, DecodeContext.child(decodeContext, 'others', { + limits: opts.limits?.others$, + totalLimits: opts.totalLimits?.others$ + }))) { + yield { + ...evt, + index: obj.others + } + } + + obj.others++ + + break + } + default: { + r.skipType(tag & 7) + break + } + } + } + + if (prefix !== '.') { + yield { + field: prefix.endsWith('.') ? prefix.substring(0, prefix.length - 1) : prefix, + type: 'end', + message: 'LimitedBatch' + } + } + }, { totalLimits: true }) + } + + return _codec + } + + export interface LimitedBatchEntriesIdsFieldEvent { + field: '.entries[].ids[]' + index: number + value: Uint8Array + } + + export interface LimitedBatchEntriesNumbersFieldEvent { + field: '.entries[].numbers[]' + index: number + value: number + } + + export interface LimitedBatchEntriesValuesFieldEvent { + field: '.entries[].values{}' + key: string + value: Uint8Array + index: number + } + + export interface LimitedBatchEntriesExpandedFieldEvent { + field: '.entries[].expanded[]' + index: number + value: number + } + + export interface LimitedBatchEntriesMessageStartEvent { + field: '.entries[]' + index: number + type: 'start' + message: string + } + + export interface LimitedBatchEntriesMessageEndEvent { + field: '.entries[]' + index: number + type: 'end' + message: string + } + + export interface LimitedBatchSingleMessageStart { + field: '.single' + type: 'start' + } + + export interface LimitedBatchSingleMessageEnd { + field: '.single' + type: 'end' + } + + export interface LimitedBatchSingleIdsFieldEvent { + field: '.single.ids[]' + index: number + value: Uint8Array + } + + export interface LimitedBatchSingleNumbersFieldEvent { + field: '.single.numbers[]' + index: number + value: number + } + + export interface LimitedBatchSingleValuesFieldEvent { + field: '.single.values{}' + key: string + value: Uint8Array + } + + export interface LimitedBatchSingleExpandedFieldEvent { + field: '.single.expanded[]' + index: number + value: number + } + + export interface LimitedBatchByKeyIdsFieldEvent { + field: '.byKey{}.ids[]' + index: number + value: LimitedEntry + key: string + } + + export interface LimitedBatchByKeyNumbersFieldEvent { + field: '.byKey{}.numbers[]' + index: number + value: LimitedEntry + key: string + } + + export interface LimitedBatchByKeyValuesFieldEvent { + field: '.byKey{}.values{}' + key: string + value: LimitedEntry + } + + export interface LimitedBatchByKeyExpandedFieldEvent { + field: '.byKey{}.expanded[]' + index: number + value: LimitedEntry + key: string + } + + export interface LimitedBatchByKeyMessageStartEvent { + field: '.byKey{}' + key: string + type: 'start' + message: string + } + + export interface LimitedBatchByKeyMessageEndEvent { + field: '.byKey{}' + key: string + type: 'end' + message: string + } + + export interface LimitedBatchOthersIdsFieldEvent { + field: '.others[].ids[]' + index: number + value: Uint8Array + } + + export interface LimitedBatchOthersNumbersFieldEvent { + field: '.others[].numbers[]' + index: number + value: number + } + + export interface LimitedBatchOthersValuesFieldEvent { + field: '.others[].values{}' + key: string + value: Uint8Array + index: number + } + + export interface LimitedBatchOthersExpandedFieldEvent { + field: '.others[].expanded[]' + index: number + value: number + } + + export interface LimitedBatchOthersMessageStartEvent { + field: '.others[]' + index: number + type: 'start' + message: string + } + + export interface LimitedBatchOthersMessageEndEvent { + field: '.others[]' + index: number + type: 'end' + message: string + } + + export function encode (obj: LimitedBatchInput): Uint8Array { + return encodeMessage(obj, LimitedBatch.codec()) + } + + export function decode (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): LimitedBatch { + return decodeMessage(buf, LimitedBatch.codec(), opts) + } + + export function stream (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Generator { + return streamMessage(buf, LimitedBatch.codec(), opts) + } +} + +export interface LimitedEntry { + ids: Uint8Array[] + numbers: number[] + values: Map> + expanded: number[] +} + +export interface LimitedEntryInput { + ids?: Uint8Array[] + numbers?: number[] + values?: Map + expanded?: number[] +} + +export namespace LimitedEntry { + export interface LimitedEntry$valuesEntry { + key: string + value: Uint8Array + } + + export interface LimitedEntry$valuesEntryInput { + key?: string + value?: Uint8Array + } + + export namespace LimitedEntry$valuesEntry { + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if ((obj.key != null && obj.key !== '')) { + w.uint32(10) + w.string(obj.key) + } + + if ((obj.value != null && obj.value.byteLength > 0)) { + w.uint32(18) + w.bytes(obj.value) + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (r, length) => { + const obj: any = { + key: '', + value: uint8ArrayAlloc(0) + } + + const end = length == null ? r.len : r.pos + length + + while (r.pos < end) { + const tag = r.uint32() + + switch (tag >>> 3) { + case 1: { + obj.key = r.string() + break + } + case 2: { + obj.value = r.bytes() + break + } + default: { + r.skipType(tag & 7) + break + } + } + } + + return obj + }, function * (r, length, prefix) { + const end = length == null ? r.len : r.pos + length + + if (prefix !== '.') { + yield { + field: prefix.endsWith('.') ? prefix.substring(0, prefix.length - 1) : prefix, + type: 'start', + message: 'LimitedEntry.LimitedEntry$valuesEntry' + } + } + + while (r.pos < end) { + const tag = r.uint32() + + switch (tag >>> 3) { + case 1: { + yield { + field: `${prefix}key`, + value: r.string() + } + break + } + case 2: { + yield { + field: `${prefix}value`, + value: r.bytes() + } + break + } + default: { + r.skipType(tag & 7) + break + } + } + } + + if (prefix !== '.') { + yield { + field: prefix.endsWith('.') ? prefix.substring(0, prefix.length - 1) : prefix, + type: 'end', + message: 'LimitedEntry.LimitedEntry$valuesEntry' + } + } + }, { totalLimits: true }) + } + + return _codec + } + + export interface LimitedEntry$valuesEntryKeyFieldEvent { + field: '.key' + value: string + } + + export interface LimitedEntry$valuesEntryValueFieldEvent { + field: '.value' + value: Uint8Array + } + + export function encode (obj: LimitedEntry$valuesEntryInput): Uint8Array { + return encodeMessage(obj, LimitedEntry$valuesEntry.codec()) + } + + export function decode (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): LimitedEntry$valuesEntry { + return decodeMessage(buf, LimitedEntry$valuesEntry.codec(), opts) + } + + export function stream (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Generator { + return streamMessage(buf, LimitedEntry$valuesEntry.codec(), opts) + } + } + + let _codec: Codec + + export const codec = (): Codec => { + if (_codec == null) { + _codec = message((obj, w, opts = {}) => { + if (opts.lengthDelimited !== false) { + w.fork() + } + + if (obj.ids != null && obj.ids.length > 0) { + for (const value of obj.ids) { + w.uint32(10) + w.bytes(value) + } + } + + if (obj.numbers != null && obj.numbers.length > 0) { + w.uint32(18) + w.fork() + + for (const value of obj.numbers) { + w.uint32(value) + } + + w.ldelim() + } + + if (obj.values != null && obj.values.size > 0) { + for (const [key, value] of obj.values.entries()) { + w.uint32(26) + LimitedEntry.LimitedEntry$valuesEntry.codec().encode({ key, value }, w) + } + } + + if (obj.expanded != null && obj.expanded.length > 0) { + for (const value of obj.expanded) { + w.uint32(32) + w.uint32(value) + } + } + + if (opts.lengthDelimited !== false) { + w.ldelim() + } + }, (r, length, opts = {}) => { + const decodeContext = DecodeContext.get(opts) + const obj: any = { + ids: [], + numbers: [], + values: new Map>(), + expanded: [] + } + + const end = length == null ? r.len : r.pos + length + + while (r.pos < end) { + const tag = r.uint32() + + switch (tag >>> 3) { + case 1: { + decodeContext?.consume('ids', opts.totalLimits?.ids) + if (opts.limits?.ids != null && obj.ids.length === opts.limits.ids) { + throw new MaxLengthError('Decode error - repeated field "ids" had too many elements') + } + + obj.ids.push(r.bytes()) + break + } + case 2: { + const b = r.bytes() + const r2 = reader(b) + + while (r2.pos < r2.len) { + decodeContext?.consume('numbers', opts.totalLimits?.numbers) + if (opts.limits?.numbers != null && obj.numbers.length === opts.limits.numbers) { + throw new MaxLengthError('Decode error - repeated field "numbers" had too many elements') + } + + obj.numbers.push(r2.uint32()) + } + + break + } + case 3: { + decodeContext?.consume('values', opts.totalLimits?.values, MaxSizeError) + if (opts.limits?.values != null && obj.values.size === opts.limits.values) { + throw new MaxSizeError('Decode error - map field "values" had too many elements') + } + + const entry = LimitedEntry.LimitedEntry$valuesEntry.codec().decode(r, r.uint32(), DecodeContext.child(decodeContext, 'values', { + limits: { + value: opts.limits?.values$value + }, + totalLimits: opts.totalLimits?.values$value == null ? undefined : { value: opts.totalLimits.values$value } + })) + obj.values.set(entry.key, entry.value) + break + } + case 4: { + decodeContext?.consume('expanded', opts.totalLimits?.expanded) + if (opts.limits?.expanded != null && obj.expanded.length === opts.limits.expanded) { + throw new MaxLengthError('Decode error - repeated field "expanded" had too many elements') + } + + obj.expanded.push(r.uint32()) + break + } + default: { + r.skipType(tag & 7) + break + } + } + } + + return obj + }, function * (r, length, prefix, opts = {}) { + const decodeContext = DecodeContext.get(opts) + const obj = { + ids: 0, + numbers: 0, + values: 0, + expanded: 0 + } + + const end = length == null ? r.len : r.pos + length + + if (prefix !== '.') { + yield { + field: prefix.endsWith('.') ? prefix.substring(0, prefix.length - 1) : prefix, + type: 'start', + message: 'LimitedEntry' + } + } + + while (r.pos < end) { + const tag = r.uint32() + + switch (tag >>> 3) { + case 1: { + decodeContext?.consume('ids', opts.totalLimits?.ids) + if (opts.limits?.ids != null && obj.ids === opts.limits.ids) { + throw new MaxLengthError('Streaming decode error - repeated field "ids" had too many elements') + } + + yield { + field: `${prefix}ids[]`, + index: obj.ids, + value: r.bytes() + } + + obj.ids++ + + break + } + case 2: { + const b = r.bytes() + const r2 = reader(b) + + while (r2.pos < r2.len) { + decodeContext?.consume('numbers', opts.totalLimits?.numbers) + if (opts.limits?.numbers != null && obj.numbers === opts.limits.numbers) { + throw new MaxLengthError('Streaming decode error - repeated field "numbers" had too many elements') + } + + yield { + field: `${prefix}numbers[]`, + index: obj.numbers, + value: r2.uint32() + } + + obj.numbers++ + } + + break + } + case 3: { + decodeContext?.consume('values', opts.totalLimits?.values, MaxSizeError) + if (opts.limits?.values != null && obj.values === opts.limits.values) { + throw new MaxLengthError('Decode error - map field "values" had too many elements') + } + + yield * LimitedEntry.LimitedEntry$valuesEntry.codec().stream(r, r.uint32(), `${prefix}values{}.`, DecodeContext.child(decodeContext, 'values', { + limits: { + value: opts.limits?.values$value + }, + totalLimits: opts.totalLimits?.values$value == null ? undefined : { value: opts.totalLimits.values$value } + })) + + obj.values++ + + break + } + case 4: { + decodeContext?.consume('expanded', opts.totalLimits?.expanded) + if (opts.limits?.expanded != null && obj.expanded === opts.limits.expanded) { + throw new MaxLengthError('Streaming decode error - repeated field "expanded" had too many elements') + } + + yield { + field: `${prefix}expanded[]`, + index: obj.expanded, + value: r.uint32() + } + + obj.expanded++ + + break + } + default: { + r.skipType(tag & 7) + break + } + } + } + + if (prefix !== '.') { + yield { + field: prefix.endsWith('.') ? prefix.substring(0, prefix.length - 1) : prefix, + type: 'end', + message: 'LimitedEntry' + } + } + }, { totalLimits: true }) + } + + return _codec + } + + export interface LimitedEntryIdsFieldEvent { + field: '.ids[]' + index: number + value: Uint8Array + } + + export interface LimitedEntryNumbersFieldEvent { + field: '.numbers[]' + index: number + value: number + } + + export interface LimitedEntryValuesFieldEvent { + field: '.values{}' + key: string + value: Uint8Array + } + + export interface LimitedEntryExpandedFieldEvent { + field: '.expanded[]' + index: number + value: number + } + + export function encode (obj: LimitedEntryInput): Uint8Array { + return encodeMessage(obj, LimitedEntry.codec()) + } + + export function decode (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): LimitedEntry { + return decodeMessage(buf, LimitedEntry.codec(), opts) + } + + export function stream (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): Generator { + return streamMessage(buf, LimitedEntry.codec(), opts) + } +} diff --git a/packages/protons/test/total-limits.spec.ts b/packages/protons/test/total-limits.spec.ts new file mode 100644 index 0000000..6d7fbd8 --- /dev/null +++ b/packages/protons/test/total-limits.spec.ts @@ -0,0 +1,147 @@ +import { expect } from 'aegir/chai' +import { decodeMessage, MaxLengthError, MaxSizeError, message, reader, streamMessage, UnsupportedDecodeLimitsError } from 'protons-runtime' +import { Uint8ArrayList } from 'uint8arraylist' +import { concat } from 'uint8arrays/concat' +import { LimitedBatch, LimitedEntry } from './fixtures/total-limits.ts' +import type { DecodeOptions } from 'protons-runtime' + +describe('total decode limits', () => { + const ids = (count: number): Uint8Array[] => Array.from({ length: count }, () => new Uint8Array(0)) + const opts = { limits: { entries$: { ids: 3 } }, totalLimits: { entries$: { ids: 5 } } } + + for (const streaming of [false, true]) { + const decode = (bytes: Uint8Array | Uint8ArrayList, options: DecodeOptions = opts): unknown => streaming + ? Array.from(LimitedBatch.stream(bytes, options)) + : LimitedBatch.decode(bytes, options) + + describe(streaming ? 'stream' : 'decode', () => { + it('rejects IDs split across individually valid entries', () => { + const bytes = LimitedBatch.encode({ entries: [{ ids: ids(3) }, { ids: ids(3) }] }) + expect(() => decode(bytes)).to.throw(MaxLengthError) + }) + + it('accepts the total boundary and resets for each operation', () => { + const bytes = LimitedBatch.encode({ entries: [{ ids: ids(2) }, { ids: ids(3) }] }) + const expected = decode(bytes, {}) + expect(decode(bytes)).to.deep.equal(expected) + expect(decode(bytes)).to.deep.equal(expected) + }) + + it('resets totals after an operation fails', () => { + const invalid = LimitedBatch.encode({ entries: [{ ids: ids(3) }, { ids: ids(3) }] }) + const valid = LimitedBatch.encode({ entries: [{ ids: ids(2) }, { ids: ids(3) }] }) + expect(() => decode(invalid)).to.throw(MaxLengthError) + expect(() => decode(valid)).not.to.throw() + }) + + it('does not mutate caller options', () => { + const options = Object.freeze({ + limits: Object.freeze({ entries$: Object.freeze({ ids: 3 }) }), + totalLimits: Object.freeze({ entries$: Object.freeze({ ids: 5 }) }) + }) + const bytes = LimitedBatch.encode({ entries: [{ ids: ids(2) }, { ids: ids(3) }] }) + expect(() => decode(bytes, options)).not.to.throw() + }) + + it('retains the per-entry limit', () => { + const bytes = LimitedBatch.encode({ entries: [{ ids: ids(4) }] }) + expect(() => decode(bytes)).to.throw(MaxLengthError) + }) + + it('enforces limits on fragmented input', () => { + const bytes = LimitedBatch.encode({ entries: [{ ids: ids(3) }, { ids: ids(3) }] }) + expect(() => decode(new Uint8ArrayList(bytes.subarray(0, 3), bytes.subarray(3)))).to.throw(MaxLengthError) + }) + + it('does not reset totals when a singular message appears again', () => { + const part = LimitedBatch.encode({ single: { ids: ids(3) } }) + const options = { totalLimits: { single: { ids: 5 } } } + expect(() => decode(concat([part, part]), options)).to.throw(MaxLengthError) + }) + + it('shares totals across map values', () => { + const bytes = LimitedBatch.encode({ byKey: new Map([['a', { ids: ids(3) }], ['b', { ids: ids(3) }]]) }) + const options = { totalLimits: { byKey$value: { ids: 5 } } } + expect(() => decode(bytes, options)).to.throw(MaxLengthError) + }) + + it('keeps identical limit objects at different field paths independent', () => { + const childLimits = { ids: 3 } + const options = { totalLimits: { entries$: childLimits, others$: childLimits } } + const bytes = LimitedBatch.encode({ entries: [{ ids: ids(3) }], others: [{ ids: ids(3) }] }) + expect(decode(bytes, options)).to.deep.equal(decode(bytes, {})) + }) + + for (const field of ['numbers', 'expanded'] as const) { + it(`counts each element of ${field}`, () => { + const bytes = LimitedBatch.encode({ entries: [{ [field]: [1, 2, 3] }, { [field]: [4, 5, 6] }] }) + const options = { totalLimits: { entries$: { [field]: 5 } } } + expect(() => decode(bytes, options)).to.throw(MaxLengthError) + }) + } + + it('counts duplicate wire map entries even when the key is overwritten', () => { + const part = LimitedEntry.encode({ values: new Map([['a', new Uint8Array(0)]]) }) + const bytes = concat([part, part, part]) + const options = { totalLimits: { values: 2 } } + const run = (): unknown => streaming + ? Array.from(LimitedEntry.stream(bytes, options)) + : LimitedEntry.decode(bytes, options) + expect(run).to.throw(MaxSizeError) + }) + + it('checks the total before reading excess bytes', () => { + const bytes = LimitedBatch.encode({ entries: [{ ids: ids(3) }, { ids: ids(3) }] }) + const r = reader(bytes) + const readBytes = r.bytes.bind(r) + let count = 0 + r.bytes = () => { count++; return readBytes() } + const run = (): unknown => streaming + ? Array.from(LimitedBatch.codec().stream(r, undefined, '.', opts)) + : LimitedBatch.codec().decode(r, undefined, opts) + expect(run).to.throw(MaxLengthError) + expect(count).to.equal(5) + }) + }) + } + + it('keeps interleaved streams with the same options independent', () => { + const bytes = LimitedBatch.encode({ entries: [{ ids: ids(2) }, { ids: ids(3) }] }) + const expected = Array.from(LimitedBatch.stream(bytes, opts)) + const first = LimitedBatch.stream(bytes, opts) + const second = LimitedBatch.stream(bytes, opts) + + for (const event of expected) { + expect(first.next(), `first stream: ${event.field}`).to.deep.equal({ value: event, done: false }) + expect(second.next(), `second stream: ${event.field}`).to.deep.equal({ value: event, done: false }) + } + + expect(first.next().done).to.equal(true) + expect(second.next().done).to.equal(true) + }) + + describe('older generated codecs', () => { + const legacy = message<{ ids: Uint8Array[] }, { ids: Uint8Array[] }>( + () => {}, + () => ({ ids: [] }), + function * () { yield * [] } + ) + const bytes = new Uint8Array(0) + const options = { totalLimits: { ids: 1 } } + + it('rejects total limits when decoding', () => { + expect(() => decodeMessage(bytes, legacy, options)).to.throw(UnsupportedDecodeLimitsError) + expect(() => legacy.decode(reader(bytes), undefined, options)).to.throw(UnsupportedDecodeLimitsError) + }) + + it('rejects total limits when streaming', () => { + expect(() => Array.from(streamMessage(bytes, legacy, options))).to.throw(UnsupportedDecodeLimitsError) + expect(() => Array.from(legacy.stream(reader(bytes), undefined, '.', options))).to.throw(UnsupportedDecodeLimitsError) + }) + + it('preserves calls without total limits', () => { + expect(decodeMessage(bytes, legacy, { limits: { ids: 1 } })).to.deep.equal({ ids: [] }) + expect(Array.from(streamMessage(bytes, legacy, { limits: { ids: 1 } }))).to.deep.equal([]) + }) + }) +})