1#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
15#[error("{0}")]
16pub struct MsgpackStructureError(String);
17
18fn reject(reason: impl Into<String>) -> MsgpackStructureError {
19 MsgpackStructureError(reason.into())
20}
21
22pub fn check_msgpack_structure(
71 bytes: &[u8],
72 max_depth: usize,
73) -> Result<(), MsgpackStructureError> {
74 msgpack_document_len(bytes, max_depth).map(|_| ())
75}
76
77pub fn msgpack_document_len(
105 bytes: &[u8],
106 max_depth: usize,
107) -> Result<usize, MsgpackStructureError> {
108 fn be(bytes: &[u8], pos: usize, width: usize) -> Result<u64, MsgpackStructureError> {
109 let end = pos
110 .checked_add(width)
111 .filter(|e| *e <= bytes.len())
112 .ok_or_else(|| reject("ends inside a length prefix"))?;
113 Ok(bytes[pos..end]
114 .iter()
115 .fold(0u64, |acc, b| (acc << 8) | u64::from(*b)))
116 }
117
118 let mut pos = 0usize;
119 let mut pending: u64 = 1; let mut open: Vec<u64> = Vec::new(); while pending > 0 {
122 while open.last() == Some(&0) {
123 open.pop();
124 }
125 let marker = *bytes
126 .get(pos)
127 .ok_or_else(|| reject("ends before the document is complete"))?;
128 pos += 1;
129 pending -= 1;
130 if let Some(innermost) = open.last_mut() {
131 *innermost -= 1;
132 }
133 let (prefix, payload, children): (usize, u64, u64) = match marker {
135 0x00..=0x7f | 0xc0 | 0xc2 | 0xc3 | 0xe0..=0xff => (0, 0, 0),
136 0x80..=0x8f => (0, 0, 2 * u64::from(marker & 0x0f)),
137 0x90..=0x9f => (0, 0, u64::from(marker & 0x0f)),
138 0xa0..=0xbf => (0, u64::from(marker & 0x1f), 0),
139 0xc1 => return Err(reject("contains the reserved marker 0xc1")),
140 0xc4 | 0xd9 => (1, be(bytes, pos, 1)?, 0),
141 0xc5 | 0xda => (2, be(bytes, pos, 2)?, 0),
142 0xc6 | 0xdb => (4, be(bytes, pos, 4)?, 0),
143 0xc7 => (1, be(bytes, pos, 1)? + 1, 0), 0xc8 => (2, be(bytes, pos, 2)? + 1, 0),
145 0xc9 => (4, be(bytes, pos, 4)? + 1, 0),
146 0xca..=0xd3 => (0, 1u64 << (marker & 0x03), 0), 0xd4..=0xd8 => (0, 1 + (1u64 << (marker - 0xd4)), 0), 0xdc => (2, 0, be(bytes, pos, 2)?),
149 0xdd => (4, 0, be(bytes, pos, 4)?),
150 0xde => (2, 0, 2 * be(bytes, pos, 2)?),
151 0xdf => (4, 0, 2 * be(bytes, pos, 4)?),
152 };
153 if matches!(marker, 0x80..=0x9f | 0xdc..=0xdf) && open.len() >= max_depth {
156 return Err(reject(format!("nests deeper than {max_depth} levels")));
157 }
158 pos += prefix;
159 let remaining = (bytes.len() - pos) as u64;
160 if payload > remaining {
161 return Err(reject("declares more bytes than the input holds"));
162 }
163 pos += usize::try_from(payload)
166 .map_err(|_| reject("declares more bytes than the input holds"))?;
167 if children > 0 {
168 open.push(children);
169 }
170 pending += children;
171 if pending > remaining - payload {
172 return Err(reject("declares more elements than the input can back"));
173 }
174 }
175 Ok(pos)
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 const MAX_DEPTH: usize = 100;
184
185 #[test]
186 fn the_error_is_a_std_error() {
187 fn is_error<E: std::error::Error + Send + Sync + 'static>() {}
188 is_error::<MsgpackStructureError>();
189 }
190
191 fn check(bytes: &[u8]) -> Result<(), String> {
192 check_msgpack_structure(bytes, MAX_DEPTH).map_err(|e| e.to_string())
193 }
194
195 fn doc_len(bytes: &[u8]) -> Result<usize, String> {
196 msgpack_document_len(bytes, MAX_DEPTH).map_err(|e| e.to_string())
197 }
198
199 fn nested(header: &[u8], count: usize, tail: &[u8]) -> Vec<u8> {
201 [header.repeat(count), tail.to_vec()].concat()
202 }
203
204 const LEVELS: [(&[u8], u8); 2] = [(&[0x91], 0x90), (&[0x81, 0xa0], 0x80)];
206
207 #[test]
208 fn accepts_every_scalar_marker_class() {
209 let docs: &[&[u8]] = &[
210 &[0x00],
211 &[0x7f],
212 &[0xe0],
213 &[0xff],
214 &[0xc0],
215 &[0xc2],
216 &[0xc3],
217 &[0xa3, b'a', b'b', b'c'],
218 &[0xd9, 0x01, b'x'],
219 &[0xda, 0x00, 0x01, b'x'],
220 &[0xdb, 0x00, 0x00, 0x00, 0x01, b'x'],
221 &[0xc4, 0x02, 0x01, 0x02],
222 &[0xc5, 0x00, 0x00],
223 &[0xc6, 0x00, 0x00, 0x00, 0x00],
224 &[0xc7, 0x01, 0x05, 0xaa],
225 &[0xc8, 0x00, 0x00, 0x05],
226 &[0xc9, 0x00, 0x00, 0x00, 0x00, 0x05],
227 &[0xca, 0, 0, 0, 0],
228 &[0xcb, 0, 0, 0, 0, 0, 0, 0, 0],
229 &[0xcc, 0],
230 &[0xcd, 0, 0],
231 &[0xce, 0, 0, 0, 0],
232 &[0xcf, 0, 0, 0, 0, 0, 0, 0, 0],
233 &[0xd0, 0],
234 &[0xd1, 0, 0],
235 &[0xd2, 0, 0, 0, 0],
236 &[0xd3, 0, 0, 0, 0, 0, 0, 0, 0],
237 &[0xd4, 0x05, 0],
238 &[0xd8, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
239 &[0xdc, 0x00, 0x01, 0xc0],
240 &[0xdd, 0x00, 0x00, 0x00, 0x01, 0xc0],
241 &[0xde, 0x00, 0x01, 0xa0, 0xc0],
242 &[0xdf, 0x00, 0x00, 0x00, 0x01, 0xa0, 0xc0],
243 ];
244 for doc in docs {
245 assert_eq!(check(doc), Ok(()), "{doc:02x?}");
246 assert_eq!(doc_len(doc), Ok(doc.len()), "{doc:02x?}");
247 }
248 }
249
250 #[test]
251 fn rejects_every_truncation_of_a_fixed_width_marker() {
252 let docs: &[&[u8]] = &[
253 &[0xd9],
254 &[0xda, 0x00],
255 &[0xdb, 0x00, 0x00, 0x00],
256 &[0xc7],
257 &[0xcb, 0, 0, 0, 0, 0, 0, 0],
258 &[0xd8, 0x05, 0],
259 &[0xdd, 0x00, 0x00],
260 &[0xa3, b'a', b'b'],
261 &[],
262 ];
263 for doc in docs {
264 assert!(check(doc).is_err(), "{doc:02x?}");
265 }
266 }
267
268 #[test]
269 fn rejects_the_reserved_marker() {
270 assert_eq!(
271 check(&[0x91, 0xc1]),
272 Err("contains the reserved marker 0xc1".to_owned())
273 );
274 }
275
276 #[test]
277 fn stops_where_the_document_ends_and_reports_its_length() {
278 assert_eq!(check(&[0xc0, 0xc1, 0xff]), Ok(()));
280 assert_eq!(doc_len(&[0xc0, 0xc1, 0xff]), Ok(1));
281 let doc = [0x81, 0xa1, b'a', 0x92, 0xc0, 0xc4, 0x02, 0xff, 0xff];
283 assert_eq!(doc_len(&doc), Ok(doc.len()));
284 assert_eq!(doc_len(&[&doc[..], &[0x81]].concat()), Ok(doc.len()));
285 }
286
287 #[test]
288 fn both_entry_points_reject_alike() {
289 let too_deep = nested(&[0x91], MAX_DEPTH + 1, &[0xc0]);
290 let cases: &[&[u8]] = &[
291 &[],
292 &[0x91, 0xc1],
293 &[0xdd, 0xff, 0xff, 0xff, 0xff],
294 &too_deep,
295 ];
296 for bytes in cases {
297 assert!(check(bytes).is_err(), "{bytes:02x?}");
298 assert_eq!(doc_len(bytes).map(|_| ()), check(bytes), "{bytes:02x?}");
299 }
300 }
301
302 #[test]
303 fn depth_bound_is_inclusive_for_arrays_and_maps() {
304 let too_deep = Err(format!("nests deeper than {MAX_DEPTH} levels"));
305 for (header, _) in LEVELS {
306 assert_eq!(check(&nested(header, MAX_DEPTH, &[0xc0])), Ok(()));
307 assert_eq!(check(&nested(header, MAX_DEPTH + 1, &[0xc0])), too_deep);
308 }
309 }
310
311 #[test]
312 fn an_empty_innermost_collection_counts_as_a_level() {
313 let too_deep = Err(format!("nests deeper than {MAX_DEPTH} levels"));
314 for (header, empty) in LEVELS {
315 assert_eq!(check(&nested(header, MAX_DEPTH - 1, &[empty])), Ok(()));
316 assert_eq!(check(&nested(header, MAX_DEPTH, &[empty])), too_deep);
317 }
318 }
319
320 #[test]
321 fn map_pairs_cost_two_slots() {
322 assert_eq!(check(&[0x81, 0xa0, 0xc0]), Ok(()));
323 assert!(check(&[0x81, 0xa0]).is_err());
324 }
325
326 #[test]
327 fn widest_claims_do_not_overflow() {
328 assert_eq!(
331 check(&[0xdf, 0xff, 0xff, 0xff, 0xff]),
332 Err("declares more elements than the input can back".to_owned())
333 );
334 assert_eq!(
335 check(&[0xc9, 0xff, 0xff, 0xff, 0xff, 0x05]),
336 Err("declares more bytes than the input holds".to_owned())
337 );
338 }
339
340 #[cfg(all(feature = "compression", feature = "checksum", feature = "messagepack"))]
341 #[test]
342 fn every_real_envelope_and_every_strict_prefix_of_one() {
343 let storage = crate::ByteStorage::new(None);
346 let bin = storage
347 .store(b"pre-scan admits real envelopes", None)
348 .unwrap();
349 let e: crate::StorageEnvelope = rmp_serde::from_slice(&bin).unwrap();
350 let legacy =
352 rmp_serde::to_vec(&(&e.compressed_data, e.checksum, e.original_size, &e.format))
353 .unwrap();
354 assert!(storage.retrieve(&legacy).is_ok());
355 for doc in [bin, legacy] {
356 assert_eq!(check(&doc), Ok(()));
357 assert_eq!(doc_len(&doc), Ok(doc.len()));
358 assert_eq!(doc_len(&[&doc[..], &doc[..]].concat()), Ok(doc.len()));
359 for end in 0..doc.len() {
360 assert!(
361 check(&doc[..end]).is_err(),
362 "prefix of {end} bytes admitted"
363 );
364 }
365 }
366 }
367}