#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use super::BlockOffset;
use crate::SeqNo;
pub fn encode_seqno_bounds(
out: &mut Vec<u8>,
bounds: &[(BlockOffset, (SeqNo, SeqNo))],
) -> crate::Result<()> {
let count =
u32::try_from(bounds.len()).map_err(|_| crate::Error::InvalidHeader("SeqnoBounds"))?;
out.extend_from_slice(&count.to_le_bytes());
for (offset, (seqno_min, seqno_max)) in bounds {
debug_assert!(
seqno_min <= seqno_max,
"seqno bounds inverted: min {seqno_min} > max {seqno_max}"
);
out.extend_from_slice(&offset.0.to_le_bytes());
out.extend_from_slice(&seqno_min.to_le_bytes());
out.extend_from_slice(&seqno_max.to_le_bytes());
}
Ok(())
}
#[derive(Debug, Default, Clone)]
pub struct SeqnoBoundsMap {
entries: Vec<(u64, (SeqNo, SeqNo))>,
}
impl SeqnoBoundsMap {
pub fn decode(bytes: &[u8]) -> crate::Result<Self> {
const ERR: crate::Error = crate::Error::InvalidHeader("SeqnoBounds");
fn take<'a>(r: &mut &'a [u8], n: usize) -> Option<&'a [u8]> {
if r.len() < n {
return None;
}
let (head, tail) = r.split_at(n);
*r = tail;
Some(head)
}
fn read_u32(r: &mut &[u8]) -> Option<u32> {
let b: [u8; 4] = take(r, 4)?.try_into().ok()?;
Some(u32::from_le_bytes(b))
}
fn read_u64(r: &mut &[u8]) -> Option<u64> {
let b: [u8; 8] = take(r, 8)?.try_into().ok()?;
Some(u64::from_le_bytes(b))
}
const ENTRY_SIZE: usize = 3 * core::mem::size_of::<u64>();
let mut r = bytes;
let count = read_u32(&mut r).ok_or(ERR)?;
match (count as usize).checked_mul(ENTRY_SIZE) {
Some(needed) if needed <= r.len() => {}
_ => return Err(ERR),
}
let mut entries = Vec::with_capacity(count as usize);
let mut prev: Option<u64> = None;
for _ in 0..count {
let offset = read_u64(&mut r).ok_or(ERR)?;
if prev.is_some_and(|p| offset <= p) {
return Err(ERR);
}
prev = Some(offset);
let seqno_min = read_u64(&mut r).ok_or(ERR)?;
let seqno_max = read_u64(&mut r).ok_or(ERR)?;
if seqno_min > seqno_max {
return Err(ERR);
}
entries.push((offset, (seqno_min, seqno_max)));
}
if !r.is_empty() {
return Err(ERR);
}
Ok(Self { entries })
}
#[must_use]
pub fn bounds_for(&self, offset: u64) -> Option<(SeqNo, SeqNo)> {
let idx = self
.entries
.binary_search_by_key(&offset, |(o, _)| *o)
.ok()?;
self.entries.get(idx).map(|(_, b)| *b)
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
}
#[cfg(test)]
#[expect(clippy::expect_used, reason = "test code")]
mod tests;