use std::io::{self, Read, Write};
pub const MAX_HEADER_BYTES: usize = 8192;
pub const MAX_BODY_BYTES: usize = 32 * 1024 * 1024;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum FrameError {
#[error("frame header exceeded the {limit}-byte bound without a terminator")]
HeaderTooLarge { limit: usize },
#[error("frame body length {actual} exceeds the {limit}-byte bound")]
BodyTooLarge { limit: usize, actual: usize },
#[error("frame header carries no valid Content-Length")]
MissingLength,
#[error("frame header line is not `Name: value`")]
MalformedHeader,
#[error("stream ended mid-frame")]
Truncated,
}
pub fn write_frame(mut writer: impl Write, body: &[u8]) -> io::Result<()> {
write!(writer, "Content-Length: {}\r\n\r\n", body.len())?;
writer.write_all(body)?;
writer.flush()
}
pub fn write_message(writer: impl Write, message: &impl serde::Serialize) -> io::Result<()> {
write_frame(writer, &serde_json::to_vec(message)?)
}
#[derive(Debug, Default)]
pub struct FrameDecoder {
buffer: Vec<u8>,
}
impl FrameDecoder {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
pub fn accept(&mut self, bytes: &[u8]) -> Result<(), FrameError> {
self.buffer.extend_from_slice(bytes);
if !self.header_complete() && self.buffer.len() > MAX_HEADER_BYTES {
return Err(FrameError::HeaderTooLarge {
limit: MAX_HEADER_BYTES,
});
}
Ok(())
}
pub fn next_frame(&mut self) -> Result<Option<Vec<u8>>, FrameError> {
let Some(header_end) = find(&self.buffer, b"\r\n\r\n") else {
return Ok(None);
};
if header_end + 4 > MAX_HEADER_BYTES {
return Err(FrameError::HeaderTooLarge {
limit: MAX_HEADER_BYTES,
});
}
let length = content_length(&self.buffer[..header_end])?;
if length > MAX_BODY_BYTES {
return Err(FrameError::BodyTooLarge {
limit: MAX_BODY_BYTES,
actual: length,
});
}
let start = header_end + 4;
if self.buffer.len() < start + length {
return Ok(None);
}
let body = self.buffer[start..start + length].to_vec();
self.buffer.drain(..start + length);
Ok(Some(body))
}
fn header_complete(&self) -> bool {
find(&self.buffer, b"\r\n\r\n").is_some()
}
}
pub fn read_frame(
reader: &mut impl Read,
decoder: &mut FrameDecoder,
) -> io::Result<Option<Vec<u8>>> {
let mut chunk = [0u8; 8192];
loop {
match decoder.next_frame() {
Ok(Some(body)) => return Ok(Some(body)),
Ok(None) => {}
Err(error) => return Err(io::Error::new(io::ErrorKind::InvalidData, error)),
}
let read = reader.read(&mut chunk)?;
if read == 0 {
return if decoder.is_empty() {
Ok(None)
} else {
Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"stream ended mid-frame",
))
};
}
decoder
.accept(&chunk[..read])
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
}
}
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack
.windows(needle.len())
.position(|window| window == needle)
}
fn content_length(header: &[u8]) -> Result<usize, FrameError> {
let header = std::str::from_utf8(header).map_err(|_| FrameError::MalformedHeader)?;
let mut length = None;
for line in header.split("\r\n") {
let (name, value) = line.split_once(':').ok_or(FrameError::MalformedHeader)?;
if name.trim().is_empty() {
return Err(FrameError::MalformedHeader);
}
if name.trim() == "Content-Length" {
length = Some(
value
.trim()
.parse::<usize>()
.map_err(|_| FrameError::MissingLength)?,
);
}
}
length.ok_or(FrameError::MissingLength)
}
#[cfg(test)]
mod tests {
use super::*;
fn frame(body: &[u8]) -> Vec<u8> {
let mut bytes = Vec::new();
write_frame(&mut bytes, body).unwrap();
bytes
}
#[test]
fn roundtrip_whole_and_concatenated() {
let mut bytes = frame(b"first");
bytes.extend(frame(b"second"));
let mut decoder = FrameDecoder::new();
decoder.accept(&bytes).unwrap();
assert_eq!(
decoder.next_frame().unwrap().as_deref(),
Some(&b"first"[..])
);
assert_eq!(
decoder.next_frame().unwrap().as_deref(),
Some(&b"second"[..])
);
assert!(decoder.next_frame().unwrap().is_none());
assert!(decoder.is_empty());
}
#[test]
fn partial_io_reassembles_byte_by_byte() {
let bytes = frame(b"{\"hello\":\"world\"}");
let mut decoder = FrameDecoder::new();
let mut frames = Vec::new();
for byte in bytes {
decoder.accept(&[byte]).unwrap();
while let Some(body) = decoder.next_frame().unwrap() {
frames.push(body);
}
}
assert_eq!(frames, vec![b"{\"hello\":\"world\"}".to_vec()]);
}
#[test]
fn extra_headers_are_ignored_like_lsp() {
let mut bytes = b"Content-Type: application/vscode-jsonrpc; charset=utf-8\r\n".to_vec();
bytes.extend(frame(b"body"));
let mut decoder = FrameDecoder::new();
decoder.accept(&bytes).unwrap();
assert_eq!(decoder.next_frame().unwrap().as_deref(), Some(&b"body"[..]));
}
#[test]
fn oversized_header_is_typed_before_unbounded_buffering() {
let mut decoder = FrameDecoder::new();
let error = decoder
.accept(&vec![b'x'; MAX_HEADER_BYTES + 1])
.unwrap_err();
assert_eq!(
error,
FrameError::HeaderTooLarge {
limit: MAX_HEADER_BYTES
}
);
}
#[test]
fn oversized_body_is_typed_at_the_header() {
let declared = MAX_BODY_BYTES + 1;
let mut decoder = FrameDecoder::new();
decoder
.accept(format!("Content-Length: {declared}\r\n\r\n").as_bytes())
.unwrap();
assert_eq!(
decoder.next_frame().unwrap_err(),
FrameError::BodyTooLarge {
limit: MAX_BODY_BYTES,
actual: declared
}
);
}
#[test]
fn missing_and_malformed_headers_are_typed() {
let mut decoder = FrameDecoder::new();
decoder.accept(b"Content-Type: text\r\n\r\n").unwrap();
assert_eq!(decoder.next_frame().unwrap_err(), FrameError::MissingLength);
let mut decoder = FrameDecoder::new();
decoder.accept(b"no-colon-here\r\n\r\n").unwrap();
assert_eq!(
decoder.next_frame().unwrap_err(),
FrameError::MalformedHeader
);
let mut decoder = FrameDecoder::new();
decoder.accept(b"Content-Length: many\r\n\r\n").unwrap();
assert_eq!(decoder.next_frame().unwrap_err(), FrameError::MissingLength);
}
#[test]
fn blocking_read_distinguishes_clean_eof_from_truncation() {
let mut bytes = frame(b"done");
let mut decoder = FrameDecoder::new();
let mut cursor = io::Cursor::new(bytes.clone());
assert_eq!(
read_frame(&mut cursor, &mut decoder).unwrap().as_deref(),
Some(&b"done"[..])
);
assert!(read_frame(&mut cursor, &mut decoder).unwrap().is_none());
bytes.truncate(bytes.len() - 2); let mut decoder = FrameDecoder::new();
let mut cursor = io::Cursor::new(bytes);
let error = read_frame(&mut cursor, &mut decoder).unwrap_err();
assert_eq!(error.kind(), io::ErrorKind::UnexpectedEof);
}
}