use broadcast_common::Stage;
use bytes::Bytes;
pub trait ByteStage: for<'a> Stage<In<'a> = &'a [u8], Out = Bytes> + Send {}
impl<T> ByteStage for T where T: for<'a> Stage<In<'a> = &'a [u8], Out = Bytes> + Send {}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
use broadcast_common::stage::{Demand, Timestamp};
fn drive<S: ByteStage>(stage: &mut S, inputs: &[&[u8]]) -> Result<Vec<Bytes>, S::Error> {
let mut out = Vec::new();
for (i, chunk) in inputs.iter().enumerate() {
stage.feed(chunk, Timestamp::from_nanos(i as u64))?;
while let Some(b) = stage.poll() {
out.push(b);
}
}
stage.finish()?;
while let Some(b) = stage.poll() {
out.push(b);
}
Ok(out)
}
struct PassThrough {
pending: Option<Bytes>,
}
impl PassThrough {
fn new() -> Self {
PassThrough { pending: None }
}
}
impl Stage for PassThrough {
type In<'a> = &'a [u8];
type Out = Bytes;
type Error = core::convert::Infallible;
fn feed(&mut self, input: &[u8], _now: Timestamp) -> Result<(), Self::Error> {
self.pending = Some(Bytes::copy_from_slice(input));
Ok(())
}
fn poll(&mut self) -> Option<Self::Out> {
self.pending.take()
}
fn finish(&mut self) -> Result<(), Self::Error> {
Ok(())
}
fn next_deadline(&self) -> Option<Timestamp> {
None
}
fn on_deadline(&mut self, _now: Timestamp) {}
fn demand(&self) -> Demand {
Demand::new(4096)
}
}
#[test]
fn passthrough_is_a_byte_stage_via_generic_driver() {
fn accepts_byte_stage<S: ByteStage>(_s: &S) {}
let stage = PassThrough::new();
accepts_byte_stage(&stage);
}
#[test]
fn passthrough_delivers_each_input_exactly_once() {
let mut stage = PassThrough::new();
let out = drive(&mut stage, &[b"abc", b"de", b"fghi"]).unwrap();
assert_eq!(
out,
alloc::vec![
Bytes::from_static(b"abc"),
Bytes::from_static(b"de"),
Bytes::from_static(b"fghi"),
]
);
}
struct FixedFramer {
chunk_size: usize,
max_queued: usize,
partial: alloc::vec::Vec<u8>,
queued: alloc::collections::VecDeque<Bytes>,
finished: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FramerError {
QueueFull { max_queued: usize },
FedAfterFinish,
}
impl FixedFramer {
fn new(chunk_size: usize, max_queued: usize) -> Self {
assert!(chunk_size > 0, "chunk_size must be > 0");
assert!(max_queued > 0, "max_queued must be > 0");
FixedFramer {
chunk_size,
max_queued,
partial: alloc::vec::Vec::new(),
queued: alloc::collections::VecDeque::new(),
finished: false,
}
}
}
impl Stage for FixedFramer {
type In<'a> = &'a [u8];
type Out = Bytes;
type Error = FramerError;
fn feed(&mut self, input: &[u8], _now: Timestamp) -> Result<(), Self::Error> {
if self.finished {
return Err(FramerError::FedAfterFinish);
}
let remaining_capacity = self.max_queued - self.queued.len();
let max_acceptable =
remaining_capacity * self.chunk_size + (self.chunk_size - 1 - self.partial.len());
if input.len() > max_acceptable {
return Err(FramerError::QueueFull {
max_queued: self.max_queued,
});
}
self.partial.extend_from_slice(input);
while self.partial.len() >= self.chunk_size {
let chunk: alloc::vec::Vec<u8> = self.partial.drain(..self.chunk_size).collect();
self.queued.push_back(Bytes::from(chunk));
}
Ok(())
}
fn poll(&mut self) -> Option<Self::Out> {
self.queued.pop_front()
}
fn finish(&mut self) -> Result<(), Self::Error> {
if !self.finished {
if !self.partial.is_empty() {
let tail = core::mem::take(&mut self.partial);
self.queued.push_back(Bytes::from(tail));
}
self.finished = true;
}
Ok(())
}
fn next_deadline(&self) -> Option<Timestamp> {
None
}
fn on_deadline(&mut self, _now: Timestamp) {}
fn demand(&self) -> Demand {
if self.queued.len() >= self.max_queued {
Demand::saturated()
} else {
let remaining_capacity = self.max_queued - self.queued.len();
let want = remaining_capacity * self.chunk_size
+ (self.chunk_size - 1 - self.partial.len());
Demand::new(want)
}
}
}
#[test]
fn framer_is_a_byte_stage_via_generic_driver() {
fn accepts_byte_stage<S: ByteStage>(_s: &S) {}
let stage = FixedFramer::new(4, 8);
accepts_byte_stage(&stage);
}
#[test]
fn framer_emits_multiple_outputs_from_one_feed_with_no_loss_or_duplication() {
let mut stage = FixedFramer::new(4, 8);
stage.feed(b"abcdefgh", Timestamp::ZERO).unwrap();
assert_eq!(stage.poll(), Some(Bytes::from_static(b"abcd")));
assert_eq!(stage.poll(), Some(Bytes::from_static(b"efgh")));
assert_eq!(stage.poll(), None);
stage.finish().unwrap();
assert_eq!(stage.poll(), None);
}
#[test]
fn framer_finish_flushes_partial_tail() {
let mut stage = FixedFramer::new(4, 8);
let out = drive(&mut stage, &[b"abc"]).unwrap();
assert_eq!(out, alloc::vec![Bytes::from_static(b"abc")]);
}
#[test]
fn framer_finish_is_idempotent() {
let mut stage = FixedFramer::new(4, 8);
stage.feed(b"ab", Timestamp::ZERO).unwrap();
stage.finish().unwrap();
assert_eq!(stage.poll(), Some(Bytes::from_static(b"ab")));
assert_eq!(stage.poll(), None);
stage.finish().unwrap();
assert_eq!(stage.poll(), None);
}
#[test]
fn framer_feed_after_finish_errors_and_is_deliberate() {
let mut stage = FixedFramer::new(4, 8);
stage.feed(b"ab", Timestamp::ZERO).unwrap();
stage.finish().unwrap();
assert_eq!(stage.poll(), Some(Bytes::from_static(b"ab")));
let err = stage.feed(b"cd", Timestamp::ZERO).unwrap_err();
assert_eq!(err, FramerError::FedAfterFinish);
assert_eq!(stage.poll(), None);
}
#[test]
fn framer_buffer_bound_holds_under_flood_and_demand_saturates() {
let chunk_size = 4;
let max_queued = 3;
let mut stage = FixedFramer::new(chunk_size, max_queued);
stage
.feed(&alloc::vec![0u8; chunk_size * max_queued], Timestamp::ZERO)
.unwrap();
assert_eq!(stage.demand(), Demand::saturated());
for _ in 0..1_000 {
let err = stage
.feed(&alloc::vec![0u8; chunk_size * 64], Timestamp::ZERO)
.unwrap_err();
assert_eq!(err, FramerError::QueueFull { max_queued });
}
let mut drained = 0usize;
while stage.poll().is_some() {
drained += 1;
}
assert_eq!(drained, max_queued);
}
}