use std::io;
use serde_json::Value;
use crate::protocol::Inbound;
pub const DEFAULT_MAX_LINE: usize = 64 * 1024 * 1024;
const HEAD_LEN: usize = 1024;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Line {
pub bytes: Vec<u8>,
pub end: u64,
pub terminated: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("line too long: {len} bytes, over the limit of {limit}")]
pub struct LineTooLong {
pub len: u64,
pub limit: usize,
pub end: u64,
pub head: Vec<u8>,
pub terminated: bool,
}
impl LineTooLong {
pub fn to_inbound(&self) -> Inbound {
let type_ = sniff_type(&self.head);
let error = self.to_string();
crate::protocol::stats::malformed(&type_, &error);
Inbound::Malformed {
type_,
error,
raw: Value::String(String::from_utf8_lossy(&self.head).into_owned()),
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ReadError {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
TooLong(#[from] LineTooLong),
}
pub fn parse_line(bytes: &[u8]) -> Option<Inbound> {
if bytes.iter().all(u8::is_ascii_whitespace) {
return None;
}
let error = match serde_json::from_slice::<Value>(bytes) {
Ok(Value::Object(obj)) => return Some(Inbound::from_object(obj)),
Ok(_) => "not a JSON object".to_owned(),
Err(e) => format!("invalid JSON: {e}"),
};
let type_ = sniff_type(bytes);
crate::protocol::stats::malformed(&type_, &error);
Some(Inbound::Malformed {
type_,
error,
raw: Value::String(String::from_utf8_lossy(bytes).into_owned()),
})
}
fn sniff_type(bytes: &[u8]) -> String {
let mut depth = 0usize;
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
b'"' => {
let Some((s, next)) = scan_string(bytes, i) else {
return String::new();
};
i = next;
if depth == 1 && s == b"type" {
let rest = skip_ws(bytes, i);
if bytes.get(rest) == Some(&b':') {
let v = skip_ws(bytes, rest + 1);
if bytes.get(v) == Some(&b'"') {
return scan_string(bytes, v)
.filter(|(s, _)| !s.contains(&b'\\'))
.map(|(s, _)| String::from_utf8_lossy(s).into_owned())
.unwrap_or_default();
}
}
}
continue;
}
b'{' | b'[' => depth += 1,
b'}' | b']' => depth = depth.saturating_sub(1),
_ => {}
}
i += 1;
}
String::new()
}
fn scan_string(bytes: &[u8], start: usize) -> Option<(&[u8], usize)> {
let mut i = start + 1;
while i < bytes.len() {
match bytes[i] {
b'\\' => i += 2,
b'"' => return Some((&bytes[start + 1..i], i + 1)),
_ => i += 1,
}
}
None
}
fn skip_ws(bytes: &[u8], mut i: usize) -> usize {
while bytes.get(i).is_some_and(u8::is_ascii_whitespace) {
i += 1;
}
i
}
pub fn encode_line<T: serde::Serialize + ?Sized>(frame: &T) -> Result<Vec<u8>, serde_json::Error> {
let mut out = serde_json::to_vec(frame)?;
out.push(b'\n');
Ok(out)
}
#[derive(Debug)]
struct Splitter {
buf: Vec<u8>,
max: usize,
pos: u64,
skipping: Option<u64>,
}
type Split = Result<Line, LineTooLong>;
impl Splitter {
fn new(max: usize, start: u64) -> Self {
Self {
buf: Vec::new(),
max,
pos: start,
skipping: None,
}
}
fn feed(&mut self, avail: &[u8]) -> (usize, Option<Split>) {
if let Some(i) = avail.iter().position(|&b| b == b'\n') {
self.take(&avail[..i]);
self.pos += i as u64 + 1;
(i + 1, Some(self.finish(true)))
} else {
self.take(avail);
self.pos += avail.len() as u64;
(avail.len(), None)
}
}
fn eof(&mut self) -> Option<Split> {
(!self.buf.is_empty() || self.skipping.is_some()).then(|| self.finish(false))
}
fn take(&mut self, chunk: &[u8]) {
if let Some(len) = &mut self.skipping {
*len += chunk.len() as u64;
} else if self.buf.len() + chunk.len() > self.max {
self.skipping = Some((self.buf.len() + chunk.len()) as u64);
let keep = HEAD_LEN.saturating_sub(self.buf.len()).min(chunk.len());
self.buf.extend_from_slice(&chunk[..keep]);
self.buf.truncate(HEAD_LEN);
self.buf.shrink_to_fit();
} else {
self.buf.extend_from_slice(chunk);
}
}
fn finish(&mut self, terminated: bool) -> Split {
let bytes = core::mem::take(&mut self.buf);
match self.skipping.take() {
Some(len) => Err(LineTooLong {
len,
limit: self.max,
end: self.pos,
head: bytes,
terminated,
}),
None => Ok(Line {
bytes,
end: self.pos,
terminated,
}),
}
}
}
fn split_result(s: Option<Split>) -> Result<Option<Line>, ReadError> {
match s {
None => Ok(None),
Some(Ok(line)) => Ok(Some(line)),
Some(Err(e)) => Err(ReadError::TooLong(e)),
}
}
#[derive(Debug)]
pub struct LineReader<R> {
r: io::BufReader<R>,
s: Splitter,
}
impl<R: io::Read> LineReader<R> {
pub fn new(r: R) -> Self {
Self {
r: io::BufReader::new(r),
s: Splitter::new(DEFAULT_MAX_LINE, 0),
}
}
#[must_use]
pub fn max_line_len(mut self, max: usize) -> Self {
self.s.max = max;
self
}
#[must_use]
pub fn start_offset(mut self, start: u64) -> Self {
self.s.pos = start;
self
}
pub fn position(&self) -> u64 {
self.s.pos
}
pub fn next_line(&mut self) -> Result<Option<Line>, ReadError> {
loop {
let avail = match io::BufRead::fill_buf(&mut self.r) {
Ok(a) => a,
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e.into()),
};
if avail.is_empty() {
return split_result(self.s.eof());
}
let (n, line) = self.s.feed(avail);
io::BufRead::consume(&mut self.r, n);
if line.is_some() {
return split_result(line);
}
}
}
}
#[cfg(feature = "runtime")]
pub use self::runtime::{AsyncLineReader, FrameReader, FrameWriter};
#[cfg(feature = "runtime")]
mod runtime {
use std::io;
use tokio::io::{AsyncBufReadExt as _, AsyncRead, AsyncWrite, AsyncWriteExt as _, BufReader};
use super::{
DEFAULT_MAX_LINE, Line, ReadError, Splitter, encode_line, parse_line, split_result,
};
use crate::protocol::Inbound;
#[derive(Debug)]
pub struct AsyncLineReader<R> {
r: BufReader<R>,
s: Splitter,
}
impl<R: AsyncRead + Unpin> AsyncLineReader<R> {
pub fn new(r: R) -> Self {
Self {
r: BufReader::new(r),
s: Splitter::new(DEFAULT_MAX_LINE, 0),
}
}
#[must_use]
pub fn max_line_len(mut self, max: usize) -> Self {
self.s.max = max;
self
}
#[must_use]
pub fn start_offset(mut self, start: u64) -> Self {
self.s.pos = start;
self
}
pub fn position(&self) -> u64 {
self.s.pos
}
pub async fn next_line(&mut self) -> Result<Option<Line>, ReadError> {
loop {
let avail = match self.r.fill_buf().await {
Ok(a) => a,
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => return Err(e.into()),
};
if avail.is_empty() {
return split_result(self.s.eof());
}
let (n, line) = self.s.feed(avail);
self.r.consume(n);
if line.is_some() {
return split_result(line);
}
}
}
}
#[derive(Debug)]
pub struct FrameReader<R> {
lines: AsyncLineReader<R>,
}
impl<R: AsyncRead + Unpin> FrameReader<R> {
pub fn new(r: R) -> Self {
Self::from_lines(AsyncLineReader::new(r))
}
pub fn from_lines(lines: AsyncLineReader<R>) -> Self {
Self { lines }
}
pub fn position(&self) -> u64 {
self.lines.position()
}
pub async fn next(&mut self) -> io::Result<Option<Inbound>> {
Ok(self.next_at().await?.map(|(frame, _)| frame))
}
pub async fn next_at(&mut self) -> io::Result<Option<(Inbound, u64)>> {
loop {
match self.lines.next_line().await {
Ok(None) => return Ok(None),
Ok(Some(line)) => {
if let Some(frame) = parse_line(&line.bytes) {
return Ok(Some((frame, line.end)));
}
}
Err(ReadError::TooLong(e)) => return Ok(Some((e.to_inbound(), e.end))),
Err(ReadError::Io(e)) => return Err(e),
}
}
}
}
#[derive(Debug)]
pub struct FrameWriter<W> {
w: W,
}
impl<W: AsyncWrite + Unpin> FrameWriter<W> {
pub fn new(w: W) -> Self {
Self { w }
}
pub async fn write<T: serde::Serialize + ?Sized>(&mut self, frame: &T) -> io::Result<()> {
let line =
encode_line(frame).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
self.write_line(&line).await
}
pub async fn write_line(&mut self, line: &[u8]) -> io::Result<()> {
self.w.write_all(line).await?;
self.w.flush().await
}
pub async fn shutdown(&mut self) -> io::Result<()> {
self.w.shutdown().await
}
pub fn into_inner(self) -> W {
self.w
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Trickle<'a>(&'a [u8]);
impl io::Read for Trickle<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let Some((&b, rest)) = self.0.split_first() else {
return Ok(0);
};
if buf.is_empty() {
return Ok(0);
}
buf[0] = b;
self.0 = rest;
Ok(1)
}
}
fn all<R: io::Read>(mut r: LineReader<R>) -> Vec<Result<Line, String>> {
let mut out = Vec::new();
loop {
match r.next_line() {
Ok(None) => return out,
Ok(Some(l)) => out.push(Ok(l)),
Err(e) => out.push(Err(e.to_string())),
}
}
}
fn line(s: &str, end: u64, terminated: bool) -> Result<Line, String> {
Ok(Line {
bytes: s.as_bytes().to_vec(),
end,
terminated,
})
}
#[test]
fn splits_whatever_the_read_size() {
let input = b"{\"a\":1}\n\n{\"b\":2}\r\nlast";
let want = vec![
line("{\"a\":1}", 8, true),
line("", 9, true),
line("{\"b\":2}\r", 18, true),
line("last", 22, false),
];
assert_eq!(all(LineReader::new(&input[..])), want);
assert_eq!(all(LineReader::new(Trickle(input))), want);
}
#[test]
fn clean_eof_after_a_newline() {
assert_eq!(all(LineReader::new(&b"x\n"[..])), vec![line("x", 2, true)]);
assert_eq!(all(LineReader::new(&b""[..])), []);
}
#[test]
fn a_long_line_is_skipped_and_reading_goes_on() {
let long = format!("{{\"type\":\"result\",\"pad\":\"{}\"}}", "x".repeat(3000));
let input = format!("ok\n{long}\nnext\n{long}");
for r in [
all(LineReader::new(input.as_bytes()).max_line_len(100)),
all(LineReader::new(Trickle(input.as_bytes())).max_line_len(100)),
] {
assert_eq!(r.len(), 4);
assert_eq!(r[0], line("ok", 3, true));
assert!(r[1].as_ref().is_err_and(|e| e.contains("too long")));
assert_eq!(r[2], line("next", 3 + long.len() as u64 + 1 + 5, true));
assert!(r[3].is_err());
}
let mut r = LineReader::new(input.as_bytes()).max_line_len(100);
r.next_line().unwrap();
let Err(ReadError::TooLong(e)) = r.next_line() else {
panic!("not too long")
};
assert_eq!(e.len, long.len() as u64);
assert_eq!(e.limit, 100);
assert_eq!(e.head.len(), HEAD_LEN);
assert!(e.terminated);
assert!(
e.to_inbound().ends_turn(),
"a giant result still ends the turn"
);
}
#[test]
fn a_line_at_the_limit_is_kept() {
let r = all(LineReader::new(&b"abcd\nabcde\n"[..]).max_line_len(4));
assert_eq!(r[0], line("abcd", 5, true));
assert!(r[1].is_err());
}
#[test]
fn offsets_count_from_the_start() {
let mut r = LineReader::new(&b"a\nb\n"[..]).start_offset(100);
assert_eq!(r.next_line().unwrap().unwrap().end, 102);
assert_eq!(r.next_line().unwrap().unwrap().end, 104);
assert_eq!(r.position(), 104);
}
#[test]
fn parse_line_never_fails() {
assert!(parse_line(b" \r").is_none());
let Some(Inbound::Malformed { type_, error, raw }) = parse_line(b"garbage") else {
panic!()
};
assert_eq!(type_, "");
assert!(error.starts_with("invalid JSON"));
assert_eq!(raw, Value::String("garbage".into()));
let Some(Inbound::Malformed { error, .. }) = parse_line(b"[1]") else {
panic!()
};
assert_eq!(error, "not a JSON object");
let cut = parse_line(br#"{"type":"result","subtype":"succ"#).unwrap();
assert!(cut.ends_turn(), "a cut-off result still ends the turn");
assert!(matches!(
parse_line(br#"{"type":"brand_new"}"#),
Some(Inbound::Other { .. })
));
}
#[test]
fn sniffs_the_top_level_type() {
assert_eq!(sniff_type(br#" {"type":"user","x":1"#), "user");
assert_eq!(
sniff_type(br#"{"a":{"type":"no"},"b":"\"type\"","type" : "result","#),
"result"
);
assert_eq!(sniff_type(br#"{"x":{"type":"user"}"#), "");
assert_eq!(sniff_type(br#"{"x":"cut off"#), "");
assert_eq!(sniff_type(br#"{"type":"us"#), "");
assert_eq!(sniff_type(br#"{"type":"a\"b"}"#), "");
assert_eq!(sniff_type(b"garbage"), "");
}
#[test]
fn encodes_one_line() {
let l = encode_line(&serde_json::json!({"t": "a\nb"})).unwrap();
assert_eq!(l, b"{\"t\":\"a\\nb\"}\n");
}
}