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}