use crate::{Error, Result};
use serde_json::Value;
use tokio::io::{AsyncBufRead, AsyncBufReadExt};
pub(crate) async fn read_frame<R: AsyncBufRead + Unpin>(
input: &mut R,
limit: usize,
) -> Result<Option<Value>> {
let mut bytes = Vec::new();
loop {
let available = input
.fill_buf()
.await
.map_err(|_| Error::new("HOST_DISCONNECTED", "host stdout could not be read"))?;
if available.is_empty() {
return if bytes.is_empty() {
Ok(None)
} else {
Err(Error::new(
"HOST_PROTOCOL_ERROR",
"host exited inside a frame",
))
};
}
let newline = available.iter().position(|byte| *byte == b'\n');
let consumed = newline.map_or(available.len(), |index| index + 1);
if bytes.len().saturating_add(consumed) > limit {
return Err(Error::new(
"HOST_PROTOCOL_ERROR",
"host frame exceeded the configured byte limit",
));
}
bytes.extend_from_slice(&available[..consumed]);
input.consume(consumed);
if newline.is_some() {
let line = std::str::from_utf8(&bytes)
.map_err(|_| Error::new("HOST_PROTOCOL_ERROR", "host frame was not UTF-8"))?
.trim();
if line.is_empty() || (!line.starts_with('{') && !line.starts_with('[')) {
bytes.clear();
continue;
}
return serde_json::from_str(line)
.map(Some)
.map_err(|_| Error::new("HOST_PROTOCOL_ERROR", "host sent malformed JSON"));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use tokio::io::{AsyncWriteExt, BufReader};
#[tokio::test]
async fn split_unicode_coalesced_frames_and_diagnostics() {
let (mut write, read) = tokio::io::duplex(16);
let task = tokio::spawn(async move {
let bytes = "noise\n{\"text\":\"é 😀\u{2028}\u{2029}\\nend\"}\r\n{\"value\":false}\n"
.as_bytes();
for byte in bytes {
write.write_all(&[*byte]).await.unwrap();
}
});
let mut input = BufReader::new(read);
assert_eq!(
read_frame(&mut input, 1024).await.unwrap(),
Some(json!({"text":"é 😀\u{2028}\u{2029}\nend"}))
);
assert_eq!(
read_frame(&mut input, 1024).await.unwrap(),
Some(json!({"value":false}))
);
assert_eq!(read_frame(&mut input, 1024).await.unwrap(), None);
task.await.unwrap();
}
#[tokio::test]
async fn malformed_oversize_and_truncated_frames_fail() {
for bytes in [b"{bad}\n".as_slice(), b"{}", b"{\"long\":123456789}\n"] {
let mut input = BufReader::new(bytes);
assert_eq!(
read_frame(&mut input, 12).await.unwrap_err().code,
"HOST_PROTOCOL_ERROR"
);
}
}
#[tokio::test]
async fn seeded_chunk_and_malformed_input_corpus_stays_bounded_and_panic_free() {
let cases = std::env::var("RUST_TEST_FUZZ_CASES")
.ok()
.map(|value| value.parse::<usize>().unwrap())
.unwrap_or(2000);
assert!((2000..=1000000).contains(&cases));
let mut seed = 0x5D59_8701_u64;
let mut next = || {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
seed
};
for index in 0..cases {
let number = next() % 100000;
let expected = json!({"index":index,"text":"é 😀\n\r\n\u{2028}\u{2029}","zero":0,"absentOrNull":null,"false":false,"number":number});
let mut bytes = b"diagnostic\n".to_vec();
bytes.extend(serde_json::to_vec(&expected).unwrap());
bytes.extend(b"\r\n");
let capacity = (next() % 64 + 1) as usize;
let mut input = BufReader::with_capacity(capacity, bytes.as_slice());
assert_eq!(
read_frame(&mut input, 1024).await.unwrap(),
Some(expected),
"seed case {index}, capacity {capacity}"
);
assert_eq!(read_frame(&mut input, 1024).await.unwrap(), None);
let length = (next() % 512) as usize;
let mut arbitrary: Vec<u8> = (0..length).map(|_| next() as u8).collect();
arbitrary.push(b'\n');
let mut input = BufReader::with_capacity(capacity, arbitrary.as_slice());
if let Ok(Some(value)) = read_frame(&mut input, 128).await {
assert!(value.is_object() || value.is_array());
}
}
let deep = format!("{}0{}\n", "[".repeat(256), "]".repeat(256));
assert_eq!(
read_frame(&mut BufReader::new(deep.as_bytes()), 4096)
.await
.unwrap_err()
.code,
"HOST_PROTOCOL_ERROR"
);
}
}