1use crate::{Error, Result};
14
15pub const MAX_EVENT_BYTES: usize = 8 << 20;
17
18#[derive(Debug, Default)]
20pub struct SseDecoder {
21 buf: Vec<u8>,
22 start: usize,
23 scanned: usize,
24 data: String,
25}
26
27impl SseDecoder {
28 #[must_use]
30 pub fn new() -> Self {
31 Self::default()
32 }
33
34 pub fn push(&mut self, chunk: &[u8]) -> Result<()> {
36 if self.start > 0 && self.start * 2 >= self.buf.len() {
37 self.buf.drain(..self.start);
38 self.scanned -= self.start;
39 self.start = 0;
40 }
41 if self.buf.len() - self.start + chunk.len() > MAX_EVENT_BYTES {
42 return Err(Error::TooLarge { limit: MAX_EVENT_BYTES as u64 });
43 }
44 self.buf.extend_from_slice(chunk);
45 Ok(())
46 }
47
48 pub fn next_data(&mut self) -> Result<Option<&str>> {
52 loop {
53 let Some((end, next)) = self.find_boundary() else { return Ok(None) };
54 let event = &self.buf[self.start..end];
55 self.start = next;
56 self.scanned = next;
57 self.data.clear();
58 let mut has_data = false;
59 for line in event.split(|&b| b == b'\n') {
60 let line = line.strip_suffix(b"\r").unwrap_or(line);
61 let Some(rest) = line.strip_prefix(b"data:") else { continue };
62 let rest = rest.strip_prefix(b" ").unwrap_or(rest);
63 if has_data {
64 self.data.push('\n');
65 }
66 has_data = true;
67 let text = std::str::from_utf8(rest)
68 .map_err(|_| Error::Protocol { message: "sse data is not utf-8".into() })?;
69 self.data.push_str(text);
70 }
71 if has_data {
72 return Ok(Some(&self.data));
73 }
74 }
75 }
76
77 #[must_use]
79 pub fn has_partial(&self) -> bool {
80 self.buf[self.start..].iter().any(|b| !b.is_ascii_whitespace())
81 }
82
83 fn find_boundary(&mut self) -> Option<(usize, usize)> {
86 let b = &self.buf;
87 let mut i = self.scanned.max(self.start);
88 while i < b.len() {
89 if b[i] == b'\n' {
90 if b.get(i + 1) == Some(&b'\n') {
92 return Some((i, i + 2));
93 }
94 if b.get(i + 1) == Some(&b'\r') && b.get(i + 2) == Some(&b'\n') {
95 return Some((i, i + 3));
96 }
97 if i + 2 >= b.len() {
98 self.scanned = i;
100 return None;
101 }
102 }
103 i += 1;
104 }
105 self.scanned = b.len();
106 None
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 fn collect(chunks: &[&[u8]]) -> Vec<String> {
115 let mut d = SseDecoder::new();
116 let mut out = Vec::new();
117 for c in chunks {
118 d.push(c).unwrap();
119 while let Some(data) = d.next_data().unwrap() {
120 out.push(data.to_owned());
121 }
122 }
123 out
124 }
125
126 #[test]
127 fn whole_events() {
128 assert_eq!(collect(&[b"data: a\n\ndata: b\n\n"]), ["a", "b"]);
129 }
130
131 #[test]
132 fn events_split_at_every_byte() {
133 let src =
134 b"data: {\"x\":\"\xc3\xa9\"}\r\n\r\n: keep-alive\n\nevent: e\ndata: l1\ndata: l2\n\n";
135 let chunks: Vec<&[u8]> = src.chunks(1).collect();
136 assert_eq!(collect(&chunks), ["{\"x\":\"\u{e9}\"}", "l1\nl2"]);
137 }
138
139 #[test]
140 fn no_data_events_are_skipped() {
141 assert_eq!(collect(&[b": comment\n\nid: 1\n\n"]), Vec::<String>::new());
142 }
143
144 #[test]
145 fn partial_tail_is_reported() {
146 let mut d = SseDecoder::new();
147 d.push(b"data: a\n\ndata: trunc").unwrap();
148 assert_eq!(d.next_data().unwrap(), Some("a"));
149 assert_eq!(d.next_data().unwrap(), None);
150 assert!(d.has_partial());
151 }
152
153 #[test]
154 fn oversize_events_are_refused() {
155 let mut d = SseDecoder::new();
156 let big = vec![b'x'; MAX_EVENT_BYTES];
157 d.push(&big).unwrap();
158 assert!(d.push(b"y").is_err());
159 }
160
161 #[test]
162 fn buffer_compacts_under_steady_streaming() {
163 let mut d = SseDecoder::new();
164 for _ in 0..10_000 {
165 d.push(b"data: 0123456789\n\n").unwrap();
166 assert_eq!(d.next_data().unwrap(), Some("0123456789"));
167 }
168 assert!(d.buf.capacity() < 4096, "consumed events do not accumulate");
169 }
170}