use crate::buffer::Buffer;
use crate::types::ErrorCode;
use crate::types::Result;
pub const DEFAULT_CHUNK_SIZE: u32 = 128;
pub const MAX_CHUNK_STREAMS: usize = 4096;
pub const MAX_REASSEMBLY_BYTES_PER_CONN: usize = 32 * 1024 * 1024;
pub const DEFAULT_MAX_MSG_LENGTH: u32 = 4 * 1024 * 1024;
pub const DEFAULT_MAX_ACTIVE_CSIDS: usize = 256;
#[derive(Debug)]
pub struct ChunkStream {
pub csid: u32,
pub chunk_size: u32,
pub type0_timestamp: u32,
pub type0_msg_length: u32,
pub type0_msg_type_id: u8,
pub type0_msg_stream_id: u32,
pub type0_ext_ts: bool,
pub last_delta: u32,
pub reassembly_bytes_read: u32,
pub reassembly_buf: Buffer,
pub last_payload: Vec<u8>,
pub in_use: bool,
}
impl Default for ChunkStream {
fn default() -> Self {
Self {
csid: 0,
chunk_size: DEFAULT_CHUNK_SIZE,
type0_timestamp: 0,
type0_msg_length: 0,
type0_msg_type_id: 0,
type0_msg_stream_id: 0,
type0_ext_ts: false,
last_delta: 0,
reassembly_bytes_read: 0,
reassembly_buf: Buffer::new(),
last_payload: Vec::new(),
in_use: false,
}
}
}
impl ChunkStream {
pub fn reset(&mut self, default_chunk_size: u32) {
self.type0_timestamp = 0;
self.type0_msg_length = 0;
self.type0_msg_type_id = 0;
self.type0_msg_stream_id = 0;
self.type0_ext_ts = false;
self.last_delta = 0;
self.reassembly_bytes_read = 0;
self.reassembly_buf.reset();
self.last_payload.clear();
self.last_payload.shrink_to_fit();
self.chunk_size = default_chunk_size;
}
}
#[derive(Debug)]
pub struct ChunkRegistry {
pub streams: Vec<ChunkStream>,
pub default_chunk_size: u32,
pub max_msg_length: u32,
pub max_active_csids: usize,
pub max_reassembly_bytes: usize,
pub initialized: bool,
}
impl Default for ChunkRegistry {
fn default() -> Self {
Self::new()
}
}
impl ChunkRegistry {
pub fn new() -> Self {
Self {
streams: Vec::new(),
default_chunk_size: DEFAULT_CHUNK_SIZE,
max_msg_length: DEFAULT_MAX_MSG_LENGTH,
max_active_csids: DEFAULT_MAX_ACTIVE_CSIDS,
max_reassembly_bytes: MAX_REASSEMBLY_BYTES_PER_CONN,
initialized: true,
}
}
pub fn init(&mut self) {
self.initialized = true;
self.default_chunk_size = DEFAULT_CHUNK_SIZE;
}
pub fn get_or_create(&mut self, csid: u32) -> Result<&mut ChunkStream> {
let idx = self.streams.iter().position(|s| s.csid == csid && s.in_use);
if let Some(i) = idx {
return Ok(&mut self.streams[i]);
}
let active = self.streams.iter().filter(|s| s.in_use).count();
if active >= self.max_active_csids {
return Err(ErrorCode::Chunk);
}
if let Some(i) = self.streams.iter().position(|s| !s.in_use) {
self.streams[i].csid = csid;
self.streams[i].in_use = true;
self.streams[i].reset(self.default_chunk_size);
return Ok(&mut self.streams[i]);
}
if self.streams.len() >= MAX_CHUNK_STREAMS {
return Err(ErrorCode::Chunk);
}
let mut stream = ChunkStream::default();
stream.csid = csid;
stream.in_use = true;
stream.chunk_size = self.default_chunk_size;
self.streams.push(stream);
let last = self.streams.len() - 1;
Ok(&mut self.streams[last])
}
pub fn get(&self, csid: u32) -> Option<&ChunkStream> {
self.streams.iter().find(|s| s.csid == csid && s.in_use)
}
pub fn get_mut(&mut self, csid: u32) -> Option<&mut ChunkStream> {
self.streams.iter_mut().find(|s| s.csid == csid && s.in_use)
}
pub fn set_all_chunk_size(&mut self, chunk_size: u32) {
self.default_chunk_size = chunk_size;
for stream in &mut self.streams {
if stream.in_use {
stream.chunk_size = chunk_size;
}
}
}
pub fn can_grow_reassembly(&self, cs: &ChunkStream, additional: u32) -> Result<()> {
let total: usize = self
.streams
.iter()
.filter(|s| s.in_use)
.map(|s| s.reassembly_buf.available())
.sum();
if total + additional as usize > self.max_reassembly_bytes {
return Err(ErrorCode::Chunk);
}
Ok(())
}
pub fn reset_stream(&mut self, csid: u32) {
if let Some(stream) = self.streams.iter_mut().find(|s| s.csid == csid && s.in_use) {
stream.reset(self.default_chunk_size);
}
}
pub fn destroy(&mut self) {
self.streams.clear();
self.initialized = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_opening_too_many_active_csids() {
let mut reg = ChunkRegistry::new();
reg.max_active_csids = 2;
assert!(reg.get_or_create(1).is_ok());
assert!(reg.get_or_create(2).is_ok());
assert!(matches!(reg.get_or_create(3), Err(ErrorCode::Chunk)));
}
#[test]
fn completed_messages_do_not_pin_multi_megabyte_reassembly_capacity() {
use crate::chunk::reader::{chunk_read, ChunkMessage};
use crate::chunk::writer::chunk_write;
let payload = vec![0xAB_u8; 256 * 1024];
let mut reg = ChunkRegistry::new();
reg.max_active_csids = 4;
for csid in 3..7u32 {
let msg = ChunkMessage {
csid,
fmt: 0,
timestamp: 0,
msg_length: payload.len() as u32,
msg_type_id: 0x09,
msg_stream_id: 1,
is_complete: false,
};
let mut wire = Buffer::new();
chunk_write(&mut wire, &msg, &payload, payload.len(), 128).unwrap();
let mut out_msg = ChunkMessage::default();
let mut ptr = std::ptr::null();
let mut len = 0;
loop {
let rc = chunk_read(&mut wire, &mut reg, None, &mut out_msg, &mut ptr, &mut len)
.unwrap();
if rc == 1 {
break;
}
assert_eq!(rc, 0);
}
}
let retained: usize = reg
.streams
.iter()
.filter(|s| s.in_use)
.map(|s| s.reassembly_buf.capacity())
.sum();
assert!(
retained <= 4 * crate::buffer::BUFFER_RESET_CAPACITY,
"retained {retained} bytes of reassembly capacity"
);
}
#[test]
fn reset_releases_last_payload_capacity() {
let mut stream = ChunkStream::default();
stream.last_payload = vec![0u8; 256 * 1024];
assert!(stream.last_payload.capacity() >= 256 * 1024);
stream.reset(DEFAULT_CHUNK_SIZE);
assert!(stream.last_payload.is_empty());
assert_eq!(stream.last_payload.capacity(), 0);
}
}