1use crate::error::PluginError;
42use crate::wire::{self, WireError};
43
44pub const FRAME_ITEM: u8 = 0;
46pub const FRAME_END: u8 = 1;
48pub const FRAME_ERROR: u8 = 2;
50
51pub const FRAME_HEADER_LEN: usize = 1 + 4;
53
54#[derive(Debug, Clone, PartialEq)]
60pub enum Frame {
61 Item(Vec<u8>),
63 End,
65 Error(PluginError),
67}
68
69#[derive(Debug, thiserror::Error)]
71pub enum FrameError {
72 #[error("truncated frame: needed {needed} bytes, had {had}")]
74 Truncated { needed: usize, had: usize },
75
76 #[error("unknown frame tag: {0}")]
78 UnknownTag(u8),
79
80 #[error("malformed error-frame payload: {0}")]
82 Payload(#[from] WireError),
83
84 #[error("malformed frame: {0}")]
87 Malformed(String),
88}
89
90impl Frame {
91 pub fn encode(&self) -> Result<Vec<u8>, WireError> {
93 let (tag, payload): (u8, Vec<u8>) = match self {
94 Frame::Item(bytes) => (FRAME_ITEM, bytes.clone()),
95 Frame::End => (FRAME_END, Vec::new()),
96 Frame::Error(err) => (FRAME_ERROR, wire::serialize(err)?),
97 };
98 let mut out = Vec::with_capacity(FRAME_HEADER_LEN + payload.len());
99 out.push(tag);
100 out.extend_from_slice(&(payload.len() as u32).to_le_bytes());
101 out.extend_from_slice(&payload);
102 Ok(out)
103 }
104
105 pub fn decode(bytes: &[u8]) -> Result<Frame, FrameError> {
109 let (frame, consumed) = Frame::read(bytes)?;
110 if consumed != bytes.len() {
111 return Err(FrameError::Malformed(format!(
112 "{} trailing byte(s) after frame",
113 bytes.len() - consumed
114 )));
115 }
116 Ok(frame)
117 }
118
119 pub fn read(bytes: &[u8]) -> Result<(Frame, usize), FrameError> {
123 if bytes.len() < FRAME_HEADER_LEN {
124 return Err(FrameError::Truncated {
125 needed: FRAME_HEADER_LEN,
126 had: bytes.len(),
127 });
128 }
129 let tag = bytes[0];
130 let len = u32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]) as usize;
131 let end = FRAME_HEADER_LEN + len;
132 if bytes.len() < end {
133 return Err(FrameError::Truncated {
134 needed: end,
135 had: bytes.len(),
136 });
137 }
138 let payload = &bytes[FRAME_HEADER_LEN..end];
139 let frame = match tag {
140 FRAME_ITEM => Frame::Item(payload.to_vec()),
141 FRAME_END => {
142 if len != 0 {
143 return Err(FrameError::Malformed(format!(
144 "END frame carries {len} payload byte(s)"
145 )));
146 }
147 Frame::End
148 }
149 FRAME_ERROR => Frame::Error(wire::deserialize::<PluginError>(payload)?),
150 other => return Err(FrameError::UnknownTag(other)),
151 };
152 Ok((frame, end))
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 fn item(payload: &[u8]) -> Frame {
161 Frame::Item(payload.to_vec())
162 }
163
164 #[test]
165 fn item_round_trip() {
166 let f = item(&[1, 2, 3, 4]);
167 let bytes = f.encode().unwrap();
168 assert_eq!(bytes[0], FRAME_ITEM);
169 assert_eq!(Frame::decode(&bytes).unwrap(), f);
170 }
171
172 #[test]
173 fn end_round_trip() {
174 let bytes = Frame::End.encode().unwrap();
175 assert_eq!(bytes[0], FRAME_END);
176 assert_eq!(bytes.len(), FRAME_HEADER_LEN);
177 assert_eq!(Frame::decode(&bytes).unwrap(), Frame::End);
178 }
179
180 #[test]
181 fn error_round_trip() {
182 let err = PluginError::new("BOOM", "it broke");
183 let f = Frame::Error(err.clone());
184 let bytes = f.encode().unwrap();
185 assert_eq!(bytes[0], FRAME_ERROR);
186 assert_eq!(Frame::decode(&bytes).unwrap(), Frame::Error(err));
187 }
188
189 #[test]
190 fn empty_item_is_valid() {
191 let f = item(&[]);
192 let bytes = f.encode().unwrap();
193 assert_eq!(Frame::decode(&bytes).unwrap(), f);
194 }
195
196 #[test]
197 fn read_walks_concatenated_frames() {
198 let mut buf = Vec::new();
199 buf.extend_from_slice(&item(&[9]).encode().unwrap());
200 buf.extend_from_slice(&item(&[8, 7]).encode().unwrap());
201 buf.extend_from_slice(&Frame::End.encode().unwrap());
202
203 let (f1, n1) = Frame::read(&buf).unwrap();
204 assert_eq!(f1, item(&[9]));
205 let (f2, n2) = Frame::read(&buf[n1..]).unwrap();
206 assert_eq!(f2, item(&[8, 7]));
207 let (f3, n3) = Frame::read(&buf[n1 + n2..]).unwrap();
208 assert_eq!(f3, Frame::End);
209 assert_eq!(n1 + n2 + n3, buf.len());
210 }
211
212 #[test]
213 fn truncated_header_is_rejected() {
214 let err = Frame::read(&[FRAME_ITEM, 0, 0]).unwrap_err();
215 assert!(matches!(err, FrameError::Truncated { .. }));
216 }
217
218 #[test]
219 fn truncated_payload_is_rejected() {
220 let mut bytes = vec![FRAME_ITEM];
222 bytes.extend_from_slice(&8u32.to_le_bytes());
223 bytes.extend_from_slice(&[1, 2]);
224 let err = Frame::decode(&bytes).unwrap_err();
225 assert!(matches!(err, FrameError::Truncated { needed: 13, had: 7 }));
226 }
227
228 #[test]
229 fn unknown_tag_is_rejected() {
230 let mut bytes = vec![99u8];
231 bytes.extend_from_slice(&0u32.to_le_bytes());
232 assert!(matches!(
233 Frame::decode(&bytes).unwrap_err(),
234 FrameError::UnknownTag(99)
235 ));
236 }
237
238 #[test]
239 fn end_with_payload_is_rejected() {
240 let mut bytes = vec![FRAME_END];
241 bytes.extend_from_slice(&3u32.to_le_bytes());
242 bytes.extend_from_slice(&[1, 2, 3]);
243 assert!(matches!(
244 Frame::decode(&bytes).unwrap_err(),
245 FrameError::Malformed(_)
246 ));
247 }
248
249 #[test]
250 fn trailing_bytes_after_single_decode_rejected() {
251 let mut bytes = item(&[1]).encode().unwrap();
252 bytes.push(0xFF);
253 assert!(matches!(
254 Frame::decode(&bytes).unwrap_err(),
255 FrameError::Malformed(_)
256 ));
257 }
258
259 #[test]
260 fn garbage_is_rejected_not_panicking() {
261 for g in [vec![], vec![0u8], vec![2u8, 0, 0, 0, 1]] {
262 let _ = Frame::read(&g); }
264 }
265}