coap_message_implementations/
option_extension.rs

1//! Tools for handling the extension encoding of CoAP options
2
3use core::convert::TryInto;
4
5/// Nibble value for "1 byte extension"
6pub const VALUE_1B: u8 = 13u8;
7
8/// Nibble value for "2 byte extension"
9pub const VALUE_2B: u8 = 14u8;
10
11/// Nibble value reserved for the payload marker
12pub const VALUE_RESERVED: u8 = 15u8;
13
14/// The offset added to a 1-byte extended value
15pub const OFFSET_1B: u16 = 13u16;
16
17/// The offset added to a 2-byte extended value
18pub const OFFSET_2B: u16 = 269u16;
19
20#[derive(Debug)]
21#[cfg_attr(feature = "defmt", derive(defmt::Format))]
22/// All the ways decoding an extension value can go wrong
23pub enum ExtensionError {
24    /// The reserved value 15 was present in the nibble.
25    Reserved15,
26    /// The remaining message did not contain the necessary bytes to read the next.
27    OutOfBytes,
28    /// The encoded value exceeds the encodable maximum.
29    ///
30    /// While the encoding itself can represent values up to `2^16 + OFFSET_2B`, all practical uses
31    /// are capped to `2^16` anyway, allowing the value to be stored in a [u16] and to err outside
32    /// of that.
33    Overflow,
34}
35
36/// Decode an extended value.
37///
38/// Given a value that was initially extracted from an Option Delta or Option Length nibble of an
39/// option, read up to 2 extended bytes from the slice that contains the remaining data, and
40/// advance the slice by that amount.
41///
42/// This returns `Some(())` on success, or None to indicate any error condition (which may be
43/// exhaustion of the slice, the presence of the value 15, or an overflow induced by an
44/// almost-maximum value in the 2-byte extension
45///
46/// Typical use
47/// -----------
48///
49/// ```
50/// # use coap_message_implementations::option_extension::take_extension;
51/// let data = b"\xbd\x01long-path-name\xff";
52///
53/// let nibble = data[0];
54/// let mut delta = (nibble >> 4).into();
55/// let mut length = (nibble & 0x0f).into();
56///
57/// let mut view = &data[1..];
58/// // Delta is 11; this does not modify anything
59/// take_extension(&mut delta, &mut view).unwrap();
60/// // Length is 13 + 1; this takes one byte off view
61/// take_extension(&mut length, &mut view).unwrap();
62///
63/// assert!(delta == 11);
64/// assert!(length == 14);
65/// assert!(&view[..14] == b"long-path-name");
66/// ```
67pub fn take_extension(value: &mut u16, slice: &mut &[u8]) -> Result<(), ExtensionError> {
68    assert!(*value < 16);
69    match *value as u8 {
70        VALUE_RESERVED => Err(ExtensionError::Reserved15),
71        VALUE_2B => {
72            *value = u16::from_be_bytes(
73                slice
74                    .get(..2)
75                    .ok_or(ExtensionError::OutOfBytes)?
76                    .try_into()
77                    .expect("Slice is 2-long by construction"),
78            )
79            .checked_add(OFFSET_2B)
80            .ok_or(ExtensionError::Overflow)?;
81            *slice = &slice[2..];
82            Ok(())
83        }
84        VALUE_1B => {
85            *value = OFFSET_1B + u16::from(*slice.first().ok_or(ExtensionError::OutOfBytes)?);
86            *slice = &slice[1..];
87            Ok(())
88        }
89        _ => Ok(()),
90    }
91}
92
93/// Write an extended option delta or length value into a vector.
94///
95/// The resulting nibble then needs to be shifted and stored in the respective half the previously
96/// pushed value.
97///
98/// While this can be used to push the extension data into a
99/// [`heapless::Vec`](https://docs.rs/heapless/latest/heapless/struct.Vec.html), beware that
100/// heapless indicates its error condition by panicking. Before calling push_extensions on such a
101/// target, the caller may want to check whether 2 more bytes (the maximum this function appends)
102/// are still free.
103fn push_extensions(target: &mut impl core::iter::Extend<u8>, value: u16) -> u8 {
104    if value < OFFSET_1B {
105        value as u8
106    } else if value < OFFSET_2B {
107        let value = (value - OFFSET_1B) as u8;
108        target.extend([value]);
109        VALUE_1B
110    } else {
111        let value = value - OFFSET_2B;
112        target.extend(value.to_be_bytes());
113        VALUE_2B
114    }
115}
116
117/// Encode delta and length into a small returned buffer
118///
119/// This function performs all of the option encoding steps except handling the actual data value.
120///
121/// Typical use
122/// -----------
123///
124/// ```
125/// # use std::convert::TryInto;
126/// # use coap_message_implementations::option_extension::encode_extensions;
127///
128/// let mut last_option = 11u16;
129/// let mut buffer = heapless::Vec::<u8, 20>::new();
130///
131/// let option = 11u16;
132/// let delta = option - last_option;
133/// let last_option = option;
134/// let data = b"core";
135///
136/// buffer.extend_from_slice(encode_extensions(
137///         delta,
138///         data.len().try_into().expect("Too long to express"),
139///         ).as_ref()).expect("Buffer too short");
140/// buffer.extend_from_slice(data).expect("Buffer too short");
141///
142/// assert!(buffer == b"\x04core");
143///
144/// ```
145#[inline]
146pub fn encode_extensions(delta: u16, length: u16) -> impl AsRef<[u8]> {
147    let mut ret = heapless::Vec::<u8, 5>::new();
148    ret.push(0).expect("Vector is sufficiently large");
149    ret[0] |= push_extensions(&mut ret, delta) << 4;
150    ret[0] |= push_extensions(&mut ret, length);
151
152    ret
153}