#![forbid(unsafe_code)]
#![cfg_attr(not(feature = "u32"), allow(unreachable_pub))]
#[allow(unused_imports)] use alloc::{vec, vec::Vec};
pub const MAX_BYTES: usize = 5;
const TAG_THRESHOLD: u8 = 252;
const NUM_TIERS: usize = 4;
const fn tier_offset(n: usize) -> u32 {
if n == 0 {
return 0;
}
if n == 1 {
return TAG_THRESHOLD as u32;
}
let mut result = TAG_THRESHOLD as u32;
let mut power = 1u32; let mut i = 2;
while i <= n {
power = power.saturating_mul(256);
result = result.saturating_add(power);
i += 1;
}
result
}
const OFFSETS: [u32; NUM_TIERS + 1] = [
tier_offset(0),
tier_offset(1),
tier_offset(2),
tier_offset(3),
tier_offset(4),
];
const BOUNDS: [u32; NUM_TIERS + 1] = [
tier_offset(1), tier_offset(2),
tier_offset(3),
tier_offset(4),
u32::MAX, ];
#[inline]
#[must_use]
pub const fn encoded_len(value: u32) -> usize {
if value < BOUNDS[0] {
return 1;
}
let bw = 32 - value.leading_zeros();
let candidate = ((bw - 1) / 8 + 2) as usize;
#[allow(clippy::indexing_slicing)]
if value < BOUNDS[candidate - 2] {
candidate - 1
} else {
candidate
}
}
#[allow(clippy::cast_possible_truncation, clippy::indexing_slicing)]
pub fn encode(value: u32, buf: &mut Vec<u8>) {
if value < BOUNDS[0] {
buf.push((value & 0xFF) as u8);
return;
}
let bw = 32 - value.leading_zeros();
let mut tier = ((bw - 1) / 8 + 1) as usize;
if value < BOUNDS[tier - 1] {
tier -= 1;
}
let tag = (TAG_THRESHOLD as usize + tier - 1) as u8;
let payload = (value - OFFSETS[tier]) << (8 * (NUM_TIERS - tier));
let pb = payload.to_be_bytes();
let original_len = buf.len();
buf.extend_from_slice(&[tag, pb[0], pb[1], pb[2], pb[3]]);
buf.truncate(original_len + tier + 1);
}
#[derive(Debug, Clone, Copy)]
pub struct EncodedU32 {
buf: [u8; MAX_BYTES],
len: u8,
}
impl PartialEq for EncodedU32 {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.as_slice() == other.as_slice()
}
}
impl Eq for EncodedU32 {}
impl core::hash::Hash for EncodedU32 {
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_slice().hash(state);
}
}
impl PartialOrd for EncodedU32 {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for EncodedU32 {
#[inline]
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.as_slice().cmp(other.as_slice())
}
}
impl EncodedU32 {
#[inline]
#[must_use]
#[cfg_attr(not(feature = "u32"), allow(clippy::trivially_copy_pass_by_ref))]
pub const fn len(&self) -> usize {
self.len as usize
}
#[inline]
#[must_use]
#[cfg_attr(
not(feature = "u32"),
allow(clippy::trivially_copy_pass_by_ref, clippy::unused_self)
)]
pub const fn is_empty(&self) -> bool {
false
}
#[inline]
#[must_use]
#[allow(clippy::indexing_slicing)] pub const fn as_slice(&self) -> &[u8] {
self.buf.split_at(self.len as usize).0
}
}
impl core::ops::Deref for EncodedU32 {
type Target = [u8];
#[inline]
fn deref(&self) -> &[u8] {
self.as_slice()
}
}
impl AsRef<[u8]> for EncodedU32 {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl core::borrow::Borrow<[u8]> for EncodedU32 {
#[inline]
fn borrow(&self) -> &[u8] {
self.as_slice()
}
}
impl IntoIterator for EncodedU32 {
type Item = u8;
type IntoIter = core::iter::Take<core::array::IntoIter<u8, MAX_BYTES>>;
fn into_iter(self) -> Self::IntoIter {
let len = self.len();
self.buf.into_iter().take(len)
}
}
impl<'a> IntoIterator for &'a EncodedU32 {
type Item = &'a u8;
type IntoIter = core::slice::Iter<'a, u8>;
fn into_iter(self) -> Self::IntoIter {
self.as_slice().iter()
}
}
#[inline]
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::indexing_slicing)]
pub const fn encoded_bytes(value: u32) -> EncodedU32 {
if value < BOUNDS[0] {
return EncodedU32 {
buf: [(value & 0xFF) as u8, 0, 0, 0, 0],
len: 1,
};
}
let bw = 32 - value.leading_zeros();
let mut tier = ((bw - 1) / 8 + 1) as usize;
if value < BOUNDS[tier - 1] {
tier -= 1;
}
let tag = (TAG_THRESHOLD as usize + tier - 1) as u8;
let payload = (value - OFFSETS[tier]) << (8 * (NUM_TIERS - tier));
let pb = payload.to_be_bytes();
EncodedU32 {
buf: [tag, pb[0], pb[1], pb[2], pb[3]],
len: (tier + 1) as u8,
}
}
#[inline]
#[allow(clippy::many_single_char_names)] pub const fn decode(buf: &[u8]) -> Result<(u32, usize), DecodeError> {
let Some((&tag, rest)) = buf.split_first() else {
return Err(DecodeError::BufferTooShort);
};
if tag < TAG_THRESHOLD {
return Ok((tag as u32, 1));
}
let (offset, payload, consumed) = match tag {
0xFC => match rest {
&[a, ..] => (OFFSETS[1], u32::from_be_bytes([0, 0, 0, a]), 2),
_ => return Err(DecodeError::BufferTooShort),
},
0xFD => match rest {
&[a, b, ..] => (OFFSETS[2], u32::from_be_bytes([0, 0, a, b]), 3),
_ => return Err(DecodeError::BufferTooShort),
},
0xFE => match rest {
&[a, b, c, ..] => (OFFSETS[3], u32::from_be_bytes([0, a, b, c]), 4),
_ => return Err(DecodeError::BufferTooShort),
},
_ => match rest {
&[a, b, c, d, ..] => (OFFSETS[4], u32::from_be_bytes([a, b, c, d]), 5),
_ => return Err(DecodeError::BufferTooShort),
},
};
match offset.checked_add(payload) {
Some(value) => Ok((value, consumed)),
None => Err(DecodeError::Overflow),
}
}
#[derive(Debug)]
#[must_use = "iterators are lazy; consume with for/.collect/.next"]
pub struct DecodeIter<'a> {
cursor: &'a [u8],
fused_err: bool,
}
#[inline]
pub const fn decode_iter(buf: &[u8]) -> DecodeIter<'_> {
DecodeIter {
cursor: buf,
fused_err: false,
}
}
impl Iterator for DecodeIter<'_> {
type Item = Result<u32, DecodeError>;
fn next(&mut self) -> Option<Self::Item> {
if self.fused_err || self.cursor.is_empty() {
return None;
}
match decode(self.cursor) {
Ok((value, consumed)) => {
self.cursor = match self.cursor.get(consumed..) {
Some(rest) => rest,
None => &[],
};
Some(Ok(value))
}
Err(err) => {
self.fused_err = true;
Some(Err(err))
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
if self.fused_err || self.cursor.is_empty() {
return (0, Some(0));
}
(1, Some(self.cursor.len()))
}
}
impl core::iter::FusedIterator for DecodeIter<'_> {}
#[cfg_attr(not(feature = "u32"), allow(dead_code))]
pub fn decode_all(buf: &[u8]) -> Result<Vec<u32>, DecodeError> {
decode_iter(buf).collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum DecodeError {
#[error("buffer too short for bijou32 encoding")]
BufferTooShort,
#[error("bijou32 tier 4 payload overflows u32")]
Overflow,
}
#[cfg(test)]
#[allow(clippy::indexing_slicing, clippy::needless_range_loop, clippy::panic)]
mod tests {
use super::*;
type TestResult = Result<(), DecodeError>;
mod offset_table {
use super::*;
#[test]
fn recurrence() {
assert_eq!(OFFSETS[0], 0);
assert_eq!(OFFSETS[1], u32::from(TAG_THRESHOLD));
let mut power = 1u32; for i in 2..=NUM_TIERS {
power *= 256;
assert_eq!(
OFFSETS[i],
OFFSETS[i - 1] + power,
"OFFSETS[{i}] does not satisfy recurrence"
);
}
}
#[test]
fn known_values() {
assert_eq!(OFFSETS[1], 252);
assert_eq!(OFFSETS[2], 508);
assert_eq!(OFFSETS[3], 66_044);
assert_eq!(OFFSETS[4], 16_843_260);
}
#[test]
fn bounds_are_consistent() {
assert_eq!(BOUNDS[0], OFFSETS[1]);
for i in 1..NUM_TIERS {
assert_eq!(
BOUNDS[i],
OFFSETS[i + 1],
"BOUNDS[{i}] should equal OFFSETS[{}]",
i + 1
);
}
assert_eq!(BOUNDS[NUM_TIERS], u32::MAX);
}
}
mod round_trip {
use super::*;
#[test]
fn all_tier_boundaries() -> TestResult {
for tier in 1..=NUM_TIERS {
let min_val = OFFSETS[tier];
let max_val = if tier < NUM_TIERS {
OFFSETS[tier + 1] - 1
} else {
u32::MAX
};
let expected_len = 1 + tier;
for &value in &[min_val, max_val] {
let mut buf = Vec::new();
encode(value, &mut buf);
assert_eq!(
buf.len(),
expected_len,
"tier {tier} value {value}: expected {expected_len} bytes, got {}",
buf.len()
);
let (decoded, consumed) = decode(&buf)?;
assert_eq!(decoded, value, "tier {tier} round-trip failed for {value}");
assert_eq!(consumed, expected_len);
}
}
Ok(())
}
}
mod errors {
use super::*;
#[test]
fn empty_buffer() {
assert_eq!(decode(&[]), Err(DecodeError::BufferTooShort));
}
#[test]
fn truncated_at_every_tier() {
for tier in 1..=NUM_TIERS {
let tag = u8::try_from(usize::from(TAG_THRESHOLD) + tier - 1).unwrap_or(0xFF);
let mut buf = vec![tag];
buf.extend(core::iter::repeat_n(0x00u8, tier - 1));
assert_eq!(
decode(&buf),
Err(DecodeError::BufferTooShort),
"tier {tier} (tag 0x{tag:02X}) with {}-byte payload \
should be BufferTooShort",
tier - 1
);
}
}
#[test]
fn tier4_overflow() {
let buf = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
assert_eq!(decode(&buf), Err(DecodeError::Overflow));
}
#[test]
fn tier4_overflow_exact_boundary() -> TestResult {
let max_payload = u32::MAX - OFFSETS[4];
let overflow_payload = max_payload + 1;
let be = overflow_payload.to_be_bytes();
let buf = [0xFF, be[0], be[1], be[2], be[3]];
assert_eq!(decode(&buf), Err(DecodeError::Overflow));
let be_max = max_payload.to_be_bytes();
let buf_max = [0xFF, be_max[0], be_max[1], be_max[2], be_max[3]];
let (value, consumed) = decode(&buf_max)?;
assert_eq!(value, u32::MAX);
assert_eq!(consumed, 5);
Ok(())
}
#[test]
fn trailing_bytes_not_consumed() -> TestResult {
let (v, n) = decode(&[0x2A, 0xDE, 0xAD])?;
assert_eq!((v, n), (42, 1));
let (v, n) = decode(&[0xFC, 0x30, 0xBE, 0xEF])?;
assert_eq!((v, n), (300, 2));
Ok(())
}
#[test]
fn decode_always_advances() -> TestResult {
let pad = [0u8; MAX_BYTES + 3]; for first in 0u8..=255u8 {
let mut buf = Vec::with_capacity(1 + pad.len());
buf.push(first);
buf.extend_from_slice(&pad);
let (_, n) = decode(&buf)?;
assert!(
n >= 1,
"decode returned consumed = 0 on first byte {first:#04X}",
);
}
Ok(())
}
}
mod exhaustive {
use super::*;
#[test]
fn tier0() -> TestResult {
for value in 0..252u32 {
let mut buf = Vec::new();
encode(value, &mut buf);
assert_eq!(buf.len(), 1);
assert_eq!(buf[0], u8::try_from(value).unwrap_or(0));
let (decoded, consumed) = decode(&buf)?;
assert_eq!(decoded, value);
assert_eq!(consumed, 1);
}
Ok(())
}
#[test]
fn tier1() -> TestResult {
for value in 252..508u32 {
let mut buf = Vec::new();
encode(value, &mut buf);
assert_eq!(buf.len(), 2, "value {value} should encode in 2 bytes");
assert_eq!(buf[0], 0xFC);
let (decoded, consumed) = decode(&buf)?;
assert_eq!(decoded, value, "round-trip failed for {value}");
assert_eq!(consumed, 2);
}
Ok(())
}
#[test]
fn tier2() -> TestResult {
for value in 508..66_044u32 {
let mut buf = Vec::new();
encode(value, &mut buf);
assert_eq!(buf.len(), 3, "value {value} should encode in 3 bytes");
assert_eq!(buf[0], 0xFD);
let (decoded, consumed) = decode(&buf)?;
assert_eq!(decoded, value, "round-trip failed for {value}");
assert_eq!(consumed, 3);
}
Ok(())
}
#[test]
fn tier3_exhaustive() -> TestResult {
for value in 66_044u32..=16_843_259u32 {
let mut buf = Vec::new();
encode(value, &mut buf);
assert_eq!(buf.len(), 4, "value {value} should encode in 4 bytes");
assert_eq!(buf[0], 0xFE);
let (decoded, consumed) = decode(&buf)?;
assert_eq!(decoded, value, "round-trip failed for {value}");
assert_eq!(consumed, 4);
}
Ok(())
}
#[test]
fn canonicality_byte_sequence_exhaustive() -> TestResult {
let check = |buf: &[u8]| -> TestResult {
if let Ok((value, consumed)) = decode(buf) {
let mut re = Vec::with_capacity(MAX_BYTES);
encode(value, &mut re);
assert_eq!(
re.as_slice(),
&buf[..consumed],
"non-canonical: decode({:02X?}) = {value}, re-encode = {:02X?}",
&buf[..consumed],
re
);
}
Ok(())
};
for b in 0u8..=255u8 {
check(&[b])?;
}
for p in 0u8..=255u8 {
check(&[0xFC, p])?;
}
for p1 in 0u8..=255u8 {
for p2 in 0u8..=255u8 {
check(&[0xFD, p1, p2])?;
}
}
for p in 0u32..(1u32 << 24) {
let bytes = p.to_be_bytes();
check(&[0xFE, bytes[1], bytes[2], bytes[3]])?;
}
Ok(())
}
}
mod boundaries {
use super::*;
#[test]
fn offset_triples() -> TestResult {
for tier in 1..=NUM_TIERS {
let offset = OFFSETS[tier];
let tier_len = 1 + tier;
let prev_len = if tier == 1 { 1 } else { 1 + (tier - 1) };
let below = offset - 1;
let mut buf = Vec::new();
encode(below, &mut buf);
assert_eq!(
buf.len(),
prev_len,
"OFFSETS[{tier}] - 1 = {below}: expected {prev_len} bytes, got {}",
buf.len()
);
let (v, n) = decode(&buf)?;
assert_eq!(v, below);
assert_eq!(n, prev_len);
buf.clear();
encode(offset, &mut buf);
assert_eq!(
buf.len(),
tier_len,
"OFFSETS[{tier}] = {offset}: expected {tier_len} bytes, got {}",
buf.len()
);
let (v, n) = decode(&buf)?;
assert_eq!(v, offset);
assert_eq!(n, tier_len);
if offset < u32::MAX {
buf.clear();
encode(offset + 1, &mut buf);
assert_eq!(
buf.len(),
tier_len,
"OFFSETS[{tier}] + 1 = {}: expected {tier_len} bytes, got {}",
offset + 1,
buf.len()
);
let (v, n) = decode(&buf)?;
assert_eq!(v, offset + 1);
assert_eq!(n, tier_len);
}
}
Ok(())
}
#[test]
fn all_zero_payloads() -> TestResult {
for tier in 1..=NUM_TIERS {
let tag = u8::try_from(usize::from(TAG_THRESHOLD) + tier - 1).unwrap_or(0xFF);
let mut buf = vec![tag];
buf.extend(core::iter::repeat_n(0x00u8, tier));
let (value, consumed) = decode(&buf)?;
assert_eq!(
value, OFFSETS[tier],
"tier {tier} all-zeros payload: expected OFFSETS[{tier}] = {}, got {value}",
OFFSETS[tier]
);
assert_eq!(consumed, 1 + tier);
let mut re = Vec::new();
encode(value, &mut re);
assert_eq!(re, buf, "tier {tier} all-zeros round-trip mismatch");
}
Ok(())
}
#[test]
fn all_ones_payloads() -> TestResult {
for tier in 1..=NUM_TIERS {
let tag = u8::try_from(usize::from(TAG_THRESHOLD) + tier - 1).unwrap_or(0xFF);
let mut buf = vec![tag];
buf.extend(core::iter::repeat_n(0xFFu8, tier));
let result = decode(&buf);
if tier < NUM_TIERS {
let (value, consumed) = result?;
let expected = OFFSETS[tier + 1] - 1;
assert_eq!(
value, expected,
"tier {tier} all-ones payload: expected {expected}, got {value}"
);
assert_eq!(consumed, 1 + tier);
let mut re = Vec::new();
encode(value, &mut re);
assert_eq!(re, buf, "tier {tier} all-ones round-trip mismatch");
} else {
assert_eq!(
result,
Err(DecodeError::Overflow),
"tier 4 all-ones should overflow"
);
}
}
Ok(())
}
}
mod bijectivity {
use super::*;
#[test]
fn overlong_encoding_decodes_to_different_value() -> TestResult {
for tier in 1..NUM_TIERS {
let value = OFFSETS[tier];
let payload = value - OFFSETS[tier];
let wider_tier = tier + 1;
let tag = u8::try_from(usize::from(TAG_THRESHOLD) + wider_tier - 1).unwrap_or(0xFF);
let mut forged = vec![tag];
let be = payload.to_be_bytes();
forged.extend_from_slice(be.get(4 - wider_tier..).unwrap_or(&[]));
let (decoded, _) = decode(&forged)?;
assert_ne!(
decoded, value,
"tier {tier}: overlong encoding of {value} decoded back \
to {value} — bijectivity violated"
);
assert_eq!(
decoded, OFFSETS[wider_tier],
"tier {tier}: forged payload 0 in tier {wider_tier} should \
decode to OFFSETS[{wider_tier}]"
);
}
Ok(())
}
}
mod streaming {
use super::*;
#[test]
fn consecutive_decode() -> TestResult {
let values: &[(u32, u32)] = &[
(0, 0),
(42, 300),
(252, 508),
(0x01_0001, u32::MAX),
(u32::MAX, 0),
];
for &(a, b) in values {
let mut buf = Vec::new();
encode(a, &mut buf);
encode(b, &mut buf);
let (decoded_a, consumed_a) = decode(&buf)?;
assert_eq!(decoded_a, a, "first value mismatch for ({a}, {b})");
let (decoded_b, consumed_b) = decode(&buf[consumed_a..])?;
assert_eq!(decoded_b, b, "second value mismatch for ({a}, {b})");
assert_eq!(
consumed_a + consumed_b,
buf.len(),
"total consumed mismatch for ({a}, {b})"
);
}
Ok(())
}
}
mod encoded_bytes {
use super::*;
#[test]
fn agrees_with_encode() {
let probes: &[u32] = &[
0,
1,
251,
252,
507,
508,
65_535,
66_043,
66_044,
16_843_259,
16_843_260,
1u32 << 24,
u32::MAX - 1,
u32::MAX,
];
for &v in probes {
let enc = encoded_bytes(v);
let mut via_encode = Vec::new();
encode(v, &mut via_encode);
assert_eq!(enc.len(), via_encode.len(), "len mismatch for {v}");
assert_eq!(&*enc, via_encode.as_slice(), "bytes mismatch for {v}");
}
}
#[test]
fn deref_asref_as_slice_consistent() {
let enc = encoded_bytes(300);
let via_deref: &[u8] = &enc;
let via_asref: &[u8] = enc.as_ref();
let via_as_slice: &[u8] = enc.as_slice();
assert_eq!(via_deref, &[0xFC, 0x30]);
assert_eq!(via_asref, &[0xFC, 0x30]);
assert_eq!(via_as_slice, &[0xFC, 0x30]);
}
#[test]
fn into_iter_walks_only_encoded_bytes() {
let enc = encoded_bytes(300); let collected: Vec<u8> = enc.into_iter().collect();
assert_eq!(collected, [0xFC, 0x30]);
}
#[test]
fn ref_into_iter_yields_encoded_bytes() {
let enc = encoded_bytes(300);
let collected: Vec<u8> = (&enc).into_iter().copied().collect();
assert_eq!(collected, [0xFC, 0x30]);
}
#[test]
fn copy_semantics() {
let enc = encoded_bytes(42);
let dup = enc;
assert_eq!(&*enc, &[0x2A]);
assert_eq!(&*dup, &[0x2A]);
}
#[test]
fn is_empty_is_always_false() {
for v in [0u32, 1, 251, 252, u32::MAX] {
assert!(!encoded_bytes(v).is_empty(), "is_empty true for {v}");
}
}
#[test]
fn borrow_impl() {
use alloc::collections::BTreeMap;
let mut map: BTreeMap<EncodedU32, &'static str> = BTreeMap::new();
map.insert(encoded_bytes(42), "the answer");
assert_eq!(map.get(&[0x2A][..]), Some(&"the answer"));
}
#[test]
fn eq_partial_eq() {
let a = encoded_bytes(300);
let b = encoded_bytes(300);
let c = encoded_bytes(301);
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn round_trip_via_decode() -> TestResult {
for v in [0u32, 251, 252, 65_535, 1u32 << 24, u32::MAX] {
let enc = encoded_bytes(v);
let (decoded, n) = decode(&enc)?;
assert_eq!(decoded, v);
assert_eq!(n, enc.len());
}
Ok(())
}
}
mod iter {
use super::*;
#[test]
fn empty_buffer() {
let collected: Vec<_> = decode_iter(&[]).collect();
assert!(collected.is_empty());
}
#[test]
fn round_trip() -> TestResult {
let values: &[u32] = &[0, 1, 42, 251, 252, 507, 508, 65_535, 1u32 << 24, u32::MAX];
let mut buf = Vec::new();
for &v in values {
encode(v, &mut buf);
}
let collected: Result<Vec<u32>, _> = decode_iter(&buf).collect();
assert_eq!(collected?, values);
Ok(())
}
#[test]
fn fuses_after_error() {
let mut iter = decode_iter(&[0xFC]);
assert_eq!(iter.next(), Some(Err(DecodeError::BufferTooShort)));
assert_eq!(iter.next(), None, "iter must fuse after error");
assert_eq!(iter.next(), None, "iter must remain fused");
}
#[test]
fn overflow_error_then_fused() {
let buf = [0xFFu8; 5];
let mut iter = decode_iter(&buf);
assert_eq!(iter.next(), Some(Err(DecodeError::Overflow)));
assert_eq!(iter.next(), None);
}
#[test]
fn partial_success_then_error() {
let mut iter = decode_iter(&[0x42, 0xFC]);
assert_eq!(iter.next(), Some(Ok(0x42)));
assert_eq!(iter.next(), Some(Err(DecodeError::BufferTooShort)));
assert_eq!(iter.next(), None);
}
#[test]
fn size_hint() {
let empty = decode_iter(&[]);
assert_eq!(empty.size_hint(), (0, Some(0)));
let with_data = decode_iter(&[0x42, 0x99]);
assert_eq!(with_data.size_hint(), (1, Some(2)));
let mut errored = decode_iter(&[0xFC]);
let _ = errored.next();
assert_eq!(errored.size_hint(), (0, Some(0)));
}
#[test]
fn composable_with_combinators() {
let mut buf = Vec::new();
for v in [10u32, 20, 30, 40, 50] {
encode(v, &mut buf);
}
let sum: u32 = decode_iter(&buf).filter_map(Result::ok).sum();
assert_eq!(sum, 150);
let first_two: Vec<_> = decode_iter(&buf).take(2).filter_map(Result::ok).collect();
assert_eq!(first_two, [10, 20]);
}
}
mod decode_all {
use super::*;
#[test]
fn empty_buffer() -> TestResult {
assert_eq!(decode_all(&[])?, Vec::<u32>::new());
Ok(())
}
#[test]
fn round_trip() -> TestResult {
let values: &[u32] = &[0, 42, 300, 65_535, 1u32 << 24, u32::MAX];
let mut buf = Vec::new();
for &v in values {
encode(v, &mut buf);
}
assert_eq!(decode_all(&buf)?, values);
Ok(())
}
#[test]
fn short_circuits_on_first_error() {
assert_eq!(decode_all(&[0x42, 0xFC]), Err(DecodeError::BufferTooShort));
}
#[test]
fn overflow_propagates() {
assert_eq!(decode_all(&[0xFF; 5]), Err(DecodeError::Overflow));
}
#[test]
fn agrees_with_decode_iter_collect() {
let inputs: &[&[u8]] = &[&[], &[0x00], &[0x42, 0xFC, 0x34], &[0xFC]];
for &input in inputs {
let via_iter: Result<Vec<u32>, _> = decode_iter(input).collect();
let via_all = decode_all(input);
assert_eq!(via_iter, via_all, "disagreement on {input:02X?}");
}
}
}
mod encode_api {
use super::*;
#[test]
fn appends_to_non_empty_buffer() -> TestResult {
let mut buf = vec![0xDE, 0xAD];
encode(300, &mut buf);
assert_eq!(&buf[..2], &[0xDE, 0xAD]);
let (value, consumed) = decode(&buf[2..])?;
assert_eq!(value, 300);
assert_eq!(consumed, 2);
assert_eq!(buf.len(), 4);
Ok(())
}
#[test]
fn max_bytes_equals_encoded_len_of_max() {
let mut buf = Vec::new();
encode(u32::MAX, &mut buf);
assert_eq!(
buf.len(),
MAX_BYTES,
"MAX_BYTES disagrees with encode(u32::MAX).len()"
);
assert_eq!(encoded_len(u32::MAX), MAX_BYTES);
assert_eq!(encoded_bytes(u32::MAX).len(), MAX_BYTES);
}
}
mod test_vectors {
use super::*;
const VECTORS: &[(u32, &[u8])] = &[
(0, &[0x00]),
(1, &[0x01]),
(42, &[0x2A]),
(251, &[0xFB]),
(252, &[0xFC, 0x00]),
(300, &[0xFC, 0x30]),
(507, &[0xFC, 0xFF]),
(508, &[0xFD, 0x00, 0x00]),
(65_535, &[0xFD, 0xFE, 0x03]),
(66_043, &[0xFD, 0xFF, 0xFF]),
(66_044, &[0xFE, 0x00, 0x00, 0x00]),
(16_843_259, &[0xFE, 0xFF, 0xFF, 0xFF]),
(16_843_260, &[0xFF, 0x00, 0x00, 0x00, 0x00]),
(u32::MAX, &[0xFF, 0xFE, 0xFE, 0xFE, 0x03]),
];
#[test]
fn encode_vectors() {
for &(value, expected) in VECTORS {
let mut buf = Vec::new();
super::encode(value, &mut buf);
assert_eq!(
buf.as_slice(),
expected,
"encode({value}) mismatch: got {buf:02X?}, expected {expected:02X?}"
);
}
}
#[test]
fn decode_vectors() -> TestResult {
for &(expected_value, bytes) in VECTORS {
let (value, consumed) = super::decode(bytes)?;
assert_eq!(
value, expected_value,
"decode({bytes:02X?}): got {value}, expected {expected_value}"
);
assert_eq!(consumed, bytes.len());
}
Ok(())
}
}
#[cfg(feature = "bolero")]
mod property {
use super::*;
#[test]
#[cfg_attr(miri, ignore)]
fn round_trip() {
bolero::check!().with_arbitrary::<u32>().for_each(|&value| {
let mut buf = Vec::new();
encode(value, &mut buf);
let (decoded, consumed) = decode(&buf).unwrap_or_else(|e| {
panic!("round-trip decode failed for {value}: {e}");
});
assert_eq!(decoded, value, "round-trip failed for {value}");
assert_eq!(consumed, buf.len());
});
}
#[test]
#[cfg_attr(miri, ignore)]
fn encoded_len_matches() {
bolero::check!().with_arbitrary::<u32>().for_each(|&value| {
let mut buf = Vec::new();
encode(value, &mut buf);
assert_eq!(encoded_len(value), buf.len());
});
}
#[test]
#[cfg_attr(miri, ignore)]
fn encoded_bytes_matches() {
bolero::check!().with_arbitrary::<u32>().for_each(|&value| {
let mut buf = Vec::new();
encode(value, &mut buf);
let enc = encoded_bytes(value);
assert_eq!(&*enc, buf.as_slice(), "value {value}");
});
}
#[test]
#[cfg_attr(miri, ignore)]
fn decode_never_panics() {
bolero::check!()
.with_arbitrary::<Vec<u8>>()
.for_each(|buf| {
let _ = decode(buf);
});
}
#[test]
#[cfg_attr(miri, ignore)]
fn decode_always_advances() {
bolero::check!()
.with_arbitrary::<Vec<u8>>()
.for_each(|buf| {
if let Ok((_, consumed)) = decode(buf) {
assert!(consumed >= 1, "decode of {buf:02X?} returned consumed = 0");
}
});
}
#[test]
#[cfg_attr(miri, ignore)]
fn lexicographic_order() {
bolero::check!()
.with_arbitrary::<(u32, u32)>()
.for_each(|&(a, b)| {
let enc_a = encoded_bytes(a);
let enc_b = encoded_bytes(b);
let slice_a: &[u8] = &enc_a;
let slice_b: &[u8] = &enc_b;
assert_eq!(
a.cmp(&b),
slice_a.cmp(slice_b),
"order mismatch: {a} vs {b}, \
encoded {slice_a:02X?} vs {slice_b:02X?}",
);
});
}
#[test]
#[cfg_attr(miri, ignore)]
fn bijective() {
bolero::check!()
.with_arbitrary::<Vec<u8>>()
.for_each(|buf| {
if let Ok((value, consumed)) = decode(buf) {
let mut re_encoded = Vec::new();
encode(value, &mut re_encoded);
assert_eq!(
re_encoded.as_slice(),
buf.get(..consumed).unwrap_or_default(),
"bijection violated: decode({:02X?}) = {value}, \
re-encode = {:02X?}",
buf.get(..consumed).unwrap_or_default(),
re_encoded
);
}
});
}
#[test]
#[cfg_attr(miri, ignore)]
fn decode_all_roundtrips_arbitrary_streams() {
bolero::check!()
.with_arbitrary::<Vec<u32>>()
.for_each(|xs| {
let mut buf = Vec::new();
for &x in xs {
encode(x, &mut buf);
}
assert_eq!(decode_all(&buf).as_deref(), Ok(xs.as_slice()));
});
}
#[test]
#[cfg_attr(miri, ignore)]
fn decode_iter_matches_manual_cursor() {
bolero::check!()
.with_arbitrary::<Vec<u8>>()
.for_each(|buf| {
let via_iter: Vec<Result<u32, DecodeError>> = decode_iter(buf).collect();
let mut manual = Vec::new();
let mut cursor: &[u8] = buf;
loop {
if cursor.is_empty() {
break;
}
match decode(cursor) {
Ok((v, n)) => {
manual.push(Ok(v));
cursor = cursor.get(n..).unwrap_or_default();
}
Err(e) => {
manual.push(Err(e));
break; }
}
}
assert_eq!(
via_iter, manual,
"decode_iter disagreed with manual loop on {buf:02X?}"
);
});
}
#[test]
#[cfg_attr(miri, ignore)]
fn decode_iter_fuses_after_any_error() {
bolero::check!()
.with_arbitrary::<Vec<u8>>()
.for_each(|buf| {
let mut it = decode_iter(buf);
let mut seen_err = false;
for item in it.by_ref() {
assert!(
!seen_err,
"decode_iter yielded after an error on {buf:02X?}"
);
if item.is_err() {
seen_err = true;
}
}
if seen_err {
assert!(it.next().is_none(), "must stay fused");
assert!(it.next().is_none(), "must stay fused");
}
});
}
}
}