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
}