use core::fmt;
use nom::bytes::complete::take;
use nom::IResult;
pub(crate) trait LengthWidth:
Copy + Eq + TryFrom<u64> + TryFrom<i64> + fmt::Display + fmt::Debug
{
fn widen(self) -> u64;
}
impl LengthWidth for usize {
fn widen(self) -> u64 {
self as u64
}
}
impl LengthWidth for u32 {
fn widen(self) -> u64 {
u64::from(self)
}
}
pub(crate) fn narrow_vuint_len<T: LengthWidth>(len: u64) -> Option<T> {
T::try_from(len).ok()
}
pub(crate) fn narrow_vint_len<T: LengthWidth>(len: i64) -> Option<T> {
T::try_from(len).ok()
}
pub fn take_vint_length(len: i64) -> impl Fn(&[u8]) -> IResult<&[u8], &[u8]> {
move |input: &[u8]| match narrow_vint_len::<usize>(len) {
Some(n) => take(n)(input),
None => Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::TooLarge,
))),
}
}
pub fn take_vuint_length(len: u64) -> impl Fn(&[u8]) -> IResult<&[u8], &[u8]> {
move |input: &[u8]| match narrow_vuint_len::<usize>(len) {
Some(n) => take(n)(input),
None => Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::TooLarge,
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
const TRUNCATING: [u64; 3] = [1u64 << 32, (1u64 << 32) + 3, u64::MAX];
#[test]
fn a_length_that_fits_consumes_exactly_that_many_bytes() {
let data = [1u8, 2, 3, 4, 5];
let (rest, body) = take_vint_length(3)(&data).expect("3 of 5 bytes");
assert_eq!(body, &[1, 2, 3]);
assert_eq!(rest, &[4, 5]);
let (rest, body) = take_vuint_length(0)(&data).expect("an empty run");
assert_eq!(body, &[] as &[u8]);
assert_eq!(rest, &data);
}
#[test]
fn narrowing_an_unsigned_length_at_32_bit_width_rejects_what_a_cast_would_truncate() {
assert_eq!((1u64 << 32) as u32, 0, "case setup: the low bits");
assert_eq!(((1u64 << 32) + 3) as u32, 3, "case setup: the low bits");
for len in TRUNCATING {
let truncated = len as u32;
assert_ne!(
u64::from(truncated),
len,
"case setup: the cast must lose information for {len}"
);
assert_eq!(
narrow_vuint_len::<u32>(len),
None,
"length {len} does not fit 32 bits and must be REJECTED, \
not narrowed to {truncated}"
);
}
}
#[test]
fn narrowing_a_signed_length_at_32_bit_width_rejects_what_a_cast_would_truncate() {
assert_eq!(((1i64 << 32) + 3) as u32, 3, "case setup: the low bits");
for len in [1i64 << 32, (1i64 << 32) + 3, i64::MAX, -1, i64::MIN] {
assert_eq!(
narrow_vint_len::<u32>(len),
None,
"length {len} must be rejected, never narrowed"
);
}
}
#[test]
fn a_representable_length_narrows_to_itself_at_both_widths() {
assert_eq!(narrow_vuint_len::<u32>(7), Some(7u32));
assert_eq!(narrow_vuint_len::<usize>(7), Some(7usize));
assert_eq!(narrow_vint_len::<u32>(7), Some(7u32));
assert_eq!(narrow_vint_len::<usize>(0), Some(0usize));
assert_eq!(
narrow_vuint_len::<u32>(u32::MAX as u64),
Some(u32::MAX),
"the widest 32-bit count is representable and must be accepted"
);
}
#[test]
fn a_negative_length_is_rejected_as_too_large_not_as_eof() {
let data = [1u8, 2, 3];
for len in [-1i64, -2, i64::MIN] {
match take_vint_length(len)(&data) {
Err(nom::Err::Error(e)) => assert_eq!(
e.code,
nom::error::ErrorKind::TooLarge,
"len {len} must be rejected by the width guard, not by `take`"
),
other => panic!("len {len}: expected a TooLarge parse error, got {other:?}"),
}
}
}
#[test]
fn a_length_wider_than_the_input_is_never_a_short_run() {
let data = [1u8, 2, 3, 4, 5];
for len in TRUNCATING {
assert!(
take_vuint_length(len)(&data).is_err(),
"declared length {len} must not resolve to a short run"
);
}
for len in [1i64 << 32, (1i64 << 32) + 3, i64::MAX] {
assert!(
take_vint_length(len)(&data).is_err(),
"declared length {len} must not resolve to a short run"
);
}
}
}