use std::io::{self, BufRead, Write};
use std::sync::{Arc, Mutex};
pub const MAX_DAP_FRAME_BYTES: usize = 16 * 1024 * 1024;
pub type SharedWriter = Arc<Mutex<Box<dyn Write + Send>>>;
fn read_content_length<R: BufRead>(reader: &mut R) -> io::Result<Option<usize>> {
let mut content_length: Option<usize> = None;
loop {
let mut line = String::new();
if reader.read_line(&mut line)? == 0 {
return Ok(None);
}
let trimmed = line.trim();
if trimmed.is_empty() {
match content_length {
Some(length) => return Ok(Some(length)),
None => continue,
}
}
let Some((name, value)) = trimmed.split_once(':') else {
continue;
};
if !name.trim().eq_ignore_ascii_case("Content-Length") {
continue;
}
let length: usize = value.trim().parse().map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("malformed DAP Content-Length {:?}", value.trim()),
)
})?;
content_length = Some(bounded_frame_length(length)?);
}
}
fn bounded_frame_length(content_length: usize) -> io::Result<usize> {
if content_length > MAX_DAP_FRAME_BYTES {
Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"DAP Content-Length {content_length} exceeds limit {MAX_DAP_FRAME_BYTES} bytes"
),
))
} else {
Ok(content_length)
}
}
pub fn read_frame<R: BufRead>(reader: &mut R) -> io::Result<Option<Vec<u8>>> {
loop {
let Some(content_length) = read_content_length(reader)? else {
return Ok(None);
};
if content_length == 0 {
continue;
}
let mut body = vec![0u8; content_length];
if reader.read_exact(&mut body).is_err() {
return Ok(None);
}
return Ok(Some(body));
}
}
pub fn write_frame<W: Write + ?Sized>(writer: &mut W, body: &[u8]) -> io::Result<()> {
write!(writer, "Content-Length: {}\r\n\r\n", body.len())?;
writer.write_all(body)?;
writer.flush()
}
pub fn write_json_frame<T: serde::Serialize>(stdout: &SharedWriter, value: &T) -> io::Result<()> {
let body = serde_json::to_vec(value).map_err(|e| io::Error::other(format!("encode: {e}")))?;
let mut guard = stdout
.lock()
.map_err(|_| io::Error::other("stdout mutex poisoned"))?;
write_frame(&mut **guard, &body)
}
#[cfg(test)]
mod tests {
use super::*;
fn read_all(input: &[u8]) -> io::Result<Option<Vec<u8>>> {
read_frame(&mut io::BufReader::new(input))
}
fn frame(body: &str) -> Vec<u8> {
let mut out = Vec::new();
write_frame(&mut out, body.as_bytes()).expect("write");
out
}
#[test]
fn round_trips_a_frame() {
let encoded = frame(r#"{"seq":1}"#);
assert_eq!(encoded, b"Content-Length: 9\r\n\r\n{\"seq\":1}");
assert_eq!(
read_all(&encoded).expect("read").expect("frame"),
br#"{"seq":1}"#
);
}
#[test]
fn header_names_match_case_insensitively() {
let input = b"content-length: 2\r\n\r\nhi";
assert_eq!(read_all(input).expect("read").expect("frame"), b"hi");
}
#[test]
fn unrelated_headers_are_skipped() {
let input = b"Content-Type: application/vscode-jsonrpc\r\nContent-Length: 2\r\n\r\nhi";
assert_eq!(read_all(input).expect("read").expect("frame"), b"hi");
}
#[test]
fn accepts_a_frame_at_exactly_the_limit() {
let header = format!("Content-Length: {MAX_DAP_FRAME_BYTES}\r\n\r\n");
let mut reader = io::BufReader::new(header.as_bytes());
assert_eq!(
read_content_length(&mut reader).expect("at limit"),
Some(MAX_DAP_FRAME_BYTES)
);
}
#[test]
fn rejects_a_frame_over_the_limit_without_allocating() {
let header = format!("Content-Length: {}\r\n\r\n", MAX_DAP_FRAME_BYTES + 1);
let error = read_all(header.as_bytes()).expect_err("oversized frame");
assert_eq!(error.kind(), io::ErrorKind::InvalidData);
assert!(error.to_string().contains("exceeds limit"), "{error}");
}
#[test]
fn rejects_a_length_too_wide_for_usize() {
let input = b"Content-Length: 999999999999999999999999\r\n\r\n{}";
let error = read_all(input).expect_err("overflowing length");
assert_eq!(error.kind(), io::ErrorKind::InvalidData);
}
#[test]
fn truncated_body_reports_eof_rather_than_a_partial_frame() {
let input = b"Content-Length: 64\r\n\r\n{\"seq\":1}";
assert_eq!(read_all(input).expect("read"), None);
}
#[test]
fn truncated_header_block_reports_eof() {
assert_eq!(read_all(b"Content-Length: 12\r\n").expect("read"), None);
}
#[test]
fn empty_input_is_a_clean_eof() {
assert_eq!(read_all(b"").expect("read"), None);
}
#[test]
fn reads_consecutive_frames_from_one_stream() {
let mut stream = frame("ab");
stream.extend(frame("cde"));
let mut reader = io::BufReader::new(stream.as_slice());
assert_eq!(
read_frame(&mut reader).expect("read").expect("first"),
b"ab"
);
assert_eq!(
read_frame(&mut reader).expect("read").expect("second"),
b"cde"
);
assert_eq!(read_frame(&mut reader).expect("read"), None);
}
}