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    /// Return the trailing bytes after a consumed end marker.
173    ///
174    /// This is typically used by higher-level codecs whose options are followed
175    /// by payload bytes.
176    pub fn remainder(&self) -> &'a [u8] {
177        if self.finished {
178            &self.data[self.pos..]
179        } else {
180            &[]
181        }
182    }
183}
184
185impl<'a> Iterator for OptionDecoder<'a> {
186    type Item = Result<(u16, &'a [u8]), ParseError>;
187
188    fn next(&mut self) -> Option<Self::Item> {
189        if self.finished || self.errored {
190            return None;
191        }
192        if self.pos >= self.data.len() {
193            self.finished = true;
194            return None;
195        }
196
197        let first = self.data[self.pos];
198        if first == 0xFF {
199            self.pos += 1;
200            self.finished = true;
201            return None;
202        }
203
204        self.pos += 1;
205        let delta_nibble = first >> 4;
206        let len_nibble = first & 0x0F;
207        let (delta, delta_len) = match read_extended(&self.data[self.pos..], delta_nibble) {
208            Ok(value) => value,
209            Err(err) => {
210                self.errored = true;
211                return Some(Err(err));
212            }
213        };
214        self.pos += delta_len;
215        let (len, len_len) = match read_extended(&self.data[self.pos..], len_nibble) {
216            Ok(value) => value,
217            Err(err) => {
218                self.errored = true;
219                return Some(Err(err));
220            }
221        };
222        self.pos += len_len;
223
224        if self.pos + len as usize > self.data.len() {
225            self.errored = true;
226            return Some(Err(ParseError::Truncated));
227        }
228
229        let number = self
230            .last_number
231            .checked_add(delta)
232            .ok_or(ParseError::MalformedOption);
233        let number = match number {
234            Ok(value) => value,
235            Err(err) => {
236                self.errored = true;
237                return Some(Err(err));
238            }
239        };
240        let value = &self.data[self.pos..self.pos + len as usize];
241        self.pos += len as usize;
242        self.last_number = number;
243        Some(Ok((number, value)))
244    }
245}
246
247fn encoded_len(value: u16) -> usize {
248    match value {
249        0..=12 => 0,
250        13..=268 => 1,
251        _ => 2,
252    }
253}
254
255fn write_extended(buf: &mut [u8], value: u16) -> Result<u8, EncodeError> {
256    match value {
257        0..=12 => Ok(value as u8),
258        13..=268 => {
259            if buf.is_empty() {
260                return Err(EncodeError::BufferTooSmall);
261            }
262            buf[0] = (value - 13) as u8;
263            Ok(13)
264        }
265        _ => {
266            if buf.len() < 2 {
267                return Err(EncodeError::BufferTooSmall);
268            }
269            let extended = value - 269;
270            buf[..2].copy_from_slice(&extended.to_be_bytes());
271            Ok(14)
272        }
273    }
274}
275
276fn read_extended(data: &[u8], nibble: u8) -> Result<(u16, usize), ParseError> {
277    match nibble {
278        0..=12 => Ok((nibble as u16, 0)),
279        13 => {
280            if data.is_empty() {
281                return Err(ParseError::Truncated);
282            }
283            Ok((data[0] as u16 + 13, 1))
284        }
285        14 => {
286            if data.len() < 2 {
287                return Err(ParseError::Truncated);
288            }
289            Ok((u16::from_be_bytes([data[0], data[1]]) + 269, 2))
290        }
291        _ => Err(ParseError::InvalidOptionNibble),
292    }
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298
299    // ── parse_be_u32 ──────────────────────────────────────────────────────────
300
301    #[test]
302    fn parse_be_u32_values() {
303        assert_eq!(parse_be_u32(&[]).unwrap(), 0);
304        assert_eq!(parse_be_u32(&[1]).unwrap(), 1);
305        assert_eq!(parse_be_u32(&[1, 0]).unwrap(), 256);
306        assert_eq!(parse_be_u32(&[1, 0, 0]).unwrap(), 65536);
307        assert_eq!(parse_be_u32(&[0, 1, 0]).unwrap(), 256); // non-minimal: still parses
308        assert_eq!(parse_be_u32(&[0xFF, 0xFF, 0xFF, 0xFF]).unwrap(), u32::MAX);
309        assert!(parse_be_u32(&[0; 5]).is_err());
310    }
311
312    // ── parse_be_i32 ──────────────────────────────────────────────────────────
313
314    #[test]
315    fn parse_be_i32_values() {
316        assert_eq!(parse_be_i32(&[]).unwrap(), 0);
317        assert_eq!(parse_be_i32(&[0x7F]).unwrap(), 127);
318        assert_eq!(parse_be_i32(&[0x00, 0x80]).unwrap(), 128);
319        assert_eq!(parse_be_i32(&[0xFF]).unwrap(), -1);
320        assert_eq!(parse_be_i32(&[0x80]).unwrap(), -128);
321        assert_eq!(parse_be_i32(&[0xFF, 0x7F]).unwrap(), -129);
322        assert_eq!(parse_be_i32(&[0x80, 0x00, 0x00, 0x00]).unwrap(), i32::MIN);
323        assert_eq!(parse_be_i32(&[0x7F, 0xFF, 0xFF, 0xFF]).unwrap(), i32::MAX);
324        assert!(parse_be_i32(&[0; 5]).is_err());
325    }
326
327    // ── put_u32 / put_i32 round-trips ─────────────────────────────────────────
328
329    #[test]
330    fn put_u32_round_trips() {
331        let cases: &[u32] = &[0, 1, 127, 128, 255, 256, u32::MAX];
332        for &v in cases {
333            let mut buf = [0u8; 16];
334            let mut enc = OptionEncoder::new(&mut buf);
335            enc.put_u32(1, v).unwrap();
336            let len = enc.finish();
337            let (_, value) = OptionDecoder::new(&buf[..len]).next().unwrap().unwrap();
338            assert_eq!(parse_be_u32(value).unwrap(), v, "failed for u32 {v}");
339        }
340    }
341
342    #[test]
343    fn put_i32_round_trips() {
344        let cases: &[i32] = &[0, 1, 127, 128, -1, -128, -129, i32::MIN, i32::MAX];
345        for &v in cases {
346            let mut buf = [0u8; 16];
347            let mut enc = OptionEncoder::new(&mut buf);
348            enc.put_i32(1, v).unwrap();
349            let len = enc.finish();
350            let (_, value) = OptionDecoder::new(&buf[..len]).next().unwrap().unwrap();
351            assert_eq!(parse_be_i32(value).unwrap(), v, "failed for i32 {v}");
352        }
353    }
354
355    // ── wire format ───────────────────────────────────────────────────────────
356
357    // Option 5, value [0xAB]: delta=5 (nibble), len=1 (nibble) → header=0x51, body=[0xAB].
358    #[test]
359    fn wire_inline_delta_and_length() {
360        let mut buf = [0u8; 8];
361        let mut enc = OptionEncoder::new(&mut buf);
362        enc.put(5, &[0xAB]).unwrap();
363        assert_eq!(enc.finish(), 2);
364        assert_eq!(&buf[..2], &[0x51, 0xAB]);
365    }
366
367    // Option 0, empty value: header=0x00.
368    #[test]
369    fn wire_option_zero_empty_value() {
370        let mut buf = [0u8; 4];
371        let mut enc = OptionEncoder::new(&mut buf);
372        enc.put(0, &[]).unwrap();
373        assert_eq!(enc.finish(), 1);
374        assert_eq!(buf[0], 0x00);
375    }
376
377    // Delta=13 encodes as nibble 13 + 1 extended byte (delta - 13 = 0).
378    #[test]
379    fn wire_extended_delta_1byte_boundary() {
380        let mut buf = [0u8; 8];
381        let mut enc = OptionEncoder::new(&mut buf);
382        enc.put(13, &[]).unwrap();
383        assert_eq!(enc.finish(), 2);
384        // nibble=0xD (13), len_nibble=0 → header=0xD0; ext_delta=13-13=0x00
385        assert_eq!(&buf[..2], &[0xD0, 0x00]);
386    }
387
388    // Delta=268 is the largest that fits in a 1-byte extended field (268-13=255).
389    #[test]
390    fn wire_extended_delta_1byte_max() {
391        let mut buf = [0u8; 8];
392        let mut enc = OptionEncoder::new(&mut buf);
393        enc.put(268, &[]).unwrap();
394        assert_eq!(enc.finish(), 2);
395        assert_eq!(&buf[..2], &[0xD0, 0xFF]);
396    }
397
398    // Delta=269 requires a 2-byte extended field (nibble=14, value-269=0 → [0x00,0x00]).
399    #[test]
400    fn wire_extended_delta_2byte_boundary() {
401        let mut buf = [0u8; 8];
402        let mut enc = OptionEncoder::new(&mut buf);
403        enc.put(269, &[]).unwrap();
404        assert_eq!(enc.finish(), 3);
405        assert_eq!(&buf[..3], &[0xE0, 0x00, 0x00]);
406    }
407
408    // Value length=13 encodes in 1 extended length byte.
409    #[test]
410    fn wire_extended_length_1byte() {
411        let mut buf = [0u8; 32];
412        let value = [0u8; 13];
413        {
414            let mut enc = OptionEncoder::new(&mut buf);
415            enc.put(0, &value).unwrap();
416            assert_eq!(enc.finish(), 15);
417        }
418        // header: delta_nibble=0, len_nibble=13(0xD) → 0x0D; ext_len=13-13=0x00
419        assert_eq!(buf[0], 0x0D);
420        assert_eq!(buf[1], 0x00);
421        assert_eq!(&buf[2..15], &value);
422    }
423
424    // end_marker writes 0xFF.
425    #[test]
426    fn wire_end_marker() {
427        let mut buf = [0u8; 4];
428        let mut enc = OptionEncoder::new(&mut buf);
429        enc.put(1, &[0x01]).unwrap();
430        enc.end_marker().unwrap();
431        let len = enc.finish();
432        assert_eq!(buf[len - 1], 0xFF);
433    }
434
435    // ── OptionEncoder: multiple options and delta accumulation ────────────────
436
437    #[test]
438    fn encoder_multiple_options_sequential() {
439        let mut buf = [0u8; 16];
440        let mut enc = OptionEncoder::new(&mut buf);
441        enc.put(1, &[0x01]).unwrap();
442        enc.put(3, &[0x02]).unwrap(); // delta from 1 → 3 is 2
443        enc.put(3, &[0x03]).unwrap(); // delta 0 — same option again
444        let len = enc.finish();
445
446        let items: Vec<_> = OptionDecoder::new(&buf[..len])
447            .collect::<Result<_, _>>()
448            .unwrap();
449        assert_eq!(items, vec![(1, &[0x01u8][..]), (3, &[0x02][..]), (3, &[0x03][..])]);
450    }
451
452    #[test]
453    fn encoder_with_last_number_continues_delta() {
454        // Pretend option 10 was already written; encode option 12 (delta=2).
455        let mut buf = [0u8; 8];
456        let mut enc = OptionEncoder::with_last_number(&mut buf, 10);
457        enc.put(12, &[0xBB]).unwrap();
458        let len = enc.finish();
459        // delta=2, len=1 → header=0x21, body=0xBB
460        assert_eq!(&buf[..len], &[0x21, 0xBB]);
461    }
462
463    #[test]
464    fn encoder_large_option_number_round_trip() {
465        let mut buf = [0u8; 16];
466        let mut enc = OptionEncoder::new(&mut buf);
467        enc.put(u16::MAX, &[0xCC]).unwrap();
468        let len = enc.finish();
469        let (num, val) = OptionDecoder::new(&buf[..len]).next().unwrap().unwrap();
470        assert_eq!(num, u16::MAX);
471        assert_eq!(val, &[0xCC]);
472    }
473
474    // ── OptionEncoder: error paths ────────────────────────────────────────────
475
476    #[test]
477    fn encoder_out_of_order_returns_error() {
478        let mut buf = [0u8; 16];
479        let mut enc = OptionEncoder::new(&mut buf);
480        enc.put(5, &[]).unwrap();
481        assert_eq!(enc.put(3, &[]), Err(EncodeError::OptionOutOfOrder));
482    }
483
484    #[test]
485    fn encoder_buffer_too_small_returns_error() {
486        let mut buf = [0u8; 1]; // only 1 byte: enough for header but not body
487        let mut enc = OptionEncoder::new(&mut buf);
488        assert_eq!(enc.put(0, &[0x01]), Err(EncodeError::BufferTooSmall));
489    }
490
491    #[test]
492    fn encoder_end_marker_buffer_too_small() {
493        let mut buf = [0u8; 0];
494        let mut enc = OptionEncoder::new(&mut buf);
495        assert_eq!(enc.end_marker(), Err(EncodeError::BufferTooSmall));
496    }
497
498    // ── OptionDecoder: basic iteration ────────────────────────────────────────
499
500    #[test]
501    fn decoder_empty_input_yields_nothing() {
502        assert!(OptionDecoder::new(&[]).next().is_none());
503    }
504
505    #[test]
506    fn decoder_end_marker_only_yields_nothing() {
507        let mut dec = OptionDecoder::new(&[0xFF]);
508        assert!(dec.next().is_none());
509        assert_eq!(dec.remainder(), &[] as &[u8]);
510    }
511
512    #[test]
513    fn decoder_remainder_after_end_marker() {
514        // Encode one option, write end marker, append trailing bytes manually.
515        let mut buf = [0u8; 16];
516        let mut enc = OptionEncoder::new(&mut buf);
517        enc.put(1, &[0xAA]).unwrap();
518        enc.end_marker().unwrap();
519        let opt_len = enc.finish();
520        buf[opt_len] = 0xDE;
521        buf[opt_len + 1] = 0xAD;
522
523        let mut dec = OptionDecoder::new(&buf[..opt_len + 2]);
524        let _ = dec.next().unwrap().unwrap(); // consume the option
525        assert!(dec.next().is_none());        // end marker stops iteration
526        assert_eq!(dec.remainder(), &[0xDE, 0xAD]);
527    }
528
529    #[test]
530    fn decoder_remainder_empty_without_end_marker() {
531        let mut buf = [0u8; 8];
532        let mut enc = OptionEncoder::new(&mut buf);
533        enc.put(1, &[0x01]).unwrap();
534        let len = enc.finish();
535
536        let mut dec = OptionDecoder::new(&buf[..len]);
537        let _ = dec.next().unwrap().unwrap();
538        assert!(dec.next().is_none());
539        // No end marker was written, so remainder is empty even after exhaustion.
540        assert_eq!(dec.remainder(), &[] as &[u8]);
541    }
542
543    #[test]
544    fn decoder_remainder_empty_before_exhausted() {
545        // remainder() returns empty while the decoder hasn't finished yet.
546        let mut buf = [0u8; 8];
547        let mut enc = OptionEncoder::new(&mut buf);
548        enc.put(1, &[0x01]).unwrap();
549        enc.end_marker().unwrap();
550        let len = enc.finish();
551
552        let dec = OptionDecoder::new(&buf[..len]);
553        // Haven't called next() yet — not finished.
554        assert_eq!(dec.remainder(), &[] as &[u8]);
555    }
556
557    // ── OptionDecoder: error paths ────────────────────────────────────────────
558
559    #[test]
560    fn decoder_truncated_value_returns_error() {
561        // Header says value is 3 bytes long, but only 1 byte follows.
562        let data = [0x03, 0xAB]; // delta=0, len=3, but only 1 value byte
563        let mut dec = OptionDecoder::new(&data);
564        assert!(matches!(dec.next(), Some(Err(ParseError::Truncated))));
565        assert!(dec.next().is_none()); // error stops further iteration
566    }
567
568    #[test]
569    fn decoder_truncated_extended_delta_returns_error() {
570        // Nibble 13 means 1 extended delta byte follows, but the buffer is empty after header.
571        let data = [0xD0]; // delta_nibble=13 but no ext byte
572        let mut dec = OptionDecoder::new(&data);
573        assert!(matches!(dec.next(), Some(Err(ParseError::Truncated))));
574        assert!(dec.next().is_none());
575    }
576
577    #[test]
578    fn decoder_invalid_nibble_returns_error() {
579        // Nibble value 15 (0xF) is reserved/invalid.
580        let data = [0xF0]; // delta_nibble=15
581        let mut dec = OptionDecoder::new(&data);
582        assert!(matches!(
583            dec.next(),
584            Some(Err(ParseError::InvalidOptionNibble))
585        ));
586        assert!(dec.next().is_none());
587    }
588
589    #[test]
590    fn decoder_option_number_overflow_returns_error() {
591        // Two options: first at u16::MAX, second with delta=1 → overflow.
592        let mut buf = [0u8; 16];
593        let mut enc = OptionEncoder::new(&mut buf);
594        enc.put(u16::MAX, &[]).unwrap();
595        let len = enc.finish();
596
597        // Manually append another option with delta=1 encoded inline.
598        buf[len] = 0x10; // delta_nibble=1, len_nibble=0
599        let mut dec = OptionDecoder::new(&buf[..len + 1]);
600        let _ = dec.next().unwrap().unwrap(); // u16::MAX decoded OK
601        assert!(matches!(dec.next(), Some(Err(ParseError::MalformedOption))));
602        assert!(dec.next().is_none());
603    }
604}