1#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
14#[error("{0}")]
15pub struct MsgpackStructureError(String);
16
17fn reject(reason: impl Into<String>) -> MsgpackStructureError {
18 MsgpackStructureError(reason.into())
19}
20
21pub fn check_msgpack_structure(
69 bytes: &[u8],
70 max_depth: usize,
71) -> Result<(), MsgpackStructureError> {
72 fn be(bytes: &[u8], pos: usize, width: usize) -> Result<u64, MsgpackStructureError> {
73 let end = pos
74 .checked_add(width)
75 .filter(|e| *e <= bytes.len())
76 .ok_or_else(|| reject("ends inside a length prefix"))?;
77 Ok(bytes[pos..end]
78 .iter()
79 .fold(0u64, |acc, b| (acc << 8) | u64::from(*b)))
80 }
81
82 let mut pos = 0usize;
83 let mut pending: u64 = 1; let mut open: Vec<u64> = Vec::new(); while pending > 0 {
86 while open.last() == Some(&0) {
87 open.pop();
88 }
89 let marker = *bytes
90 .get(pos)
91 .ok_or_else(|| reject("ends before the document is complete"))?;
92 pos += 1;
93 pending -= 1;
94 if let Some(innermost) = open.last_mut() {
95 *innermost -= 1;
96 }
97 let (prefix, payload, children): (usize, u64, u64) = match marker {
99 0x00..=0x7f | 0xc0 | 0xc2 | 0xc3 | 0xe0..=0xff => (0, 0, 0),
100 0x80..=0x8f => (0, 0, 2 * u64::from(marker & 0x0f)),
101 0x90..=0x9f => (0, 0, u64::from(marker & 0x0f)),
102 0xa0..=0xbf => (0, u64::from(marker & 0x1f), 0),
103 0xc1 => return Err(reject("contains the reserved marker 0xc1")),
104 0xc4 | 0xd9 => (1, be(bytes, pos, 1)?, 0),
105 0xc5 | 0xda => (2, be(bytes, pos, 2)?, 0),
106 0xc6 | 0xdb => (4, be(bytes, pos, 4)?, 0),
107 0xc7 => (1, be(bytes, pos, 1)? + 1, 0), 0xc8 => (2, be(bytes, pos, 2)? + 1, 0),
109 0xc9 => (4, be(bytes, pos, 4)? + 1, 0),
110 0xca..=0xd3 => (0, 1u64 << (marker & 0x03), 0), 0xd4..=0xd8 => (0, 1 + (1u64 << (marker - 0xd4)), 0), 0xdc => (2, 0, be(bytes, pos, 2)?),
113 0xdd => (4, 0, be(bytes, pos, 4)?),
114 0xde => (2, 0, 2 * be(bytes, pos, 2)?),
115 0xdf => (4, 0, 2 * be(bytes, pos, 4)?),
116 };
117 if matches!(marker, 0x80..=0x9f | 0xdc..=0xdf) && open.len() >= max_depth {
120 return Err(reject(format!("nests deeper than {max_depth} levels")));
121 }
122 pos += prefix;
123 let remaining = (bytes.len() - pos) as u64;
124 if payload > remaining {
125 return Err(reject("declares more bytes than the input holds"));
126 }
127 pos += usize::try_from(payload)
130 .map_err(|_| reject("declares more bytes than the input holds"))?;
131 if children > 0 {
132 open.push(children);
133 }
134 pending += children;
135 if pending > remaining - payload {
136 return Err(reject("declares more elements than the input can back"));
137 }
138 }
139 Ok(())
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 const MAX_DEPTH: usize = 100;
148
149 #[test]
150 fn the_error_is_a_std_error() {
151 fn is_error<E: std::error::Error + Send + Sync + 'static>() {}
152 is_error::<MsgpackStructureError>();
153 }
154
155 fn check(bytes: &[u8]) -> Result<(), String> {
156 check_msgpack_structure(bytes, MAX_DEPTH).map_err(|e| e.to_string())
157 }
158
159 fn nested(header: &[u8], count: usize, tail: &[u8]) -> Vec<u8> {
161 [header.repeat(count), tail.to_vec()].concat()
162 }
163
164 const LEVELS: [(&[u8], u8); 2] = [(&[0x91], 0x90), (&[0x81, 0xa0], 0x80)];
166
167 #[test]
168 fn accepts_every_scalar_marker_class() {
169 let docs: &[&[u8]] = &[
170 &[0x00],
171 &[0x7f],
172 &[0xe0],
173 &[0xff],
174 &[0xc0],
175 &[0xc2],
176 &[0xc3],
177 &[0xa3, b'a', b'b', b'c'],
178 &[0xd9, 0x01, b'x'],
179 &[0xda, 0x00, 0x01, b'x'],
180 &[0xdb, 0x00, 0x00, 0x00, 0x01, b'x'],
181 &[0xc4, 0x02, 0x01, 0x02],
182 &[0xc5, 0x00, 0x00],
183 &[0xc6, 0x00, 0x00, 0x00, 0x00],
184 &[0xc7, 0x01, 0x05, 0xaa],
185 &[0xc8, 0x00, 0x00, 0x05],
186 &[0xc9, 0x00, 0x00, 0x00, 0x00, 0x05],
187 &[0xca, 0, 0, 0, 0],
188 &[0xcb, 0, 0, 0, 0, 0, 0, 0, 0],
189 &[0xcc, 0],
190 &[0xcd, 0, 0],
191 &[0xce, 0, 0, 0, 0],
192 &[0xcf, 0, 0, 0, 0, 0, 0, 0, 0],
193 &[0xd0, 0],
194 &[0xd1, 0, 0],
195 &[0xd2, 0, 0, 0, 0],
196 &[0xd3, 0, 0, 0, 0, 0, 0, 0, 0],
197 &[0xd4, 0x05, 0],
198 &[0xd8, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
199 &[0xdc, 0x00, 0x01, 0xc0],
200 &[0xdd, 0x00, 0x00, 0x00, 0x01, 0xc0],
201 &[0xde, 0x00, 0x01, 0xa0, 0xc0],
202 &[0xdf, 0x00, 0x00, 0x00, 0x01, 0xa0, 0xc0],
203 ];
204 for doc in docs {
205 assert_eq!(check(doc), Ok(()), "{doc:02x?}");
206 }
207 }
208
209 #[test]
210 fn rejects_every_truncation_of_a_fixed_width_marker() {
211 let docs: &[&[u8]] = &[
212 &[0xd9],
213 &[0xda, 0x00],
214 &[0xdb, 0x00, 0x00, 0x00],
215 &[0xc7],
216 &[0xcb, 0, 0, 0, 0, 0, 0, 0],
217 &[0xd8, 0x05, 0],
218 &[0xdd, 0x00, 0x00],
219 &[0xa3, b'a', b'b'],
220 &[],
221 ];
222 for doc in docs {
223 assert!(check(doc).is_err(), "{doc:02x?}");
224 }
225 }
226
227 #[test]
228 fn rejects_the_reserved_marker() {
229 assert_eq!(
230 check(&[0x91, 0xc1]),
231 Err("contains the reserved marker 0xc1".to_owned())
232 );
233 }
234
235 #[test]
236 fn leaves_trailing_bytes_to_the_decoder() {
237 assert_eq!(check(&[0xc0, 0xc1, 0xff]), Ok(()));
238 }
239
240 #[test]
241 fn depth_bound_is_inclusive_for_arrays_and_maps() {
242 let too_deep = Err(format!("nests deeper than {MAX_DEPTH} levels"));
243 for (header, _) in LEVELS {
244 assert_eq!(check(&nested(header, MAX_DEPTH, &[0xc0])), Ok(()));
245 assert_eq!(check(&nested(header, MAX_DEPTH + 1, &[0xc0])), too_deep);
246 }
247 }
248
249 #[test]
250 fn an_empty_innermost_collection_counts_as_a_level() {
251 let too_deep = Err(format!("nests deeper than {MAX_DEPTH} levels"));
252 for (header, empty) in LEVELS {
253 assert_eq!(check(&nested(header, MAX_DEPTH - 1, &[empty])), Ok(()));
254 assert_eq!(check(&nested(header, MAX_DEPTH, &[empty])), too_deep);
255 }
256 }
257
258 #[test]
259 fn map_pairs_cost_two_slots() {
260 assert_eq!(check(&[0x81, 0xa0, 0xc0]), Ok(()));
261 assert!(check(&[0x81, 0xa0]).is_err());
262 }
263
264 #[test]
265 fn widest_claims_do_not_overflow() {
266 assert_eq!(
269 check(&[0xdf, 0xff, 0xff, 0xff, 0xff]),
270 Err("declares more elements than the input can back".to_owned())
271 );
272 assert_eq!(
273 check(&[0xc9, 0xff, 0xff, 0xff, 0xff, 0x05]),
274 Err("declares more bytes than the input holds".to_owned())
275 );
276 }
277
278 #[cfg(all(feature = "compression", feature = "checksum", feature = "messagepack"))]
279 #[test]
280 fn every_real_envelope_and_every_strict_prefix_of_one() {
281 let storage = crate::ByteStorage::new(None);
284 let bin = storage
285 .store(b"pre-scan admits real envelopes", None)
286 .unwrap();
287 let e: crate::StorageEnvelope = rmp_serde::from_slice(&bin).unwrap();
288 let legacy =
290 rmp_serde::to_vec(&(&e.compressed_data, e.checksum, e.original_size, &e.format))
291 .unwrap();
292 assert!(storage.retrieve(&legacy).is_ok());
293 for doc in [bin, legacy] {
294 assert_eq!(check(&doc), Ok(()));
295 for end in 0..doc.len() {
296 assert!(
297 check(&doc[..end]).is_err(),
298 "prefix of {end} bytes admitted"
299 );
300 }
301 }
302 }
303}