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/// ```
70pub fn take_extension(value: &mut u16, slice: &mut &[u8]) -> Result<(), ExtensionError> {
71 assert!(*value < 16);
72 match *value as u8 {
73 VALUE_RESERVED => Err(ExtensionError::Reserved15),
74 VALUE_2B => {
75 *value = u16::from_be_bytes(
76 slice
77 .get(..2)
78 .ok_or(ExtensionError::OutOfBytes)?
79 .try_into()
80 .expect("Slice is 2-long by construction"),
81 )
82 .checked_add(OFFSET_2B)
83 .ok_or(ExtensionError::Overflow)?;
84 *slice = &slice[2..];
85 Ok(())
86 }
87 VALUE_1B => {
88 *value = OFFSET_1B + u16::from(*slice.first().ok_or(ExtensionError::OutOfBytes)?);
89 *slice = &slice[1..];
90 Ok(())
91 }
92 _ => Ok(()),
93 }
94}
95
96/// Write an extended option delta or length value into a vector.
97///
98/// The resulting nibble then needs to be shifted and stored in the respective half the previously
99/// pushed value.
100///
101/// While this can be used to push the extension data into a
102/// [`heapless::Vec`](https://docs.rs/heapless/latest/heapless/struct.Vec.html), beware that
103/// heapless indicates its error condition by panicking. Before calling push_extensions on such a
104/// target, the caller may want to check whether 2 more bytes (the maximum this function appends)
105/// are still free.
106fn push_extensions(target: &mut impl core::iter::Extend<u8>, value: u16) -> u8 {
107 if value < OFFSET_1B {
108 value as u8
109 } else if value < OFFSET_2B {
110 let value = (value - OFFSET_1B) as u8;
111 target.extend([value]);
112 VALUE_1B
113 } else {
114 let value = value - OFFSET_2B;
115 target.extend(value.to_be_bytes());
116 VALUE_2B
117 }
118}
119
120/// Encode delta and length into a small returned buffer
121///
122/// This function performs all of the option encoding steps except handling the actual data value.
123///
124/// Typical use
125/// -----------
126///
127/// ```
128/// # use std::convert::TryInto;
129/// # use coap_message_implementations::option_extension::encode_extensions;
130///
131/// let mut last_option = 11u16;
132/// let mut buffer = heapless::Vec::<u8, 20>::new();
133///
134/// let option = 11u16;
135/// let delta = option - last_option;
136/// let last_option = option;
137/// let data = b"core";
138///
139/// buffer.extend_from_slice(encode_extensions(
140/// delta,
141/// data.len().try_into().expect("Too long to express"),
142/// ).as_ref()).expect("Buffer too short");
143/// buffer.extend_from_slice(data).expect("Buffer too short");
144///
145/// assert!(buffer == b"\x04core");
146///
147/// ```
148#[inline]
149pub fn encode_extensions(delta: u16, length: u16) -> impl AsRef<[u8]> {
150 let mut ret = heapless::Vec::<u8, 5>::new();
151 ret.push(0).expect("Vector is sufficiently large");
152 ret[0] |= push_extensions(&mut ret, delta) << 4;
153 ret[0] |= push_extensions(&mut ret, length);
154
155 ret
156}