diff --git a/packages/cultcache-rs/README.md b/packages/cultcache-rs/README.md index 2d1fa671..7bdbd99b 100644 --- a/packages/cultcache-rs/README.md +++ b/packages/cultcache-rs/README.md @@ -459,7 +459,7 @@ manual registration ceremony, not to loosen the closed-world boundary. Payload nesting is bounded at 256 MessagePack containers, checked before any recursion; past the bound the refusal is a typed `PayloadTooDeep`. Typed decode of stored bytes then grows its stack: the decoder checks the remaining stack -before its first frame and `serde_stacker` checks it at every container, moving +at entry and `serde_stacker` checks it at every container, moving onto a fresh segment whenever less than 512 KiB remains, so a payload inside the bound decodes on a small thread instead of aborting the process. Envelope and store reads (single-file, redb, v1 snapshots) use the same grown decoder and @@ -471,9 +471,11 @@ record may have, and the derives refuse a larger one at compile time: put its large data behind a `Vec` or a `Box`. A field of a type the derives did not produce that is larger than that, behind a `Box` or `Vec`, is outside the guarantee. The decoder's own frames have a fixed floor that no payload changes: at the bound a thread decodes a record of -this size from about 56 KiB in a release build and 104 KiB in a debug build, against 16 and 88 KiB for a function -of the same signature that decodes nothing, plus the caller's own frames. Stack growth was verified on -x86_64-pc-windows-msvc once, by hand, on Starfire (`decode_stack`, debug and release); later `stacker` bumps repeat it. +this size from about 56 KiB in a release build and 72 KiB in a debug build for a caller that only tests the result +(`is_ok`), plus the caller's own frames. The figure depends on how the caller holds the result: one that moves the +record out holds a second copy and needs more (about 104 KiB in debug). Stack growth was verified on +x86_64-pc-windows-msvc by hand (`decode_stack`, WINREV, 13 tests, debug and release); a `stacker` bump or a change +to the stack-size tests repeats it. The crate does not build for wasm32 (`fs2`), and no wasm behaviour is claimed. diff --git a/packages/cultcache-rs/src/lib.rs b/packages/cultcache-rs/src/lib.rs index 4714ab79..3f895fb4 100644 --- a/packages/cultcache-rs/src/lib.rs +++ b/packages/cultcache-rs/src/lib.rs @@ -687,9 +687,8 @@ const DECODE_STACK_SEGMENT: usize = 2 * 1024 * 1024; /// ``` pub const MAX_RECORD_INLINE_BYTES: usize = 16 * 1024; -/// The one grown decoder of stored bytes: the stack check comes first, before any frame that holds -/// a `T`, then `serde_stacker` re-checks at every container serde's impls descend through, skipped -/// values included. No nesting bound is applied here; [`decode_payload`] walks first. +/// The one grown decoder of stored bytes: the outer stack check, then `serde_stacker` re-checks at +/// every container serde's impls descend through, skipped values included. No nesting bound is applied here; [`decode_payload`] walks first. fn decode_grown(bytes: &[u8]) -> std::result::Result { stacker::maybe_grow(DECODE_RED_ZONE, DECODE_STACK_SEGMENT, || { let mut reader = rmp_serde::Deserializer::from_read_ref(bytes); @@ -705,10 +704,12 @@ fn decode_grown(bytes: &[u8]) -> std::result::Re /// bytes. Readers of stored envelope payloads outside this crate decode through it too. /// /// A payload inside the bound decodes on any thread that has the decoder's fixed floor, which no payload -/// changes: at the bound, about 56 KiB in release and 104 KiB in debug, against 16 and 88 KiB for a -/// same-signature function that decodes nothing, plus the caller's own frames. That holds for every -/// record the derives produce, which [`MAX_RECORD_INLINE_BYTES`] bounds. Stack growth was verified on -/// Windows once, by hand; a `stacker` bump repeats it. +/// changes: at the bound, about 56 KiB in release and 72 KiB in debug for a caller that only tests the +/// result (`is_ok`), plus the caller's own frames. The figure depends on how the caller holds the result: +/// one that moves the record out holds a second copy and needs more (about 104 KiB in debug). That holds +/// for every record the derives produce, which [`MAX_RECORD_INLINE_BYTES`] bounds. Stack growth was +/// verified on x86_64-pc-windows-msvc by hand (`decode_stack`, WINREV, 13 tests, debug and release); a +/// `stacker` bump or a change to the stack-size tests repeats it. pub fn decode_payload(payload: &[u8]) -> Result { check_payload_depth(payload)?; decode_grown(payload).map_err(opaque_decode_error)