From 5db12c49c93b3613c58a50e42cceef0bd5eaf648 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Mon, 6 Apr 2026 23:12:24 +0530 Subject: [PATCH 1/7] added guardrails keys to the Config interface --- javascript/packages/core/lib/type.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/javascript/packages/core/lib/type.ts b/javascript/packages/core/lib/type.ts index 52f320869f..cdab57f597 100644 --- a/javascript/packages/core/lib/type.ts +++ b/javascript/packages/core/lib/type.ts @@ -265,6 +265,8 @@ export interface Config { refTracking: boolean | null; useSliceString: boolean; maxDepth?: number; + maxBinarySize?: number; + maxCollectionSize?: number; hooks: { afterCodeGenerated?: (code: string) => string; }; From 4e24e0e0c64e435b9cb0ece6def03efc22319b44 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Mon, 6 Apr 2026 23:15:29 +0530 Subject: [PATCH 2/7] added size checking and non-negativity of guardrail keys --- javascript/packages/core/lib/fory.ts | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/javascript/packages/core/lib/fory.ts b/javascript/packages/core/lib/fory.ts index 2d039b169d..810be373ef 100644 --- a/javascript/packages/core/lib/fory.ts +++ b/javascript/packages/core/lib/fory.ts @@ -45,6 +45,8 @@ export default class { config: Config; depth = 0; maxDepth: number; + maxBinarySize: number | undefined; + maxCollectionSize: number | undefined; constructor(config?: Partial) { this.config = this.initConfig(config); @@ -53,6 +55,18 @@ export default class { throw new Error(`maxDepth must be an integer >= ${MIN_DEPTH_LIMIT} but got ${maxDepth}`); } this.maxDepth = maxDepth; + if (config?.maxBinarySize !== undefined) { + if (!Number.isInteger(config.maxBinarySize) || config.maxBinarySize < 0) { + throw new Error(`maxBinarySize must be a non-negative integer but got ${config.maxBinarySize}`); + } + } + this.maxBinarySize = config?.maxBinarySize; + if (config?.maxCollectionSize !== undefined) { + if (!Number.isInteger(config.maxCollectionSize) || config.maxCollectionSize < 0) { + throw new Error(`maxCollectionSize must be a non-negative integer but got ${config.maxCollectionSize}`); + } + } + this.maxCollectionSize = config?.maxCollectionSize; this.binaryReader = new BinaryReader(this.config); this.binaryWriter = new BinaryWriter(this.config); this.referenceResolver = new ReferenceResolver(this.binaryReader); @@ -68,6 +82,8 @@ export default class { refTracking: config?.refTracking !== null ? Boolean(config?.refTracking) : null, useSliceString: Boolean(config?.useSliceString), maxDepth: config?.maxDepth, + maxBinarySize: config?.maxBinarySize, + maxCollectionSize: config?.maxCollectionSize, hooks: config?.hooks || {}, compatible: Boolean(config?.compatible), }; @@ -91,6 +107,24 @@ export default class { this.depth--; } + checkCollectionSize(size: number): void { + if (this.maxCollectionSize !== undefined && size > this.maxCollectionSize) { + throw new Error( + `Collection size ${size} exceeds maxCollectionSize ${this.maxCollectionSize}. ` + + "The data may be malicious, or increase maxCollectionSize if needed." + ); + } + } + + checkBinarySize(size: number): void { + if (this.maxBinarySize !== undefined && size > this.maxBinarySize) { + throw new Error( + `Binary size ${size} exceeds maxBinarySize ${this.maxBinarySize}. ` + + "The data may be malicious, or increase maxBinarySize if needed." + ); + } + } + private resetRead(): void { this.referenceResolver.resetRead(); this.typeMetaResolver.resetRead(); From 225a5e0502b95e880986e24d83b105e409e5b355 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Mon, 6 Apr 2026 23:16:35 +0530 Subject: [PATCH 3/7] added collection check size at read accessors --- javascript/packages/core/lib/gen/collection.ts | 2 ++ javascript/packages/core/lib/gen/map.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/javascript/packages/core/lib/gen/collection.ts b/javascript/packages/core/lib/gen/collection.ts index 396885abba..a085777590 100644 --- a/javascript/packages/core/lib/gen/collection.ts +++ b/javascript/packages/core/lib/gen/collection.ts @@ -152,6 +152,7 @@ class CollectionAnySerializer { read(accessor: (result: any, index: number, v: any) => void, createCollection: (len: number) => any, fromRef: boolean): any { void fromRef; const len = this.fory.binaryReader.readVarUint32Small7(); + this.fory.checkCollectionSize(len); const flags = this.fory.binaryReader.readUint8(); const isSame = flags & CollectionFlags.SAME_TYPE; const includeNone = flags & CollectionFlags.HAS_NULL; @@ -304,6 +305,7 @@ export abstract class CollectionSerializerGenerator extends BaseSerializerGenera const refFlag = this.scope.uniqueName("refFlag"); return ` const ${len} = ${this.builder.reader.readVarUint32Small7()}; + fory.checkCollectionSize(${len}); const ${flags} = ${this.builder.reader.readUint8()}; const ${result} = ${this.newCollection(len)}; ${this.maybeReference(result, refState)} diff --git a/javascript/packages/core/lib/gen/map.ts b/javascript/packages/core/lib/gen/map.ts index 7f80bbc20d..326cf46052 100644 --- a/javascript/packages/core/lib/gen/map.ts +++ b/javascript/packages/core/lib/gen/map.ts @@ -242,6 +242,7 @@ class MapAnySerializer { read(fromRef: boolean): any { let count = this.fory.binaryReader.readVarUint32Small7(); + this.fory.checkCollectionSize(count); const result = new Map(); if (fromRef) { this.fory.referenceResolver.reference(result); @@ -409,6 +410,7 @@ export class MapSerializerGenerator extends BaseSerializerGenerator { return ` let ${count} = ${this.builder.reader.readVarUint32Small7()}; + fory.checkCollectionSize(${count}); const ${result} = new Map(); if (${refState}) { ${this.builder.referenceResolver.reference(result)} From fc8e10c4e2144cf3eb13ef8d31735e2bee92b5e2 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Mon, 6 Apr 2026 23:17:22 +0530 Subject: [PATCH 4/7] added binary check size at read accessor --- javascript/packages/core/lib/gen/typedArray.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/javascript/packages/core/lib/gen/typedArray.ts b/javascript/packages/core/lib/gen/typedArray.ts index 50ae7c4328..cc217ba48e 100644 --- a/javascript/packages/core/lib/gen/typedArray.ts +++ b/javascript/packages/core/lib/gen/typedArray.ts @@ -47,6 +47,7 @@ function build(inner: TypeInfo, creator: string, size: number) { return ` const ${len} = ${this.builder.reader.readVarUInt32()}; + fory.checkBinarySize(${len}); const ${copied} = ${this.builder.reader.buffer(len)} const ${result} = new ${creator}(${copied}.buffer, ${copied}.byteOffset, ${copied}.byteLength / ${size}); ${this.maybeReference(result, refState)} From c832da604a77781499b4b754ca5d122b7e9f32df Mon Sep 17 00:00:00 2001 From: ayush00git Date: Mon, 6 Apr 2026 23:27:04 +0530 Subject: [PATCH 5/7] added default values for sizes --- javascript/packages/core/lib/fory.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/javascript/packages/core/lib/fory.ts b/javascript/packages/core/lib/fory.ts index 810be373ef..5087ffdada 100644 --- a/javascript/packages/core/lib/fory.ts +++ b/javascript/packages/core/lib/fory.ts @@ -32,6 +32,8 @@ import { MetaStringResolver } from "./metaStringResolver"; const DEFAULT_DEPTH_LIMIT = 50 as const; const MIN_DEPTH_LIMIT = 2 as const; +const DEFAULT_MAX_COLLECTION_SIZE = 1_000_000 as const; +const DEFAULT_MAX_BINARY_SIZE = 64 * 1024 * 1024; // 64 MiB export default class { binaryReader: BinaryReader; @@ -45,8 +47,8 @@ export default class { config: Config; depth = 0; maxDepth: number; - maxBinarySize: number | undefined; - maxCollectionSize: number | undefined; + maxBinarySize: number; + maxCollectionSize: number; constructor(config?: Partial) { this.config = this.initConfig(config); @@ -55,18 +57,16 @@ export default class { throw new Error(`maxDepth must be an integer >= ${MIN_DEPTH_LIMIT} but got ${maxDepth}`); } this.maxDepth = maxDepth; - if (config?.maxBinarySize !== undefined) { - if (!Number.isInteger(config.maxBinarySize) || config.maxBinarySize < 0) { - throw new Error(`maxBinarySize must be a non-negative integer but got ${config.maxBinarySize}`); - } + const maxBinarySize = config?.maxBinarySize ?? DEFAULT_MAX_BINARY_SIZE; + if (!Number.isInteger(maxBinarySize) || maxBinarySize < 0) { + throw new Error(`maxBinarySize must be a non-negative integer but got ${maxBinarySize}`); } - this.maxBinarySize = config?.maxBinarySize; - if (config?.maxCollectionSize !== undefined) { - if (!Number.isInteger(config.maxCollectionSize) || config.maxCollectionSize < 0) { - throw new Error(`maxCollectionSize must be a non-negative integer but got ${config.maxCollectionSize}`); - } + this.maxBinarySize = maxBinarySize; + const maxCollectionSize = config?.maxCollectionSize ?? DEFAULT_MAX_COLLECTION_SIZE; + if (!Number.isInteger(maxCollectionSize) || maxCollectionSize < 0) { + throw new Error(`maxCollectionSize must be a non-negative integer but got ${maxCollectionSize}`); } - this.maxCollectionSize = config?.maxCollectionSize; + this.maxCollectionSize = maxCollectionSize; this.binaryReader = new BinaryReader(this.config); this.binaryWriter = new BinaryWriter(this.config); this.referenceResolver = new ReferenceResolver(this.binaryReader); @@ -108,7 +108,7 @@ export default class { } checkCollectionSize(size: number): void { - if (this.maxCollectionSize !== undefined && size > this.maxCollectionSize) { + if (size > this.maxCollectionSize) { throw new Error( `Collection size ${size} exceeds maxCollectionSize ${this.maxCollectionSize}. ` + "The data may be malicious, or increase maxCollectionSize if needed." @@ -117,7 +117,7 @@ export default class { } checkBinarySize(size: number): void { - if (this.maxBinarySize !== undefined && size > this.maxBinarySize) { + if (size > this.maxBinarySize) { throw new Error( `Binary size ${size} exceeds maxBinarySize ${this.maxBinarySize}. ` + "The data may be malicious, or increase maxBinarySize if needed." From 44c4e0bb0a67ab134f9d7d12d6c2267e5189d60e Mon Sep 17 00:00:00 2001 From: ayush00git Date: Mon, 6 Apr 2026 23:38:39 +0530 Subject: [PATCH 6/7] added test file --- javascript/test/sizeLimit.test.ts | 267 ++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 javascript/test/sizeLimit.test.ts diff --git a/javascript/test/sizeLimit.test.ts b/javascript/test/sizeLimit.test.ts new file mode 100644 index 0000000000..e967cc5abe --- /dev/null +++ b/javascript/test/sizeLimit.test.ts @@ -0,0 +1,267 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Fory, { Type } from '../packages/core/index'; +import { describe, expect, test } from '@jest/globals'; + +describe('size-limit guardrails', () => { + describe('configuration', () => { + test('should have default limits matching Go', () => { + const fory = new Fory(); + expect(fory.maxBinarySize).toBe(64 * 1024 * 1024); + expect(fory.maxCollectionSize).toBe(1_000_000); + }); + + test('should accept custom maxBinarySize', () => { + const fory = new Fory({ maxBinarySize: 1024 }); + expect(fory.maxBinarySize).toBe(1024); + }); + + test('should accept custom maxCollectionSize', () => { + const fory = new Fory({ maxCollectionSize: 500 }); + expect(fory.maxCollectionSize).toBe(500); + }); + + test('should accept zero as a valid limit', () => { + const fory = new Fory({ maxBinarySize: 0, maxCollectionSize: 0 }); + expect(fory.maxBinarySize).toBe(0); + expect(fory.maxCollectionSize).toBe(0); + }); + + test('should reject negative maxBinarySize', () => { + expect(() => new Fory({ maxBinarySize: -1 })).toThrow( + 'maxBinarySize must be a non-negative integer' + ); + }); + + test('should reject non-integer maxBinarySize', () => { + expect(() => new Fory({ maxBinarySize: 1.5 })).toThrow( + 'maxBinarySize must be a non-negative integer' + ); + }); + + test('should reject NaN maxBinarySize', () => { + expect(() => new Fory({ maxBinarySize: NaN })).toThrow( + 'maxBinarySize must be a non-negative integer' + ); + }); + + test('should reject negative maxCollectionSize', () => { + expect(() => new Fory({ maxCollectionSize: -10 })).toThrow( + 'maxCollectionSize must be a non-negative integer' + ); + }); + + test('should reject non-integer maxCollectionSize', () => { + expect(() => new Fory({ maxCollectionSize: 2.7 })).toThrow( + 'maxCollectionSize must be a non-negative integer' + ); + }); + + test('should work with other options combined', () => { + const fory = new Fory({ + maxDepth: 100, + maxBinarySize: 1024, + maxCollectionSize: 500, + refTracking: true, + compatible: true, + }); + expect(fory.maxDepth).toBe(100); + expect(fory.maxBinarySize).toBe(1024); + expect(fory.maxCollectionSize).toBe(500); + }); + }); + + describe('checkCollectionSize', () => { + test('should not throw when size is within default limit', () => { + const fory = new Fory(); + expect(() => fory.checkCollectionSize(999999)).not.toThrow(); + }); + + test('should not throw when size is within limit', () => { + const fory = new Fory({ maxCollectionSize: 100 }); + expect(() => fory.checkCollectionSize(100)).not.toThrow(); + expect(() => fory.checkCollectionSize(0)).not.toThrow(); + }); + + test('should throw when size exceeds limit', () => { + const fory = new Fory({ maxCollectionSize: 100 }); + expect(() => fory.checkCollectionSize(101)).toThrow( + 'Collection size 101 exceeds maxCollectionSize 100' + ); + }); + + test('error message should include helpful suggestion', () => { + const fory = new Fory({ maxCollectionSize: 10 }); + expect(() => fory.checkCollectionSize(20)).toThrow( + 'increase maxCollectionSize if needed' + ); + }); + }); + + describe('checkBinarySize', () => { + test('should not throw when size is within default limit', () => { + const fory = new Fory(); + expect(() => fory.checkBinarySize(999999)).not.toThrow(); + }); + + test('should not throw when size is within limit', () => { + const fory = new Fory({ maxBinarySize: 1024 }); + expect(() => fory.checkBinarySize(1024)).not.toThrow(); + expect(() => fory.checkBinarySize(0)).not.toThrow(); + }); + + test('should throw when size exceeds limit', () => { + const fory = new Fory({ maxBinarySize: 1024 }); + expect(() => fory.checkBinarySize(1025)).toThrow( + 'Binary size 1025 exceeds maxBinarySize 1024' + ); + }); + }); + + describe('list deserialization with maxCollectionSize', () => { + test('should deserialize list within limit', () => { + const fory = new Fory({ maxCollectionSize: 10 }); + const { serialize, deserialize } = fory.registerSerializer(Type.array(Type.int32())); + const data = [1, 2, 3]; + const result = deserialize(serialize(data)); + expect(result).toEqual(data); + }); + + test('should throw when list exceeds maxCollectionSize', () => { + const serializeFory = new Fory(); + const { serialize } = serializeFory.registerSerializer(Type.array(Type.int32())); + const bytes = serialize([1, 2, 3, 4, 5]); + + const deserializeFory = new Fory({ maxCollectionSize: 3 }); + const { deserialize } = deserializeFory.registerSerializer(Type.array(Type.int32())); + expect(() => deserialize(bytes)).toThrow('exceeds maxCollectionSize'); + }); + + test('should deserialize list at exact limit', () => { + const fory = new Fory({ maxCollectionSize: 3 }); + const { serialize, deserialize } = fory.registerSerializer(Type.array(Type.int32())); + const data = [1, 2, 3]; + const result = deserialize(serialize(data)); + expect(result).toEqual(data); + }); + }); + + describe('set deserialization with maxCollectionSize', () => { + test('should deserialize set within limit', () => { + const fory = new Fory({ maxCollectionSize: 10, refTracking: true }); + const { serialize, deserialize } = fory.registerSerializer(Type.set(Type.int32())); + const data = new Set([1, 2, 3]); + const result = deserialize(serialize(data)); + expect(result).toEqual(data); + }); + + test('should throw when set exceeds maxCollectionSize', () => { + const serializeFory = new Fory({ refTracking: true }); + const { serialize } = serializeFory.registerSerializer(Type.set(Type.int32())); + const bytes = serialize(new Set([1, 2, 3, 4, 5])); + + const deserializeFory = new Fory({ maxCollectionSize: 3, refTracking: true }); + const { deserialize } = deserializeFory.registerSerializer(Type.set(Type.int32())); + expect(() => deserialize(bytes)).toThrow('exceeds maxCollectionSize'); + }); + }); + + describe('map deserialization with maxCollectionSize', () => { + test('should deserialize map within limit', () => { + const fory = new Fory({ maxCollectionSize: 10, refTracking: true }); + const { serialize, deserialize } = fory.registerSerializer( + Type.map(Type.string(), Type.int32()) + ); + const data = new Map([['a', 1], ['b', 2]]); + const result = deserialize(serialize(data)); + expect(result).toEqual(data); + }); + + test('should throw when map exceeds maxCollectionSize', () => { + const serializeFory = new Fory({ refTracking: true }); + const { serialize } = serializeFory.registerSerializer( + Type.map(Type.string(), Type.int32()) + ); + const bytes = serialize(new Map([['a', 1], ['b', 2], ['c', 3], ['d', 4]])); + + const deserializeFory = new Fory({ maxCollectionSize: 2, refTracking: true }); + const { deserialize } = deserializeFory.registerSerializer( + Type.map(Type.string(), Type.int32()) + ); + expect(() => deserialize(bytes)).toThrow('exceeds maxCollectionSize'); + }); + }); + + describe('binary deserialization with maxBinarySize', () => { + test('should deserialize binary within limit', () => { + const fory = new Fory({ maxBinarySize: 1024, refTracking: true }); + const { serialize, deserialize } = fory.registerSerializer(Type.struct("test.binary", { + data: Type.binary(), + })); + const data = { data: new Uint8Array([1, 2, 3]) }; + const result = deserialize(serialize(data)); + expect(result!.data![0]).toBe(1); + expect(result!.data![1]).toBe(2); + expect(result!.data![2]).toBe(3); + }); + + test('should throw when binary exceeds maxBinarySize', () => { + const serializeFory = new Fory({ refTracking: true }); + const { serialize } = serializeFory.registerSerializer(Type.struct("test.binary2", { + data: Type.binary(), + })); + const bytes = serialize({ data: new Uint8Array(100) }); + + const deserializeFory = new Fory({ maxBinarySize: 50, refTracking: true }); + const { deserialize } = deserializeFory.registerSerializer(Type.struct("test.binary2", { + data: Type.binary(), + })); + expect(() => deserialize(bytes)).toThrow('exceeds maxBinarySize'); + }); + }); + + describe('default limits allow normal payloads', () => { + test('should allow large collections within default limit', () => { + const fory = new Fory({ refTracking: true }); + const { serialize, deserialize } = fory.registerSerializer(Type.array(Type.int32())); + const bigArray = Array.from({ length: 1000 }, (_, i) => i); + const result = deserialize(serialize(bigArray)); + expect(result).toEqual(bigArray); + }); + }); + + describe('polymorphic (any-typed) collection paths', () => { + test('should enforce maxCollectionSize on untyped list', () => { + const serializeFory = new Fory({ refTracking: true }); + const bytes = serializeFory.serialize([1, "two", 3.0]); + + const deserializeFory = new Fory({ maxCollectionSize: 2, refTracking: true }); + expect(() => deserializeFory.deserialize(bytes)).toThrow('exceeds maxCollectionSize'); + }); + + test('should enforce maxCollectionSize on untyped map', () => { + const serializeFory = new Fory({ refTracking: true }); + const bytes = serializeFory.serialize(new Map([["a", 1], ["b", 2], ["c", 3]])); + + const deserializeFory = new Fory({ maxCollectionSize: 2, refTracking: true }); + expect(() => deserializeFory.deserialize(bytes)).toThrow('exceeds maxCollectionSize'); + }); + }); +}); From 9dc2e087a9dde6d6fe4b846dc1e68caf4523ac8e Mon Sep 17 00:00:00 2001 From: ayush00git Date: Tue, 7 Apr 2026 00:19:22 +0530 Subject: [PATCH 7/7] add guards to bool, float, bfloat --- .../packages/core/lib/gen/typedArray.ts | 11 ++- javascript/test/sizeLimit.test.ts | 84 +++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/javascript/packages/core/lib/gen/typedArray.ts b/javascript/packages/core/lib/gen/typedArray.ts index cc217ba48e..ac59edb13d 100644 --- a/javascript/packages/core/lib/gen/typedArray.ts +++ b/javascript/packages/core/lib/gen/typedArray.ts @@ -86,6 +86,7 @@ class BoolArraySerializerGenerator extends BaseSerializerGenerator { const idx = this.scope.uniqueName("idx"); return ` const ${len} = ${this.builder.reader.readVarUInt32()}; + fory.checkCollectionSize(${len}); const ${result} = new Array(${len}); ${this.maybeReference(result, refState)} for (let ${idx} = 0; ${idx} < ${len}; ${idx}++) { @@ -121,10 +122,13 @@ class Float16ArraySerializerGenerator extends BaseSerializerGenerator { read(accessor: (expr: string) => string, refState: string): string { const result = this.scope.uniqueName("result"); + const rawLen = this.scope.uniqueName("rawLen"); const len = this.scope.uniqueName("len"); const idx = this.scope.uniqueName("idx"); return ` - const ${len} = ${this.builder.reader.readVarUInt32()} / 2; + const ${rawLen} = ${this.builder.reader.readVarUInt32()}; + fory.checkBinarySize(${rawLen}); + const ${len} = ${rawLen} / 2; const ${result} = new Array(${len}); ${this.maybeReference(result, refState)} for (let ${idx} = 0; ${idx} < ${len}; ${idx}++) { @@ -160,10 +164,13 @@ class BFloat16ArraySerializerGenerator extends BaseSerializerGenerator { read(accessor: (expr: string) => string, refState: string): string { const result = this.scope.uniqueName("result"); + const rawLen = this.scope.uniqueName("rawLen"); const len = this.scope.uniqueName("len"); const idx = this.scope.uniqueName("idx"); return ` - const ${len} = ${this.builder.reader.readVarUInt32()} / 2; + const ${rawLen} = ${this.builder.reader.readVarUInt32()}; + fory.checkBinarySize(${rawLen}); + const ${len} = ${rawLen} / 2; const ${result} = new Array(${len}); ${this.maybeReference(result, refState)} for (let ${idx} = 0; ${idx} < ${len}; ${idx}++) { diff --git a/javascript/test/sizeLimit.test.ts b/javascript/test/sizeLimit.test.ts index e967cc5abe..7a53ba5243 100644 --- a/javascript/test/sizeLimit.test.ts +++ b/javascript/test/sizeLimit.test.ts @@ -264,4 +264,88 @@ describe('size-limit guardrails', () => { expect(() => deserializeFory.deserialize(bytes)).toThrow('exceeds maxCollectionSize'); }); }); + + describe('bool array deserialization with maxCollectionSize', () => { + // BoolArraySerializerGenerator reads an element count — guarded by checkCollectionSize + test('should deserialize bool array within limit', () => { + const fory = new Fory({ maxCollectionSize: 10 }); + const { serialize, deserialize } = fory.registerSerializer(Type.struct("test.boolArr", { + flags: Type.boolArray(), + })); + const data = { flags: [true, false, true] }; + const result = deserialize(serialize(data)); + expect(result!.flags).toEqual([true, false, true]); + }); + + test('should throw when bool array exceeds maxCollectionSize', () => { + const serializeFory = new Fory(); + const { serialize } = serializeFory.registerSerializer(Type.struct("test.boolArr2", { + flags: Type.boolArray(), + })); + const bytes = serialize({ flags: [true, false, true, true, false] }); + + const deserializeFory = new Fory({ maxCollectionSize: 3 }); + const { deserialize } = deserializeFory.registerSerializer(Type.struct("test.boolArr2", { + flags: Type.boolArray(), + })); + expect(() => deserialize(bytes)).toThrow('exceeds maxCollectionSize'); + }); + }); + + describe('float16 array deserialization with maxBinarySize', () => { + // Float16ArraySerializerGenerator writes byte count (elements * 2) — guarded by checkBinarySize + test('should deserialize float16 array within limit', () => { + const fory = new Fory({ maxBinarySize: 1024 }); + const { serialize, deserialize } = fory.registerSerializer(Type.struct("test.f16Arr", { + vals: Type.float16Array(), + })); + const data = { vals: [1.0, 2.0, 3.0] }; + const result = deserialize(serialize(data)); + expect(result!.vals!.length).toBe(3); + }); + + test('should throw when float16 array byte length exceeds maxBinarySize', () => { + const serializeFory = new Fory(); + const { serialize } = serializeFory.registerSerializer(Type.struct("test.f16Arr2", { + vals: Type.float16Array(), + })); + // 10 elements × 2 bytes each = 20 raw bytes on the wire + const bytes = serialize({ vals: Array.from({ length: 10 }, (_, i) => i * 0.5) }); + + const deserializeFory = new Fory({ maxBinarySize: 10 }); // 10 < 20 + const { deserialize } = deserializeFory.registerSerializer(Type.struct("test.f16Arr2", { + vals: Type.float16Array(), + })); + expect(() => deserialize(bytes)).toThrow('exceeds maxBinarySize'); + }); + }); + + describe('bfloat16 array deserialization with maxBinarySize', () => { + // BFloat16ArraySerializerGenerator writes byte count (elements * 2) — same pattern as float16 + test('should deserialize bfloat16 array within limit', () => { + const fory = new Fory({ maxBinarySize: 1024 }); + const { serialize, deserialize } = fory.registerSerializer(Type.struct("test.bf16Arr", { + vals: Type.bfloat16Array(), + })); + const data = { vals: [1.0, 2.0, 3.0] }; + const result = deserialize(serialize(data)); + expect(result!.vals!.length).toBe(3); + }); + + test('should throw when bfloat16 array byte length exceeds maxBinarySize', () => { + const serializeFory = new Fory(); + const { serialize } = serializeFory.registerSerializer(Type.struct("test.bf16Arr2", { + vals: Type.bfloat16Array(), + })); + // 10 elements × 2 bytes each = 20 raw bytes on the wire + const bytes = serialize({ vals: Array.from({ length: 10 }, (_, i) => i * 0.5) }); + + const deserializeFory = new Fory({ maxBinarySize: 10 }); // 10 < 20 + const { deserialize } = deserializeFory.registerSerializer(Type.struct("test.bf16Arr2", { + vals: Type.bfloat16Array(), + })); + expect(() => deserialize(bytes)).toThrow('exceeds maxBinarySize'); + }); + }); }); +