1#[derive(Debug, Clone, PartialEq, Eq)]
2pub struct SseEvent {
3 pub event: Option<String>,
4 pub data: String,
5}
6
7#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
8pub enum StreamParseError {
9 #[error("SSE buffer exceeded {0} bytes")]
10 BufferLimit(usize),
11 #[error("stream ended with an incomplete SSE event")]
12 Incomplete,
13}
14
15pub struct SseDecoder {
16 buffer: String,
17 scanned: usize,
20 max_buffer_bytes: usize,
21}
22
23impl Default for SseDecoder {
24 fn default() -> Self {
25 Self::new(1024 * 1024)
26 }
27}
28
29impl SseDecoder {
30 pub fn new(max_buffer_bytes: usize) -> Self {
31 Self {
32 buffer: String::new(),
33 scanned: 0,
34 max_buffer_bytes,
35 }
36 }
37
38 pub fn push(&mut self, chunk: &str) -> Result<Vec<SseEvent>, StreamParseError> {
39 self.buffer.push_str(chunk);
40 if self.buffer.len() > self.max_buffer_bytes {
41 return Err(StreamParseError::BufferLimit(self.max_buffer_bytes));
42 }
43 let mut events = Vec::new();
44 let mut search = self.scanned;
49 let mut consumed = 0;
50 while let Some(offset) = event_end(&self.buffer[search..]) {
51 let end = search + offset;
52 let block = self.buffer[consumed..end].replace('\r', "");
53 let delimiter_len = if self.buffer[end..].starts_with("\r\n\r\n") {
54 4
55 } else {
56 2
57 };
58 consumed = end + delimiter_len;
59 search = consumed;
60 if let Some(event) = parse_event(&block) {
61 events.push(event);
62 }
63 }
64 self.buffer.drain(..consumed);
65 let mut scanned = self.buffer.len().saturating_sub(DELIMITER_OVERLAP);
67 while scanned > 0 && !self.buffer.is_char_boundary(scanned) {
68 scanned -= 1;
69 }
70 self.scanned = scanned;
71 Ok(events)
72 }
73
74 #[cfg(fuzzing)]
80 pub fn fuzz_buffered(&self) -> &str {
81 &self.buffer
82 }
83
84 pub fn finish(self) -> Result<(), StreamParseError> {
85 if self.buffer.trim().is_empty() {
86 Ok(())
87 } else {
88 Err(StreamParseError::Incomplete)
89 }
90 }
91}
92
93const DELIMITER_OVERLAP: usize = 3;
95
96fn event_end(buffer: &str) -> Option<usize> {
102 let bytes = buffer.as_bytes();
103 for index in 0..bytes.len().saturating_sub(1) {
104 if bytes[index] == b'\n' && bytes[index + 1] == b'\n' {
105 return Some(index);
106 }
107 if bytes[index] == b'\r' && bytes[index..].starts_with(b"\r\n\r\n") {
108 return Some(index);
109 }
110 }
111 None
112}
113
114fn parse_event(block: &str) -> Option<SseEvent> {
115 let mut event = None;
116 let mut data = Vec::new();
117 for line in block.lines() {
118 if line.starts_with(':') {
119 continue;
120 }
121 if let Some(value) = line.strip_prefix("event:") {
122 event = Some(value.trim_start().to_owned());
123 } else if let Some(value) = line.strip_prefix("data:") {
124 data.push(value.trim_start());
125 }
126 }
127 (!data.is_empty()).then(|| SseEvent {
128 event,
129 data: data.join("\n"),
130 })
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn parses_fragmented_and_multiline_events() {
139 let mut decoder = SseDecoder::default();
140 assert!(
141 decoder
142 .push("event: delta\r\ndata: {\"a\":")
143 .unwrap()
144 .is_empty()
145 );
146 let events = decoder.push("1}\r\ndata: tail\r\n\r\n").unwrap();
147 assert_eq!(events[0].event.as_deref(), Some("delta"));
148 assert_eq!(events[0].data, "{\"a\":1}\ntail");
149 decoder.finish().unwrap();
150 }
151
152 #[test]
156 fn every_chunk_boundary_decodes_a_body_identically() {
157 for body in [
158 "data: one\n\ndata: two\n\n",
159 "event: delta\r\ndata: {\"a\":1}\r\n\r\n: keep-alive\r\n\r\ndata: [DONE]\r\n\r\n",
160 "data: first\ndata: second\n\ndata: tail",
161 ": comment only\n\n\n\ndata: \n\n",
162 ] {
163 let mut whole = SseDecoder::default();
164 let expected = whole.push(body).unwrap();
165 for cut in 1..body.len() {
166 if !body.is_char_boundary(cut) {
167 continue;
168 }
169 let mut split = SseDecoder::default();
170 let mut events = split.push(&body[..cut]).unwrap();
171 events.extend(split.push(&body[cut..]).unwrap());
172 assert_eq!(events, expected, "{body:?} split at {cut}");
173 }
174 }
175 }
176
177 #[test]
180 fn a_truncated_final_event_is_refused_by_finish() {
181 let mut decoder = SseDecoder::default();
182 assert!(decoder.push("data: complete\n\ndata: trunc").unwrap().len() == 1);
183 assert_eq!(decoder.finish(), Err(StreamParseError::Incomplete));
184 }
185
186 #[test]
189 fn a_delimiter_split_byte_by_byte_still_terminates_an_event() {
190 let body = "event: delta\r\ndata: one\r\n\r\ndata: two\n\n";
191 let mut decoder = SseDecoder::default();
192 let mut events = Vec::new();
193 for byte in 0..body.len() {
194 events.extend(decoder.push(&body[byte..=byte]).unwrap());
195 }
196 assert_eq!(events.len(), 2);
197 assert_eq!(events[0].event.as_deref(), Some("delta"));
198 assert_eq!(events[0].data, "one");
199 assert_eq!(events[1].data, "two");
200 decoder.finish().unwrap();
201 }
202
203 #[test]
207 fn a_chunk_of_many_tiny_events_is_parsed_in_one_pass() {
208 let events = 200_000;
209 let mut decoder = SseDecoder::new(8 * 1024 * 1024);
210 let decoded = decoder.push(&"data: x\n\n".repeat(events)).unwrap();
211 assert_eq!(decoded.len(), events);
212 decoder.finish().unwrap();
213 }
214
215 #[test]
218 fn an_unterminated_event_trips_the_buffer_limit() {
219 let mut decoder = SseDecoder::new(64);
220 assert_eq!(
221 decoder.push(&"data: ".repeat(64)),
222 Err(StreamParseError::BufferLimit(64))
223 );
224 }
225}