Files
settled-reach/server/src/bridge/framing.rs
T
jpmschweitzerandClaude Opus 4.6 9860fc0857 fix(simulation): address bridge review feedback
Replace .lock().unwrap() with .expect("mutex poisoned") in LocalBridge
for clearer panic messages. Document 16MB MAX_MESSAGE_SIZE rationale
in framing.rs with entity count sizing analysis.

Addresses Hoshe PR review suggestions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 19:32:35 +01:00

113 lines
3.2 KiB
Rust

// MessagePack framing protocol
// 4-byte big-endian length prefix + payload
// Implements D-020 IPC transport layer
use std::io::{self, Read, Write};
/// Maximum message size: 16 MB.
/// Sized for ObserverSnapshot with ~1000 entities (each ~40 bytes serialized),
/// plus generous headroom for future field additions. A full-map dump of 10k
/// entities would be ~400 KB, well within this limit.
const MAX_MESSAGE_SIZE: u32 = 16 * 1024 * 1024;
/// Write a length-prefixed message to a writer.
/// Format: [4-byte BE length][payload]
pub fn write_framed(writer: &mut impl Write, payload: &[u8]) -> io::Result<()> {
let len = payload.len() as u32;
if len > MAX_MESSAGE_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"message too large: {} bytes (max {})",
len, MAX_MESSAGE_SIZE
),
));
}
writer.write_all(&len.to_be_bytes())?;
writer.write_all(payload)?;
writer.flush()?;
Ok(())
}
/// Read a length-prefixed message from a reader.
/// Returns Ok(None) on clean EOF (connection closed).
/// Returns error on incomplete/corrupted reads.
pub fn read_framed(reader: &mut impl Read) -> io::Result<Option<Vec<u8>>> {
// Read 4-byte length prefix
let mut len_bytes = [0u8; 4];
match reader.read_exact(&mut len_bytes) {
Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
Err(e) => return Err(e),
}
let len = u32::from_be_bytes(len_bytes);
if len > MAX_MESSAGE_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"message too large: {} bytes (max {})",
len, MAX_MESSAGE_SIZE
),
));
}
// Read payload
let mut payload = vec![0u8; len as usize];
reader.read_exact(&mut payload)?;
Ok(Some(payload))
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn write_then_read_roundtrip() {
let payload = b"hello, world!";
let mut buffer = Vec::new();
write_framed(&mut buffer, payload).expect("write failed");
let mut cursor = Cursor::new(buffer);
let result = read_framed(&mut cursor).expect("read failed");
assert_eq!(result.unwrap(), payload);
}
#[test]
fn empty_payload_roundtrip() {
let payload = b"";
let mut buffer = Vec::new();
write_framed(&mut buffer, payload).expect("write failed");
let mut cursor = Cursor::new(buffer);
let result = read_framed(&mut cursor).expect("read failed");
assert_eq!(result.unwrap(), payload);
}
#[test]
fn rejects_oversized_message() {
let mut buffer = Vec::new();
let oversized_payload = vec![0u8; (MAX_MESSAGE_SIZE + 1) as usize];
let result = write_framed(&mut buffer, &oversized_payload);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("too large"));
}
#[test]
fn eof_returns_none() {
let buffer = Vec::new();
let mut cursor = Cursor::new(buffer);
let result = read_framed(&mut cursor).expect("read failed");
assert_eq!(result, None);
}
}