use alloc::vec::Vec;
pub const DEFAULT_MAX_UNIT_SIZE: usize = 16 * 1024 * 1024;
#[derive(Debug, Clone)]
pub struct PusiAccumulator {
buf: Vec<u8>,
started: bool,
max_unit_size: usize,
}
impl Default for PusiAccumulator {
fn default() -> Self {
Self::new()
}
}
impl PusiAccumulator {
#[must_use]
pub fn new() -> Self {
Self::with_max_unit_size(DEFAULT_MAX_UNIT_SIZE)
}
#[must_use]
pub fn with_max_unit_size(max: usize) -> Self {
Self {
buf: Vec::new(),
started: false,
max_unit_size: max,
}
}
#[must_use]
pub fn max_unit_size(&self) -> usize {
self.max_unit_size
}
#[must_use]
pub fn feed(&mut self, pusi: bool, payload: &[u8]) -> Option<Vec<u8>> {
if pusi {
let completed = if self.started && !self.buf.is_empty() {
Some(core::mem::take(&mut self.buf))
} else {
self.buf.clear();
None
};
if payload.len() > self.max_unit_size {
self.started = false;
} else {
self.started = true;
self.buf.extend_from_slice(payload);
}
return completed;
}
if !self.started {
return None;
}
if self.buf.len().saturating_add(payload.len()) > self.max_unit_size {
self.buf.clear();
self.started = false;
return None;
}
self.buf.extend_from_slice(payload);
None
}
#[must_use]
pub fn flush(&mut self) -> Option<Vec<u8>> {
self.started = false;
if self.buf.is_empty() {
None
} else {
Some(core::mem::take(&mut self.buf))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn ignores_payload_before_first_pusi() {
let mut a = PusiAccumulator::new();
assert_eq!(a.feed(false, b"junk"), None);
assert_eq!(a.feed(true, b"real"), None);
assert_eq!(a.flush().as_deref(), Some(b"real".as_slice()));
}
#[test]
fn pusi_closes_previous_unit_and_flush_returns_last() {
let mut a = PusiAccumulator::new();
assert_eq!(a.feed(true, b"AA"), None);
assert_eq!(a.feed(false, b"aa"), None);
assert_eq!(a.feed(true, b"BB").as_deref(), Some(b"AAaa".as_slice()));
assert_eq!(a.flush().as_deref(), Some(b"BB".as_slice()));
assert_eq!(a.flush(), None);
}
#[test]
fn empty_unit_is_not_emitted() {
let mut a = PusiAccumulator::new();
assert_eq!(a.feed(true, b""), None);
assert_eq!(
a.feed(true, b"X"),
None,
"an empty unit is dropped, not returned"
);
assert_eq!(a.flush().as_deref(), Some(b"X".as_slice()));
}
#[test]
fn overflow_discards_unit_until_next_pusi() {
let mut a = PusiAccumulator::with_max_unit_size(10);
assert_eq!(a.feed(true, &[1; 6]), None);
assert_eq!(a.feed(false, &[2; 6]), None);
assert_eq!(a.feed(false, &[3; 2]), None);
assert_eq!(
a.feed(true, b"next"),
None,
"the oversized unit was discarded"
);
assert_eq!(a.flush().as_deref(), Some(b"next".as_slice()));
}
#[test]
fn unit_exactly_at_cap_is_kept() {
let mut a = PusiAccumulator::with_max_unit_size(10);
assert_eq!(a.feed(true, &[1; 6]), None);
assert_eq!(a.feed(false, &[2; 4]), None);
assert_eq!(a.flush(), Some(vec![1, 1, 1, 1, 1, 1, 2, 2, 2, 2]));
}
#[test]
fn oversized_first_payload_is_discarded() {
let mut a = PusiAccumulator::with_max_unit_size(4);
assert_eq!(a.feed(true, &[9; 5]), None);
assert_eq!(a.feed(false, &[9; 1]), None);
assert_eq!(a.flush(), None);
}
#[test]
fn default_cap_is_16_mib() {
assert_eq!(DEFAULT_MAX_UNIT_SIZE, 16_777_216);
assert_eq!(PusiAccumulator::new().max_unit_size(), 16_777_216);
}
}