use crate::LynnError;
use crate::domain::model::message_validation::{
MAX_MESSAGE_SIZE, MIN_MESSAGE_SIZE, validate_message_length,
};
pub const DEFAULT_MAX_BUFFER_SIZE: usize = 16 * 1024 * 1024;
pub fn validate_message_format(
data: &[u8],
message_header_mark: u16,
message_tail_mark: u16,
) -> Result<usize, LynnError> {
if data.len() < 10 {
return Err(LynnError::protocol(format!(
"Message too short: {} bytes (minimum 10 for header+length)",
data.len()
)));
}
let header = u16::from_le_bytes([data[0], data[1]]);
if header != message_header_mark {
return Err(LynnError::protocol(format!(
"Invalid header mark: 0x{:04X} (expected 0x{:04X})",
header, message_header_mark
)));
}
let msg_len = u64::from_le_bytes([
data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9],
]);
let body_len = validate_message_length(msg_len)?;
let total_expected_size = 10 + body_len + 2; if data.len() < total_expected_size {
return Err(LynnError::protocol(format!(
"Incomplete message: {} bytes (expected {} for complete message)",
data.len(),
total_expected_size
)));
}
let tail_pos = 10 + body_len;
let tail = u16::from_le_bytes([data[tail_pos], data[tail_pos + 1]]);
if tail != message_tail_mark {
return Err(LynnError::protocol(format!(
"Invalid tail mark: 0x{:04X} (expected 0x{:04X})",
tail, message_tail_mark
)));
}
Ok(body_len)
}
pub struct SafeBuffer {
data: Vec<u8>,
max_size: usize,
}
impl SafeBuffer {
pub fn new(max_size: usize) -> Self {
Self {
data: Vec::with_capacity(4096), max_size,
}
}
pub fn extend(&mut self, data: &[u8]) -> Result<(), LynnError> {
if data.len() > self.max_size {
return Err(LynnError::buffer(format!(
"Single data chunk too large: {} bytes (maximum {})",
data.len(),
self.max_size
)));
}
if self.data.len() + data.len() > self.max_size {
return Err(LynnError::buffer(format!(
"Buffer overflow: current={} bytes, adding={} bytes, maximum={} bytes",
self.data.len(),
data.len(),
self.max_size
)));
}
self.data.extend_from_slice(data);
Ok(())
}
pub fn clear(&mut self) {
self.data.clear();
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn as_slice(&self) -> &[u8] {
&self.data
}
pub fn max_size(&self) -> usize {
self.max_size
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_message_format() {
let header: u16 = 0x23E9;
let tail: u16 = 0x1E27;
let mut data = vec![0u8; 20];
data[0..2].copy_from_slice(&header.to_le_bytes());
data[2..10].copy_from_slice(&6u64.to_le_bytes());
data[16..18].copy_from_slice(&tail.to_le_bytes());
assert!(validate_message_format(&data, header, tail).is_ok());
}
#[test]
fn test_safe_buffer() {
let mut buffer = SafeBuffer::new(100);
assert!(buffer.extend(&[1, 2, 3]).is_ok());
assert_eq!(buffer.len(), 3);
assert!(buffer.extend(&[0u8; 200]).is_err());
}
#[test]
fn rejects_too_short_data() {
let err = validate_message_format(&[0u8; 9], 0x23E9, 0x1E27).unwrap_err();
assert!(err.to_string().contains("too short"));
}
#[test]
fn rejects_wrong_header_mark() {
let mut data = vec![0u8; 20];
data[0..2].copy_from_slice(&0x1234u16.to_le_bytes());
data[2..10].copy_from_slice(&6u64.to_le_bytes());
data[16..18].copy_from_slice(&0x1E27u16.to_le_bytes());
let err = validate_message_format(&data, 0x23E9, 0x1E27).unwrap_err();
assert!(err.to_string().contains("header mark"));
}
#[test]
fn rejects_incomplete_message() {
let mut data = vec![0u8; 17]; data[0..2].copy_from_slice(&0x23E9u16.to_le_bytes());
data[2..10].copy_from_slice(&6u64.to_le_bytes());
let err = validate_message_format(&data, 0x23E9, 0x1E27).unwrap_err();
assert!(err.to_string().contains("Incomplete"));
}
#[test]
fn rejects_wrong_tail_mark() {
let mut data = vec![0u8; 20];
data[0..2].copy_from_slice(&0x23E9u16.to_le_bytes());
data[2..10].copy_from_slice(&6u64.to_le_bytes());
data[16..18].copy_from_slice(&0x9999u16.to_le_bytes());
let err = validate_message_format(&data, 0x23E9, 0x1E27).unwrap_err();
assert!(err.to_string().contains("tail mark"));
}
#[test]
fn rejects_invalid_length_field() {
let mut data = vec![0u8; 20];
data[0..2].copy_from_slice(&0x23E9u16.to_le_bytes());
data[2..10].copy_from_slice(&u64::MAX.to_le_bytes());
assert!(validate_message_format(&data, 0x23E9, 0x1E27).is_err());
}
#[test]
fn safe_buffer_rejects_single_oversized_chunk() {
let mut buffer = SafeBuffer::new(10);
let err = buffer.extend(&[0u8; 11]).unwrap_err();
assert!(err.to_string().contains("chunk too large"));
}
#[test]
fn safe_buffer_rejects_cumulative_overflow() {
let mut buffer = SafeBuffer::new(10);
assert!(buffer.extend(&[0u8; 6]).is_ok());
assert!(buffer.extend(&[0u8; 6]).is_err());
assert_eq!(buffer.len(), 6, "failed extend must not mutate the buffer");
}
#[test]
fn safe_buffer_clear_and_inspect() {
let mut buffer = SafeBuffer::new(64);
assert!(buffer.is_empty());
assert_eq!(buffer.max_size(), 64);
assert!(buffer.extend(b"abc").is_ok());
assert_eq!(buffer.as_slice(), b"abc");
buffer.clear();
assert!(buffer.is_empty());
assert_eq!(buffer.len(), 0);
}
}