diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index a267e98..b202d49 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -30,10 +30,12 @@ jobs: # Mutation suite first, same doctrine as the version-floor guard below: # prove the fail-closed guards still fail before trusting the verify. python3 tools/test_wire_format_reference.py + python3 tools/test_decode_bounds_reference.py python3 tools/interop-reference.py verify python3 tools/interop-v2-reference.py verify python3 tools/encryption-verify.py python3 tools/wire-format-reference.py verify + python3 tools/decode-bounds-reference.py verify - name: Python reference verify (optional deps — AES-GCM seal + msgpack third-encoder + lz4 C-implementation conformance) run: | @@ -42,6 +44,7 @@ jobs: python3 tools/interop-v2-reference.py verify python3 tools/encryption-verify.py --require-seal python3 tools/wire-format-reference.py verify --require-extras + python3 tools/decode-bounds-reference.py verify --require-extras - name: JS cross-check (independent encoder + @noble/hashes + WebCrypto) run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 7954064..5fdbd8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to the CacheKit Protocol Specification. ## [Unreleased] +### Interop mode — untrusted-decode bounds pinned as a cross-SDK invariant (LAB-2503) + +- New [`spec/interop-mode.md` → Decode bounds](spec/interop-mode.md#decode-bounds): + readers MUST bound nesting depth (≥ 32, ≤ 1024), MUST NOT pre-allocate beyond + what the input can back (Σ declared slots ≤ input bytes − 1), and MUST fail + closed with a catchable error. Follow-up to the LAB-2487 measurements. +- New [`test-vectors/decode-bounds.json`](test-vectors/decode-bounds.json) + (13 reject + 2 accept) with [`tools/decode-bounds-reference.py`](tools/decode-bounds-reference.py) + and its mutation suite; to be vendored and CI-executed by + [cachekit-py#276](https://github.com/cachekit-io/cachekit-py/pull/276) and + [cachekit-rs#73](https://github.com/cachekit-io/cachekit-rs/pull/73) (both open). +- [`spec/wire-format.md` → Security Limits](spec/wire-format.md#security-limits) + cross-references the rules for the envelope bytes and the payload inside them. +- The single shared depth value stays [protocol#20](https://github.com/cachekit-io/protocol/issues/20)'s + open item. + ### Wire format — compressed-byte reproducibility scoped per-vector (LAB-1751) - LZ4 compressed bytes are **not canonical** across conforming block encoders. diff --git a/sdk-feature-matrix.md b/sdk-feature-matrix.md index a1f64e9..ef8c4a2 100644 --- a/sdk-feature-matrix.md +++ b/sdk-feature-matrix.md @@ -280,12 +280,12 @@ its spec: | Requirement | Python | Rust | TypeScript | PHP | | :--- | :---: | :---: | :---: | :---: | | Key generation (Blake2b) | ✅ Compliant | N/A auto mode¹⁴ — interop/v1 keygen ✅ merged ([#33](https://github.com/cachekit-io/cachekit-rs/pull/33)); `#[cachekit]` mints interop keys ([#35](https://github.com/cachekit-io/cachekit-rs/pull/35)) | ✅ Compliant | ⚠️ Untested | -| Wire format (ByteStorage) | ✅ Compliant¹⁵ | ✅ Canonical (`cachekit-core`) — unused for stored values¹⁵ | ✅ Compliant | ⚠️ Untested | +| Wire format (ByteStorage) | ✅ Compliant¹⁵ — envelope-bytes decode-bounds pre-scan (LAB-2503) pending verification | ✅ Canonical (`cachekit-core`) — unused for stored values¹⁵ | ✅ Compliant — envelope-bytes decode-bounds pre-scan (LAB-2503) pending verification | ⚠️ Untested | | Storage container (auto mode)¹⁵ | CK v3 frame (Python-internal) | Plain MessagePack (`rmp` named) — no envelope | Bare ByteStorage envelope (default) | — | | Encryption (AES-256-GCM) | ✅ Compliant | ✅ Canonical (cachekit-core) | ✅ Compliant | ⚠️ Untested | | AAD v0x03 | ✅ Compliant (5 components — every auto serializer appends `original_type`; interop mode is the sole 4-component path) | ✅ Compliant (4 components) | ✅ Compliant (4 components) | ❌ Not implemented | | SaaS API | ✅ Compliant | ✅ Compliant (CachekitIO backend) | ✅ Compliant | ❌ Not implemented | -| Test vectors in CI¹⁶ | ✅ interop/v1 (full set, incl. AAD + encryption through the real stack) | ✅ interop/v1 (full set) since [#33](https://github.com/cachekit-io/cachekit-rs/pull/33) | ✅ interop/v1 (full set, incl. its key vectors) + inline Python-generated AAD-construction and encryption (decrypt-Python-ciphertext) vectors | ⚠️ Pending | +| Test vectors in CI¹⁶ | ✅ interop/v1 (full set, incl. AAD + encryption through the real stack); `decode-bounds.json` vendored + CI-executed pending [cachekit-py#276](https://github.com/cachekit-io/cachekit-py/pull/276) (LAB-2503) | ✅ interop/v1 (full set) since [#33](https://github.com/cachekit-io/cachekit-rs/pull/33); `decode-bounds.json` vendored + CI-executed pending [cachekit-rs#73](https://github.com/cachekit-io/cachekit-rs/pull/73) (LAB-2503) | ✅ interop/v1 (full set, incl. its key vectors) + inline Python-generated AAD-construction and encryption (decrypt-Python-ciphertext) vectors; decode bounds enforced ([#112](https://github.com/cachekit-io/cachekit-ts/pull/112)), `decode-bounds.json` not yet vendored | ⚠️ Pending | | Interop mode ([spec](spec/interop-mode.md), opt-in) | ✅ Released — PyPI 0.14.0+¹⁷ ([#220](https://github.com/cachekit-io/cachekit-py/pull/220)) | ✅ Released — crates.io 0.4.0+ ([#33](https://github.com/cachekit-io/cachekit-rs/pull/33)) | ✅ Released — npm 0.1.3+ ([#71](https://github.com/cachekit-io/cachekit-ts/pull/71)) | ❌ Not implemented | > [!NOTE] @@ -293,7 +293,7 @@ its spec: > > ¹⁵ Auto-mode **stored bytes** are SDK-internal and differ per SDK — see [wire-format.md → SDK Storage Containers](spec/wire-format.md#sdk-storage-containers-auto-mode). Python stores the ByteStorage envelope *inside* its CK v3 frame; `cachekit-rs` does not use the envelope for values at all (it uses `cachekit-core` only for encryption). Cross-SDK value compatibility is exclusively an [interop-mode](spec/interop-mode.md) property (protocol#11). > -> ¹⁶ "Test vectors in CI" = vectors the SDK's own default CI executes. Beyond the SDKs, this repo's `verify.yml` CI-verifies `interop-mode.json`, `encryption.json`, `python-frame.json`, `file-backend.json` ([`tools/file-backend-reference.py`](tools/file-backend-reference.py)), and — since LAB-423 — `wire-format.json` ([`tools/wire-format-reference.py`](tools/wire-format-reference.py)) against reference implementations. `cache-keys.json` (regenerated by cachekit-py v0.12.0, byte-identical to the v0.5.0 originals) is vendored and CI-verified in cachekit-py since [cachekit-py#229](https://github.com/cachekit-io/cachekit-py/pull/229) (LAB-425). +> ¹⁶ "Test vectors in CI" = vectors the SDK's own default CI executes. Beyond the SDKs, this repo's `verify.yml` CI-verifies `interop-mode.json`, `encryption.json`, `python-frame.json`, `file-backend.json` ([`tools/file-backend-reference.py`](tools/file-backend-reference.py)), and — since LAB-423 — `wire-format.json` ([`tools/wire-format-reference.py`](tools/wire-format-reference.py)), and — since LAB-2503 — `decode-bounds.json` ([`tools/decode-bounds-reference.py`](tools/decode-bounds-reference.py), `verify` in both the stdlib and the optional-deps legs) against reference implementations. `cache-keys.json` (regenerated by cachekit-py v0.12.0, byte-identical to the v0.5.0 originals) is vendored and CI-verified in cachekit-py since [cachekit-py#229](https://github.com/cachekit-io/cachekit-py/pull/229) (LAB-425). > > ¹⁷ Version cells are **floors** (`X+`), not snapshots — they stay true as new versions publish; check the registry for the current release. Python's floor is the first *installable* one: interop merged under the `v0.13.0` tag, but neither `0.12.0` nor `0.13.0` was ever published to PyPI, so `0.14.0` is the earliest PyPI release containing interop mode. Do not "correct" this to 0.13.0 from the cachekit-py changelog alone. diff --git a/spec/interop-mode.md b/spec/interop-mode.md index 6948dc2..14acc14 100644 --- a/spec/interop-mode.md +++ b/spec/interop-mode.md @@ -35,6 +35,7 @@ - [Encryption in Interop Mode](#encryption-in-interop-mode) - [SaaS Considerations](#saas-considerations) - [SDK Implementation Requirements](#sdk-implementation-requirements) + - [Decode bounds](#decode-bounds) - [Design Decisions](#design-decisions) - [Test Vectors](#test-vectors) @@ -438,6 +439,47 @@ strings** (TypeScript has no UUID type): callers MUST use the lowercase hyphenat form, or `"550E8400-…"` from TS will silently miss the key a Python `uuid.UUID` argument produced. +### Decode bounds + +Interop values are read from a backend the SDK does not control, so every decoder +is an untrusted-input parser. A MessagePack collection header costs 1–5 bytes but +may declare up to 2³²−1 elements, and an eager decoder pre-allocates the container +*before* decoding its children; depth-first decoding stacks those allocations, so a +few KB of nested headers can drive hundreds of MB of transient heap (measured +15 KB → ~400 MB in `@msgpack/msgpack` 3.1.3; 10 KB → 67 MB in `msgpack-python` +1.2.1 with `array32` headers claiming `len(input)` elements). A reader MUST +therefore: + +1. **Bound nesting depth.** The bound MUST be at least 32 and MUST NOT exceed 1024. + (Today: TypeScript 100, Rust 100, Python 1024. A single shared value is + [protocol#20](https://github.com/cachekit-io/protocol/issues/20)'s open item; + until it is ratified, writers SHOULD keep values within 32 levels.) +2. **Never pre-allocate beyond what the input can back.** Every declared element or + byte needs at least one input byte, so a structurally incomplete document + (Σ declared slots > input bytes − 1) MUST be rejected *without* materialising it. + A map pair counts as two slots (key + value). Every per-header term and the running + sum MUST be computed in at least 64 bits or with checked/saturating arithmetic, and + an overflow is itself a rejection: two `array32` headers already exceed 2³², and a + 32-bit accumulator that wraps to a small value passes the budget + (`array32_sum_wraps_u32` and `map32_half_claim_wraps_u32_mul` pin the shapes). + Do not assume a decoder is lazy: `rmp-serde` reads str/bin lazily but serde's + `Vec` visitor still pre-allocates up to 1 MiB per collection from the + declared length. A header-only structural walk before decoding (the pre-scan in + `cachekit-ts`, `Unpacker.skip()` in `cachekit-py`, `check_structure` in + `cachekit-rs`) is sufficient. +3. **Fail closed, catchably.** Rejection surfaces as a decode error the SDK read + path turns into a cache miss — never an uncaught crash or an OOM abort. + +These bounds are SDK-owned invariants, not library defaults: each SDK pins them +explicitly and regression-tests them, so a decoder dependency bump cannot silently +re-open the amplifier. +[`test-vectors/decode-bounds.json`](../test-vectors/decode-bounds.json) pins the +bytes every decoder MUST reject (13) and MUST accept (2); the same rules apply to +any other untrusted MessagePack decode in an SDK (auto-mode payloads after the +envelope is unwrapped, invalidation events). The 40× residual — a *legal* payload +still materialises far more than its byte size in language objects — is bounded by +each SDK's input-size cap, not by these rules. + --- ## Design Decisions @@ -472,6 +514,17 @@ not re-litigated by accident. | `encryption_vectors` | 1 | Full HKDF-SHA256 → AES-256-GCM round-trip over plain-msgpack plaintext with the interop AAD (fixed nonce; decrypt-verified) | | `error_vectors` | 9 | Inputs that MUST be rejected (NaN, +Inf and −Inf as independent vectors, int overflow/underflow, naive datetime, bad segments incl. trailing newline). The `error` text is a maintainer note, not a normative message | +[`test-vectors/decode-bounds.json`](../test-vectors/decode-bounds.json) (see +[Decode bounds](#decode-bounds)) adds 13 `reject_vectors` (nested-header bombs, +over-claiming `array32`/`map32`/`bin32`/`str32` headers, a truncated array and map, +two shapes that wrap 32-bit slot arithmetic) and 2 +`accept_vectors` (32-deep nesting, a fully backed `array16`) that every SDK's +untrusted decoder MUST honour; `tools/decode-bounds-reference.py verify` checks the +file against its recipes and, when `msgpack-python` is installed, that the real +decoder rejects/accepts each vector (rejection only — msgpack-python's default limits +reject them, which proves each vector trips a stock decoder's limits; each SDK's explicit bound and +no-pre-allocation guard are tested in that SDK, not here). + Inputs use a tagged-JSON convention (`{"$set": …}`, `{"$float": "2.0"}`, `{"$int": "…"}`, `{"$datetime": "…"}`, `{"$uuid": "…"}`, `{"$bytes": ""}`) documented in the file header, because JSON alone cannot express sets, bytes, floats diff --git a/spec/wire-format.md b/spec/wire-format.md index 74e5e89..57a8222 100644 --- a/spec/wire-format.md +++ b/spec/wire-format.md @@ -264,9 +264,9 @@ bytes are therefore - A conforming reader MUST decompress every pinned vector's `compressed_data` to its pinned input, **and MUST enforce [Retrieve Flow](#retrieve-flow) steps - 4, 5 and 9 while doing so.** Read-side conformance is not "the vectors pass": + 2, 4, 5 and 9 while doing so.** Read-side conformance is not "the vectors pass": every pinned vector is well-formed and declares a truthful `original_size`, so - they evidence **none** of those bounds, and a reader that omits all three + they evidence **none** of those bounds, and a reader that omits all four decompresses all of them successfully. The vectors prove decode interoperability; the bounds in [Security Limits](#security-limits) are a separate, non-negotiable obligation that no fixture can demonstrate. @@ -376,8 +376,13 @@ let checksum: [u8; 8] = xxh3_64(&original_data).to_be_bytes(); > Additionally, a decoder MUST validate any declared MessagePack `bin`/array > length header against the remaining input bytes **before** allocating for it — > a 5-byte `bin32` header can otherwise declare a 4 GiB allocation from a -> ~30-byte envelope. (Slice-based decoders such as `rmp-serde` satisfy this -> inherently; readers that pre-allocate from length fields must check.) +> ~30-byte envelope. No decoder satisfies this inherently — even slice-based +> ones pre-allocate collections from declared lengths. The envelope bytes *and* +> the payload inside them +> are both untrusted MessagePack — decode each under the depth and allocation +> rules in [interop-mode.md → Decode bounds](interop-mode.md#decode-bounds), +> pinned by `test-vectors/decode-bounds.json`, running the structural pre-scan +> before materialising `StorageEnvelope`. | Limit | Value | Purpose | | :--- | ---: | :--- | @@ -439,7 +444,8 @@ Input: raw_data (bytes), format (string, default "msgpack") Input: envelope_bytes 1. Validate: envelope_bytes.length <= 512 MiB -2. Deserialize: envelope = msgpack_decode(envelope_bytes) as StorageEnvelope +2. Deserialize: pre-scan envelope_bytes (decode bounds, see Security Limits), then + envelope = msgpack_decode(envelope_bytes) as StorageEnvelope // accept BOTH element[0] encodings: bin AND array-of-ints 3. Validate: envelope.compressed_data.length <= 512 MiB 4. Validate: envelope.original_size <= 512 MiB diff --git a/test-vectors/decode-bounds.json b/test-vectors/decode-bounds.json new file mode 100644 index 0000000..21b9180 --- /dev/null +++ b/test-vectors/decode-bounds.json @@ -0,0 +1,258 @@ +{ + "version": "1.0.0", + "spec": "spec/interop-mode.md#decode-bounds", + "generator": "tools/decode-bounds-reference.py generate (CPython stdlib)", + "scope": "Any untrusted MessagePack decode in any SDK: interop/v1 values, the ByteStorage envelope bytes before StorageEnvelope is materialised, auto-mode payloads after the envelope is unwrapped, invalidation events. The bytes are plain MessagePack with no envelope.", + "rules": { + "depth": "Readers MUST bound nesting depth. The bound MUST be >= 32 and MUST be <= 1024; every reject vector tagged 'depth' nests deeper than 1024.", + "overclaim": "Readers MUST NOT pre-allocate for a collection/str/bin header more than the remaining input can back (each element or byte needs >= 1 input byte), and MUST reject a structurally incomplete document. Every reject vector tagged 'overclaim' has declared_slots > input_len - 1 (the root header is the only byte that is not an element). A map pair counts as two slots (key + value). Every per-header term and the running sum MUST be computed in >= 64 bits or with checked/saturating arithmetic; an overflow is itself a rejection.", + "failure_mode": "Rejection MUST surface as a catchable decode error that the SDK read path turns into a cache miss (fail-closed), never an uncaught crash or an OOM abort." + }, + "field_notes": { + "construction": "input = bytes.fromhex(repeat_hex) * count + bytes.fromhex(suffix_hex)", + "nesting_depth": "collection headers along the deepest spine (str/bin count as 0)", + "declared_slots": "sum of every header's declared element/byte count; a map pair counts as two slots (key + value); a nested header counts as one element of its parent", + "reject_reasons": "which rule(s) the vector violates; a maintainer note, not a normative message" + }, + "reject_vectors": [ + { + "name": "nested_array16_depth_2048", + "description": "2048 nested array16 headers each claiming 2000 elements, 0 backing bytes. The LAB-2487 amplifier shape: an eager decoder pre-allocates 2000 slots per level before hitting EOF. 2000 < input_len, so a per-collection cap of len(input) does NOT reject it.", + "construction": { + "repeat_hex": "dc07d0", + "count": 2048, + "suffix_hex": "" + }, + "input_hex": "dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0dc07d0", + "input_len": 6144, + "nesting_depth": 2048, + "declared_slots": 4096000, + "reject_reasons": [ + "depth", + "overclaim" + ] + }, + { + "name": "nested_array32_input_len_depth_1100", + "description": "1100 nested array32 headers each claiming exactly len(input)=5500 elements. Defeats a per-collection cap of len(input): peak pre-allocation is depth x len(input) x slot size.", + "construction": { + "repeat_hex": "dd0000157c", + "count": 1100, + "suffix_hex": "" + }, + "input_hex": "dd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157cdd0000157c", + "input_len": 5500, + "nesting_depth": 1100, + "declared_slots": 6050000, + "reject_reasons": [ + "depth", + "overclaim" + ] + }, + { + "name": "nested_map16_depth_2048", + "description": "Map twin of nested_array16_depth_2048 (map pre-allocation is typically larger per slot).", + "construction": { + "repeat_hex": "de07d0", + "count": 2048, + "suffix_hex": "" + }, + "input_hex": "de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0de07d0", + "input_len": 6144, + "nesting_depth": 2048, + "declared_slots": 8192000, + "reject_reasons": [ + "depth", + "overclaim" + ] + }, + { + "name": "nested_fixarray_depth_2048_complete", + "description": "Structurally COMPLETE document ([[...[null]...]]) nested 2048 deep: every header is backed, so only the depth bound rejects it. Isolates the depth rule from the allocation rule.", + "construction": { + "repeat_hex": "91", + "count": 2048, + "suffix_hex": "c0" + }, + "input_hex": "9191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191919191c0", + "input_len": 2049, + "nesting_depth": 2048, + "declared_slots": 2048, + "reject_reasons": [ + "depth" + ] + }, + { + "name": "array16_overclaim_shallow", + "description": "One array16 header claiming 10 000 elements with 3 backing bytes.", + "construction": { + "repeat_hex": "dc2710", + "count": 1, + "suffix_hex": "010203" + }, + "input_hex": "dc2710010203", + "input_len": 6, + "nesting_depth": 1, + "declared_slots": 10000, + "reject_reasons": [ + "overclaim" + ] + }, + { + "name": "array32_max_claim_alone", + "description": "A lone 5-byte array32 header claiming 2^32-1 elements.", + "construction": { + "repeat_hex": "ddffffffff", + "count": 1, + "suffix_hex": "" + }, + "input_hex": "ddffffffff", + "input_len": 5, + "nesting_depth": 1, + "declared_slots": 4294967295, + "reject_reasons": [ + "overclaim" + ] + }, + { + "name": "map32_max_claim_alone", + "description": "A lone 5-byte map32 header claiming 2^32-1 pairs (2^33-2 slots: each pair is a key and a value).", + "construction": { + "repeat_hex": "dfffffffff", + "count": 1, + "suffix_hex": "" + }, + "input_hex": "dfffffffff", + "input_len": 5, + "nesting_depth": 1, + "declared_slots": 8589934590, + "reject_reasons": [ + "overclaim" + ] + }, + { + "name": "array32_sum_wraps_u32", + "description": "array32 claiming 2^32-1 elements whose first element is an array32 claiming 1: the declared slots sum to exactly 2^32, which a 32-bit accumulator wraps to 0 and then passes the slot budget.", + "construction": { + "repeat_hex": "ddffffffff", + "count": 1, + "suffix_hex": "dd00000001" + }, + "input_hex": "ddffffffffdd00000001", + "input_len": 10, + "nesting_depth": 2, + "declared_slots": 4294967296, + "reject_reasons": [ + "overclaim" + ] + }, + { + "name": "map32_half_claim_wraps_u32_mul", + "description": "A lone map32 header claiming 2^31 pairs: the per-header term 2 x pairs is exactly 2^32, which a 32-bit multiply wraps to 0 before it is ever added to the budget.", + "construction": { + "repeat_hex": "df80000000", + "count": 1, + "suffix_hex": "" + }, + "input_hex": "df80000000", + "input_len": 5, + "nesting_depth": 1, + "declared_slots": 4294967296, + "reject_reasons": [ + "overclaim" + ] + }, + { + "name": "fixmap_short_by_one", + "description": "fixmap claiming 1 pair with the key present and the value missing: the map twin of fixarray_short_by_one. Counting one slot per pair (instead of two) accepts it.", + "construction": { + "repeat_hex": "81", + "count": 1, + "suffix_hex": "c0" + }, + "input_hex": "81c0", + "input_len": 2, + "nesting_depth": 1, + "declared_slots": 2, + "reject_reasons": [ + "overclaim" + ] + }, + { + "name": "bin32_overclaim", + "description": "bin32 header claiming 2^32-1 bytes with 1 backing byte (a 6-byte document declaring a 4 GiB buffer).", + "construction": { + "repeat_hex": "c6ffffffff", + "count": 1, + "suffix_hex": "41" + }, + "input_hex": "c6ffffffff41", + "input_len": 6, + "nesting_depth": 0, + "declared_slots": 4294967295, + "reject_reasons": [ + "overclaim" + ] + }, + { + "name": "str32_overclaim", + "description": "str32 twin of bin32_overclaim.", + "construction": { + "repeat_hex": "dbffffffff", + "count": 1, + "suffix_hex": "41" + }, + "input_hex": "dbffffffff41", + "input_len": 6, + "nesting_depth": 0, + "declared_slots": 4294967295, + "reject_reasons": [ + "overclaim" + ] + }, + { + "name": "fixarray_short_by_one", + "description": "fixarray claiming 5 elements with 4 present: the minimal truncated document.", + "construction": { + "repeat_hex": "95", + "count": 1, + "suffix_hex": "c0c0c0c0" + }, + "input_hex": "95c0c0c0c0", + "input_len": 5, + "nesting_depth": 1, + "declared_slots": 5, + "reject_reasons": [ + "overclaim" + ] + } + ], + "accept_vectors": [ + { + "name": "nested_fixarray_depth_32", + "description": "[[...[null]...]] nested 32 deep, complete. A conforming reader MUST accept it: the depth bound may not be tighter than 32.", + "construction": { + "repeat_hex": "91", + "count": 32, + "suffix_hex": "c0" + }, + "input_hex": "9191919191919191919191919191919191919191919191919191919191919191c0", + "input_len": 33, + "nesting_depth": 32, + "declared_slots": 32 + }, + { + "name": "array16_256_backed_nils", + "description": "array16 header claiming 256 elements with all 256 present. A *16 header that is fully backed by input is legitimate; the allocation rule is about backing, not header width.", + "construction": { + "repeat_hex": "dc0100", + "count": 1, + "suffix_hex": "c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0" + }, + "input_hex": "dc0100c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0", + "input_len": 259, + "nesting_depth": 1, + "declared_slots": 256 + } + ] +} diff --git a/tools/decode-bounds-reference.py b/tools/decode-bounds-reference.py new file mode 100644 index 0000000..5451954 --- /dev/null +++ b/tools/decode-bounds-reference.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 +"""Reference tool for test-vectors/decode-bounds.json (untrusted-decode bounds). + +Normative rules and the measurements behind them: spec/interop-mode.md → Decode +bounds. This file pins the bytes every SDK's decoder MUST reject (13) and MUST +accept (2, so the bound cannot over-tighten). + +Usage: + verify (default) stdlib-only. Checks the file equals the recipes below and + that each vector's depth/slot tags match its arithmetic. When + `msgpack` (msgpack-python) is importable, additionally checks the + real decoder rejects every reject vector and accepts every accept + vector. Rejection only: msgpack-python's own default limits reject + them, which proves each vector trips a stock decoder's limits; each SDK's explicit bound + and no-pre-allocation guard are tested in that SDK, not here. + `--require-extras` turns a missing msgpack into a failure (CI's + optional-deps leg). + generate Rewrites the vector file from the recipes below. +""" + +from __future__ import annotations + +import json +import logging +from pathlib import Path +import sys + +ROOT = Path(__file__).resolve().parents[1] +VECTORS = ROOT / "test-vectors" / "decode-bounds.json" + +# Every SDK's current depth bound is <= MAX_DEPTH_CEILING (ts 100, rs 100, py +# 1024), so a reject vector nested deeper than this is rejected by all of them; +# every SDK accepts at least MIN_DEPTH_FLOOR levels. +MAX_DEPTH_CEILING = 1024 +MIN_DEPTH_FLOOR = 32 + + +def u16(n: int) -> str: + return n.to_bytes(2, "big").hex() + + +def u32(n: int) -> str: + return n.to_bytes(4, "big").hex() + + +def recipe(name: str, description: str, repeat_hex: str, count: int, suffix_hex: str = "", *, + depth: int, slots: int, reasons: list[str]) -> dict: + data = bytes.fromhex(repeat_hex) * count + bytes.fromhex(suffix_hex) + return { + "name": name, + "description": description, + "construction": {"repeat_hex": repeat_hex, "count": count, "suffix_hex": suffix_hex}, + "input_hex": data.hex(), + "input_len": len(data), + "nesting_depth": depth, + "declared_slots": slots, + "reject_reasons": reasons, + } + + +def build() -> dict: + reject = [ + recipe("nested_array16_depth_2048", + "2048 nested array16 headers each claiming 2000 elements, 0 backing bytes. The LAB-2487 " + "amplifier shape: an eager decoder pre-allocates 2000 slots per level before hitting EOF. " + "2000 < input_len, so a per-collection cap of len(input) does NOT reject it.", + "dc" + u16(2000), 2048, depth=2048, slots=2048 * 2000, reasons=["depth", "overclaim"]), + recipe("nested_array32_input_len_depth_1100", + "1100 nested array32 headers each claiming exactly len(input)=5500 elements. Defeats a " + "per-collection cap of len(input): peak pre-allocation is depth x len(input) x slot size.", + "dd" + u32(5500), 1100, depth=1100, slots=1100 * 5500, reasons=["depth", "overclaim"]), + recipe("nested_map16_depth_2048", + "Map twin of nested_array16_depth_2048 (map pre-allocation is typically larger per slot).", + "de" + u16(2000), 2048, depth=2048, slots=2048 * 2 * 2000, reasons=["depth", "overclaim"]), + recipe("nested_fixarray_depth_2048_complete", + "Structurally COMPLETE document ([[...[null]...]]) nested 2048 deep: every header is backed, " + "so only the depth bound rejects it. Isolates the depth rule from the allocation rule.", + "91", 2048, "c0", depth=2048, slots=2048, reasons=["depth"]), + recipe("array16_overclaim_shallow", + "One array16 header claiming 10 000 elements with 3 backing bytes.", + "dc" + u16(10000), 1, "010203", depth=1, slots=10000, reasons=["overclaim"]), + recipe("array32_max_claim_alone", + "A lone 5-byte array32 header claiming 2^32-1 elements.", + "dd" + u32(0xFFFFFFFF), 1, depth=1, slots=0xFFFFFFFF, reasons=["overclaim"]), + recipe("map32_max_claim_alone", + "A lone 5-byte map32 header claiming 2^32-1 pairs (2^33-2 slots: each pair is a key and a value).", + "df" + u32(0xFFFFFFFF), 1, depth=1, slots=2 * 0xFFFFFFFF, reasons=["overclaim"]), + recipe("array32_sum_wraps_u32", + "array32 claiming 2^32-1 elements whose first element is an array32 claiming 1: the declared " + "slots sum to exactly 2^32, which a 32-bit accumulator wraps to 0 and then passes the slot budget.", + "dd" + u32(0xFFFFFFFF), 1, "dd" + u32(1), depth=2, slots=0xFFFFFFFF + 1, reasons=["overclaim"]), + recipe("map32_half_claim_wraps_u32_mul", + "A lone map32 header claiming 2^31 pairs: the per-header term 2 x pairs is exactly 2^32, which a " + "32-bit multiply wraps to 0 before it is ever added to the budget.", + "df" + u32(0x80000000), 1, depth=1, slots=2 * 0x80000000, reasons=["overclaim"]), + recipe("fixmap_short_by_one", + "fixmap claiming 1 pair with the key present and the value missing: the map twin of " + "fixarray_short_by_one. Counting one slot per pair (instead of two) accepts it.", + "81", 1, "c0", depth=1, slots=2, reasons=["overclaim"]), + recipe("bin32_overclaim", + "bin32 header claiming 2^32-1 bytes with 1 backing byte (a 6-byte document declaring a 4 GiB buffer).", + "c6" + u32(0xFFFFFFFF), 1, "41", depth=0, slots=0xFFFFFFFF, reasons=["overclaim"]), + recipe("str32_overclaim", + "str32 twin of bin32_overclaim.", + "db" + u32(0xFFFFFFFF), 1, "41", depth=0, slots=0xFFFFFFFF, reasons=["overclaim"]), + recipe("fixarray_short_by_one", + "fixarray claiming 5 elements with 4 present: the minimal truncated document.", + "95", 1, "c0c0c0c0", depth=1, slots=5, reasons=["overclaim"]), + ] + accept = [ + recipe("nested_fixarray_depth_32", + "[[...[null]...]] nested 32 deep, complete. A conforming reader MUST accept it: the depth " + "bound may not be tighter than 32.", + "91", MIN_DEPTH_FLOOR, "c0", depth=MIN_DEPTH_FLOOR, slots=MIN_DEPTH_FLOOR, reasons=[]), + recipe("array16_256_backed_nils", + "array16 header claiming 256 elements with all 256 present. A *16 header that is fully " + "backed by input is legitimate; the allocation rule is about backing, not header width.", + "dc" + u16(256), 1, "c0" * 256, depth=1, slots=256, reasons=[]), + ] + for v in accept: + del v["reject_reasons"] + return { + "version": "1.0.0", + "spec": "spec/interop-mode.md#decode-bounds", + "generator": "tools/decode-bounds-reference.py generate (CPython stdlib)", + "scope": "Any untrusted MessagePack decode in any SDK: interop/v1 values, the ByteStorage envelope bytes " + "before StorageEnvelope is materialised, auto-mode payloads after the envelope is unwrapped, " + "invalidation events. The bytes are plain MessagePack with no envelope.", + "rules": { + "depth": f"Readers MUST bound nesting depth. The bound MUST be >= {MIN_DEPTH_FLOOR} and MUST be " + f"<= {MAX_DEPTH_CEILING}; every reject vector tagged 'depth' nests deeper than " + f"{MAX_DEPTH_CEILING}.", + "overclaim": "Readers MUST NOT pre-allocate for a collection/str/bin header more than the remaining " + "input can back (each element or byte needs >= 1 input byte), and MUST reject a " + "structurally incomplete document. Every reject vector tagged 'overclaim' has " + "declared_slots > input_len - 1 (the root header is the only byte that is not an element). " + "A map pair counts as two slots (key + value). Every per-header term and the running sum " + "MUST be computed in >= 64 bits or with checked/saturating arithmetic; an overflow is " + "itself a rejection.", + "failure_mode": "Rejection MUST surface as a catchable decode error that the SDK read path turns " + "into a cache miss (fail-closed), never an uncaught crash or an OOM abort.", + }, + "field_notes": { + "construction": "input = bytes.fromhex(repeat_hex) * count + bytes.fromhex(suffix_hex)", + "nesting_depth": "collection headers along the deepest spine (str/bin count as 0)", + "declared_slots": "sum of every header's declared element/byte count; a map pair counts as two slots (key + value); a nested header counts as one element of its parent", + "reject_reasons": "which rule(s) the vector violates; a maintainer note, not a normative message", + }, + "reject_vectors": reject, + "accept_vectors": accept, + } + + +def check(condition: bool, name: str, detail: str) -> None: # noqa: FBT001 + """Fail closed even under ``python -O`` (asserts would be stripped).""" + if not condition: + raise ValueError(f"{name}: {detail}") + + +def verify(document: dict, *, require_extras: bool = False) -> tuple[int, str]: + fresh = build() + check(document == fresh, "document", "vector file differs from the recipes; run `generate`") + # input_hex / input_len are derived from `construction` by recipe(), so the equality + # above already proves them; what still needs checking is the hand-entered tags. + for v in document["reject_vectors"] + document["accept_vectors"]: + reasons = v.get("reject_reasons", []) + check(("depth" in reasons) == (v["nesting_depth"] > MAX_DEPTH_CEILING), v["name"], "depth tag mismatch") + # Slot budget: every declared element (including a nested header) costs >= 1 input + # byte; only the root header is not itself an element. So sum(declared) <= len - 1. + check(("overclaim" in reasons) == (v["declared_slots"] > v["input_len"] - 1), v["name"], "overclaim tag mismatch") + if not reasons: + check(v["nesting_depth"] <= MIN_DEPTH_FLOOR, v["name"], "accept vector deeper than the floor") + + total = len(document["reject_vectors"]) + len(document["accept_vectors"]) + try: + import msgpack # type: ignore[import-not-found] + except ImportError: + if require_extras: + raise ValueError("--require-extras set but msgpack is not importable") from None + return total, "stdlib only (msgpack absent)" + + for v in document["reject_vectors"]: + data = bytes.fromhex(v["input_hex"]) + try: + msgpack.unpackb(data) + # unpackb surfaces every unpack failure as a ValueError (StackError, FormatError, + # ExtraData, max_*_len; it wraps OutOfData — the streaming Unpacker does not). Anything + # else is the failure_mode rule being violated, so it must fail the run, not count. + except ValueError: + continue + except (MemoryError, RecursionError) as e: + raise ValueError(f"{v['name']}: msgpack-python violated failure_mode ({type(e).__name__})") from e + raise ValueError(f"{v['name']}: msgpack-python decoded a reject vector") + for v in document["accept_vectors"]: + msgpack.unpackb(bytes.fromhex(v["input_hex"])) + return total, f"msgpack-python {msgpack.version} rejects/accepts as required" + + +def main() -> None: + usage = f"usage: {sys.argv[0]} [verify|generate] [--require-extras]" + args = sys.argv[1:] + require_extras = "--require-extras" in args + modes = [a for a in args if a != "--require-extras"] + mode = modes[0] if modes else "verify" + # Fail closed on anything unexpected: a typo in the flag must not silently drop the + # extras requirement CI relies on, and generate has no extras to require. + if len(modes) > 1 or mode not in ("verify", "generate") or (require_extras and mode != "verify"): + sys.exit(usage) + if mode == "generate": + VECTORS.write_text(json.dumps(build(), indent=2) + "\n", encoding="utf-8") + logging.info("wrote %s", VECTORS.relative_to(ROOT)) + return + try: + count, leg = verify(json.loads(VECTORS.read_text(encoding="utf-8")), require_extras=require_extras) + except ValueError as e: + sys.exit(f"decode-bounds verify FAILED: {e}") + logging.info("decode-bounds: %d vectors OK (%s)", count, leg) + + +if __name__ == "__main__": + # stdout, matching the pre-logging behaviour and the other tools' report lines. + logging.basicConfig(level=logging.INFO, format="%(message)s", stream=sys.stdout) + main() diff --git a/tools/test_decode_bounds_reference.py b/tools/test_decode_bounds_reference.py new file mode 100644 index 0000000..86e8b79 --- /dev/null +++ b/tools/test_decode_bounds_reference.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +"""Mutation tests for decode-bounds-reference.py's fail-closed guards. + +Same doctrine as test_wire_format_reference.py: poison the input and watch each guard +fire, so a guard that degrades to always-pass is caught before `verify` is trusted. +Nothing here touches test-vectors/decode-bounds.json. + +Run: python3 tools/test_decode_bounds_reference.py (exit 1 on any failure) +""" + +from __future__ import annotations + +import copy +import importlib.util +import subprocess +import sys +import types +from collections.abc import Callable +from pathlib import Path +from unittest.mock import patch + +TOOL = Path(__file__).resolve().parent / "decode-bounds-reference.py" +SPEC = importlib.util.spec_from_file_location("dbr", TOOL) +dbr = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(dbr) + + +def expect_raises(name: str, fn: Callable[[], object], needle: str) -> str | None: + try: + fn() + except ValueError as e: + return None if needle in str(e) else f"{name}: raised but message lacks {needle!r}: {e}" + return f"{name}: did not raise" + + +def with_recipes(doc: dict) -> Callable[[], object]: + """Run verify() with build() patched to agree with `doc`, so only the tag checks stand.""" + def run() -> object: + with patch.object(dbr, "build", lambda: copy.deepcopy(doc)): + return dbr.verify(doc) + return run + + +def with_msgpack(unpackb: Callable[[bytes], object] | None, doc: dict, *, require_extras: bool = False) -> Callable[[], object]: + """Run verify() against a fake msgpack module (None = import blocked).""" + def run() -> object: + fake = None + if unpackb is not None: + fake = types.ModuleType("msgpack") + fake.unpackb, fake.version = unpackb, (0, 0, 0) + with patch.dict(sys.modules, {"msgpack": fake}): + return dbr.verify(doc, require_extras=require_extras) + return run + + +def raise_memory_error(_: bytes) -> None: + raise MemoryError + + +def cli_rejects(name: str, *args: str) -> str | None: + """The CLI must exit non-zero on anything it does not understand (fail closed).""" + rc = subprocess.run([sys.executable, str(TOOL), *args], capture_output=True, check=False).returncode + return None if rc != 0 else f"{name}: exit 0 for {args}" + + +def main() -> None: + good = dbr.build() + results: list[str | None] = [] + + dbr.verify(good) # baseline: the real recipes pass + + drifted = copy.deepcopy(good) + drifted["reject_vectors"][0]["input_hex"] = "c0" + drifted["reject_vectors"][0]["input_hex"][2:] + results.append(expect_raises("file drift", lambda: dbr.verify(drifted), "differs from the recipes")) + + bad_depth = copy.deepcopy(good) + bad_depth["reject_vectors"][0]["nesting_depth"] = 5 # tagged 'depth' but no longer deeper than the ceiling + results.append(expect_raises("depth tag", with_recipes(bad_depth), "depth tag mismatch")) + + bad_slots = copy.deepcopy(good) + bad_slots["reject_vectors"][-1]["declared_slots"] = 1 # tagged 'overclaim' but no longer over-claims + results.append(expect_raises("overclaim tag", with_recipes(bad_slots), "overclaim tag mismatch")) + + deep_accept = copy.deepcopy(good) + deep_accept["accept_vectors"][0]["nesting_depth"] = dbr.MIN_DEPTH_FLOOR + 1 + results.append(expect_raises("accept floor", with_recipes(deep_accept), "deeper than the floor")) + + results.append(expect_raises("require-extras", with_msgpack(None, good, require_extras=True), "not importable")) + results.append(expect_raises("decoded reject", with_msgpack(lambda _: None, good), "decoded a reject vector")) + results.append(expect_raises("OOM not counted as reject", with_msgpack(raise_memory_error, good), "violated failure_mode")) + results.append(cli_rejects("flag typo", "verify", "--require-extra")) + results.append(cli_rejects("unknown mode", "bogus")) + results.append(cli_rejects("two modes", "verify", "generate")) + results.append(cli_rejects("generate with extras", "generate", "--require-extras")) + + failures = [f for f in results if f] + for f in failures: + print("FAIL", f, file=sys.stderr) + if failures: + sys.exit(1) + print(f"decode-bounds mutation suite: {len(results)} guards fire as required") + + +if __name__ == "__main__": + main()