use core::iter::FusedIterator;
use crate::error::{Error, Result};
pub const CHUNK_LEN_PREFIX_SIZE: usize = 4;
pub struct ChunkCodec;
impl ChunkCodec {
#[inline]
pub fn encode(items: &[&[u8]], buf: &mut Vec<u8>) {
buf.clear();
let total_len: usize = items.iter().map(|f| CHUNK_LEN_PREFIX_SIZE + f.len()).sum();
buf.reserve(total_len);
for item in items {
debug_assert!(item.len() <= u32::MAX as usize);
let len = item.len() as u32;
buf.extend_from_slice(&len.to_be_bytes());
buf.extend_from_slice(item);
}
}
#[inline]
pub fn encode_to_vec(items: &[&[u8]]) -> Vec<u8> {
let total_len: usize = items.iter().map(|f| CHUNK_LEN_PREFIX_SIZE + f.len()).sum();
let mut buf = Vec::with_capacity(total_len);
for item in items {
debug_assert!(item.len() <= u32::MAX as usize);
let len = item.len() as u32;
buf.extend_from_slice(&len.to_be_bytes());
buf.extend_from_slice(item);
}
buf
}
#[inline]
pub fn append(item: &[u8], buf: &mut Vec<u8>) -> Result<()> {
let needed = match CHUNK_LEN_PREFIX_SIZE.checked_add(item.len()) {
Some(n) if item.len() <= u32::MAX as usize => n,
_ => return Err(Error::KeyLengthOverflow(item.len())),
};
buf.reserve(needed);
let len = item.len() as u32;
buf.extend_from_slice(&len.to_be_bytes());
buf.extend_from_slice(item);
Ok(())
}
#[inline]
pub fn iter(slice: &[u8]) -> Result<ChunkIter<'_>> {
let mut remaining = slice;
let mut count = 0;
while let Some((len_bytes, rest)) = remaining.split_first_chunk::<CHUNK_LEN_PREFIX_SIZE>() {
let len = u32::from_be_bytes(*len_bytes) as usize;
if len > rest.len() {
return Err(Error::BufferTooShort {
expected: len,
actual: rest.len(),
});
}
remaining = &rest[len..];
count += 1;
}
if !remaining.is_empty() {
return Err(Error::BufferTooShort {
expected: CHUNK_LEN_PREFIX_SIZE,
actual: remaining.len(),
});
}
Ok(ChunkIter { slice, count })
}
}
#[derive(Clone, Copy, Debug)]
pub struct ChunkIter<'a> {
slice: &'a [u8],
count: usize,
}
impl<'a> Iterator for ChunkIter<'a> {
type Item = &'a [u8];
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let (len_bytes, rest) = self.slice.split_first_chunk::<CHUNK_LEN_PREFIX_SIZE>()?;
let len = u32::from_be_bytes(*len_bytes) as usize;
let (item, remaining) = rest.split_at_checked(len)?;
self.slice = remaining;
self.count = self.count.saturating_sub(1);
Some(item)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(self.count, Some(self.count))
}
}
impl ExactSizeIterator for ChunkIter<'_> {
#[inline]
fn len(&self) -> usize {
self.count
}
}
impl FusedIterator for ChunkIter<'_> {}
#[cfg(test)]
mod tests {
use super::{CHUNK_LEN_PREFIX_SIZE, ChunkCodec};
#[test]
fn chunk_codec_roundtrip() {
let items: [&[u8]; 4] = [b"", b"alpha", &[0u8, 255, 7], b"omega"];
let mut buf = Vec::new();
ChunkCodec::encode(&items, &mut buf);
assert_eq!(
buf.len(),
items
.iter()
.map(|i| CHUNK_LEN_PREFIX_SIZE + i.len())
.sum::<usize>()
);
assert_eq!(ChunkCodec::encode_to_vec(&items), buf);
let mut app = Vec::new();
for it in items {
ChunkCodec::append(it, &mut app).unwrap();
}
assert_eq!(app, buf);
let parsed: Vec<&[u8]> = ChunkCodec::iter(&buf).unwrap().collect();
assert_eq!(parsed.as_slice(), items.as_slice());
}
#[test]
fn chunk_codec_boundary_defense() {
let empty: [&[u8]; 0] = [];
let buf = ChunkCodec::encode_to_vec(&empty);
assert!(buf.is_empty());
assert_eq!(ChunkCodec::iter(&buf).unwrap().count(), 0);
let mut corrupt = Vec::new();
corrupt.extend_from_slice(&10u32.to_be_bytes());
corrupt.extend_from_slice(b"abc");
assert!(ChunkCodec::iter(&corrupt).is_err());
assert!(ChunkCodec::iter(&[0, 0, 0]).is_err());
let items = [b"hello".as_slice(), b"world".as_slice()];
let full = ChunkCodec::encode_to_vec(&items);
let first_end = CHUNK_LEN_PREFIX_SIZE + items[0].len();
for len in 0..=full.len() {
if len == full.len() {
let iter = ChunkCodec::iter(&full[..len]).unwrap();
assert_eq!(iter.len(), 2, "len={len} 条目数不符");
assert_eq!(iter.count(), 2, "len={len} 迭代产出不符");
} else if len == first_end {
let iter = ChunkCodec::iter(&full[..len]).unwrap();
assert_eq!(iter.len(), 1, "len={len} 条目数不符");
assert_eq!(iter.count(), 1, "len={len} 迭代产出不符");
} else if len == 0 {
let iter = ChunkCodec::iter(&full[..len]).unwrap();
assert_eq!(iter.len(), 0, "len={len} 条目数不符");
assert_eq!(iter.count(), 0, "len={len} 迭代产出不符");
} else {
assert!(
ChunkCodec::iter(&full[..len]).is_err(),
"len={len} 残缺前缀必须报错"
);
}
}
}
}