umsh_core/
options.rs

1use crate::{EncodeError, ParseError};
2
3/// Incremental encoder for CoAP-style delta/length option blocks.
4///
5/// The encoder writes directly into a caller-supplied buffer and tracks the last
6/// emitted option number so the on-wire delta encoding remains canonical.
7#[derive(Debug)]
8pub struct OptionEncoder<'a> {
9    buf: &'a mut [u8],
10    pos: usize,
11    last_number: u16,
12    wrote_any: bool,
13}
14
15impl<'a> OptionEncoder<'a> {
16    /// Create an encoder starting at option number `0`.
17    pub fn new(buf: &'a mut [u8]) -> Self {
18        Self {
19            buf,
20            pos: 0,
21            last_number: 0,
22            wrote_any: false,
23        }
24    }
25
26    /// Create an encoder that continues from an already-emitted option number.
27    pub fn with_last_number(buf: &'a mut [u8], last_number: u16) -> Self {
28        Self {
29            buf,
30            pos: 0,
31            last_number,
32            wrote_any: true,
33        }
34    }
35
36    /// Encode one option value.
37    pub fn put(&mut self, number: u16, value: &[u8]) -> Result<(), EncodeError> {
38        if self.wrote_any && number < self.last_number {
39            return Err(EncodeError::OptionOutOfOrder);
40        }
41        let delta = if self.wrote_any {
42            number - self.last_number
43        } else {
44            number
45        };
46        let delta_len = encoded_len(delta);
47        let value_len = encoded_len(value.len() as u16);
48        let required = 1 + delta_len + value_len + value.len();
49        if self.pos + required > self.buf.len() {
50            return Err(EncodeError::BufferTooSmall);
51        }
52
53        let header_pos = self.pos;
54        self.pos += 1;
55        let delta_nibble = write_extended(&mut self.buf[self.pos..], delta)?;
56        self.pos += delta_len;
57        let len_nibble = write_extended(&mut self.buf[self.pos..], value.len() as u16)?;
58        self.pos += value_len;
59        self.buf[header_pos] = (delta_nibble << 4) | len_nibble;
60        self.buf[self.pos..self.pos + value.len()].copy_from_slice(value);
61        self.pos += value.len();
62        self.last_number = number;
63        self.wrote_any = true;
64        Ok(())
65    }
66
67    /// Append the `0xFF` end marker for an option block.
68    pub fn end_marker(&mut self) -> Result<(), EncodeError> {
69        if self.pos >= self.buf.len() {
70            return Err(EncodeError::BufferTooSmall);
71        }
72        self.buf[self.pos] = 0xFF;
73        self.pos += 1;
74        Ok(())
75    }
76
77    /// Encode a `u32` in minimal big-endian form (leading zero bytes stripped).
78    pub fn put_u32(&mut self, number: u16, value: u32) -> Result<(), EncodeError> {
79        let (bytes, len) = minimal_u32(value);
80        self.put(number, &bytes[4 - len..])
81    }
82
83    /// Encode an `i32` in minimal big-endian form (leading sign-extension bytes stripped).
84    pub fn put_i32(&mut self, number: u16, value: i32) -> Result<(), EncodeError> {
85        let (bytes, len) = minimal_i32(value);
86        self.put(number, &bytes[4 - len..])
87    }
88
89    /// Finish the encoder and return the number of bytes written.
90    pub fn finish(self) -> usize {
91        self.pos
92    }
93}
94
95/// Parse a minimal big-endian unsigned integer (leading zero bytes stripped).
96///
97/// Returns `ParseError::MalformedOption` if `bytes.len() > 4`.
98pub fn parse_be_u32(bytes: &[u8]) -> Result<u32, ParseError> {
99    if bytes.len() > 4 {
100        return Err(ParseError::MalformedOption);
101    }
102    let mut arr = [0u8; 4];
103    arr[4 - bytes.len()..].copy_from_slice(bytes);
104    Ok(u32::from_be_bytes(arr))
105}
106
107/// Parse a minimal big-endian signed integer (leading sign-extension bytes stripped).
108///
109/// An empty slice decodes as `0`. Returns `ParseError::MalformedOption` if `bytes.len() > 4`.
110pub fn parse_be_i32(bytes: &[u8]) -> Result<i32, ParseError> {
111    if bytes.is_empty() {
112        return Ok(0);
113    }
114    if bytes.len() > 4 {
115        return Err(ParseError::MalformedOption);
116    }
117    let sign = if bytes[0] & 0x80 != 0 { 0xFF } else { 0x00 };
118    let mut arr = [sign; 4];
119    arr[4 - bytes.len()..].copy_from_slice(bytes);
120    Ok(i32::from_be_bytes(arr))
121}
122
123fn minimal_u32(v: u32) -> ([u8; 4], usize) {
124    let bytes = v.to_be_bytes();
125    let skip = bytes.iter().position(|&b| b != 0).unwrap_or(4);
126    (bytes, 4 - skip)
127}
128
129fn minimal_i32(v: i32) -> ([u8; 4], usize) {
130    if v == 0 {
131        return ([0u8; 4], 0);
132    }
133    let bytes = v.to_be_bytes();
134    let mut skip = 0;
135    if v > 0 {
136        while skip < 3 && bytes[skip] == 0x00 && (bytes[skip + 1] & 0x80 == 0) {
137            skip += 1;
138        }
139    } else {
140        while skip < 3 && bytes[skip] == 0xFF && (bytes[skip + 1] & 0x80 != 0) {
141            skip += 1;
142        }
143    }
144    (bytes, 4 - skip)
145}
146
147/// Incremental decoder for CoAP-style delta/length option blocks.
148///
149/// This iterator yields absolute option numbers together with borrowed value
150/// slices from the original buffer.
151#[derive(Clone, Debug)]
152pub struct OptionDecoder<'a> {
153    data: &'a [u8],
154    pos: usize,
155    last_number: u16,
156    finished: bool,
157    errored: bool,
158}
159
160impl<'a> OptionDecoder<'a> {
161    /// Create a decoder over a complete encoded option block.
162    pub fn new(data: &'a [u8]) -> Self {
163        Self {
164            data,
165            pos: 0,
166            last_number: 0,
167            finished: false,
168            errored: false,
169        }
170    }
171
172    /// Byte offset of the next undecoded byte in the underlying buffer.
173    ///
174    /// Sampled before a call to `next`, this is the offset of that option's
175    /// header, allowing higher-level codecs to retain sub-ranges of an option
176    /// block verbatim.
177    pub fn position(&self) -> usize {
178        self.pos
179    }
180
181    /// The absolute option number in effect for delta decoding.
182    pub fn last_number(&self) -> u16 {
183        self.last_number
184    }
185
186    /// Return the trailing bytes after a consumed end marker.
187    ///
188    /// This is typically used by higher-level codecs whose options are followed
189    /// by payload bytes.
190    pub fn remainder(&self) -> &'a [u8] {
191        if self.finished {
192            &self.data[self.pos..]
193        } else {
194            &[]
195        }
196    }
197}
198
199impl<'a> Iterator for OptionDecoder<'a> {
200    type Item = Result<(u16, &'a [u8]), ParseError>;
201
202    fn next(&mut self) -> Option<Self::Item> {
203        if self.finished || self.errored {
204            return None;
205        }
206        if self.pos >= self.data.len() {
207            self.finished = true;
208            return None;
209        }
210
211        let first = self.data[self.pos];
212        if first == 0xFF {
213            self.pos += 1;
214            self.finished = true;
215            return None;
216        }
217
218        self.pos += 1;
219        let delta_nibble = first >> 4;
220        let len_nibble = first & 0x0F;
221        let (delta, delta_len) = match read_extended(&self.data[self.pos..], delta_nibble) {
222            Ok(value) => value,
223            Err(err) => {
224                self.errored = true;
225                return Some(Err(err));
226            }
227        };
228        self.pos += delta_len;
229        let (len, len_len) = match read_extended(&self.data[self.pos..], len_nibble) {
230            Ok(value) => value,
231            Err(err) => {
232                self.errored = true;
233                return Some(Err(err));
234            }
235        };
236        self.pos += len_len;
237
238        if self.pos + len as usize > self.data.len() {
239            self.errored = true;
240            return Some(Err(ParseError::Truncated));
241        }
242
243        let number = self
244            .last_number
245            .checked_add(delta)
246            .ok_or(ParseError::MalformedOption);
247        let number = match number {
248            Ok(value) => value,
249            Err(err) => {
250                self.errored = true;
251                return Some(Err(err));
252            }
253        };
254        let value = &self.data[self.pos..self.pos + len as usize];
255        self.pos += len as usize;
256        self.last_number = number;
257        Some(Ok((number, value)))
258    }
259}
260
261fn encoded_len(value: u16) -> usize {
262    match value {
263        0..=12 => 0,
264        13..=268 => 1,
265        _ => 2,
266    }
267}
268
269fn write_extended(buf: &mut [u8], value: u16) -> Result<u8, EncodeError> {
270    match value {
271        0..=12 => Ok(value as u8),
272        13..=268 => {
273            if buf.is_empty() {
274                return Err(EncodeError::BufferTooSmall);
275            }
276            buf[0] = (value - 13) as u8;
277            Ok(13)
278        }
279        _ => {
280            if buf.len() < 2 {
281                return Err(EncodeError::BufferTooSmall);
282            }
283            let extended = value - 269;
284            buf[..2].copy_from_slice(&extended.to_be_bytes());
285            Ok(14)
286        }
287    }
288}
289
290fn read_extended(data: &[u8], nibble: u8) -> Result<(u16, usize), ParseError> {
291    match nibble {
292        0..=12 => Ok((nibble as u16, 0)),
293        13 => {
294            if data.is_empty() {
295                return Err(ParseError::Truncated);
296            }
297            Ok((data[0] as u16 + 13, 1))
298        }
299        14 => {
300            if data.len() < 2 {
301                return Err(ParseError::Truncated);
302            }
303            let value = u16::from_be_bytes([data[0], data[1]])
304                .checked_add(269)
305                .ok_or(ParseError::InvalidOptionNibble)?;
306            Ok((value, 2))
307        }
308        _ => Err(ParseError::InvalidOptionNibble),
309    }
310}
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315
316    // ── parse_be_u32 ──────────────────────────────────────────────────────────
317
318    #[test]
319    fn parse_be_u32_values() {
320        assert_eq!(parse_be_u32(&[]).unwrap(), 0);
321        assert_eq!(parse_be_u32(&[1]).unwrap(), 1);
322        assert_eq!(parse_be_u32(&[1, 0]).unwrap(), 256);
323        assert_eq!(parse_be_u32(&[1, 0, 0]).unwrap(), 65536);
324        assert_eq!(parse_be_u32(&[0, 1, 0]).unwrap(), 256); // non-minimal: still parses
325        assert_eq!(parse_be_u32(&[0xFF, 0xFF, 0xFF, 0xFF]).unwrap(), u32::MAX);
326        assert!(parse_be_u32(&[0; 5]).is_err());
327    }
328
329    // ── parse_be_i32 ──────────────────────────────────────────────────────────
330
331    #[test]
332    fn parse_be_i32_values() {
333        assert_eq!(parse_be_i32(&[]).unwrap(), 0);
334        assert_eq!(parse_be_i32(&[0x7F]).unwrap(), 127);
335        assert_eq!(parse_be_i32(&[0x00, 0x80]).unwrap(), 128);
336        assert_eq!(parse_be_i32(&[0xFF]).unwrap(), -1);
337        assert_eq!(parse_be_i32(&[0x80]).unwrap(), -128);
338        assert_eq!(parse_be_i32(&[0xFF, 0x7F]).unwrap(), -129);
339        assert_eq!(parse_be_i32(&[0x80, 0x00, 0x00, 0x00]).unwrap(), i32::MIN);
340        assert_eq!(parse_be_i32(&[0x7F, 0xFF, 0xFF, 0xFF]).unwrap(), i32::MAX);
341        assert!(parse_be_i32(&[0; 5]).is_err());
342    }
343
344    // ── put_u32 / put_i32 round-trips ─────────────────────────────────────────
345
346    #[test]
347    fn put_u32_round_trips() {
348        let cases: &[u32] = &[0, 1, 127, 128, 255, 256, u32::MAX];
349        for &v in cases {
350            let mut buf = [0u8; 16];
351            let mut enc = OptionEncoder::new(&mut buf);
352            enc.put_u32(1, v).unwrap();
353            let len = enc.finish();
354            let (_, value) = OptionDecoder::new(&buf[..len]).next().unwrap().unwrap();
355            assert_eq!(parse_be_u32(value).unwrap(), v, "failed for u32 {v}");
356        }
357    }
358
359    #[test]
360    fn put_i32_round_trips() {
361        let cases: &[i32] = &[0, 1, 127, 128, -1, -128, -129, i32::MIN, i32::MAX];
362        for &v in cases {
363            let mut buf = [0u8; 16];
364            let mut enc = OptionEncoder::new(&mut buf);
365            enc.put_i32(1, v).unwrap();
366            let len = enc.finish();
367            let (_, value) = OptionDecoder::new(&buf[..len]).next().unwrap().unwrap();
368            assert_eq!(parse_be_i32(value).unwrap(), v, "failed for i32 {v}");
369        }
370    }
371
372    // ── wire format ───────────────────────────────────────────────────────────
373
374    // Option 5, value [0xAB]: delta=5 (nibble), len=1 (nibble) → header=0x51, body=[0xAB].
375    #[test]
376    fn wire_inline_delta_and_length() {
377        let mut buf = [0u8; 8];
378        let mut enc = OptionEncoder::new(&mut buf);
379        enc.put(5, &[0xAB]).unwrap();
380        assert_eq!(enc.finish(), 2);
381        assert_eq!(&buf[..2], &[0x51, 0xAB]);
382    }
383
384    // Option 0, empty value: header=0x00.
385    #[test]
386    fn wire_option_zero_empty_value() {
387        let mut buf = [0u8; 4];
388        let mut enc = OptionEncoder::new(&mut buf);
389        enc.put(0, &[]).unwrap();
390        assert_eq!(enc.finish(), 1);
391        assert_eq!(buf[0], 0x00);
392    }
393
394    // Delta=13 encodes as nibble 13 + 1 extended byte (delta - 13 = 0).
395    #[test]
396    fn wire_extended_delta_1byte_boundary() {
397        let mut buf = [0u8; 8];
398        let mut enc = OptionEncoder::new(&mut buf);
399        enc.put(13, &[]).unwrap();
400        assert_eq!(enc.finish(), 2);
401        // nibble=0xD (13), len_nibble=0 → header=0xD0; ext_delta=13-13=0x00
402        assert_eq!(&buf[..2], &[0xD0, 0x00]);
403    }
404
405    // Delta=268 is the largest that fits in a 1-byte extended field (268-13=255).
406    #[test]
407    fn wire_extended_delta_1byte_max() {
408        let mut buf = [0u8; 8];
409        let mut enc = OptionEncoder::new(&mut buf);
410        enc.put(268, &[]).unwrap();
411        assert_eq!(enc.finish(), 2);
412        assert_eq!(&buf[..2], &[0xD0, 0xFF]);
413    }
414
415    // Delta=269 requires a 2-byte extended field (nibble=14, value-269=0 → [0x00,0x00]).
416    #[test]
417    fn wire_extended_delta_2byte_boundary() {
418        let mut buf = [0u8; 8];
419        let mut enc = OptionEncoder::new(&mut buf);
420        enc.put(269, &[]).unwrap();
421        assert_eq!(enc.finish(), 3);
422        assert_eq!(&buf[..3], &[0xE0, 0x00, 0x00]);
423    }
424
425    // Value length=13 encodes in 1 extended length byte.
426    #[test]
427    fn wire_extended_length_1byte() {
428        let mut buf = [0u8; 32];
429        let value = [0u8; 13];
430        {
431            let mut enc = OptionEncoder::new(&mut buf);
432            enc.put(0, &value).unwrap();
433            assert_eq!(enc.finish(), 15);
434        }
435        // header: delta_nibble=0, len_nibble=13(0xD) → 0x0D; ext_len=13-13=0x00
436        assert_eq!(buf[0], 0x0D);
437        assert_eq!(buf[1], 0x00);
438        assert_eq!(&buf[2..15], &value);
439    }
440
441    // end_marker writes 0xFF.
442    #[test]
443    fn wire_end_marker() {
444        let mut buf = [0u8; 4];
445        let mut enc = OptionEncoder::new(&mut buf);
446        enc.put(1, &[0x01]).unwrap();
447        enc.end_marker().unwrap();
448        let len = enc.finish();
449        assert_eq!(buf[len - 1], 0xFF);
450    }
451
452    // ── OptionEncoder: multiple options and delta accumulation ────────────────
453
454    #[test]
455    fn encoder_multiple_options_sequential() {
456        let mut buf = [0u8; 16];
457        let mut enc = OptionEncoder::new(&mut buf);
458        enc.put(1, &[0x01]).unwrap();
459        enc.put(3, &[0x02]).unwrap(); // delta from 1 → 3 is 2
460        enc.put(3, &[0x03]).unwrap(); // delta 0 — same option again
461        let len = enc.finish();
462
463        let items: Vec<_> = OptionDecoder::new(&buf[..len])
464            .collect::<Result<_, _>>()
465            .unwrap();
466        assert_eq!(
467            items,
468            vec![(1, &[0x01u8][..]), (3, &[0x02][..]), (3, &[0x03][..])]
469        );
470    }
471
472    #[test]
473    fn encoder_with_last_number_continues_delta() {
474        // Pretend option 10 was already written; encode option 12 (delta=2).
475        let mut buf = [0u8; 8];
476        let mut enc = OptionEncoder::with_last_number(&mut buf, 10);
477        enc.put(12, &[0xBB]).unwrap();
478        let len = enc.finish();
479        // delta=2, len=1 → header=0x21, body=0xBB
480        assert_eq!(&buf[..len], &[0x21, 0xBB]);
481    }
482
483    #[test]
484    fn encoder_large_option_number_round_trip() {
485        let mut buf = [0u8; 16];
486        let mut enc = OptionEncoder::new(&mut buf);
487        enc.put(u16::MAX, &[0xCC]).unwrap();
488        let len = enc.finish();
489        let (num, val) = OptionDecoder::new(&buf[..len]).next().unwrap().unwrap();
490        assert_eq!(num, u16::MAX);
491        assert_eq!(val, &[0xCC]);
492    }
493
494    // ── OptionEncoder: error paths ────────────────────────────────────────────
495
496    #[test]
497    fn encoder_out_of_order_returns_error() {
498        let mut buf = [0u8; 16];
499        let mut enc = OptionEncoder::new(&mut buf);
500        enc.put(5, &[]).unwrap();
501        assert_eq!(enc.put(3, &[]), Err(EncodeError::OptionOutOfOrder));
502    }
503
504    #[test]
505    fn encoder_buffer_too_small_returns_error() {
506        let mut buf = [0u8; 1]; // only 1 byte: enough for header but not body
507        let mut enc = OptionEncoder::new(&mut buf);
508        assert_eq!(enc.put(0, &[0x01]), Err(EncodeError::BufferTooSmall));
509    }
510
511    #[test]
512    fn encoder_end_marker_buffer_too_small() {
513        let mut buf = [0u8; 0];
514        let mut enc = OptionEncoder::new(&mut buf);
515        assert_eq!(enc.end_marker(), Err(EncodeError::BufferTooSmall));
516    }
517
518    // ── OptionDecoder: basic iteration ────────────────────────────────────────
519
520    #[test]
521    fn decoder_empty_input_yields_nothing() {
522        assert!(OptionDecoder::new(&[]).next().is_none());
523    }
524
525    #[test]
526    fn decoder_end_marker_only_yields_nothing() {
527        let mut dec = OptionDecoder::new(&[0xFF]);
528        assert!(dec.next().is_none());
529        assert_eq!(dec.remainder(), &[] as &[u8]);
530    }
531
532    #[test]
533    fn decoder_remainder_after_end_marker() {
534        // Encode one option, write end marker, append trailing bytes manually.
535        let mut buf = [0u8; 16];
536        let mut enc = OptionEncoder::new(&mut buf);
537        enc.put(1, &[0xAA]).unwrap();
538        enc.end_marker().unwrap();
539        let opt_len = enc.finish();
540        buf[opt_len] = 0xDE;
541        buf[opt_len + 1] = 0xAD;
542
543        let mut dec = OptionDecoder::new(&buf[..opt_len + 2]);
544        let _ = dec.next().unwrap().unwrap(); // consume the option
545        assert!(dec.next().is_none()); // end marker stops iteration
546        assert_eq!(dec.remainder(), &[0xDE, 0xAD]);
547    }
548
549    #[test]
550    fn decoder_remainder_empty_without_end_marker() {
551        let mut buf = [0u8; 8];
552        let mut enc = OptionEncoder::new(&mut buf);
553        enc.put(1, &[0x01]).unwrap();
554        let len = enc.finish();
555
556        let mut dec = OptionDecoder::new(&buf[..len]);
557        let _ = dec.next().unwrap().unwrap();
558        assert!(dec.next().is_none());
559        // No end marker was written, so remainder is empty even after exhaustion.
560        assert_eq!(dec.remainder(), &[] as &[u8]);
561    }
562
563    #[test]
564    fn decoder_remainder_empty_before_exhausted() {
565        // remainder() returns empty while the decoder hasn't finished yet.
566        let mut buf = [0u8; 8];
567        let mut enc = OptionEncoder::new(&mut buf);
568        enc.put(1, &[0x01]).unwrap();
569        enc.end_marker().unwrap();
570        let len = enc.finish();
571
572        let dec = OptionDecoder::new(&buf[..len]);
573        // Haven't called next() yet — not finished.
574        assert_eq!(dec.remainder(), &[] as &[u8]);
575    }
576
577    // ── OptionDecoder: error paths ────────────────────────────────────────────
578
579    #[test]
580    fn decoder_truncated_value_returns_error() {
581        // Header says value is 3 bytes long, but only 1 byte follows.
582        let data = [0x03, 0xAB]; // delta=0, len=3, but only 1 value byte
583        let mut dec = OptionDecoder::new(&data);
584        assert!(matches!(dec.next(), Some(Err(ParseError::Truncated))));
585        assert!(dec.next().is_none()); // error stops further iteration
586    }
587
588    #[test]
589    fn decoder_truncated_extended_delta_returns_error() {
590        // Nibble 13 means 1 extended delta byte follows, but the buffer is empty after header.
591        let data = [0xD0]; // delta_nibble=13 but no ext byte
592        let mut dec = OptionDecoder::new(&data);
593        assert!(matches!(dec.next(), Some(Err(ParseError::Truncated))));
594        assert!(dec.next().is_none());
595    }
596
597    #[test]
598    fn decoder_invalid_nibble_returns_error() {
599        // Nibble value 15 (0xF) is reserved/invalid.
600        let data = [0xF0]; // delta_nibble=15
601        let mut dec = OptionDecoder::new(&data);
602        assert!(matches!(
603            dec.next(),
604            Some(Err(ParseError::InvalidOptionNibble))
605        ));
606        assert!(dec.next().is_none());
607    }
608
609    #[test]
610    fn decoder_option_number_overflow_returns_error() {
611        // Two options: first at u16::MAX, second with delta=1 → overflow.
612        let mut buf = [0u8; 16];
613        let mut enc = OptionEncoder::new(&mut buf);
614        enc.put(u16::MAX, &[]).unwrap();
615        let len = enc.finish();
616
617        // Manually append another option with delta=1 encoded inline.
618        buf[len] = 0x10; // delta_nibble=1, len_nibble=0
619        let mut dec = OptionDecoder::new(&buf[..len + 1]);
620        let _ = dec.next().unwrap().unwrap(); // u16::MAX decoded OK
621        assert!(matches!(dec.next(), Some(Err(ParseError::MalformedOption))));
622        assert!(dec.next().is_none());
623    }
624}