1use umsh_core::options::{OptionDecoder, OptionEncoder};
15use umsh_core::{EncodeError, ParseError};
16use umsh_ulcp::pui;
17
18pub const OPT_CURSOR: u16 = 1;
22pub const OPT_REMAINING: u16 = 2;
25
26pub const CURSOR_MAX: usize = 8;
28
29pub type Token = [u8; 2];
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub struct Envelope<'a> {
36 pub token: Token,
37 pub cursor: Option<&'a [u8]>,
40 pub remaining: Option<u32>,
42 pub frame: &'a [u8],
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub enum EnvelopeError {
49 Malformed(ParseError),
52 UnknownCritical(u16),
56 InvalidOptionValue(u16),
60}
61
62impl From<ParseError> for EnvelopeError {
63 fn from(error: ParseError) -> Self {
64 Self::Malformed(error)
65 }
66}
67
68impl<'a> Envelope<'a> {
69 pub const fn new(token: Token, frame: &'a [u8]) -> Self {
71 Self {
72 token,
73 cursor: None,
74 remaining: None,
75 frame,
76 }
77 }
78
79 pub const fn with_cursor(mut self, cursor: &'a [u8]) -> Self {
80 self.cursor = Some(cursor);
81 self
82 }
83
84 pub const fn with_remaining(mut self, remaining: u32) -> Self {
85 self.remaining = Some(remaining);
86 self
87 }
88
89 pub fn parse(payload: &'a [u8]) -> Result<Self, EnvelopeError> {
92 let [token0, token1, rest @ ..] = payload else {
93 return Err(EnvelopeError::Malformed(ParseError::Truncated));
94 };
95
96 let mut cursor = None;
97 let mut remaining = None;
98
99 let mut decoder = OptionDecoder::new(rest);
100 for result in decoder.by_ref() {
101 let (number, value) = result?;
102 match number {
103 OPT_CURSOR => {
104 if value.is_empty() || value.len() > CURSOR_MAX {
105 return Err(EnvelopeError::InvalidOptionValue(number));
106 }
107 cursor = Some(value);
108 }
109 OPT_REMAINING => {
110 let (parsed, consumed) = pui::decode(value)
111 .map_err(|_| EnvelopeError::InvalidOptionValue(number))?;
112 if consumed != value.len() {
113 return Err(EnvelopeError::InvalidOptionValue(number));
114 }
115 remaining = Some(parsed);
116 }
117 _ if number % 2 == 1 => return Err(EnvelopeError::UnknownCritical(number)),
121 _ => {}
122 }
123 }
124
125 Ok(Self {
129 token: [*token0, *token1],
130 cursor,
131 remaining,
132 frame: decoder.remainder(),
133 })
134 }
135
136 pub fn encode(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
138 if buf.len() < 2 {
139 return Err(EncodeError::BufferTooSmall);
140 }
141 buf[0] = self.token[0];
142 buf[1] = self.token[1];
143 let mut pos = 2;
144
145 {
146 let mut enc = OptionEncoder::new(&mut buf[pos..]);
147 if let Some(cursor) = self.cursor {
148 if cursor.is_empty() || cursor.len() > CURSOR_MAX {
149 return Err(EncodeError::OptionValueTooLarge);
150 }
151 enc.put(OPT_CURSOR, cursor)?;
152 }
153 if let Some(remaining) = self.remaining {
154 let mut value = [0u8; pui::MAX_LEN];
155 let len = pui::encode(remaining, &mut value)
156 .map_err(|_| EncodeError::OptionValueTooLarge)?;
157 enc.put(OPT_REMAINING, &value[..len])?;
158 }
159 enc.end_marker()?;
161 pos += enc.finish();
162 }
163
164 if pos + self.frame.len() > buf.len() {
165 return Err(EncodeError::BufferTooSmall);
166 }
167 buf[pos..pos + self.frame.len()].copy_from_slice(self.frame);
168 Ok(pos + self.frame.len())
169 }
170
171 pub fn overhead(&self) -> usize {
173 let mut scratch = [0u8; OVERHEAD_MAX];
174 let bare = Self {
175 frame: &[],
176 ..*self
177 };
178 bare.encode(&mut scratch).unwrap_or(scratch.len())
181 }
182}
183
184pub const OVERHEAD_MAX: usize = 2 + 1 + CURSOR_MAX + 1 + pui::MAX_LEN + 1; #[cfg(test)]
196mod tests {
197 use super::*;
198
199 const FRAME: &[u8] = &[0x80, 0x02, 0x01];
200
201 fn round_trip(envelope: &Envelope<'_>) -> ([u8; 64], usize) {
202 let mut buf = [0u8; 64];
203 let len = envelope.encode(&mut buf).expect("encode");
204 let parsed = Envelope::parse(&buf[..len]).expect("parse");
205 assert_eq!(&parsed, envelope);
206 (buf, len)
207 }
208
209 #[test]
210 fn a_bare_envelope_is_token_marker_frame() {
211 let (buf, len) = round_trip(&Envelope::new([0xAB, 0xCD], FRAME));
212 assert_eq!(&buf[..len], &[0xAB, 0xCD, 0xFF, 0x80, 0x02, 0x01]);
213 }
214
215 #[test]
216 fn options_round_trip() {
217 round_trip(&Envelope::new([1, 2], FRAME).with_cursor(&[9, 8, 7]));
218 round_trip(&Envelope::new([1, 2], FRAME).with_remaining(0));
219 round_trip(&Envelope::new([1, 2], FRAME).with_remaining(pui::MAX_VALUE));
220 round_trip(
221 &Envelope::new([1, 2], FRAME)
222 .with_cursor(&[0; CURSOR_MAX])
223 .with_remaining(300),
224 );
225 }
226
227 #[test]
228 fn an_empty_frame_parses_as_an_empty_frame() {
229 let envelope = Envelope::parse(&[1, 2, 0xFF]).expect("parse");
232 assert!(envelope.frame.is_empty());
233 }
234
235 #[test]
236 fn a_payload_shorter_than_a_token_is_malformed() {
237 assert_eq!(
238 Envelope::parse(&[1]),
239 Err(EnvelopeError::Malformed(ParseError::Truncated))
240 );
241 }
242
243 #[test]
244 fn an_unknown_critical_option_is_reported_and_an_elective_one_is_not() {
245 let mut buf = [0u8; 32];
246 let encoded = {
247 let mut enc = OptionEncoder::new(&mut buf[2..]);
248 enc.put(3, &[0]).unwrap();
249 enc.end_marker().unwrap();
250 2 + enc.finish()
251 };
252 assert_eq!(
253 Envelope::parse(&buf[..encoded]),
254 Err(EnvelopeError::UnknownCritical(3))
255 );
256
257 let mut buf = [0u8; 32];
258 let encoded = {
259 let mut enc = OptionEncoder::new(&mut buf[2..]);
260 enc.put(4, &[0]).unwrap();
261 enc.end_marker().unwrap();
262 2 + enc.finish()
263 };
264 let envelope = Envelope::parse(&buf[..encoded]).expect("elective options are skipped");
265 assert_eq!(envelope.cursor, None);
266 }
267
268 #[test]
269 fn a_cursor_outside_one_to_eight_octets_is_rejected() {
270 for width in [0usize, CURSOR_MAX + 1] {
271 let mut buf = [0u8; 32];
272 let encoded = {
273 let mut enc = OptionEncoder::new(&mut buf[2..]);
274 enc.put(OPT_CURSOR, &[0u8; 16][..width]).unwrap();
275 enc.end_marker().unwrap();
276 2 + enc.finish()
277 };
278 assert_eq!(
279 Envelope::parse(&buf[..encoded]),
280 Err(EnvelopeError::InvalidOptionValue(OPT_CURSOR))
281 );
282 }
283 }
284
285 #[test]
286 fn a_remaining_that_is_not_one_whole_pui_is_rejected() {
287 for value in [&[][..], &[0xFF, 0xFF, 0xFF, 0x7F][..], &[0x00, 0x00][..]] {
288 let mut buf = [0u8; 32];
289 let encoded = {
290 let mut enc = OptionEncoder::new(&mut buf[2..]);
291 enc.put(OPT_REMAINING, value).unwrap();
292 enc.end_marker().unwrap();
293 2 + enc.finish()
294 };
295 assert_eq!(
296 Envelope::parse(&buf[..encoded]),
297 Err(EnvelopeError::InvalidOptionValue(OPT_REMAINING))
298 );
299 }
300 }
301
302 #[test]
303 fn a_buffer_one_octet_short_fails_rather_than_truncating() {
304 let envelope = Envelope::new([1, 2], FRAME);
305 let mut buf = [0u8; 64];
306 let len = envelope.encode(&mut buf).unwrap();
307 assert_eq!(
308 envelope.encode(&mut buf[..len - 1]),
309 Err(EncodeError::BufferTooSmall)
310 );
311 }
312
313 #[test]
314 fn overhead_bounds_every_envelope_this_crate_can_write() {
315 let worst = Envelope::new([0, 0], &[])
316 .with_cursor(&[0; CURSOR_MAX])
317 .with_remaining(pui::MAX_VALUE);
318 assert_eq!(worst.overhead(), OVERHEAD_MAX);
319 assert!(Envelope::new([0, 0], &[]).overhead() <= OVERHEAD_MAX);
320 }
321}