#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("{0}")]
pub struct MsgpackStructureError(String);
fn reject(reason: impl Into<String>) -> MsgpackStructureError {
MsgpackStructureError(reason.into())
}
pub fn check_msgpack_structure(
bytes: &[u8],
max_depth: usize,
) -> Result<(), MsgpackStructureError> {
msgpack_document_len(bytes, max_depth).map(|_| ())
}
pub fn msgpack_document_len(
bytes: &[u8],
max_depth: usize,
) -> Result<usize, MsgpackStructureError> {
fn be(bytes: &[u8], pos: usize, width: usize) -> Result<u64, MsgpackStructureError> {
let end = pos
.checked_add(width)
.filter(|e| *e <= bytes.len())
.ok_or_else(|| reject("ends inside a length prefix"))?;
Ok(bytes[pos..end]
.iter()
.fold(0u64, |acc, b| (acc << 8) | u64::from(*b)))
}
let mut pos = 0usize;
let mut pending: u64 = 1; let mut open: Vec<u64> = Vec::new(); while pending > 0 {
while open.last() == Some(&0) {
open.pop();
}
let marker = *bytes
.get(pos)
.ok_or_else(|| reject("ends before the document is complete"))?;
pos += 1;
pending -= 1;
if let Some(innermost) = open.last_mut() {
*innermost -= 1;
}
let (prefix, payload, children): (usize, u64, u64) = match marker {
0x00..=0x7f | 0xc0 | 0xc2 | 0xc3 | 0xe0..=0xff => (0, 0, 0),
0x80..=0x8f => (0, 0, 2 * u64::from(marker & 0x0f)),
0x90..=0x9f => (0, 0, u64::from(marker & 0x0f)),
0xa0..=0xbf => (0, u64::from(marker & 0x1f), 0),
0xc1 => return Err(reject("contains the reserved marker 0xc1")),
0xc4 | 0xd9 => (1, be(bytes, pos, 1)?, 0),
0xc5 | 0xda => (2, be(bytes, pos, 2)?, 0),
0xc6 | 0xdb => (4, be(bytes, pos, 4)?, 0),
0xc7 => (1, be(bytes, pos, 1)? + 1, 0), 0xc8 => (2, be(bytes, pos, 2)? + 1, 0),
0xc9 => (4, be(bytes, pos, 4)? + 1, 0),
0xca..=0xd3 => (0, 1u64 << (marker & 0x03), 0), 0xd4..=0xd8 => (0, 1 + (1u64 << (marker - 0xd4)), 0), 0xdc => (2, 0, be(bytes, pos, 2)?),
0xdd => (4, 0, be(bytes, pos, 4)?),
0xde => (2, 0, 2 * be(bytes, pos, 2)?),
0xdf => (4, 0, 2 * be(bytes, pos, 4)?),
};
if matches!(marker, 0x80..=0x9f | 0xdc..=0xdf) && open.len() >= max_depth {
return Err(reject(format!("nests deeper than {max_depth} levels")));
}
pos += prefix;
let remaining = (bytes.len() - pos) as u64;
if payload > remaining {
return Err(reject("declares more bytes than the input holds"));
}
pos += usize::try_from(payload)
.map_err(|_| reject("declares more bytes than the input holds"))?;
if children > 0 {
open.push(children);
}
pending += children;
if pending > remaining - payload {
return Err(reject("declares more elements than the input can back"));
}
}
Ok(pos)
}
#[cfg(test)]
mod tests {
use super::*;
const MAX_DEPTH: usize = 100;
#[test]
fn the_error_is_a_std_error() {
fn is_error<E: std::error::Error + Send + Sync + 'static>() {}
is_error::<MsgpackStructureError>();
}
fn check(bytes: &[u8]) -> Result<(), String> {
check_msgpack_structure(bytes, MAX_DEPTH).map_err(|e| e.to_string())
}
fn doc_len(bytes: &[u8]) -> Result<usize, String> {
msgpack_document_len(bytes, MAX_DEPTH).map_err(|e| e.to_string())
}
fn nested(header: &[u8], count: usize, tail: &[u8]) -> Vec<u8> {
[header.repeat(count), tail.to_vec()].concat()
}
const LEVELS: [(&[u8], u8); 2] = [(&[0x91], 0x90), (&[0x81, 0xa0], 0x80)];
#[test]
fn accepts_every_scalar_marker_class() {
let docs: &[&[u8]] = &[
&[0x00],
&[0x7f],
&[0xe0],
&[0xff],
&[0xc0],
&[0xc2],
&[0xc3],
&[0xa3, b'a', b'b', b'c'],
&[0xd9, 0x01, b'x'],
&[0xda, 0x00, 0x01, b'x'],
&[0xdb, 0x00, 0x00, 0x00, 0x01, b'x'],
&[0xc4, 0x02, 0x01, 0x02],
&[0xc5, 0x00, 0x00],
&[0xc6, 0x00, 0x00, 0x00, 0x00],
&[0xc7, 0x01, 0x05, 0xaa],
&[0xc8, 0x00, 0x00, 0x05],
&[0xc9, 0x00, 0x00, 0x00, 0x00, 0x05],
&[0xca, 0, 0, 0, 0],
&[0xcb, 0, 0, 0, 0, 0, 0, 0, 0],
&[0xcc, 0],
&[0xcd, 0, 0],
&[0xce, 0, 0, 0, 0],
&[0xcf, 0, 0, 0, 0, 0, 0, 0, 0],
&[0xd0, 0],
&[0xd1, 0, 0],
&[0xd2, 0, 0, 0, 0],
&[0xd3, 0, 0, 0, 0, 0, 0, 0, 0],
&[0xd4, 0x05, 0],
&[0xd8, 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
&[0xdc, 0x00, 0x01, 0xc0],
&[0xdd, 0x00, 0x00, 0x00, 0x01, 0xc0],
&[0xde, 0x00, 0x01, 0xa0, 0xc0],
&[0xdf, 0x00, 0x00, 0x00, 0x01, 0xa0, 0xc0],
];
for doc in docs {
assert_eq!(check(doc), Ok(()), "{doc:02x?}");
assert_eq!(doc_len(doc), Ok(doc.len()), "{doc:02x?}");
}
}
#[test]
fn rejects_every_truncation_of_a_fixed_width_marker() {
let docs: &[&[u8]] = &[
&[0xd9],
&[0xda, 0x00],
&[0xdb, 0x00, 0x00, 0x00],
&[0xc7],
&[0xcb, 0, 0, 0, 0, 0, 0, 0],
&[0xd8, 0x05, 0],
&[0xdd, 0x00, 0x00],
&[0xa3, b'a', b'b'],
&[],
];
for doc in docs {
assert!(check(doc).is_err(), "{doc:02x?}");
}
}
#[test]
fn rejects_the_reserved_marker() {
assert_eq!(
check(&[0x91, 0xc1]),
Err("contains the reserved marker 0xc1".to_owned())
);
}
#[test]
fn stops_where_the_document_ends_and_reports_its_length() {
assert_eq!(check(&[0xc0, 0xc1, 0xff]), Ok(()));
assert_eq!(doc_len(&[0xc0, 0xc1, 0xff]), Ok(1));
let doc = [0x81, 0xa1, b'a', 0x92, 0xc0, 0xc4, 0x02, 0xff, 0xff];
assert_eq!(doc_len(&doc), Ok(doc.len()));
assert_eq!(doc_len(&[&doc[..], &[0x81]].concat()), Ok(doc.len()));
}
#[test]
fn both_entry_points_reject_alike() {
let too_deep = nested(&[0x91], MAX_DEPTH + 1, &[0xc0]);
let cases: &[&[u8]] = &[
&[],
&[0x91, 0xc1],
&[0xdd, 0xff, 0xff, 0xff, 0xff],
&too_deep,
];
for bytes in cases {
assert!(check(bytes).is_err(), "{bytes:02x?}");
assert_eq!(doc_len(bytes).map(|_| ()), check(bytes), "{bytes:02x?}");
}
}
#[test]
fn depth_bound_is_inclusive_for_arrays_and_maps() {
let too_deep = Err(format!("nests deeper than {MAX_DEPTH} levels"));
for (header, _) in LEVELS {
assert_eq!(check(&nested(header, MAX_DEPTH, &[0xc0])), Ok(()));
assert_eq!(check(&nested(header, MAX_DEPTH + 1, &[0xc0])), too_deep);
}
}
#[test]
fn an_empty_innermost_collection_counts_as_a_level() {
let too_deep = Err(format!("nests deeper than {MAX_DEPTH} levels"));
for (header, empty) in LEVELS {
assert_eq!(check(&nested(header, MAX_DEPTH - 1, &[empty])), Ok(()));
assert_eq!(check(&nested(header, MAX_DEPTH, &[empty])), too_deep);
}
}
#[test]
fn map_pairs_cost_two_slots() {
assert_eq!(check(&[0x81, 0xa0, 0xc0]), Ok(()));
assert!(check(&[0x81, 0xa0]).is_err());
}
#[test]
fn widest_claims_do_not_overflow() {
assert_eq!(
check(&[0xdf, 0xff, 0xff, 0xff, 0xff]),
Err("declares more elements than the input can back".to_owned())
);
assert_eq!(
check(&[0xc9, 0xff, 0xff, 0xff, 0xff, 0x05]),
Err("declares more bytes than the input holds".to_owned())
);
}
#[cfg(all(feature = "compression", feature = "checksum", feature = "messagepack"))]
#[test]
fn every_real_envelope_and_every_strict_prefix_of_one() {
let storage = crate::ByteStorage::new(None);
let bin = storage
.store(b"pre-scan admits real envelopes", None)
.unwrap();
let e: crate::StorageEnvelope = rmp_serde::from_slice(&bin).unwrap();
let legacy =
rmp_serde::to_vec(&(&e.compressed_data, e.checksum, e.original_size, &e.format))
.unwrap();
assert!(storage.retrieve(&legacy).is_ok());
for doc in [bin, legacy] {
assert_eq!(check(&doc), Ok(()));
assert_eq!(doc_len(&doc), Ok(doc.len()));
assert_eq!(doc_len(&[&doc[..], &doc[..]].concat()), Ok(doc.len()));
for end in 0..doc.len() {
assert!(
check(&doc[..end]).is_err(),
"prefix of {end} bytes admitted"
);
}
}
}
}