yapi_ai/aws/
eventstream.rs1use indexmap::IndexMap;
6
7#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct Message {
10 pub headers: IndexMap<String, String>,
13 pub payload: Vec<u8>,
15}
16
17impl Message {
18 pub fn header(&self, name: &str) -> Option<&str> {
20 self.headers.get(name).map(String::as_str)
21 }
22}
23
24#[derive(Debug, Default)]
26pub struct Decoder {
27 buffer: Vec<u8>,
28}
29
30fn u32_at(bytes: &[u8], at: usize) -> u32 {
31 u32::from_be_bytes([bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]])
32}
33
34impl Decoder {
35 pub fn push(&mut self, bytes: &[u8]) {
37 self.buffer.extend_from_slice(bytes);
38 }
39
40 pub fn next_message(&mut self) -> Result<Option<Message>, String> {
42 if self.buffer.len() < 12 {
43 return Ok(None);
44 }
45 let total = u32_at(&self.buffer, 0) as usize;
46 let headers_len = u32_at(&self.buffer, 4) as usize;
47 if total < 16 || headers_len > total - 16 {
48 return Err(format!("Invalid event stream message length: {total}"));
49 }
50 if self.buffer.len() < total {
51 return Ok(None);
52 }
53 let message: Vec<u8> = self.buffer.drain(..total).collect();
54 let prelude_crc = u32_at(&message, 8);
55 if crc32fast::hash(&message[..8]) != prelude_crc {
56 return Err(format!(
57 "The prelude checksum specified in the message ({prelude_crc}) does not match the calculated CRC32 checksum ({})",
58 crc32fast::hash(&message[..8])
59 ));
60 }
61 let message_crc = u32_at(&message, total - 4);
62 if crc32fast::hash(&message[..total - 4]) != message_crc {
63 return Err(format!(
64 "The message checksum ({}) did not match the expected value of {message_crc}",
65 crc32fast::hash(&message[..total - 4])
66 ));
67 }
68 let headers = parse_headers(&message[12..12 + headers_len])?;
69 Ok(Some(Message {
70 headers,
71 payload: message[12 + headers_len..total - 4].to_vec(),
72 }))
73 }
74
75 pub fn finish(&self) -> Result<(), String> {
78 match self.buffer.len() {
79 0..4 => Ok(()),
80 _ => Err("Truncated event message received.".into()),
81 }
82 }
83}
84
85fn parse_headers(mut bytes: &[u8]) -> Result<IndexMap<String, String>, String> {
86 let invalid = || "Invalid event stream header".to_owned();
87 let mut headers = IndexMap::new();
88 while !bytes.is_empty() {
89 let name_len = usize::from(bytes[0]);
90 let name = bytes.get(1..1 + name_len).ok_or_else(invalid)?;
91 let name = String::from_utf8_lossy(name).into_owned();
92 bytes = &bytes[1 + name_len..];
93 let kind = *bytes.first().ok_or_else(invalid)?;
94 bytes = &bytes[1..];
95 let fixed = |len: usize, bytes: &mut &[u8]| -> Result<Vec<u8>, String> {
96 let value = bytes.get(..len).ok_or_else(invalid)?.to_vec();
97 *bytes = &bytes[len..];
98 Ok(value)
99 };
100 let value = match kind {
101 0 => "true".to_owned(),
102 1 => "false".to_owned(),
103 2 => (fixed(1, &mut bytes)?[0] as i8).to_string(),
104 3 => i16::from_be_bytes(fixed(2, &mut bytes)?.try_into().map_err(|_| invalid())?)
105 .to_string(),
106 4 => i32::from_be_bytes(fixed(4, &mut bytes)?.try_into().map_err(|_| invalid())?)
107 .to_string(),
108 5 | 8 => i64::from_be_bytes(fixed(8, &mut bytes)?.try_into().map_err(|_| invalid())?)
109 .to_string(),
110 6 | 7 => {
111 let len = bytes.get(..2).ok_or_else(invalid)?;
112 let len = usize::from(u16::from_be_bytes([len[0], len[1]]));
113 bytes = &bytes[2..];
114 String::from_utf8_lossy(&fixed(len, &mut bytes)?).into_owned()
115 }
116 9 => yapi_types::time::hex(&fixed(16, &mut bytes)?),
117 _ => return Err(format!("Unrecognized event stream header type: {kind}")),
118 };
119 headers.insert(name, value);
120 }
121 Ok(headers)
122}
123
124pub fn encode(headers: &[(&str, &str)], payload: &[u8]) -> Vec<u8> {
126 let mut header_bytes = Vec::new();
127 for (name, value) in headers {
128 header_bytes.push(name.len() as u8);
129 header_bytes.extend_from_slice(name.as_bytes());
130 header_bytes.push(7);
131 header_bytes.extend_from_slice(&(value.len() as u16).to_be_bytes());
132 header_bytes.extend_from_slice(value.as_bytes());
133 }
134 let total = (16 + header_bytes.len() + payload.len()) as u32;
135 let mut message = Vec::new();
136 message.extend_from_slice(&total.to_be_bytes());
137 message.extend_from_slice(&(header_bytes.len() as u32).to_be_bytes());
138 let prelude_crc = crc32fast::hash(&message);
139 message.extend_from_slice(&prelude_crc.to_be_bytes());
140 message.extend_from_slice(&header_bytes);
141 message.extend_from_slice(payload);
142 let crc = crc32fast::hash(&message);
143 message.extend_from_slice(&crc.to_be_bytes());
144 message
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn split_and_corrupt_messages() {
153 let first = encode(
154 &[(":event-type", "messageStart")],
155 br#"{"role":"assistant"}"#,
156 );
157 let second = encode(&[(":message-type", "exception")], b"{}");
158 let mut decoder = Decoder::default();
159 let mut stream = first.clone();
160 stream.extend_from_slice(&second);
161 decoder.push(&stream[..7]);
162 assert_eq!(decoder.next_message(), Ok(None));
163 decoder.push(&stream[7..]);
164 let message = decoder.next_message().unwrap().unwrap();
165 assert_eq!(message.header(":event-type"), Some("messageStart"));
166 assert_eq!(message.payload, br#"{"role":"assistant"}"#);
167 assert_eq!(
168 decoder
169 .next_message()
170 .unwrap()
171 .unwrap()
172 .header(":message-type"),
173 Some("exception")
174 );
175 assert!(decoder.buffer.is_empty());
176 assert_eq!(decoder.finish(), Ok(()));
177 decoder.push(&stream[..3]);
178 assert_eq!(decoder.finish(), Ok(()));
179 decoder.push(&stream[3..4]);
180 assert!(decoder.finish().unwrap_err().contains("Truncated"));
181 decoder.buffer.clear();
182 let mut corrupt = first;
183 let last = corrupt.len() - 5;
184 corrupt[last] ^= 1;
185 decoder.push(&corrupt);
186 assert!(
187 decoder
188 .next_message()
189 .unwrap_err()
190 .contains("message checksum")
191 );
192 }
193}