use super::RawSse;
pub const MAX_EVENTSTREAM_PENDING: usize = 16 * 1024 * 1024;
const PRELUDE_LEN: usize = 12;
const CRC_LEN: usize = 4;
#[derive(Debug, Default)]
pub struct EventStreamReader {
buffer: Vec<u8>,
}
impl EventStreamReader {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn feed(&mut self, bytes: &[u8]) -> Result<Vec<RawSse>, String> {
if self.buffer.len().saturating_add(bytes.len()) > MAX_EVENTSTREAM_PENDING {
self.buffer.clear();
return Err(format!(
"eventstream buffer exceeds {MAX_EVENTSTREAM_PENDING} bytes"
));
}
self.buffer.extend_from_slice(bytes);
let mut out = Vec::new();
while let Some((msg, consumed)) = take_message(&self.buffer)? {
self.buffer.drain(..consumed);
out.push(msg.into_raw_sse());
}
Ok(out)
}
pub fn drain(&mut self) -> Option<RawSse> {
None
}
}
struct EventStreamMessage {
event_type: Option<String>,
payload: Vec<u8>,
}
impl EventStreamMessage {
fn into_raw_sse(self) -> RawSse {
let data = wrap_event_payload(self.event_type.as_deref(), &self.payload);
RawSse {
event: self.event_type,
data,
}
}
}
fn wrap_event_payload(event_type: Option<&str>, payload: &[u8]) -> String {
let text = String::from_utf8_lossy(payload).into_owned();
let Some(event_type) = event_type.filter(|name| !name.is_empty()) else {
return text;
};
let Ok(serde_json::Value::Object(map)) = serde_json::from_slice::<serde_json::Value>(payload)
else {
return text;
};
if map.contains_key(event_type) {
return text;
}
serde_json::json!({ event_type: serde_json::Value::Object(map) }).to_string()
}
fn take_message(buf: &[u8]) -> Result<Option<(EventStreamMessage, usize)>, String> {
if buf.len() < PRELUDE_LEN {
return Ok(None);
}
let total = u32::from_be_bytes(buf[0..4].try_into().unwrap()) as usize;
let headers_len = u32::from_be_bytes(buf[4..8].try_into().unwrap()) as usize;
let prelude_crc = u32::from_be_bytes(buf[8..12].try_into().unwrap());
if crc32_ieee(&buf[0..8]) != prelude_crc {
return Err("eventstream prelude CRC mismatch".into());
}
if !(PRELUDE_LEN + CRC_LEN..=MAX_EVENTSTREAM_PENDING).contains(&total) {
return Err(format!("eventstream total_length {total} is invalid"));
}
if buf.len() < total {
return Ok(None);
}
let message_crc = u32::from_be_bytes(buf[total - CRC_LEN..total].try_into().unwrap());
if crc32_ieee(&buf[..total - CRC_LEN]) != message_crc {
return Err("eventstream message CRC mismatch".into());
}
let headers_start = PRELUDE_LEN;
let headers_end = headers_start.saturating_add(headers_len);
if headers_end > total - CRC_LEN {
return Err("eventstream headers overflow payload".into());
}
let headers = parse_headers(&buf[headers_start..headers_end])?;
let payload = buf[headers_end..total - CRC_LEN].to_vec();
if headers.message_type.as_deref() == Some("exception") {
return Err(exception_error(
headers.exception_type.as_deref().unwrap_or("unknown"),
&payload,
));
}
Ok(Some((
EventStreamMessage {
event_type: headers.event_type,
payload,
},
total,
)))
}
fn exception_error(exception_type: &str, payload: &[u8]) -> String {
let message = serde_json::from_slice::<serde_json::Value>(payload)
.ok()
.and_then(|value| {
value
.get("message")
.and_then(serde_json::Value::as_str)
.map(str::to_string)
})
.unwrap_or_else(|| String::from_utf8_lossy(payload).into_owned());
format!("eventstream exception {exception_type}: {message}")
}
struct ParsedHeaders {
event_type: Option<String>,
message_type: Option<String>,
exception_type: Option<String>,
}
fn parse_headers(buf: &[u8]) -> Result<ParsedHeaders, String> {
let mut i = 0;
let mut event_type = None;
let mut message_type = None;
let mut exception_type = None;
while i < buf.len() {
let name_len = buf[i] as usize;
i += 1;
if i + name_len + 1 > buf.len() {
return Err("eventstream header name truncated".into());
}
let name = std::str::from_utf8(&buf[i..i + name_len])
.map_err(|_| "eventstream header name is not UTF-8")?;
i += name_len;
let ty = buf[i];
i += 1;
let value = read_header_value(ty, buf, &mut i)?;
match name {
":event-type" => event_type = Some(value),
":message-type" => message_type = Some(value),
":exception-type" => exception_type = Some(value),
_ => {}
}
}
Ok(ParsedHeaders {
event_type,
message_type,
exception_type,
})
}
fn read_header_value(ty: u8, buf: &[u8], i: &mut usize) -> Result<String, String> {
match ty {
0 => Ok("true".into()),
1 => Ok("false".into()),
2 => take_bytes(buf, i, 1).map(|b| b[0].to_string()),
3 => take_bytes(buf, i, 2).map(|b| i16::from_be_bytes(b.try_into().unwrap()).to_string()),
4 => take_bytes(buf, i, 4).map(|b| i32::from_be_bytes(b.try_into().unwrap()).to_string()),
5 => take_bytes(buf, i, 8).map(|b| i64::from_be_bytes(b.try_into().unwrap()).to_string()),
6 => {
let len = u16::from_be_bytes(take_bytes(buf, i, 2)?.try_into().unwrap()) as usize;
let bytes = take_bytes(buf, i, len)?;
Ok(format!("{bytes:?}"))
}
7 => {
let len = u16::from_be_bytes(take_bytes(buf, i, 2)?.try_into().unwrap()) as usize;
let bytes = take_bytes(buf, i, len)?;
String::from_utf8(bytes.to_vec())
.map_err(|_| "eventstream string header is not UTF-8".into())
}
8 => take_bytes(buf, i, 8).map(|b| i64::from_be_bytes(b.try_into().unwrap()).to_string()),
9 => take_bytes(buf, i, 16).map(|b| {
b.iter()
.map(|x| format!("{x:02x}"))
.collect::<Vec<_>>()
.join("")
}),
other => Err(format!("eventstream unknown header type {other}")),
}
}
fn take_bytes<'a>(buf: &'a [u8], i: &mut usize, n: usize) -> Result<&'a [u8], String> {
if *i + n > buf.len() {
return Err("eventstream header value truncated".into());
}
let slice = &buf[*i..*i + n];
*i += n;
Ok(slice)
}
pub fn encode_message(event_type: &str, payload: &[u8]) -> Vec<u8> {
let mut headers = Vec::new();
write_string_header(&mut headers, ":message-type", "event");
write_string_header(&mut headers, ":event-type", event_type);
write_string_header(&mut headers, ":content-type", "application/json");
encode_with_headers(&headers, payload)
}
pub fn encode_exception_message(exception_type: &str, payload: &[u8]) -> Vec<u8> {
let mut headers = Vec::new();
write_string_header(&mut headers, ":message-type", "exception");
write_string_header(&mut headers, ":exception-type", exception_type);
write_string_header(&mut headers, ":content-type", "application/json");
encode_with_headers(&headers, payload)
}
fn encode_with_headers(headers: &[u8], payload: &[u8]) -> Vec<u8> {
let total = PRELUDE_LEN + headers.len() + payload.len() + CRC_LEN;
let mut out = Vec::with_capacity(total);
out.extend_from_slice(&(total as u32).to_be_bytes());
out.extend_from_slice(&(headers.len() as u32).to_be_bytes());
let prelude_crc = crc32_ieee(&out);
out.extend_from_slice(&prelude_crc.to_be_bytes());
out.extend_from_slice(headers);
out.extend_from_slice(payload);
let message_crc = crc32_ieee(&out);
out.extend_from_slice(&message_crc.to_be_bytes());
out
}
fn write_string_header(out: &mut Vec<u8>, name: &str, value: &str) {
out.push(name.len() as u8);
out.extend_from_slice(name.as_bytes());
out.push(7);
out.extend_from_slice(&(value.len() as u16).to_be_bytes());
out.extend_from_slice(value.as_bytes());
}
fn crc32_ieee(data: &[u8]) -> u32 {
let mut crc = 0xFFFF_FFFFu32;
for &b in data {
crc ^= u32::from(b);
for _ in 0..8 {
crc = if crc & 1 == 1 {
(crc >> 1) ^ 0xEDB8_8320
} else {
crc >> 1
};
}
}
!crc
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_content_block_delta() {
let payload = br#"{"contentBlockDelta":{"delta":{"text":"hi"}}}"#;
let bytes = encode_message("contentBlockDelta", payload);
let mut reader = EventStreamReader::new();
let frames = reader.feed(&bytes).expect("feed");
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].event.as_deref(), Some("contentBlockDelta"));
assert!(frames[0].data.contains("\"text\":\"hi\""));
}
#[test]
fn split_across_chunks() {
let bytes = encode_message(
"messageStop",
br#"{"messageStop":{"stopReason":"end_turn"}}"#,
);
let mut reader = EventStreamReader::new();
let mid = bytes.len() / 2;
let first = reader.feed(&bytes[..mid]).expect("first");
assert!(first.is_empty());
let second = reader.feed(&bytes[mid..]).expect("second");
assert_eq!(second.len(), 1);
assert_eq!(second[0].event.as_deref(), Some("messageStop"));
}
#[test]
fn bad_crc_is_error() {
let mut bytes = encode_message("metadata", br#"{}"#);
let last = bytes.len() - 1;
bytes[last] ^= 0xff;
let mut reader = EventStreamReader::new();
let err = reader.feed(&bytes).expect_err("crc");
assert!(err.contains("CRC"), "{err}");
}
#[test]
fn unwrapped_event_type_payload_is_wrapped() {
let payload = br#"{"delta":{"text":"hi"},"contentBlockIndex":0}"#;
let bytes = encode_message("contentBlockDelta", payload);
let mut reader = EventStreamReader::new();
let frames = reader.feed(&bytes).expect("feed");
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].event.as_deref(), Some("contentBlockDelta"));
let value: serde_json::Value = serde_json::from_str(&frames[0].data).expect("json");
assert_eq!(
value
.pointer("/contentBlockDelta/delta/text")
.and_then(serde_json::Value::as_str),
Some("hi"),
"{}",
frames[0].data
);
}
#[test]
fn exception_frame_is_feed_error() {
let bytes = encode_exception_message(
"validationException",
br#"{"message":"The provided model identifier is invalid."}"#,
);
let mut reader = EventStreamReader::new();
let err = reader.feed(&bytes).expect_err("exception");
assert!(err.contains("validationException"), "{err}");
assert!(
err.contains("The provided model identifier is invalid."),
"{err}"
);
}
#[test]
fn exception_then_delta_in_same_buffer_is_error() {
let mut bytes =
encode_exception_message("internalServerException", br#"{"message":"boom"}"#);
bytes.extend_from_slice(&encode_message(
"contentBlockDelta",
br#"{"delta":{"text":"hi"},"contentBlockIndex":0}"#,
));
let mut reader = EventStreamReader::new();
let err = reader.feed(&bytes).expect_err("exception first");
assert!(err.contains("internalServerException"), "{err}");
assert!(err.contains("boom"), "{err}");
let again = reader.feed(&[]);
assert!(
again.is_err(),
"must not emit a later contentBlockDelta as success: {again:?}"
);
}
}