use std::collections::HashMap;
use base64::{
alphabet::URL_SAFE,
engine::{general_purpose::GeneralPurpose, DecodePaddingMode, GeneralPurposeConfig},
Engine as _,
};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
pub mod companion;
#[cfg(feature = "tokio-receiver")]
pub mod receiver;
#[cfg(feature = "tokio-sender")]
pub mod sender;
pub mod v1;
pub mod v2;
pub mod v3;
pub mod v4;
pub const HEADER_LENGTH: usize = 5;
#[derive(Debug)]
pub enum TryFromByteError {
UnknownOpcode(u8),
}
impl std::error::Error for TryFromByteError {}
impl std::fmt::Display for TryFromByteError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TryFromByteError::UnknownOpcode(opcode) => write!(f, "Unknown opcode: {opcode}"),
}
}
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Opcode {
None = 0,
Play = 1,
Pause = 2,
Resume = 3,
Stop = 4,
Seek = 5,
PlaybackUpdate = 6,
VolumeUpdate = 7,
SetVolume = 8,
PlaybackError = 9,
SetSpeed = 10,
Version = 11,
Ping = 12,
Pong = 13,
Initial = 14,
PlayUpdate = 15,
SetPlaylistItem = 16,
SubscribeEvent = 17,
UnsubscribeEvent = 18,
Event = 19,
Flatbuf = 20,
Resource = 21,
}
impl TryFrom<u8> for Opcode {
type Error = TryFromByteError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
0 => Self::None,
1 => Self::Play,
2 => Self::Pause,
3 => Self::Resume,
4 => Self::Stop,
5 => Self::Seek,
6 => Self::PlaybackUpdate,
7 => Self::VolumeUpdate,
8 => Self::SetVolume,
9 => Self::PlaybackError,
10 => Self::SetSpeed,
11 => Self::Version,
12 => Self::Ping,
13 => Self::Pong,
14 => Self::Initial,
15 => Self::PlayUpdate,
16 => Self::SetPlaylistItem,
17 => Self::SubscribeEvent,
18 => Self::UnsubscribeEvent,
19 => Self::Event,
20 => Self::Flatbuf,
21 => Self::Resource,
_ => return Err(TryFromByteError::UnknownOpcode(value)),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum PlaybackState {
Idle = 0,
Playing = 1,
Paused = 2,
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct PlaybackErrorMessage {
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct VersionMessage {
pub version: u64,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct SetSpeedMessage {
pub speed: f64,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct SetVolumeMessage {
pub volume: f64,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct SeekMessage {
pub time: f64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FCastService {
pub port: u16,
pub r#type: i32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FCastNetworkConfig {
pub name: String,
pub addresses: Vec<String>,
pub services: Vec<FCastService>,
pub txt: Option<HashMap<String, String>>,
}
impl FCastNetworkConfig {
pub fn parse_url(url: &str) -> Option<Self> {
let connection_info = url.strip_prefix("fcast://r/")?;
let b64_engine = GeneralPurpose::new(
&URL_SAFE,
GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent),
);
let json = b64_engine.decode(connection_info).ok()?;
serde_json::from_slice::<Self>(&json).ok()
}
pub fn to_url(&self) -> serde_json::Result<String> {
let net_config = serde_json::to_string(self)?;
let url = format!(
"fcast://r/{}",
base64::engine::general_purpose::URL_SAFE
.encode(net_config)
.as_str(),
);
Ok(url)
}
}
#[derive(Debug, PartialEq, Eq)]
enum ReaderState {
MissingLength,
MissingBody { length: usize },
ShouldClear { body_length: usize },
}
#[derive(Debug, PartialEq, Eq)]
pub enum ReadResult<'a> {
NeedData,
Read(&'a [u8]),
PacketTooLarge(usize),
}
#[derive(Debug)]
pub enum PushDataError {
BufferTooBig,
}
pub struct PacketReader {
buffer: Vec<u8>,
state: ReaderState,
pos: usize,
len: usize,
max_packet_size: usize,
}
impl PacketReader {
pub fn new(max_packet_size: usize, padding: usize) -> Self {
Self {
buffer: vec![0; size_of::<u32>() + max_packet_size + padding],
state: ReaderState::MissingLength,
pos: 0,
len: 0,
max_packet_size,
}
}
fn buffered(&self) -> usize {
self.len - self.pos
}
fn discard_consumed(&mut self) {
if let ReaderState::ShouldClear { body_length } = self.state {
self.pos += size_of::<u32>() + body_length;
self.state = ReaderState::MissingLength;
}
}
fn compact(&mut self) {
self.discard_consumed();
if self.pos == 0 {
return;
}
self.buffer.copy_within(self.pos..self.len, 0);
self.len -= self.pos;
self.pos = 0;
}
fn next_state(&mut self) -> ReadResult<'_> {
const LEN_SIZE: usize = std::mem::size_of::<u32>();
match self.state {
ReaderState::MissingLength => {
if self.buffered() >= LEN_SIZE {
let length = u32::from_le_bytes(
self.buffer[self.pos..self.pos + LEN_SIZE]
.try_into()
.expect("slice is LEN_SIZE bytes"),
) as usize;
if length > self.max_packet_size {
ReadResult::PacketTooLarge(length)
} else {
self.state = ReaderState::MissingBody { length };
self.next_state()
}
} else {
ReadResult::NeedData
}
}
ReaderState::MissingBody { length } => {
if self.buffered().saturating_sub(LEN_SIZE) >= length {
self.state = ReaderState::ShouldClear {
body_length: length,
};
let start = self.pos + LEN_SIZE;
ReadResult::Read(&self.buffer[start..start + length])
} else {
ReadResult::NeedData
}
}
ReaderState::ShouldClear { .. } => {
self.discard_consumed();
self.next_state()
}
}
}
pub fn push_data(&mut self, data: &[u8]) -> Result<(), PushDataError> {
if self.len + data.len() > self.buffer.len() {
self.compact();
if self.len + data.len() > self.buffer.len() {
return Err(PushDataError::BufferTooBig);
}
}
self.buffer[self.len..self.len + data.len()].copy_from_slice(data);
self.len += data.len();
Ok(())
}
pub fn spare_capacity_mut(&mut self) -> &mut [u8] {
self.compact();
&mut self.buffer[self.len..]
}
pub fn commit(&mut self, n: usize) {
debug_assert!(
self.len + n <= self.buffer.len(),
"commit({n}) overflows reader buffer (len={}, capacity={})",
self.len,
self.buffer.len()
);
self.len += n;
}
pub fn get_packet(&mut self) -> ReadResult<'_> {
self.next_state()
}
pub fn drain_unparsed(&mut self) -> Vec<u8> {
self.discard_consumed();
let data = self.buffer[self.pos..self.len].to_vec();
self.pos = 0;
self.len = 0;
self.state = ReaderState::MissingLength;
data
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_network_config_url() {
let samples = [
FCastNetworkConfig {
name: "Living Room".to_string(),
addresses: vec!["192.168.1.42".to_string()],
services: vec![FCastService {
port: 46899,
r#type: 0,
}],
txt: None,
},
FCastNetworkConfig {
name: "kitchen-tv".to_string(),
addresses: vec![
"10.0.0.5".to_string(),
"fe80::1ff:fe23:4567:890a".to_string(),
],
services: vec![FCastService {
port: 46899,
r#type: 0,
}],
txt: Some(HashMap::from([
("version".to_string(), "3".to_string()),
("id".to_string(), "abc-123".to_string()),
])),
},
FCastNetworkConfig {
name: "æøå".to_string(),
addresses: vec![],
services: vec![],
txt: Some(HashMap::new()),
},
];
for config in samples {
let url = config.to_url().expect("serializing to url should succeed");
assert!(url.starts_with("fcast://r/"), "unexpected url: {url}");
let parsed = FCastNetworkConfig::parse_url(&url)
.unwrap_or_else(|| panic!("parsing url should succeed: {url}"));
assert_eq!(parsed, config);
}
}
#[test]
fn test_parse_url_rejects_invalid() {
assert!(FCastNetworkConfig::parse_url("https://example.com").is_none());
assert!(FCastNetworkConfig::parse_url("fcast://r/not-valid-base64-$$$").is_none());
}
#[test]
fn packet_reader_single() {
let mut reader = PacketReader::new(100, 0);
reader
.push_data(&[1u32.to_le_bytes().as_slice(), [0u8].as_slice()].concat())
.unwrap();
assert_eq!(reader.get_packet(), ReadResult::Read(&[0]));
assert_eq!(reader.state, ReaderState::ShouldClear { body_length: 1 });
assert_eq!(reader.get_packet(), ReadResult::NeedData);
assert_eq!(reader.buffered(), 0);
assert_eq!(reader.state, ReaderState::MissingLength);
assert_eq!(reader.buffered(), 0);
}
#[test]
fn packet_reader_small_push() {
let mut reader = PacketReader::new(100, 0);
let length = 1u32.to_le_bytes();
reader.push_data(&[length[0], length[1]]).unwrap();
assert_eq!(reader.get_packet(), ReadResult::NeedData);
reader.push_data(&[length[2]]).unwrap();
assert_eq!(reader.get_packet(), ReadResult::NeedData);
assert_eq!(reader.state, ReaderState::MissingLength);
reader.push_data(&[length[3]]).unwrap();
assert_eq!(reader.get_packet(), ReadResult::NeedData);
reader.push_data(&[0]).unwrap();
assert_eq!(reader.get_packet(), ReadResult::Read(&[0]));
assert_eq!(reader.state, ReaderState::ShouldClear { body_length: 1 });
assert_eq!(reader.get_packet(), ReadResult::NeedData);
assert_eq!(reader.buffered(), 0);
}
#[rustfmt::skip]
#[test]
fn packet_reader_many_packets_single_push() {
let mut reader = PacketReader::new(100, 0);
reader.push_data(&[
1u32.to_le_bytes().as_slice(), [0u8].as_slice(),
2u32.to_le_bytes().as_slice(), [0u8, 1].as_slice(),
3u32.to_le_bytes().as_slice(), [0u8, 1, 2].as_slice(),
].concat()).unwrap();
assert_eq!(reader.get_packet(), ReadResult::Read(&[0]));
assert_eq!(reader.state, ReaderState::ShouldClear { body_length: 1 });
assert_eq!(reader.get_packet(), ReadResult::Read(&[0, 1]));
assert_eq!(reader.state, ReaderState::ShouldClear { body_length: 2 });
assert_eq!(reader.get_packet(), ReadResult::Read(&[0, 1, 2]));
assert_eq!(reader.state, ReaderState::ShouldClear { body_length: 3 });
assert_eq!(reader.get_packet(), ReadResult::NeedData);
assert_eq!(reader.state, ReaderState::MissingLength);
assert_eq!(reader.buffered(), 0);
}
#[test]
fn packet_reader_partial_body() {
let mut reader = PacketReader::new(100, 0);
reader
.push_data(&[4u32.to_le_bytes().as_slice(), [0u8, 1].as_slice()].concat())
.unwrap();
assert_eq!(reader.get_packet(), ReadResult::NeedData);
reader.push_data(&[2]).unwrap();
assert_eq!(reader.get_packet(), ReadResult::NeedData);
reader.push_data(&[3]).unwrap();
assert_eq!(reader.get_packet(), ReadResult::Read(&[0, 1, 2, 3]));
assert_eq!(reader.state, ReaderState::ShouldClear { body_length: 4 });
assert_eq!(reader.get_packet(), ReadResult::NeedData);
assert_eq!(reader.state, ReaderState::MissingLength);
assert_eq!(reader.buffered(), 0);
}
#[test]
fn packet_reader_large_body() {
let mut reader = PacketReader::new(100, 0);
let body = (0..10).collect::<Vec<u8>>();
reader
.push_data(&[10u32.to_le_bytes().as_slice(), body.as_slice()].concat())
.unwrap();
assert_eq!(reader.get_packet(), ReadResult::Read(&body));
assert_eq!(reader.state, ReaderState::ShouldClear { body_length: 10 });
assert_eq!(reader.get_packet(), ReadResult::NeedData);
assert_eq!(reader.state, ReaderState::MissingLength);
assert_eq!(reader.buffered(), 0);
}
#[test]
fn large_body_size() {
let mut reader = PacketReader::new(65280, 0);
let body = &[255, 255, 255, 0];
reader.push_data(body).unwrap();
assert_eq!(
reader.get_packet(),
ReadResult::PacketTooLarge(u32::from_le_bytes(*body) as usize)
);
}
#[test]
fn drain_unparsed_returns_bytes_after_packet() {
let mut reader = PacketReader::new(100, 16);
let trailing = [0x16u8, 0x03, 0x01, 0x02, 0x00, 0x42];
reader
.push_data(
&[
1u32.to_le_bytes().as_slice(),
[7u8].as_slice(),
trailing.as_slice(),
]
.concat(),
)
.unwrap();
assert_eq!(reader.get_packet(), ReadResult::Read(&[7]));
assert_eq!(reader.state, ReaderState::ShouldClear { body_length: 1 });
assert_eq!(reader.drain_unparsed(), trailing);
assert_eq!(reader.buffered(), 0);
assert_eq!(reader.state, ReaderState::MissingLength);
}
#[test]
fn drain_unparsed_without_reading_returns_everything() {
let mut reader = PacketReader::new(100, 0);
let data = [0x16u8, 0x03, 0x01, 0x00];
reader.push_data(&data).unwrap();
assert_eq!(reader.drain_unparsed(), data);
assert_eq!(reader.buffered(), 0);
assert_eq!(reader.state, ReaderState::MissingLength);
}
#[test]
fn drain_unparsed_with_partial_packet_returns_everything() {
let mut reader = PacketReader::new(100, 0);
let data = [4u32.to_le_bytes().as_slice(), [0u8, 1].as_slice()].concat();
reader.push_data(&data).unwrap();
assert_eq!(reader.get_packet(), ReadResult::NeedData);
assert_eq!(reader.state, ReaderState::MissingBody { length: 4 });
assert_eq!(reader.drain_unparsed(), data);
assert_eq!(reader.buffered(), 0);
assert_eq!(reader.state, ReaderState::MissingLength);
}
#[test]
fn drain_unparsed_when_empty_is_empty() {
let mut reader = PacketReader::new(100, 0);
assert!(reader.drain_unparsed().is_empty());
assert_eq!(reader.state, ReaderState::MissingLength);
}
#[test]
fn reader_is_reusable_after_drain() {
let mut reader = PacketReader::new(100, 16);
reader
.push_data(
&[
1u32.to_le_bytes().as_slice(),
[7u8].as_slice(),
&[0xaa, 0xbb],
]
.concat(),
)
.unwrap();
assert_eq!(reader.get_packet(), ReadResult::Read(&[7]));
assert_eq!(reader.drain_unparsed(), [0xaa, 0xbb]);
reader
.push_data(&[2u32.to_le_bytes().as_slice(), [8u8, 9].as_slice()].concat())
.unwrap();
assert_eq!(reader.get_packet(), ReadResult::Read(&[8, 9]));
assert_eq!(reader.get_packet(), ReadResult::NeedData);
assert_eq!(reader.buffered(), 0);
}
const LEN_SIZE: usize = std::mem::size_of::<u32>();
fn frame(body: &[u8]) -> Vec<u8> {
let mut v = (body.len() as u32).to_le_bytes().to_vec();
v.extend_from_slice(body);
v
}
fn drain_zerocopy(reader: &mut PacketReader, data: &[u8], chunk: usize) -> Vec<Vec<u8>> {
let mut out = Vec::new();
let mut pos = 0;
while pos < data.len() {
let spare = reader.spare_capacity_mut();
assert!(
!spare.is_empty(),
"spare capacity empty before a read (would be read as EOF)"
);
let want = if chunk == 0 {
spare.len()
} else {
chunk.min(spare.len())
};
let take = want.min(data.len() - pos);
spare[..take].copy_from_slice(&data[pos..pos + take]);
reader.commit(take);
pos += take;
loop {
match reader.get_packet() {
ReadResult::Read(p) => out.push(p.to_vec()),
ReadResult::NeedData => break,
ReadResult::PacketTooLarge(s) => panic!("unexpected PacketTooLarge({s})"),
}
}
}
out
}
fn drain_pushdata(reader: &mut PacketReader, data: &[u8], scratch: usize) -> Vec<Vec<u8>> {
let mut out = Vec::new();
let mut buf = vec![0u8; scratch];
let mut pos = 0;
while pos < data.len() {
let n = scratch.min(data.len() - pos);
buf[..n].copy_from_slice(&data[pos..pos + n]);
reader.push_data(&buf[..n]).expect("push_data overflowed");
pos += n;
loop {
match reader.get_packet() {
ReadResult::Read(p) => out.push(p.to_vec()),
ReadResult::NeedData => break,
ReadResult::PacketTooLarge(s) => panic!("unexpected PacketTooLarge({s})"),
}
}
}
out
}
#[test]
fn spare_capacity_starts_at_full_buffer() {
let mut reader = PacketReader::new(100, 16);
assert_eq!(reader.spare_capacity_mut().len(), LEN_SIZE + 100 + 16);
}
#[test]
fn commit_zero_is_noop() {
let mut reader = PacketReader::new(100, 16);
let before = reader.spare_capacity_mut().len();
reader.commit(0);
assert_eq!(reader.spare_capacity_mut().len(), before);
assert_eq!(reader.get_packet(), ReadResult::NeedData);
assert_eq!(reader.buffered(), 0);
}
#[test]
fn spare_capacity_shrinks_by_commit_and_regrows_after_consume() {
let mut reader = PacketReader::new(100, 16);
let cap = LEN_SIZE + 100 + 16;
let first = frame(&[0xAA, 0xBB, 0xCC]);
let second_prefix = 2u32.to_le_bytes();
let n = {
let spare = reader.spare_capacity_mut();
spare[..first.len()].copy_from_slice(&first);
spare[first.len()..first.len() + LEN_SIZE].copy_from_slice(&second_prefix);
first.len() + LEN_SIZE
};
reader.commit(n);
assert_eq!(reader.spare_capacity_mut().len(), cap - n);
assert_eq!(reader.get_packet(), ReadResult::Read(&[0xAA, 0xBB, 0xCC]));
assert_eq!(reader.get_packet(), ReadResult::NeedData);
assert_eq!(reader.buffered(), LEN_SIZE);
assert_eq!(reader.spare_capacity_mut().len(), cap - LEN_SIZE);
}
#[test]
fn consuming_packets_does_not_move_buffered_data() {
let mut reader = PacketReader::new(100, 16);
let stream: Vec<u8> = [frame(&[0]), frame(&[0, 1]), frame(&[0, 1, 2])].concat();
reader.push_data(&stream).unwrap();
assert_eq!(reader.get_packet(), ReadResult::Read(&[0]));
assert_eq!(reader.get_packet(), ReadResult::Read(&[0, 1]));
assert_eq!(reader.get_packet(), ReadResult::Read(&[0, 1, 2]));
assert_eq!(reader.get_packet(), ReadResult::NeedData);
assert_eq!(reader.pos, stream.len());
assert_eq!(reader.len, stream.len());
assert_eq!(
reader.spare_capacity_mut().len(),
LEN_SIZE + 100 + 16,
"refill should reclaim all consumed space"
);
assert_eq!((reader.pos, reader.len), (0, 0));
}
#[test]
fn zerocopy_single_packet() {
let mut reader = PacketReader::new(100, 16);
let body = [7u8, 8, 9];
let out = drain_zerocopy(&mut reader, &frame(&body), 0);
assert_eq!(out, vec![body.to_vec()]);
assert_eq!(reader.buffered(), 0);
}
#[test]
fn zerocopy_reassembles_across_all_chunk_sizes() {
let bodies: Vec<Vec<u8>> = vec![
vec![0], vec![1, 2],
(0..37u8).collect(),
vec![0xFF; 90], vec![42],
(0..64u8).rev().collect(),
];
let mut stream = Vec::new();
for b in &bodies {
stream.extend_from_slice(&frame(b));
}
for chunk in [1usize, 2, 3, 4, 5, 6, 7, 8, 13, 31, 64, 100, 8192, 0] {
let mut reader = PacketReader::new(100, 8192);
let out = drain_zerocopy(&mut reader, &stream, chunk);
assert_eq!(out, bodies, "mismatch at chunk size {chunk}");
assert_eq!(
reader.buffered(),
0,
"buffer not drained at chunk size {chunk}"
);
}
}
#[test]
fn zerocopy_large_packet_split_byte_by_byte() {
let mut reader = PacketReader::new(100_000, 8192);
let body: Vec<u8> = (0..5000).map(|i| (i % 251) as u8).collect();
let out = drain_zerocopy(&mut reader, &frame(&body), 1);
assert_eq!(out, vec![body]);
assert_eq!(reader.buffered(), 0);
}
#[test]
fn zerocopy_matches_push_data_path() {
let bodies: Vec<Vec<u8>> = vec![vec![1], (0..50u8).collect(), vec![9; 80], vec![2, 3]];
let mut stream = Vec::new();
for b in &bodies {
stream.extend_from_slice(&frame(b));
}
for chunk in [1usize, 3, 7, 64, 128] {
let mut zc = PacketReader::new(100, 8192);
let mut pd = PacketReader::new(100, 8192);
let zc_out = drain_zerocopy(&mut zc, &stream, chunk);
let pd_out = drain_pushdata(&mut pd, &stream, chunk);
assert_eq!(
zc_out, pd_out,
"zero-copy vs push_data diverged at chunk {chunk}"
);
assert_eq!(zc_out, bodies);
}
}
#[test]
fn zerocopy_full_buffer_still_yields_a_packet() {
let max = 64usize;
let padding = 16usize;
let mut reader = PacketReader::new(max, padding);
let big = frame(&vec![0x5Au8; max]); let mut trailing = 16u32.to_le_bytes().to_vec();
trailing.extend_from_slice(&[0xEE; 12]);
assert_eq!(trailing.len(), 16);
let n = {
let spare = reader.spare_capacity_mut();
assert_eq!(spare.len(), LEN_SIZE + max + padding);
spare[..big.len()].copy_from_slice(&big);
spare[big.len()..big.len() + trailing.len()].copy_from_slice(&trailing);
big.len() + trailing.len()
};
reader.commit(n);
assert_eq!(
reader.spare_capacity_mut().len(),
0,
"buffer should be exactly full"
);
assert_eq!(reader.get_packet(), ReadResult::Read(&[0x5A; 64]));
assert_eq!(reader.get_packet(), ReadResult::NeedData);
assert_eq!(reader.buffered(), 16);
assert_eq!(
reader.spare_capacity_mut().len(),
LEN_SIZE + max + padding - 16
);
}
#[test]
fn zerocopy_never_false_eof_under_back_to_back_max_packets() {
let max = 200usize;
let mut reader = PacketReader::new(max, 64);
let bodies: Vec<Vec<u8>> = (0..15)
.map(|i| vec![i as u8; max]) .collect();
let mut stream = Vec::new();
for b in &bodies {
stream.extend_from_slice(&frame(b));
}
let out = drain_zerocopy(&mut reader, &stream, 0);
assert_eq!(out, bodies);
assert_eq!(reader.buffered(), 0);
}
#[test]
fn zerocopy_drain_unparsed_recovers_tls_prefix() {
let mut reader = PacketReader::new(100, 16);
let version = frame(&[Opcode::Version as u8, b'{', b'}']);
let handshake = [0x16u8, 0x03, 0x01, 0x02, 0x00, 0x42];
let n = {
let spare = reader.spare_capacity_mut();
spare[..version.len()].copy_from_slice(&version);
spare[version.len()..version.len() + handshake.len()].copy_from_slice(&handshake);
version.len() + handshake.len()
};
reader.commit(n);
assert_eq!(
reader.get_packet(),
ReadResult::Read(&[Opcode::Version as u8, b'{', b'}'])
);
assert_eq!(reader.drain_unparsed(), handshake);
assert_eq!(reader.buffered(), 0);
assert_eq!(reader.state, ReaderState::MissingLength);
}
#[test]
fn zerocopy_too_large_prefix_is_reported() {
let mut reader = PacketReader::new(64, 16);
let prefix = 65u32.to_le_bytes();
reader.spare_capacity_mut()[..LEN_SIZE].copy_from_slice(&prefix);
reader.commit(LEN_SIZE);
assert_eq!(reader.get_packet(), ReadResult::PacketTooLarge(65));
}
#[test]
fn zerocopy_randomized_reassembly() {
let mut rng: u64 = 0x9E37_79B9_7F4A_7C15;
let mut next = || {
rng ^= rng << 13;
rng ^= rng >> 7;
rng ^= rng << 17;
rng
};
let max = 300usize;
for _ in 0..400 {
let n_packets = (next() % 12) as usize + 1;
let bodies: Vec<Vec<u8>> = (0..n_packets)
.map(|_| {
let len = (next() as usize % max) + 1; (0..len).map(|_| next() as u8).collect()
})
.collect();
let mut stream = Vec::new();
for b in &bodies {
stream.extend_from_slice(&frame(b));
}
let chunk = (next() as usize % 40) + 1; let mut reader = PacketReader::new(max, 8192);
let out = drain_zerocopy(&mut reader, &stream, chunk);
assert_eq!(
out, bodies,
"randomized reassembly mismatch (chunk={chunk})"
);
assert_eq!(reader.buffered(), 0);
}
}
}