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