1use super::tags::*;
6
7pub fn skip_value(buf: &[u8], offset: usize) -> Option<usize> {
13 skip_value_depth(buf, offset, 0)
14}
15
16fn skip_value_depth(buf: &[u8], offset: usize, depth: u16) -> Option<usize> {
17 if depth > MAX_DEPTH {
18 return None;
19 }
20 let tag = get(buf, offset)?;
21 match tag {
22 0x00..=0x7f => Some(offset + 1),
24 0xe0..=0xff => Some(offset + 1),
26 NIL | FALSE | TRUE => Some(offset + 1),
28
29 0x80..=0x8f => {
31 let count = (tag & 0x0f) as usize;
32 skip_n_pairs(buf, offset + 1, count, depth)
33 }
34 MAP16 => {
35 let count = read_u16_be(buf, offset + 1)? as usize;
36 skip_n_pairs(buf, offset + 3, count, depth)
37 }
38 MAP32 => {
39 let count = read_u32_be(buf, offset + 1)? as usize;
40 skip_n_pairs(buf, offset + 5, count, depth)
41 }
42
43 0x90..=0x9f => {
45 let count = (tag & 0x0f) as usize;
46 skip_n_values(buf, offset + 1, count, depth)
47 }
48 ARRAY16 => {
49 let count = read_u16_be(buf, offset + 1)? as usize;
50 skip_n_values(buf, offset + 3, count, depth)
51 }
52 ARRAY32 => {
53 let count = read_u32_be(buf, offset + 1)? as usize;
54 skip_n_values(buf, offset + 5, count, depth)
55 }
56
57 0xa0..=0xbf => {
59 let len = (tag & 0x1f) as usize;
60 checked_advance(buf, offset, 1 + len)
61 }
62 STR8 => {
63 let len = get(buf, offset + 1)? as usize;
64 checked_advance(buf, offset, 2 + len)
65 }
66 STR16 => {
67 let len = read_u16_be(buf, offset + 1)? as usize;
68 checked_advance(buf, offset, 3 + len)
69 }
70 STR32 => {
71 let len = read_u32_be(buf, offset + 1)? as usize;
72 checked_advance(buf, offset, 5 + len)
73 }
74
75 BIN8 => {
77 let len = get(buf, offset + 1)? as usize;
78 checked_advance(buf, offset, 2 + len)
79 }
80 BIN16 => {
81 let len = read_u16_be(buf, offset + 1)? as usize;
82 checked_advance(buf, offset, 3 + len)
83 }
84 BIN32 => {
85 let len = read_u32_be(buf, offset + 1)? as usize;
86 checked_advance(buf, offset, 5 + len)
87 }
88
89 FLOAT32 => checked_advance(buf, offset, 5),
91 FLOAT64 => checked_advance(buf, offset, 9),
92 UINT8 | INT8 => checked_advance(buf, offset, 2),
93 UINT16 | INT16 => checked_advance(buf, offset, 3),
94 UINT32 | INT32 => checked_advance(buf, offset, 5),
95 UINT64 | INT64 => checked_advance(buf, offset, 9),
96
97 FIXEXT1 => checked_advance(buf, offset, 3),
99 FIXEXT2 => checked_advance(buf, offset, 4),
100 FIXEXT4 => checked_advance(buf, offset, 6),
101 FIXEXT8 => checked_advance(buf, offset, 10),
102 FIXEXT16 => checked_advance(buf, offset, 18),
103 EXT8 => {
104 let len = get(buf, offset + 1)? as usize;
105 checked_advance(buf, offset, 3 + len)
106 }
107 EXT16 => {
108 let len = read_u16_be(buf, offset + 1)? as usize;
109 checked_advance(buf, offset, 4 + len)
110 }
111 EXT32 => {
112 let len = read_u32_be(buf, offset + 1)? as usize;
113 checked_advance(buf, offset, 6 + len)
114 }
115
116 _ => None,
118 }
119}
120
121fn skip_n_values(buf: &[u8], mut pos: usize, count: usize, depth: u16) -> Option<usize> {
122 for _ in 0..count {
123 pos = skip_value_depth(buf, pos, depth + 1)?;
124 }
125 Some(pos)
126}
127
128fn skip_n_pairs(buf: &[u8], mut pos: usize, count: usize, depth: u16) -> Option<usize> {
129 for _ in 0..count {
130 pos = skip_value_depth(buf, pos, depth + 1)?; pos = skip_value_depth(buf, pos, depth + 1)?; }
133 Some(pos)
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crate::msgpack_scan::reader::read_str;
140
141 use serde_json::json;
142
143 fn encode(v: &serde_json::Value) -> Vec<u8> {
145 nodedb_types::json_msgpack::json_to_msgpack(v).expect("encode")
146 }
147
148 #[test]
149 fn skip_positive_fixint() {
150 let buf = [0x05, 0xff];
151 assert_eq!(skip_value(&buf, 0), Some(1));
152 }
153
154 #[test]
155 fn skip_negative_fixint() {
156 let buf = [0xe0, 0x00];
157 assert_eq!(skip_value(&buf, 0), Some(1));
158 }
159
160 #[test]
161 fn skip_nil_bool() {
162 assert_eq!(skip_value(&[NIL], 0), Some(1));
163 assert_eq!(skip_value(&[TRUE], 0), Some(1));
164 assert_eq!(skip_value(&[FALSE], 0), Some(1));
165 }
166
167 #[test]
168 fn skip_float64() {
169 let buf = encode(&json!(9.81));
170 assert_eq!(skip_value(&buf, 0), Some(buf.len()));
171 }
172
173 #[test]
174 fn skip_string() {
175 let buf = encode(&json!("hello"));
176 assert_eq!(skip_value(&buf, 0), Some(buf.len()));
177 }
178
179 #[test]
180 fn skip_map() {
181 let buf = encode(&json!({"a": 1, "b": 2}));
182 assert_eq!(skip_value(&buf, 0), Some(buf.len()));
183 }
184
185 #[test]
186 fn skip_nested_array() {
187 let buf = encode(&json!([[1, 2], [3, 4, 5]]));
188 assert_eq!(skip_value(&buf, 0), Some(buf.len()));
189 }
190
191 #[test]
192 fn skip_truncated_returns_none() {
193 let buf = [FLOAT64, 0x40]; assert_eq!(skip_value(&buf, 0), None);
195 }
196
197 #[test]
198 fn skip_bin() {
199 let buf = [BIN8, 3, 0xde, 0xad, 0xbe, 0xff];
201 assert_eq!(skip_value(&buf, 0), Some(5));
202 }
203
204 #[test]
205 fn skip_ext() {
206 let buf = [FIXEXT1, 0x01, 0xab, 0xff];
208 assert_eq!(skip_value(&buf, 0), Some(3));
209 }
210
211 #[test]
212 fn skip_empty_containers() {
213 assert_eq!(skip_value(&[0x80], 0), Some(1));
215 assert_eq!(skip_value(&[0x90], 0), Some(1));
217 }
218
219 #[test]
221 fn fuzz_tag_boundaries() {
222 let boundary_tags: &[(u8, bool)] = &[
226 (0x00, true), (0x7f, true), (0x80, true), (0x8f, false), (0x90, true), (0x9f, false), (0xa0, true), (0xbf, false), (0xc0, true), (0xc1, false), (0xc2, true), (0xc3, true), (0xe0, true), (0xff, true), ];
241 for &(tag, self_contained) in boundary_tags {
242 let buf = [tag; 64]; let result = skip_value(&buf, 0);
244 if self_contained {
245 assert!(result.is_some(), "tag 0x{tag:02x} should skip OK");
246 } else if tag == 0xc1 {
247 assert_eq!(result, None, "0xc1 must always return None");
248 }
249 }
251 }
252
253 #[test]
255 fn fuzz_adversarial_length_fields() {
256 let buf = [0xdbu8, 0xff, 0xff, 0xff, 0xff, b'x', b'y'];
258 assert_eq!(skip_value(&buf, 0), None);
259 assert_eq!(read_str(&buf, 0), None);
260
261 let buf = [0xdau8, 0xff, 0xff, b'x'];
263 assert_eq!(skip_value(&buf, 0), None);
264
265 let buf = [0xddu8, 0xff, 0xff, 0xff, 0xff];
267 assert_eq!(skip_value(&buf, 0), None);
268
269 let buf = [0xdfu8, 0xff, 0xff, 0xff, 0xff];
271 assert_eq!(skip_value(&buf, 0), None);
272
273 let buf = [0xdcu8, 0xff, 0xff];
275 assert_eq!(skip_value(&buf, 0), None);
276
277 let buf = [0xdeu8, 0xff, 0xff];
279 assert_eq!(skip_value(&buf, 0), None);
280
281 let buf = [0xc6u8, 0xff, 0xff, 0xff, 0xff, 0x00];
283 assert_eq!(skip_value(&buf, 0), None);
284
285 let buf = [0xc9u8, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00];
287 assert_eq!(skip_value(&buf, 0), None);
288 }
289
290 #[test]
293 fn fuzz_malicious_nesting_depth() {
294 let depth = 200usize;
297 let mut buf = vec![0x91u8; depth]; buf.push(0xc0u8); assert_eq!(
302 skip_value(&buf, 0),
303 None,
304 "deeply nested arrays must return None to guard against stack overflow"
305 );
306
307 let mut map_buf: Vec<u8> = Vec::new();
310 for i in 0..(depth as u8) {
311 map_buf.push(0x81); map_buf.push(0xa1); map_buf.push(b'a'.wrapping_add(i % 26));
314 }
316 map_buf.push(0xc0); assert_eq!(
319 skip_value(&map_buf, 0),
320 None,
321 "deeply nested maps must return None"
322 );
323 }
324
325 #[test]
328 fn fuzz_fixed_width_numeric_skip_offsets() {
329 let cases: &[(u8, usize)] = &[
331 (0xca, 5), (0xcb, 9), (0xcc, 2), (0xcd, 3), (0xce, 5), (0xcf, 9), (0xd0, 2), (0xd1, 3), (0xd2, 5), (0xd3, 9), ];
342 for &(tag, size) in cases {
343 let mut buf = vec![0u8; size + 4]; buf[0] = tag;
345 let result = skip_value(&buf, 0);
346 assert_eq!(
347 result,
348 Some(size),
349 "tag 0x{tag:02x} should advance by {size} bytes"
350 );
351 }
352 }
353
354 #[test]
356 fn fuzz_fixext_skip_offsets() {
357 let cases: &[(u8, usize)] = &[
359 (0xd4, 3), (0xd5, 4), (0xd6, 6), (0xd7, 10), (0xd8, 18), ];
365 for &(tag, size) in cases {
366 let mut buf = vec![0u8; size + 4];
367 buf[0] = tag;
368 let result = skip_value(&buf, 0);
369 assert_eq!(
370 result,
371 Some(size),
372 "fixext tag 0x{tag:02x} should advance by {size} bytes"
373 );
374 }
375 }
376}