use base64::Engine as _;
use crate::error::{Error, ErrorKind, WireKind};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Frame {
pub event: String,
pub data: String,
}
const MAX_TERMINATOR_LEN: usize = 4;
pub const MAX_PENDING_BYTES: usize = 4 * 1024 * 1024;
#[derive(Debug, Default)]
pub struct SseParser {
buffer: Vec<u8>,
scanned: usize,
}
impl SseParser {
pub fn new() -> Self {
Self::default()
}
pub fn feed(&mut self, chunk: &[u8]) -> Result<Vec<Frame>, Error> {
self.buffer.extend_from_slice(chunk);
let mut frames = Vec::new();
loop {
let from = self.scanned.saturating_sub(MAX_TERMINATOR_LEN - 1);
let Some((body, consumed)) = find_frame_end(&self.buffer, from) else {
self.scanned = self.buffer.len();
break;
};
let raw = self.buffer[..body].to_vec();
self.buffer.drain(..consumed);
self.scanned = 0;
if let Some(frame) = parse_frame(&raw) {
frames.push(frame);
}
}
if self.buffer.len() > MAX_PENDING_BYTES {
let held = self.buffer.len();
self.buffer = Vec::new();
self.scanned = 0;
return Err(Error::new(
ErrorKind::Protocol,
format!(
"the event stream sent {held} bytes with no frame terminator, over the \
{MAX_PENDING_BYTES}-byte ceiling on undelimited data. The daemon's largest \
frame is one 16 KiB pipe read base64-encoded, about 24 KiB, so a residue \
this size means the response body is not server-sent events — most often \
a proxy answering with an error page instead of the stream."
),
));
}
Ok(frames)
}
pub fn pending(&self) -> usize {
self.buffer.len()
}
}
fn find_frame_end(buffer: &[u8], from: usize) -> Option<(usize, usize)> {
let tail = buffer.get(from..)?;
let mut earliest: Option<(usize, usize)> = None;
for terminator in [
b"\r\n\r\n".as_slice(),
b"\n\n".as_slice(),
b"\r\r".as_slice(),
] {
let Some(idx) = find(tail, terminator) else {
continue;
};
let idx = from + idx;
if earliest.is_none_or(|(start, _)| idx < start) {
earliest = Some((idx, terminator.len()));
}
}
let (start, width) = earliest?;
Some((start, start + width))
}
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack
.windows(needle.len())
.position(|window| window == needle)
}
fn parse_frame(raw: &[u8]) -> Option<Frame> {
let mut event = String::new();
let mut data_lines: Vec<String> = Vec::new();
let normalized: Vec<u8> = {
let mut out = Vec::with_capacity(raw.len());
let mut iter = raw.iter().copied().peekable();
while let Some(byte) = iter.next() {
if byte == b'\r' {
if iter.peek() == Some(&b'\n') {
iter.next();
}
out.push(b'\n');
} else {
out.push(byte);
}
}
out
};
for line in normalized.split(|byte| *byte == b'\n') {
if line.is_empty() || line.starts_with(b":") {
continue;
}
let (name, value) = match find(line, b":") {
Some(idx) => (&line[..idx], &line[idx + 1..]),
None => (line, b"".as_slice()),
};
let value = value.strip_prefix(b" ").unwrap_or(value);
match String::from_utf8_lossy(name).as_ref() {
"event" => event = String::from_utf8_lossy(value).into_owned(),
"data" => data_lines.push(String::from_utf8_lossy(value).into_owned()),
_ => {}
}
}
if data_lines.is_empty() {
return None;
}
Some(Frame {
event,
data: data_lines.join("\n"),
})
}
#[derive(Debug)]
pub enum ExecEvent {
Output {
stream: protocol::exec::StreamKind,
offset: u64,
data: Vec<u8>,
},
Gap { from: u64, to: u64 },
Exit(protocol::exec::ExitEvent),
}
pub fn decode(frame: &Frame) -> Result<Option<ExecEvent>, Error> {
match frame.event.as_str() {
protocol::exec::EVENT_OUTPUT => {
let Ok(payload) = serde_json::from_str::<protocol::exec::OutputEvent>(&frame.data)
else {
return Ok(None);
};
let data = base64::engine::general_purpose::STANDARD
.decode(&payload.output)
.map_err(|err| {
Error::wire(
WireKind::ProtocolError,
format!(
"an output event at offset {} carried undecodable base64, so \
those bytes cannot be delivered: {err}",
payload.offset
),
)
})?;
Ok(Some(ExecEvent::Output {
stream: payload.stream,
offset: payload.offset,
data,
}))
}
protocol::exec::EVENT_GAP => {
let Ok(payload) = serde_json::from_str::<protocol::exec::GapEvent>(&frame.data) else {
return Ok(None);
};
Ok(Some(ExecEvent::Gap {
from: payload.from,
to: payload.to,
}))
}
protocol::exec::EVENT_EXIT => {
let Ok(payload) = serde_json::from_str::<protocol::exec::ExitEvent>(&frame.data) else {
return Ok(None);
};
Ok(Some(ExecEvent::Exit(payload)))
}
_ => Ok(None),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn output_frame(offset: u64, bytes: &[u8]) -> String {
let encoded = base64::engine::general_purpose::STANDARD.encode(bytes);
format!(
"event: output\ndata: {{\"offset\":{offset},\"stream\":\"stdout\",\
\"output\":\"{encoded}\"}}\n\n"
)
}
#[test]
fn a_frame_split_across_every_possible_read_boundary_still_parses() {
let wire = output_frame(0, b"hello");
let mut parser = SseParser::new();
let mut frames = Vec::new();
for byte in wire.as_bytes() {
frames.extend(parser.feed(&[*byte]).expect("under the pending ceiling"));
}
assert_eq!(frames.len(), 1, "the split frame did not reassemble");
assert_eq!(parser.pending(), 0, "a complete stream left bytes behind");
let event = decode(&frames[0]).expect("decodes").expect("is an event");
match event {
ExecEvent::Output { offset, data, .. } => {
assert_eq!(offset, 0);
assert_eq!(data, b"hello");
}
other => panic!("expected output, got {other:?}"),
}
}
#[test]
fn one_read_carrying_three_frames_yields_three() {
let wire = format!(
"{}{}{}",
output_frame(0, b"a"),
output_frame(1, b"b"),
"event: exit\ndata: {\"exit_code\":0,\"signal\":null,\"truncated\":false,\
\"writers_may_be_alive\":false,\"offset\":2}\n\n"
);
let mut parser = SseParser::new();
let frames = parser
.feed(wire.as_bytes())
.expect("under the pending ceiling");
assert_eq!(frames.len(), 3);
assert!(matches!(
decode(&frames[2]).expect("decodes"),
Some(ExecEvent::Exit(_))
));
}
#[test]
fn a_trailing_partial_frame_is_held_until_it_completes() {
let wire = output_frame(0, b"xyz");
let (head, tail) = wire.split_at(wire.len() - 4);
let mut parser = SseParser::new();
assert!(
parser
.feed(head.as_bytes())
.expect("under the pending ceiling")
.is_empty(),
"an incomplete frame was emitted"
);
assert!(parser.pending() > 0);
let frames = parser
.feed(tail.as_bytes())
.expect("under the pending ceiling");
assert_eq!(frames.len(), 1);
}
#[test]
fn a_terminator_straddling_a_chunk_boundary_is_still_found() {
for terminator in ["\n\n", "\r\n\r\n", "\r\r"] {
let wire = format!("event: gap\ndata: {{\"from\":0,\"to\":4}}{terminator}");
for split in 1..wire.len() {
let mut parser = SseParser::new();
let mut frames = Vec::new();
frames.extend(
parser
.feed(&wire.as_bytes()[..split])
.expect("under the pending ceiling"),
);
frames.extend(
parser
.feed(&wire.as_bytes()[split..])
.expect("under the pending ceiling"),
);
assert_eq!(
frames.len(),
1,
"{terminator:?} split at {split} did not reassemble"
);
assert_eq!(
parser.pending(),
0,
"{terminator:?} split at {split} left bytes behind"
);
assert!(matches!(
decode(&frames[0]).expect("decodes"),
Some(ExecEvent::Gap { from: 0, to: 4 })
));
}
}
}
#[test]
fn an_unterminated_stream_is_refused_at_the_pending_ceiling() {
let mut parser = SseParser::new();
let chunk = vec![b'x'; 8 * 1024];
let mut fed = 0usize;
loop {
match parser.feed(&chunk) {
Ok(frames) => {
assert!(frames.is_empty(), "undelimited data produced a frame");
fed += chunk.len();
assert!(
parser.pending() <= MAX_PENDING_BYTES,
"the buffer passed the ceiling without erroring: {}",
parser.pending()
);
assert!(
fed <= MAX_PENDING_BYTES + chunk.len(),
"the ceiling never fired"
);
}
Err(err) => {
assert_eq!(err.kind(), ErrorKind::Protocol);
assert!(
!err.retryable(),
"a reconnect would refill the buffer from the same body"
);
assert_eq!(
err.wire_kind(),
None,
"no status produced this; it is a client-side ceiling"
);
let message = err.to_string();
assert!(
message.contains(&MAX_PENDING_BYTES.to_string()),
"the message must name the ceiling: {message}"
);
assert!(
message.contains("no frame terminator"),
"the message must say what was wrong: {message}"
);
assert_eq!(
parser.pending(),
0,
"the refused bytes are dropped, not held for a reader that is gone"
);
return;
}
}
}
}
#[test]
fn scanning_an_unterminated_buffer_stays_linear() {
let chunk = vec![b'x'; 8 * 1024];
let started = tokio::time::Instant::now();
let mut parser = SseParser::new();
for _ in 0..800 {
if parser.pending() + chunk.len() > MAX_PENDING_BYTES {
parser = SseParser::new();
}
parser.feed(&chunk).expect("under the pending ceiling");
}
let elapsed = started.elapsed();
assert!(
elapsed < std::time::Duration::from_secs(10),
"800 x 8 KiB unterminated feeds took {elapsed:?}, over the 10s budget; the \
quadratic rescan is back (the linear path is ~0.75s even under heavy load, \
and the quadratic one is ~160s)"
);
}
#[test]
fn every_blank_line_spelling_terminates_a_frame() {
for terminator in ["\n\n", "\r\n\r\n", "\r\r"] {
let wire = format!("event: gap\r\ndata: {{\"from\":0,\"to\":4}}{terminator}");
let mut parser = SseParser::new();
let frames = parser
.feed(wire.as_bytes())
.expect("under the pending ceiling");
assert_eq!(frames.len(), 1, "{terminator:?} did not terminate a frame");
assert!(matches!(
decode(&frames[0]).expect("decodes"),
Some(ExecEvent::Gap { from: 0, to: 4 })
));
}
}
#[test]
fn a_keepalive_comment_is_not_a_frame() {
let mut parser = SseParser::new();
assert!(
parser
.feed(b":\n\n")
.expect("under the pending ceiling")
.is_empty()
);
assert!(
parser
.feed(b": keep-alive-text\n\n")
.expect("under the pending ceiling")
.is_empty()
);
assert_eq!(parser.pending(), 0);
let wire = format!(":\n\n{}", output_frame(0, b"real"));
let mut parser = SseParser::new();
assert_eq!(
parser
.feed(wire.as_bytes())
.expect("under the pending ceiling")
.len(),
1
);
}
#[test]
fn exactly_one_leading_space_is_stripped_from_a_field_value() {
let mut parser = SseParser::new();
let frames = parser
.feed(b"event: gap\ndata: {\"from\":1,\"to\":2}\n\n")
.expect("under the pending ceiling");
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].event, " gap", "more than one space was stripped");
assert_eq!(frames[0].data, " {\"from\":1,\"to\":2}");
assert!(
decode(&frames[0]).expect("decodes").is_none(),
"a mis-stripped name must not resolve to a known event"
);
}
#[test]
fn an_unrecognized_or_unparseable_frame_is_dropped_rather_than_raised() {
let mut parser = SseParser::new();
let frames = parser
.feed(
b"event: something-new\ndata: {}\n\n\
event: gap\ndata: not json at all\n\n",
)
.expect("under the pending ceiling");
assert_eq!(frames.len(), 2);
for frame in &frames {
assert!(
decode(frame).expect("no error").is_none(),
"{frame:?} should be dropped"
);
}
}
#[test]
fn undecodable_output_base64_is_an_error_rather_than_a_silent_drop() {
let mut parser = SseParser::new();
let frames = parser.feed(
b"event: output\ndata: {\"offset\":7,\"stream\":\"stdout\",\"output\":\"!!!!\"}\n\n",
).expect("under the pending ceiling");
let err = decode(&frames[0]).expect_err("bad base64 must be reported");
assert!(err.to_string().contains('7'), "{err}");
}
#[test]
fn a_multi_line_data_payload_joins_with_newlines() {
let mut parser = SseParser::new();
let frames = parser
.feed(b"event: gap\ndata: {\"from\":0,\ndata: \"to\":5}\n\n")
.expect("under the pending ceiling");
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].data, "{\"from\":0,\n\"to\":5}");
assert!(matches!(
decode(&frames[0]).expect("decodes"),
Some(ExecEvent::Gap { from: 0, to: 5 })
));
}
}