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, thiserror::Error)]
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 #[error("reserved value 15 present")]
26 Reserved15,
27 /// The remaining message did not contain the necessary bytes to read the next.
28 #[error("premature end of message")]
29 OutOfBytes,
30 /// The encoded value exceeds the encodable maximum.
31 ///
32 /// While the encoding itself can represent values up to `2^16 + OFFSET_2B`, all practical uses
33 /// are capped to `2^16` anyway, allowing the value to be stored in a [u16] and to err outside
34 /// of that.
35 #[error("delta overflow")]
36 Overflow,
37}
38
39/// Decode an extended value.
40///
41/// Given a value that was initially extracted from an Option Delta or Option Length nibble of an
42/// option, read up to 2 extended bytes from the slice that contains the remaining data, and
43/// advance the slice by that amount.
44///
45/// This returns `Some(())` on success, or None to indicate any error condition (which may be
46/// exhaustion of the slice, the presence of the value 15, or an overflow induced by an
47/// almost-maximum value in the 2-byte extension
48///
49/// Typical use
50/// -----------
51///
52/// ```
53/// # use coap_message_implementations::option_extension::take_extension;
54/// let data = b"\xbd\x01long-path-name\xff";
55///
56/// let nibble = data[0];
57/// let mut delta = (nibble >> 4).into();
58/// let mut length = (nibble & 0x0f).into();
59///
60/// let mut view = &data[1..];
61/// // Delta is 11; this does not modify anything
62/// take_extension(&mut delta, &mut view).unwrap();
63/// // Length is 13 + 1; this takes one byte off view
64/// take_extension(&mut length, &mut view).unwrap();
65///
66/// assert!(delta == 11);
67/// assert!(length == 14);
68/// assert!(&view[..14] == b"long-path-name");
69/// ```
70///
71/// # Errors
72///
73/// … indicate that the message is not well-formed.
74///
75/// # Panics
76///
77/// … when the initial `*value` exceeds the possible values of a nibble.
78pub fn take_extension(value: &mut u16, slice: &mut &[u8]) -> Result<(), ExtensionError> {
79 assert!(*value < 16);
80 match *value as u8 {
81 VALUE_RESERVED => Err(ExtensionError::Reserved15),
82 VALUE_2B => {
83 *value = u16::from_be_bytes(
84 slice
85 .get(..2)
86 .ok_or(ExtensionError::OutOfBytes)?
87 .try_into()
88 .expect("Slice is 2-long by construction"),
89 )
90 .checked_add(OFFSET_2B)
91 .ok_or(ExtensionError::Overflow)?;
92 *slice = &slice[2..];
93 Ok(())
94 }
95 VALUE_1B => {
96 *value = OFFSET_1B + u16::from(*slice.first().ok_or(ExtensionError::OutOfBytes)?);
97 *slice = &slice[1..];
98 Ok(())
99 }
100 _ => Ok(()),
101 }
102}
103
104/// Write an extended option delta or length value into a vector.
105///
106/// The resulting nibble then needs to be shifted and stored in the respective half the previously
107/// pushed value.
108///
109/// While this can be used to push the extension data into a
110/// [`heapless::Vec`](https://docs.rs/heapless/latest/heapless/struct.Vec.html), beware that
111/// heapless indicates its error condition by panicking. Before calling `push_extensions` on such a
112/// target, the caller may want to check whether 2 more bytes (the maximum this function appends)
113/// are still free.
114fn push_extensions(target: &mut impl core::iter::Extend<u8>, value: u16) -> u8 {
115 if value < OFFSET_1B {
116 value as u8
117 } else if value < OFFSET_2B {
118 let value = (value - OFFSET_1B) as u8;
119 target.extend([value]);
120 VALUE_1B
121 } else {
122 let value = value - OFFSET_2B;
123 target.extend(value.to_be_bytes());
124 VALUE_2B
125 }
126}
127
128/// Encode delta and length into a small returned buffer
129///
130/// This function performs all of the option encoding steps except handling the actual data value.
131///
132/// Typical use
133/// -----------
134///
135/// ```
136/// # use std::convert::TryInto;
137/// # use coap_message_implementations::option_extension::encode_extensions;
138///
139/// let mut last_option = 11u16;
140/// let mut buffer = heapless::Vec::<u8, 20>::new();
141///
142/// let option = 11u16;
143/// let delta = option - last_option;
144/// let last_option = option;
145/// let data = b"core";
146///
147/// buffer.extend_from_slice(encode_extensions(
148/// delta,
149/// data.len().try_into().expect("Too long to express"),
150/// ).as_ref()).expect("Buffer too short");
151/// buffer.extend_from_slice(data).expect("Buffer too short");
152///
153/// assert!(buffer == b"\x04core");
154///
155/// ```
156#[inline]
157#[must_use]
158#[allow(
159 clippy::missing_panics_doc,
160 reason = "length based buffers can not trigger due to sufficient buffer size"
161)]
162pub fn encode_extensions(delta: u16, length: u16) -> impl AsRef<[u8]> {
163 let mut ret = heapless::Vec::<u8, 5>::new();
164 ret.push(0).expect("Vector is sufficiently large");
165 ret[0] |= push_extensions(&mut ret, delta) << 4;
166 ret[0] |= push_extensions(&mut ret, length);
167
168 ret
169}
170
171/// Given the first byte of an encoded option, returns the length of the option header (i.e., any
172/// value from 1 for the most compact options, up to 5 for 16-bit deltas and lengths).
173pub(crate) fn option_header_len(header: u8) -> u8 {
174 let mut res = 1;
175 match header >> 4 {
176 VALUE_2B => res += 2,
177 VALUE_1B => res += 1,
178 _ => (),
179 }
180 match header & 0b1111 {
181 VALUE_2B => res += 2,
182 VALUE_1B => res += 1,
183 _ => (),
184 }
185 res
186}