use super::{Header, PixelConfig, ID};
pub const MAX_DATA_LENGTH: usize = 480 * 3;
#[derive(Debug, Clone)]
pub struct FrameBuilder {
pub pixel_config: PixelConfig,
pub id: ID,
sequence_number: u8,
}
impl FrameBuilder {
pub fn new(pixel_config: PixelConfig, id: ID) -> Self {
Self {
pixel_config,
id,
sequence_number: 1,
}
}
pub fn sequence_number(&self) -> u8 {
self.sequence_number
}
pub fn for_each_frame<F, E>(
&mut self,
data: &[u8],
offset: u32,
scratch: &mut [u8],
f: F,
) -> Result<(), E>
where
F: FnMut(&[u8]) -> Result<(), E>,
{
let header = Header {
pixel_config: self.pixel_config,
id: self.id,
..Default::default()
};
self.frames_with(header, data, offset, scratch, f)
}
pub fn frames_with<F, E>(
&mut self,
mut header: Header,
data: &[u8],
offset: u32,
scratch: &mut [u8],
mut f: F,
) -> Result<(), E>
where
F: FnMut(&[u8]) -> Result<(), E>,
{
header.packet_type.push(false);
let total = data.len();
let num_iterations = total.div_ceil(MAX_DATA_LENGTH);
let mut chunk_index = 0usize;
let mut data_offset = 0usize;
while data_offset < total {
chunk_index += 1;
if chunk_index == num_iterations {
header.packet_type.push(true);
}
header.sequence_number = self.sequence_number;
let chunk_end = core::cmp::min(data_offset + MAX_DATA_LENGTH, total);
let chunk = &data[data_offset..chunk_end];
header.length = chunk.len() as u16;
header.offset = offset + data_offset as u32;
let header_len = header.write_into(scratch);
scratch[header_len..header_len + chunk.len()].copy_from_slice(chunk);
f(&scratch[..header_len + chunk.len()])?;
if self.sequence_number > 15 {
self.sequence_number = 1;
} else {
self.sequence_number += 1;
}
data_offset += MAX_DATA_LENGTH;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::PixelConfig;
#[test]
fn single_frame_matches_expected_bytes() {
let mut builder = FrameBuilder::new(PixelConfig::default(), ID::Default);
let mut scratch = [0u8; 1500];
let mut frames: Vec<Vec<u8>> = Vec::new();
builder
.for_each_frame(&[255, 0, 0, 255, 0, 0, 255, 0, 0], 0, &mut scratch, |frame| {
frames.push(frame.to_vec());
Ok::<(), ()>(())
})
.unwrap();
assert_eq!(frames.len(), 1);
assert_eq!(
frames[0],
vec![
0x41, 0x01, 0x0D, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0xFF, 0x00, 0x00,
0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00
]
);
}
#[test]
fn chunks_large_data_and_sets_push_on_last() {
let mut builder = FrameBuilder::new(PixelConfig::default(), ID::Default);
let mut scratch = [0u8; 1500];
let data = vec![7u8; MAX_DATA_LENGTH * 2 + 30];
let mut pushes: Vec<bool> = Vec::new();
let mut seqs: Vec<u8> = Vec::new();
builder
.for_each_frame(&data, 0, &mut scratch, |frame| {
let h = Header::from(frame);
pushes.push(h.packet_type.push);
seqs.push(h.sequence_number);
Ok::<(), ()>(())
})
.unwrap();
assert_eq!(pushes, vec![false, false, true]);
assert_eq!(seqs, vec![1, 2, 3]);
}
#[test]
fn frame_roundtrips_through_packet_ref() {
use crate::packet::PacketRef;
let mut builder = FrameBuilder::new(PixelConfig::default(), ID::Custom(42));
let mut scratch = [0u8; 1500];
let payload: Vec<u8> = (0..90u8).collect();
builder
.for_each_frame(&payload, 30, &mut scratch, |frame| {
let parsed = PacketRef::from_bytes(frame).unwrap();
assert_eq!(parsed.header.offset, 30);
assert_eq!(parsed.header.id, ID::Custom(42));
assert_eq!(parsed.header.length as usize, payload.len());
assert_eq!(parsed.data, &payload[..]);
Ok::<(), ()>(())
})
.unwrap();
}
#[test]
fn empty_data_produces_no_frames() {
let mut builder = FrameBuilder::new(PixelConfig::default(), ID::Default);
let mut scratch = [0u8; 1500];
let mut count = 0;
builder
.for_each_frame(&[], 0, &mut scratch, |_| {
count += 1;
Ok::<(), ()>(())
})
.unwrap();
assert_eq!(count, 0);
}
}