use bondrewd::Bitfields;
use core::slice::SliceIndex;
use super::encoded_message::EncodedMessage;
mod encoded_value;
use encoded_value::*;
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u16)]
pub enum CoapOptionName {
IfMatch = 1,
UriHost = 3,
Etag = 4,
IfNoneMatch = 5,
Observe = 6,
UriPort = 7,
LocationPath = 8,
UriPath = 11,
ContentFormat = 12,
MaxAge = 14,
UriQuery = 15,
Accept = 17,
LocationQuery = 20,
ProxyUri = 35,
ProxyScheme = 39,
Size1 = 60,
}
impl TryFrom<u16> for CoapOptionName {
type Error = Error;
fn try_from(val: u16) -> Result<Self, Self::Error> {
let option = match val {
1 => CoapOptionName::IfMatch,
3 => CoapOptionName::UriHost,
4 => CoapOptionName::Etag,
5 => CoapOptionName::IfNoneMatch,
6 => CoapOptionName::Observe,
7 => CoapOptionName::UriPort,
8 => CoapOptionName::LocationPath,
11 => CoapOptionName::UriPath,
12 => CoapOptionName::ContentFormat,
14 => CoapOptionName::MaxAge,
15 => CoapOptionName::UriQuery,
17 => CoapOptionName::Accept,
20 => CoapOptionName::LocationQuery,
35 => CoapOptionName::ProxyUri,
39 => CoapOptionName::ProxyScheme,
60 => CoapOptionName::Size1,
_ => return Err(Error::InvalidOption),
};
Ok(option)
}
}
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
ParseDelta,
ParseLength,
OutOfBoundsIndexing,
InvalidOption,
}
#[derive(Bitfields, Debug, PartialEq, Eq)]
#[bondrewd(enforce_bytes = 1)]
struct OptionHeader {
#[bondrewd(bit_length = 4)]
pub delta: u8,
#[bondrewd(bit_length = 4)]
pub length: u8,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct CoapOption<'value> {
pub name: CoapOptionName,
pub value: &'value [u8],
}
impl core::cmp::Ord for CoapOption<'_> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.name.cmp(&other.name)
}
}
impl core::cmp::PartialOrd for CoapOption<'_> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
pub fn sort_options_vec<const MAX_OPTION_COUNT: usize>(
mut options: heapless::Vec<CoapOption<'_>, MAX_OPTION_COUNT>,
) -> heapless::Vec<CoapOption<'_>, MAX_OPTION_COUNT> {
for i in 0..options.len() {
for j in 0..options.len() - 1 - i {
if options[j] > options[j + 1] {
options.swap(j, j + 1);
}
}
}
options
}
pub fn encode_to_buf(buf: &mut [u8], options: &[CoapOption<'_>]) -> Result<usize, super::Error> {
let mut index = 0;
let mut previous_number = 0;
for option in options {
let delta = option.name as u16 - previous_number;
let length = option.value.len();
let delta = EncodedValue::encode(delta);
let length = EncodedValue::encode(length as u16);
let header = OptionHeader {
delta: delta.header_value(),
length: length.header_value(),
};
let header = header.into_bytes();
for x in header {
*buf.get_mut(index).ok_or(super::Error::OutOfMemory)? = x;
index += 1;
}
index += delta.write_into(&mut buf[index..]);
index += length.write_into(&mut buf[index..]);
for x in option.value {
*buf.get_mut(index).ok_or(super::Error::OutOfMemory)? = *x;
index += 1;
}
previous_number = option.name as u16;
}
Ok(index)
}
pub struct OptionIterator<'encmsg, 'msgbuf> {
message: &'encmsg EncodedMessage<'msgbuf>,
pub(crate) next_start_index: usize,
previous_number: u16,
already_errored: bool,
}
impl<'encmsg, 'msgbuf> OptionIterator<'encmsg, 'msgbuf> {
pub fn new(message: &'encmsg EncodedMessage<'msgbuf>, next_start_index: usize) -> Self {
Self {
message,
next_start_index,
previous_number: 0,
already_errored: false,
}
}
fn access<I>(&self, index: I) -> Result<&'msgbuf <I as SliceIndex<[u8]>>::Output, Error>
where
I: SliceIndex<[u8]>,
{
self.message
.data
.get(index)
.ok_or(Error::OutOfBoundsIndexing)
}
fn try_next(&mut self) -> Result<CoapOption<'msgbuf>, Error> {
let start_index = self.next_start_index;
let mut header = [0_u8; OptionHeader::BYTE_SIZE];
header.clone_from_slice(self.access(start_index..start_index + OptionHeader::BYTE_SIZE)?);
let header = OptionHeader::from_bytes(header);
let OptionHeader { delta, length } = header;
let start_index = start_index + OptionHeader::BYTE_SIZE;
let (delta, length_offset) = if delta <= 12 {
(delta as u16, 0)
} else if delta == 13 {
(*self.access(start_index)? as u16 + 13, 1)
} else if delta == 14 {
let mut buf = [0_u8; 2];
buf.clone_from_slice(self.access(start_index..start_index + 2)?);
(u16::from_be_bytes(buf) + 269, 2)
} else {
return Err(Error::ParseDelta);
};
let start_index = start_index + length_offset;
let (length, value_offset) = if length <= 12 {
(length as u16, 0)
} else if length == 13 {
(*self.access(start_index)? as u16 + 13, 1)
} else if length == 14 {
let mut buf = [0_u8; 2];
buf.clone_from_slice(self.access(start_index..start_index + 2)?);
(u16::from_be_bytes(buf) + 269, 2)
} else {
return Err(Error::ParseLength);
};
self.previous_number += delta;
let start_index = start_index + value_offset;
let value = self.access(start_index..start_index + length as usize)?;
self.next_start_index = start_index + length as usize;
Ok(CoapOption {
name: self.previous_number.try_into()?,
value,
})
}
}
impl<'encmsg, 'msgbuf> Iterator for OptionIterator<'encmsg, 'msgbuf> {
type Item = Result<CoapOption<'msgbuf>, Error>;
fn next(&mut self) -> Option<Self::Item> {
let next_byte = match self.message.data.get(self.next_start_index) {
Some(b) => *b,
None => {
self.message.set_payload_offset(self.next_start_index);
return None;
}
};
if next_byte == 0xFF {
self.message.set_payload_offset(self.next_start_index);
return None;
}
match self.try_next() {
Ok(o) => Some(Ok(o)),
Err(_) if self.already_errored => None,
Err(e) => {
self.already_errored = true;
Some(Err(e))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::message::encoded_message::EncodedMessage;
#[test]
fn empty() {
let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0xFF]).unwrap();
let mut iter = OptionIterator::new(&msg, 4);
assert!(iter.next().is_none());
}
#[test]
fn one_option() {
let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0x41, 0x11]).unwrap();
let mut iter = OptionIterator::new(&msg, 4);
let opt = iter.next().unwrap().expect("one option expected");
assert_eq!(opt.name, CoapOptionName::Etag);
assert_eq!(opt.value.len(), 1);
assert_eq!(opt.value[0], 0x11);
assert!(iter.next().is_none());
let msg: EncodedMessage<'_> =
EncodedMessage::try_new(&[0, 0, 0, 0, 0x41, 0x11, 0xFF, 0x12, 0x34]).unwrap();
let mut iter = OptionIterator::new(&msg, 4);
let opt = iter.next().unwrap().expect("one option expected");
assert_eq!(opt.name, CoapOptionName::Etag);
assert_eq!(opt.value.len(), 1);
assert_eq!(opt.value[0], 0x11);
assert!(iter.next().is_none());
}
#[test]
fn two_options() {
let msg: EncodedMessage<'_> =
EncodedMessage::try_new(&[0, 0, 0, 0, 0x41, 0x11, 0x12, 0x11, 0x22]).unwrap();
let mut iter = OptionIterator::new(&msg, 4);
let opt = iter.next().unwrap().expect("one option expected");
assert_eq!(opt.name, CoapOptionName::Etag);
assert_eq!(opt.value.len(), 1);
assert_eq!(opt.value[0], 0x11);
let opt = iter.next().unwrap().expect("another option expected");
assert_eq!(opt.name, CoapOptionName::IfNoneMatch);
assert_eq!(opt.value.len(), 2);
assert_eq!(&opt.value[..], &[0x11, 0x22]);
assert!(iter.next().is_none());
}
#[test]
fn extended_delta_and_length() {
let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0xD0, 0x04]).unwrap();
let mut iter = OptionIterator::new(&msg, 4);
let opt = iter.next().unwrap().expect("one option expected");
assert_eq!(opt.name, CoapOptionName::Accept);
let msg: EncodedMessage<'_> =
EncodedMessage::try_new(&[0, 0, 0, 0, 0xE0, 0x00, 0x04]).unwrap();
let mut iter = OptionIterator::new(&msg, 4);
let opt = iter.next().unwrap();
assert!(matches!(opt, Err(Error::InvalidOption)));
let mut data = [0; 30];
data[0..2].copy_from_slice(&[0xBD, 0x04]);
let msg: EncodedMessage<'_> = EncodedMessage::try_new(&data).unwrap();
let mut iter = OptionIterator::new(&msg, 0);
let opt = iter.next().unwrap().expect("one option expected");
assert_eq!(opt.value.len(), 17);
let mut data = [0; 300];
data[0..3].copy_from_slice(&[0xBE, 0x00, 0x04]);
let msg: EncodedMessage<'_> = EncodedMessage::try_new(&data).unwrap();
let mut iter = OptionIterator::new(&msg, 0);
let opt = iter.next().unwrap().expect("one option expected");
assert_eq!(opt.value.len(), 273);
let mut data = [0; 30];
data[0..3].copy_from_slice(&[0xDD, 0x2F, 0x04]);
let msg: EncodedMessage<'_> = EncodedMessage::try_new(&data).unwrap();
let mut iter = OptionIterator::new(&msg, 0);
let opt = iter.next().unwrap().expect("one option expected");
assert!(matches!(opt.name, CoapOptionName::Size1));
assert_eq!(opt.value.len(), 17);
}
#[test]
fn missing_bytes() {
let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0x41]).unwrap();
let mut iter = OptionIterator::new(&msg, 4);
assert!(iter.next().unwrap().is_err());
let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0xD0]).unwrap();
let mut iter = OptionIterator::new(&msg, 4);
assert!(iter.next().unwrap().is_err());
}
#[test]
fn wrong_delta_or_length() {
let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0xF1]).unwrap();
let mut iter = OptionIterator::new(&msg, 4);
assert!(iter.next().unwrap().is_err());
let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0x4F]).unwrap();
let mut iter = OptionIterator::new(&msg, 4);
assert!(iter.next().unwrap().is_err());
}
#[test]
fn next_after_err_is_none() {
let msg: EncodedMessage<'_> = EncodedMessage::try_new(&[0, 0, 0, 0, 0xF1]).unwrap();
let mut iter = OptionIterator::new(&msg, 4);
assert!(iter.next().unwrap().is_err());
assert!(iter.next().is_none());
}
#[test]
fn sort_options() {
use CoapOptionName::*;
sorted_options(&[IfMatch, UriHost, Etag, IfNoneMatch]).unwrap();
sorted_options(&[IfMatch, IfMatch, IfMatch, IfMatch]).unwrap();
sorted_options(&[IfNoneMatch, Etag, UriHost, IfMatch]).unwrap();
sorted_options(&[IfMatch, UriHost, Etag, Etag]).unwrap();
sorted_options(&[IfMatch, UriHost, IfNoneMatch, Etag]).unwrap();
sorted_options(&[UriHost, IfMatch, IfNoneMatch, Etag]).unwrap();
}
fn sorted_options(option_names: &[CoapOptionName]) -> Result<(), &'static str> {
let mut options = heapless::Vec::new();
let values = b"abcdefghijklmnopqrstuvw";
for (i, name) in option_names.iter().enumerate() {
options
.push(CoapOption {
name: *name,
value: &values[i..i + 1],
})
.unwrap();
}
let options = sort_options_vec(options);
is_sorted(options)
}
fn is_sorted(options: heapless::Vec<CoapOption<'_>, 32>) -> Result<(), &'static str> {
for items in options.windows(2) {
if items[0] > items[1] {
return Err("not sorted");
}
if items[0].cmp(&items[1]) == std::cmp::Ordering::Equal
&& items[0].value[0] >= items[1].value[0]
{
return Err("not stable");
}
}
Ok(())
}
}