1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
use msgpack_document_len;
use ReadRefReader;
use ;
use crateCachekitError;
/// Maximum MessagePack nesting depth accepted from an untrusted payload.
///
/// Every cache read decodes bytes the backend controls, and `rmp-serde`
/// recurses once per nesting level, so the failure mode of unbounded depth is
/// a **stack overflow** — an uncatchable process abort. `rmp-serde`'s own
/// default (1024) is not a safe bound: measured (rmp-serde 1.3.1, 2026-09) on
/// a 2 MiB thread stack — the tokio worker default — a debug build overflows
/// between 512 and 768 nested arrays, i.e. a ~700-byte forged entry; a release
/// build fits 1024 with little margin, and frame size grows with the target
/// type. 100 matches cachekit-ts (`DEFAULT_MAX_DEPTH`), leaves a wide margin on
/// every profile and target (wasm32 included), and sits inside the protocol's
/// required `32..=1024` window (`spec/interop-mode.md` → Decode bounds).
///
/// Enforced by cachekit-core's `msgpack_document_len`, which runs before
/// every decode in [`deserialize`] and [`crate::interop::deserialize`]. Depth
/// counts every array and map header on the deepest path, empty ones included;
/// scalars, str, bin and ext are leaves.
/// Exactly `MAX_DECODE_DEPTH` levels are accepted, and one more is rejected
/// with a `decode bound:` error before `rmp-serde` recurses.
pub const MAX_DECODE_DEPTH: usize = 100;
/// A decoder reading a byte slice in place.
pub type SliceDeserializer<'a> = Deserializer;
/// Build a depth- and allocation-bounded `rmp_serde::Deserializer` over
/// `bytes`, which must hold exactly one MessagePack document.
///
/// Every decode of backend-supplied bytes (auto-mode [`deserialize`] and
/// [`crate::interop::deserialize`]) MUST go through here so the bounds cannot
/// drift between paths. It runs cachekit-core's header-only
/// `msgpack_document_len` walk first. The walk proves that a complete
/// document lies within `bytes`, that it nests no deeper than
/// [`MAX_DECODE_DEPTH`], and that no header declares more elements or bytes
/// than the remaining input can back. Its rejection becomes a
/// `CachekitError::Serialization` starting `decode bound:`.
///
/// Why depth is checked by the walk and not only by `rmp-serde`: a guard that
/// admits a document deeper than the bound leaves the rejection to the decoder,
/// mid-recursion, after it has materialised the outer levels.
///
/// Why the walk is needed even though `rmp-serde` reads str/bin lazily: serde's
/// sequence visitors (`Vec<T>`, and the `Content` buffer that
/// `#[serde(untagged)]` targets decode through) reserve
/// `min(declared_len × size_of::<Element>(), 1 MiB)` bytes per collection from
/// `size_hint`, so without the walk a 500-byte payload of nested
/// `array32(0xFFFFFFFF)` headers requests `MAX_DECODE_DEPTH` MiB before the
/// first EOF error — an OOM kill on a Cloudflare Workers isolate.
///
/// It then rejects any byte after the document: no writer emits one, a lenient
/// read would decode a foreign container by its first byte (a cachekit-py CK
/// frame begins with `0x43`, the complete document 67), and after a decrypt the
/// spec requires a parse mismatch to fail. The walk leaves trailing bytes to
/// its caller and returns where the document ends, so the same walk settles
/// this: any length short of `bytes.len()` means bytes follow the document.
/// Finally it sets `rmp-serde`'s own depth limit as a backstop just above
/// [`MAX_DECODE_DEPTH`]: its default (1024) is deep enough to overflow a
/// debug-build thread stack.
///
/// The decoder reads the input in place (`from_read_ref`), so str and bin
/// payloads are handed to serde as borrowed slices. The default `ReadReader`
/// copies each one through an internal buffer first, one extra allocation and
/// copy per field on every cache read.
///
/// A successful decode consumes the whole document: rmp-serde rejects an array
/// or map its target leaves unread. Ext is the exception: a target that reads
/// only an ext's type byte leaves the body unread and is accepted. No value
/// this SDK writes contains ext.
pub
/// Serialize `value` to MessagePack bytes using named fields (map format).
/// Deserialize `bytes` from MessagePack into `T` under the decode bounds:
/// cachekit-core's structural walk (depth ≤ [`MAX_DECODE_DEPTH`], no
/// overclaim) runs before anything is decoded, and `bytes` must hold exactly
/// one document — trailing bytes are rejected, as in interop mode
/// ([`crate::interop::deserialize`]).