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    /// Finish the encoder and return the number of bytes written.
78    pub fn finish(self) -> usize {
79        self.pos
80    }
81}
82
83/// Incremental decoder for CoAP-style delta/length option blocks.
84///
85/// This iterator yields absolute option numbers together with borrowed value
86/// slices from the original buffer.
87#[derive(Clone, Debug)]
88pub struct OptionDecoder<'a> {
89    data: &'a [u8],
90    pos: usize,
91    last_number: u16,
92    finished: bool,
93    errored: bool,
94}
95
96impl<'a> OptionDecoder<'a> {
97    /// Create a decoder over a complete encoded option block.
98    pub fn new(data: &'a [u8]) -> Self {
99        Self {
100            data,
101            pos: 0,
102            last_number: 0,
103            finished: false,
104            errored: false,
105        }
106    }
107
108    /// Return the trailing bytes after a consumed end marker.
109    ///
110    /// This is typically used by higher-level codecs whose options are followed
111    /// by payload bytes.
112    pub fn remainder(&self) -> &'a [u8] {
113        if self.finished {
114            &self.data[self.pos..]
115        } else {
116            &[]
117        }
118    }
119}
120
121impl<'a> Iterator for OptionDecoder<'a> {
122    type Item = Result<(u16, &'a [u8]), ParseError>;
123
124    fn next(&mut self) -> Option<Self::Item> {
125        if self.finished || self.errored {
126            return None;
127        }
128        if self.pos >= self.data.len() {
129            self.errored = true;
130            return Some(Err(ParseError::MissingOptionTerminator));
131        }
132
133        let first = self.data[self.pos];
134        if first == 0xFF {
135            self.pos += 1;
136            self.finished = true;
137            return None;
138        }
139
140        self.pos += 1;
141        let delta_nibble = first >> 4;
142        let len_nibble = first & 0x0F;
143        let (delta, delta_len) = match read_extended(&self.data[self.pos..], delta_nibble) {
144            Ok(value) => value,
145            Err(err) => {
146                self.errored = true;
147                return Some(Err(err));
148            }
149        };
150        self.pos += delta_len;
151        let (len, len_len) = match read_extended(&self.data[self.pos..], len_nibble) {
152            Ok(value) => value,
153            Err(err) => {
154                self.errored = true;
155                return Some(Err(err));
156            }
157        };
158        self.pos += len_len;
159
160        if self.pos + len as usize > self.data.len() {
161            self.errored = true;
162            return Some(Err(ParseError::Truncated));
163        }
164
165        let number = self
166            .last_number
167            .checked_add(delta)
168            .ok_or(ParseError::MalformedOption);
169        let number = match number {
170            Ok(value) => value,
171            Err(err) => {
172                self.errored = true;
173                return Some(Err(err));
174            }
175        };
176        let value = &self.data[self.pos..self.pos + len as usize];
177        self.pos += len as usize;
178        self.last_number = number;
179        Some(Ok((number, value)))
180    }
181}
182
183fn encoded_len(value: u16) -> usize {
184    match value {
185        0..=12 => 0,
186        13..=268 => 1,
187        _ => 2,
188    }
189}
190
191fn write_extended(buf: &mut [u8], value: u16) -> Result<u8, EncodeError> {
192    match value {
193        0..=12 => Ok(value as u8),
194        13..=268 => {
195            if buf.is_empty() {
196                return Err(EncodeError::BufferTooSmall);
197            }
198            buf[0] = (value - 13) as u8;
199            Ok(13)
200        }
201        _ => {
202            if buf.len() < 2 {
203                return Err(EncodeError::BufferTooSmall);
204            }
205            let extended = value - 269;
206            buf[..2].copy_from_slice(&extended.to_be_bytes());
207            Ok(14)
208        }
209    }
210}
211
212fn read_extended(data: &[u8], nibble: u8) -> Result<(u16, usize), ParseError> {
213    match nibble {
214        0..=12 => Ok((nibble as u16, 0)),
215        13 => {
216            if data.is_empty() {
217                return Err(ParseError::Truncated);
218            }
219            Ok((data[0] as u16 + 13, 1))
220        }
221        14 => {
222            if data.len() < 2 {
223                return Err(ParseError::Truncated);
224            }
225            Ok((u16::from_be_bytes([data[0], data[1]]) + 269, 2))
226        }
227        _ => Err(ParseError::InvalidOptionNibble),
228    }
229}