#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FieldOverflow {
pub field: &'static str,
pub value: u64,
pub max: u64,
}
impl core::fmt::Display for FieldOverflow {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"{} = {} exceeds the field maximum {}",
self.field, self.value, self.max
)
}
}
impl core::error::Error for FieldOverflow {}
pub fn fit_bits(value: u64, bits: u32, field: &'static str) -> Result<u64, FieldOverflow> {
assert!(
(1..=64).contains(&bits),
"fit_bits: bits must be in 1..=64, got {bits}"
);
let max = if bits == 64 {
u64::MAX
} else {
(1u64 << bits) - 1
};
if value <= max {
Ok(value)
} else {
Err(FieldOverflow { field, value, max })
}
}
pub fn fit_u8(value: usize, field: &'static str) -> Result<u8, FieldOverflow> {
let fitted = fit_bits(value as u64, 8, field)?;
Ok(fitted as u8)
}
pub fn fit_u16(value: usize, field: &'static str) -> Result<u16, FieldOverflow> {
let fitted = fit_bits(value as u64, 16, field)?;
Ok(fitted as u16)
}
pub fn fit_u24(value: usize, field: &'static str) -> Result<u32, FieldOverflow> {
let fitted = fit_bits(value as u64, 24, field)?;
Ok(fitted as u32)
}
pub fn fit_u32(value: usize, field: &'static str) -> Result<u32, FieldOverflow> {
let fitted = fit_bits(value as u64, 32, field)?;
Ok(fitted as u32)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fit_u8_boundaries() {
assert_eq!(fit_u8(255, "x"), Ok(255));
assert_eq!(
fit_u8(256, "x"),
Err(FieldOverflow {
field: "x",
value: 256,
max: 255
})
);
}
#[test]
fn fit_u16_boundaries() {
assert_eq!(fit_u16(65_535, "x"), Ok(65_535));
assert_eq!(
fit_u16(65_536, "x"),
Err(FieldOverflow {
field: "x",
value: 65_536,
max: 65_535
})
);
}
#[test]
fn fit_u24_boundaries() {
assert_eq!(fit_u24(0xFF_FFFF, "x"), Ok(0xFF_FFFF));
assert_eq!(
fit_u24(0x100_0000, "x"),
Err(FieldOverflow {
field: "x",
value: 0x100_0000,
max: 0xFF_FFFF
})
);
}
#[test]
fn fit_u32_boundaries() {
assert_eq!(fit_u32(u32::MAX as usize, "x"), Ok(u32::MAX));
assert_eq!(
fit_u32(u32::MAX as usize + 1, "x"),
Err(FieldOverflow {
field: "x",
value: u64::from(u32::MAX) + 1,
max: u64::from(u32::MAX)
})
);
}
#[test]
fn fit_bits_arbitrary_widths() {
assert_eq!(fit_bits(4095, 12, "x"), Ok(4095));
assert_eq!(
fit_bits(4096, 12, "x"),
Err(FieldOverflow {
field: "x",
value: 4096,
max: 4095
})
);
assert_eq!(fit_bits(63, 6, "x"), Ok(63));
assert_eq!(
fit_bits(64, 6, "x"),
Err(FieldOverflow {
field: "x",
value: 64,
max: 63
})
);
assert_eq!(fit_bits(u64::MAX, 64, "x"), Ok(u64::MAX));
assert_eq!(fit_bits(1, 1, "x"), Ok(1));
assert_eq!(
fit_bits(2, 1, "x"),
Err(FieldOverflow {
field: "x",
value: 2,
max: 1
})
);
}
#[test]
fn fit_u16_rejects_65540_that_as_u16_would_wrap_to_4() {
assert_eq!(65540usize as u16, 4);
assert_eq!(
fit_u16(65_540, "x"),
Err(FieldOverflow {
field: "x",
value: 65_540,
max: 65_535
})
);
}
#[test]
#[should_panic(expected = "bits must be in 1..=64")]
fn fit_bits_rejects_zero_bits() {
let _ = fit_bits(1, 0, "x");
}
#[test]
#[should_panic(expected = "bits must be in 1..=64")]
fn fit_bits_rejects_65_bits() {
let _ = fit_bits(1, 65, "x");
}
#[test]
fn display_text_is_exact() {
let err = FieldOverflow {
field: "section_length",
value: 4096,
max: 4095,
};
assert_eq!(
err.to_string(),
"section_length = 4096 exceeds the field maximum 4095"
);
}
}