coap_zero/message/options/
mod.rs

1// Copyright Open Logistics Foundation
2//
3// Licensed under the Open Logistics Foundation License 1.3.
4// For details on the licensing terms, see the LICENSE file.
5// SPDX-License-Identifier: OLFL-1.3
6
7//! CoAP Options
8//!
9//! This module handles the construction and parsing of CoAP Options.
10//! Refer to <https://www.rfc-editor.org/rfc/rfc7252#section-5.4> and
11//! <https://www.rfc-editor.org/rfc/rfc7252#section-5.10> for further information.
12
13use bondrewd::Bitfields;
14use core::slice::SliceIndex;
15
16use super::encoded_message::EncodedMessage;
17
18mod encoded_value;
19
20use encoded_value::*;
21
22/// Available CoAP Option types (refer to the RFCs for more information):
23/// - <https://www.rfc-editor.org/rfc/rfc7252#section-5.10>
24/// - <https://www.rfc-editor.org/rfc/rfc7641#section-2>
25#[allow(missing_docs)]
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
27#[repr(u16)]
28pub enum CoapOptionName {
29    IfMatch = 1,
30    UriHost = 3,
31    Etag = 4,
32    IfNoneMatch = 5,
33    Observe = 6,
34    UriPort = 7,
35    LocationPath = 8,
36    UriPath = 11,
37    ContentFormat = 12,
38    MaxAge = 14,
39    UriQuery = 15,
40    Accept = 17,
41    LocationQuery = 20,
42    ProxyUri = 35,
43    ProxyScheme = 39,
44    Size1 = 60,
45}
46
47impl TryFrom<u16> for CoapOptionName {
48    type Error = Error;
49
50    fn try_from(val: u16) -> Result<Self, Self::Error> {
51        let option = match val {
52            1 => CoapOptionName::IfMatch,
53            3 => CoapOptionName::UriHost,
54            4 => CoapOptionName::Etag,
55            5 => CoapOptionName::IfNoneMatch,
56            6 => CoapOptionName::Observe,
57            7 => CoapOptionName::UriPort,
58            8 => CoapOptionName::LocationPath,
59            11 => CoapOptionName::UriPath,
60            12 => CoapOptionName::ContentFormat,
61            14 => CoapOptionName::MaxAge,
62            15 => CoapOptionName::UriQuery,
63            17 => CoapOptionName::Accept,
64            20 => CoapOptionName::LocationQuery,
65            35 => CoapOptionName::ProxyUri,
66            39 => CoapOptionName::ProxyScheme,
67            60 => CoapOptionName::Size1,
68            _ => return Err(Error::InvalidOption),
69        };
70
71        Ok(option)
72    }
73}
74
75/// Error Type for parsing Options
76#[allow(missing_docs)]
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum Error {
79    ParseDelta,
80    ParseLength,
81    OutOfBoundsIndexing,
82    InvalidOption,
83}
84
85/// OptionHeader as transmitted over the wire
86#[derive(Bitfields, Debug, PartialEq, Eq)]
87#[bondrewd(enforce_bytes = 1)]
88struct OptionHeader {
89    #[bondrewd(bit_length = 4)]
90    pub delta: u8,
91    #[bondrewd(bit_length = 4)]
92    pub length: u8,
93}
94
95/// A decoded CoAP Option
96#[derive(Debug, Clone, Eq, PartialEq)]
97pub struct CoapOption<'value> {
98    /// Option Name that corresponds to the option number as specified in <https://www.rfc-editor.org/rfc/rfc7252#section-5.10>
99    pub name: CoapOptionName,
100    /// Option Value
101    pub value: &'value [u8],
102}
103
104impl core::cmp::Ord for CoapOption<'_> {
105    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
106        self.name.cmp(&other.name)
107    }
108}
109
110impl core::cmp::PartialOrd for CoapOption<'_> {
111    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
112        Some(self.cmp(other))
113    }
114}
115
116/// Helper function to (stable) sort a `Vec<CoapOption>`.
117pub fn sort_options_vec<const MAX_OPTION_COUNT: usize>(
118    mut options: heapless::Vec<CoapOption<'_>, MAX_OPTION_COUNT>,
119) -> heapless::Vec<CoapOption<'_>, MAX_OPTION_COUNT> {
120    for i in 0..options.len() {
121        for j in 0..options.len() - 1 - i {
122            if options[j] > options[j + 1] {
123                options.swap(j, j + 1);
124            }
125        }
126    }
127
128    options
129}
130
131/// Encodes a slice of `CoapOption`s into a buffer and returns the encoded length
132pub fn encode_to_buf(buf: &mut [u8], options: &[CoapOption<'_>]) -> Result<usize, super::Error> {
133    let mut index = 0;
134    let mut previous_number = 0;
135
136    for option in options {
137        let delta = option.name as u16 - previous_number;
138        let length = option.value.len();
139
140        let delta = EncodedValue::encode(delta);
141        let length = EncodedValue::encode(length as u16);
142
143        let header = OptionHeader {
144            delta: delta.header_value(),
145            length: length.header_value(),
146        };
147
148        let header = header.into_bytes();
149
150        for x in header {
151            *buf.get_mut(index).ok_or(super::Error::OutOfMemory)? = x;
152            index += 1;
153        }
154
155        index += delta.write_into(&mut buf[index..]);
156        index += length.write_into(&mut buf[index..]);
157
158        for x in option.value {
159            *buf.get_mut(index).ok_or(super::Error::OutOfMemory)? = *x;
160            index += 1;
161        }
162        previous_number = option.name as u16;
163    }
164    Ok(index)
165}
166
167/// An Iterator over all options in a message
168pub struct OptionIterator<'encmsg, 'msgbuf> {
169    message: &'encmsg EncodedMessage<'msgbuf>,
170    pub(crate) next_start_index: usize,
171    previous_number: u16,
172    already_errored: bool,
173}
174
175impl<'encmsg, 'msgbuf> OptionIterator<'encmsg, 'msgbuf> {
176    /// Constructs a new OptionIterator based on a given [EncodedMessage].
177    /// The options will be parsed from the message as they are accessed via the next() call.
178    pub fn new(message: &'encmsg EncodedMessage<'msgbuf>, next_start_index: usize) -> Self {
179        Self {
180            message,
181            next_start_index,
182            previous_number: 0,
183            already_errored: false,
184        }
185    }
186
187    /// Helper method to access the bytes of the underlying message. Returns `OutOfBoundsIndexing`
188    /// in case of error. This method is like
189    /// https://doc.rust-lang.org/std/primitive.slice.html#method.get but it returns a `Result`
190    /// instead which is what we need in `try_next`.
191    fn access<I>(&self, index: I) -> Result<&'msgbuf <I as SliceIndex<[u8]>>::Output, Error>
192    where
193        I: SliceIndex<[u8]>,
194    {
195        self.message
196            .data
197            .get(index)
198            .ok_or(Error::OutOfBoundsIndexing)
199    }
200
201    /// We can not use `?` for error handling in the `Iterator::next` impl so easily because this
202    /// would return `None` in error cases. `None` is used to designate "Iterator drained without
203    /// errors". So in error cases, we need to ensure that we return `Some(Err(e))` at least once.
204    /// To do so, we do nearly everything here. Having a method signature that returns a `Result`
205    /// prevents us from accidentally returning `None` in error cases.
206    fn try_next(&mut self) -> Result<CoapOption<'msgbuf>, Error> {
207        let start_index = self.next_start_index;
208
209        let mut header = [0_u8; OptionHeader::BYTE_SIZE];
210        header.clone_from_slice(self.access(start_index..start_index + OptionHeader::BYTE_SIZE)?);
211        let header = OptionHeader::from_bytes(header);
212
213        let OptionHeader { delta, length } = header;
214
215        let start_index = start_index + OptionHeader::BYTE_SIZE;
216
217        let (delta, length_offset) = if delta <= 12 {
218            (delta as u16, 0)
219        } else if delta == 13 {
220            (*self.access(start_index)? as u16 + 13, 1)
221        } else if delta == 14 {
222            let mut buf = [0_u8; 2];
223            buf.clone_from_slice(self.access(start_index..start_index + 2)?);
224            (u16::from_be_bytes(buf) + 269, 2)
225        } else {
226            // Catches delta == 0xF
227            return Err(Error::ParseDelta);
228        };
229
230        let start_index = start_index + length_offset;
231        let (length, value_offset) = if length <= 12 {
232            (length as u16, 0)
233        } else if length == 13 {
234            (*self.access(start_index)? as u16 + 13, 1)
235        } else if length == 14 {
236            let mut buf = [0_u8; 2];
237            buf.clone_from_slice(self.access(start_index..start_index + 2)?);
238            (u16::from_be_bytes(buf) + 269, 2)
239        } else {
240            // Catches length == 0xF
241            return Err(Error::ParseLength);
242        };
243
244        self.previous_number += delta;
245
246        let start_index = start_index + value_offset;
247
248        let value = self.access(start_index..start_index + length as usize)?;
249
250        // Now we know that we have successfully parsed a complete option. We are allowed before
251        // now to advance the Iterator, i.e. increment `next_start_index`.
252        self.next_start_index = start_index + length as usize;
253
254        Ok(CoapOption {
255            name: self.previous_number.try_into()?,
256            value,
257        })
258    }
259}
260
261impl<'encmsg, 'msgbuf> Iterator for OptionIterator<'encmsg, 'msgbuf> {
262    type Item = Result<CoapOption<'msgbuf>, Error>;
263
264    fn next(&mut self) -> Option<Self::Item> {
265        // There are only two possible normal termination conditions
266        // 1. We have reached the end of the message
267        let next_byte = match self.message.data.get(self.next_start_index) {
268            Some(b) => *b,
269            None => {
270                self.message.set_payload_offset(self.next_start_index);
271                return None;
272            }
273        };
274        // 2. We have found the payload marker
275        if next_byte == 0xFF {
276            self.message.set_payload_offset(self.next_start_index);
277            return None;
278        }
279
280        // Now we can only either successfully return the next value or encounter an error. In case
281        // of error, we return `Some(Err(e))` once and `None` afterwards. This ensures that the
282        // Iterator is always terminated, even in error cases.
283        match self.try_next() {
284            Ok(o) => Some(Ok(o)),
285            Err(_) if self.already_errored => None,
286            Err(e) => {
287                self.already_errored = true;
288                Some(Err(e))
289            }
290        }
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297    use crate::message::encoded_message::EncodedMessage;
298
299    #[test]
300    fn empty() {
301        let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0xFF]).unwrap();
302        let mut iter = OptionIterator::new(&msg, 4);
303        assert!(iter.next().is_none());
304    }
305
306    #[test]
307    fn one_option() {
308        let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0x41, 0x11]).unwrap();
309        let mut iter = OptionIterator::new(&msg, 4);
310        let opt = iter.next().unwrap().expect("one option expected");
311        assert_eq!(opt.name, CoapOptionName::Etag);
312        assert_eq!(opt.value.len(), 1);
313        assert_eq!(opt.value[0], 0x11);
314        assert!(iter.next().is_none());
315
316        let msg: EncodedMessage<'_> =
317            EncodedMessage::try_new(&[0, 0, 0, 0, 0x41, 0x11, 0xFF, 0x12, 0x34]).unwrap();
318        let mut iter = OptionIterator::new(&msg, 4);
319        let opt = iter.next().unwrap().expect("one option expected");
320        assert_eq!(opt.name, CoapOptionName::Etag);
321        assert_eq!(opt.value.len(), 1);
322        assert_eq!(opt.value[0], 0x11);
323        assert!(iter.next().is_none());
324    }
325
326    #[test]
327    fn two_options() {
328        let msg: EncodedMessage<'_> =
329            EncodedMessage::try_new(&[0, 0, 0, 0, 0x41, 0x11, 0x12, 0x11, 0x22]).unwrap();
330        let mut iter = OptionIterator::new(&msg, 4);
331        let opt = iter.next().unwrap().expect("one option expected");
332        assert_eq!(opt.name, CoapOptionName::Etag);
333        assert_eq!(opt.value.len(), 1);
334        assert_eq!(opt.value[0], 0x11);
335        let opt = iter.next().unwrap().expect("another option expected");
336        assert_eq!(opt.name, CoapOptionName::IfNoneMatch);
337        assert_eq!(opt.value.len(), 2);
338        assert_eq!(&opt.value[..], &[0x11, 0x22]);
339        assert!(iter.next().is_none());
340    }
341
342    #[test]
343    fn extended_delta_and_length() {
344        let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0xD0, 0x04]).unwrap();
345        let mut iter = OptionIterator::new(&msg, 4);
346        let opt = iter.next().unwrap().expect("one option expected");
347        assert_eq!(opt.name, CoapOptionName::Accept);
348
349        let msg: EncodedMessage<'_> =
350            EncodedMessage::try_new(&[0, 0, 0, 0, 0xE0, 0x00, 0x04]).unwrap();
351        let mut iter = OptionIterator::new(&msg, 4);
352        let opt = iter.next().unwrap();
353        assert!(matches!(opt, Err(Error::InvalidOption)));
354
355        let mut data = [0; 30];
356        data[0..2].copy_from_slice(&[0xBD, 0x04]);
357        let msg: EncodedMessage<'_> = EncodedMessage::try_new(&data).unwrap();
358        let mut iter = OptionIterator::new(&msg, 0);
359        let opt = iter.next().unwrap().expect("one option expected");
360        assert_eq!(opt.value.len(), 17);
361
362        let mut data = [0; 300];
363        data[0..3].copy_from_slice(&[0xBE, 0x00, 0x04]);
364        let msg: EncodedMessage<'_> = EncodedMessage::try_new(&data).unwrap();
365        let mut iter = OptionIterator::new(&msg, 0);
366        let opt = iter.next().unwrap().expect("one option expected");
367        assert_eq!(opt.value.len(), 273);
368
369        let mut data = [0; 30];
370        data[0..3].copy_from_slice(&[0xDD, 0x2F, 0x04]);
371        let msg: EncodedMessage<'_> = EncodedMessage::try_new(&data).unwrap();
372        let mut iter = OptionIterator::new(&msg, 0);
373        let opt = iter.next().unwrap().expect("one option expected");
374        assert!(matches!(opt.name, CoapOptionName::Size1));
375        assert_eq!(opt.value.len(), 17);
376    }
377
378    #[test]
379    fn missing_bytes() {
380        // length = 1 but no value byte
381        let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0x41]).unwrap();
382        let mut iter = OptionIterator::new(&msg, 4);
383        assert!(iter.next().unwrap().is_err());
384        // extended delta missing, note: "extended length missing" does not need to be tested
385        // because this would fail anyways because the value bytes are missing
386        let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0xD0]).unwrap();
387        let mut iter = OptionIterator::new(&msg, 4);
388        assert!(iter.next().unwrap().is_err());
389    }
390
391    #[test]
392    fn wrong_delta_or_length() {
393        let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0xF1]).unwrap();
394        let mut iter = OptionIterator::new(&msg, 4);
395        assert!(iter.next().unwrap().is_err());
396
397        let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0x4F]).unwrap();
398        let mut iter = OptionIterator::new(&msg, 4);
399        assert!(iter.next().unwrap().is_err());
400    }
401
402    #[test]
403    fn next_after_err_is_none() {
404        let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0xF1]).unwrap();
405        let mut iter = OptionIterator::new(&msg, 4);
406        assert!(iter.next().unwrap().is_err());
407        assert!(iter.next().is_none());
408    }
409
410    #[test]
411    fn sort_options() {
412        use CoapOptionName::*;
413        /*
414        IfMatch = 1,
415        UriHost = 3,
416        Etag = 4,
417        IfNoneMatch = 5,
418        ...
419        */
420        sorted_options(&[IfMatch, UriHost, Etag, IfNoneMatch]).unwrap();
421        sorted_options(&[IfMatch, IfMatch, IfMatch, IfMatch]).unwrap();
422        sorted_options(&[IfNoneMatch, Etag, UriHost, IfMatch]).unwrap();
423        sorted_options(&[IfMatch, UriHost, Etag, Etag]).unwrap();
424        sorted_options(&[IfMatch, UriHost, IfNoneMatch, Etag]).unwrap();
425        sorted_options(&[UriHost, IfMatch, IfNoneMatch, Etag]).unwrap();
426    }
427    fn sorted_options(option_names: &[CoapOptionName]) -> Result<(), &'static str> {
428        let mut options = heapless::Vec::new();
429        // We add increasing 1-byte values to the options to check that the sort is stable
430        let values = b"abcdefghijklmnopqrstuvw";
431        for (i, name) in option_names.iter().enumerate() {
432            options
433                .push(CoapOption {
434                    name: *name,
435                    value: &values[i..i + 1],
436                })
437                .unwrap();
438        }
439        let options = sort_options_vec(options);
440        is_sorted(options)
441    }
442    fn is_sorted(options: heapless::Vec<CoapOption<'_>, 32>) -> Result<(), &'static str> {
443        for items in options.windows(2) {
444            if items[0] > items[1] {
445                return Err("not sorted");
446            }
447            if items[0].cmp(&items[1]) == std::cmp::Ordering::Equal
448                && items[0].value[0] >= items[1].value[0]
449            {
450                return Err("not stable");
451            }
452        }
453        Ok(())
454    }
455}