coap_message_utils/option_value.rs
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 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
//! Traits and types that represent serializations of options in encoded options
use coap_message::{MessageOption, ReadableMessage};
use coap_numbers::option;
/// A trait semantically similar to TryFrom, but rather than being generic over the source, this
/// trait is specialized in being from any impl of MessageOption.
///
/// Types that implement this implicitly encode an extra piece of information, their option number:
/// Options passed in that don't have a matching number need to be ignored with a `None` return
/// value.
///
/// In passing, we also introduce a lifetime for the option (we work `from(&O)` instead of
/// `from(O)`) because this is always used with data copied out (as the MessageOption doesn't allow
/// long-term references into it anyway).
///
/// This uses an Option and not a Result, because the main way in which it is used is through
/// `take_into`, which leaves unprocessable options in the stream for later failing when critical
/// options are rejected. This pattern of usage also means that non-critical options that should
/// cause errors need to implement `TryFromOption for Result<Good, Bad>`, and raise an error of
/// their own when `Some(Bad(_))` is found.
pub trait TryFromOption: Sized {
fn try_from(value: &impl MessageOption) -> Option<Self>;
}
/// The information model for block options when used in a way that the M flag does not matter.
///
/// In serialization, the M bit is unset and ignored (RFC7959: "MUST be set as zero and ignored on
/// reception").
struct BlockDataWithoutM {
// Validity constraint: using only up to 20 bit
blknum: u32,
szx: u8,
}
/// The information model for block options when used in a way that the M flag *does* matter.
struct BlockDataWithM {
coordinates: BlockDataWithoutM,
m: bool,
}
impl core::ops::Deref for BlockDataWithM {
type Target = BlockDataWithoutM;
fn deref(&self) -> &BlockDataWithoutM {
&self.coordinates
}
}
/// Block1 option data (as used in either the request or the response)
pub struct Block1Data(BlockDataWithM);
/// Request data from a Block2 request
///
/// As the M flag is unused in requests, it is not captured in here (and ignored at construction).
pub struct Block2RequestData(BlockDataWithoutM);
/// Error that occurs when constructing a `Block2RequestData` from a message or an option.
///
/// It is singular and contains no details (for there is no usable action from them), but usually
/// stems from either the option being repeated or having an excessively large value.
#[derive(Debug)]
pub struct BadBlock2Option;
const M_BIT: u32 = 0x08;
const SZX_MASK: u32 = 0x07;
// Block options are only up to 3 bytes long
const BLOCK_MAX: u32 = 0xffffff;
impl BlockDataWithoutM {
fn from_u32(o: u32) -> Option<Self> {
if o > BLOCK_MAX {
return None;
}
Some(Self {
szx: (o & SZX_MASK) as u8,
blknum: o >> 4,
})
}
fn to_u32(&self) -> u32 {
(self.blknum << 4) | self.szx as u32
}
/// Size of a single block
pub fn size(&self) -> u16 {
1 << (4 + self.szx)
}
/// Number of bytes before the indicated block
pub fn start(&self) -> u32 {
self.size() as u32 * self.blknum
}
}
impl BlockDataWithM {
fn from_u32(o: u32) -> Option<Self> {
Some(Self {
coordinates: BlockDataWithoutM::from_u32(o)?,
m: o & M_BIT != 0,
})
}
fn to_u32(&self) -> u32 {
self.coordinates.to_u32() | if self.m { M_BIT } else { 0 }
}
}
impl Block2RequestData {
/// Extract a request block 2 value from a request message.
///
/// Absence of the option is not an error and results in the default value to be returned;
/// exceeding length or duplicate entries are an error and are indicated by returning an error,
/// which should be responded to with a Bad Option error.
pub fn from_message(message: &impl ReadableMessage) -> Result<Self, BadBlock2Option> {
let mut b2options = message.options().filter(|o| o.number() == option::BLOCK2);
match b2options.next() {
None => Ok(Self::default()),
Some(o) => {
if b2options.next().is_none() {
Self::from_option(&o)
} else {
Err(BadBlock2Option)
}
}
}
}
/// Extract a request block 2 value from a single option. An error is indicated on a malformed
/// (ie. overly long) option.
///
/// Compared to [Block2RequestData::from_message()], this can easily be packed into a single
/// loop that processes all options and fails on unknown critical ones; on the other hand, this
/// does not automate the check for duplicate options.
///
/// # Panics
///
/// In debug mode if the option is not Block2
pub fn from_option(option: &impl MessageOption) -> Result<Self, BadBlock2Option> {
debug_assert!(option.number() == option::BLOCK2);
let o: u32 = option.value_uint().ok_or(BadBlock2Option)?;
BlockDataWithoutM::from_u32(o)
.map(Self)
.ok_or(BadBlock2Option)
}
pub fn to_option_value(&self, more: bool) -> u32 {
self.0.to_u32() | if more { 0x08 } else { 0 }
}
/// Size of a single block
pub fn size(&self) -> u16 {
self.0.size()
}
/// Number of bytes before the indicated block
pub fn start(&self) -> usize {
self.0.start() as _
}
/// Return a block that has identical .start(), but a block size smaller or equal to the given
/// one.
///
/// Returns None if the given size is not expressible as a CoAP block (ie. is less than 16).
pub fn shrink(mut self, size: u16) -> Option<Self> {
while self.size() > size {
if self.0.szx == 0 {
return None;
}
self.0.szx -= 1;
self.0.blknum *= 2;
}
Some(self)
}
}
impl Block1Data {
/// Number of bytes before the indicated block
pub fn start(&self) -> usize {
self.0.start() as _
}
pub fn more(&self) -> bool {
self.0.m
}
pub fn to_option_value(&self) -> u32 {
self.0.to_u32()
}
}
impl Default for Block2RequestData {
fn default() -> Self {
Self(BlockDataWithoutM { szx: 6, blknum: 0 })
}
}
impl TryFromOption for Block2RequestData {
fn try_from(value: &impl MessageOption) -> Option<Self> {
if value.number() != coap_numbers::option::BLOCK2 {
return None;
}
Self::from_option(value).ok()
}
}
impl TryFromOption for Block1Data {
fn try_from(o: &impl MessageOption) -> Option<Self> {
if o.number() != coap_numbers::option::BLOCK1 {
return None;
}
Some(Self(BlockDataWithM::from_u32(o.value_uint()?)?))
}
}