coap_message_utils/
option_value.rs

1//! Traits and types that represent serializations of options in encoded options
2
3use coap_message::{MessageOption, ReadableMessage};
4use coap_numbers::option;
5
6/// A trait semantically similar to TryFrom, but rather than being generic over the source, this
7/// trait is specialized in being from any impl of MessageOption.
8///
9/// Types that implement this implicitly encode an extra piece of information, their option number:
10/// Options passed in that don't have a matching number need to be ignored with a `None` return
11/// value.
12///
13/// In passing, we also introduce a lifetime for the option (we work `from(&O)` instead of
14/// `from(O)`) because this is always used with data copied out (as the MessageOption doesn't allow
15/// long-term references into it anyway).
16///
17/// This uses an Option and not a Result, because the main way in which it is used is through
18/// `take_into`, which leaves unprocessable options in the stream for later failing when critical
19/// options are rejected. This pattern of usage also means that non-critical options that should
20/// cause errors need to implement `TryFromOption for Result<Good, Bad>`, and raise an error of
21/// their own when `Some(Bad(_))` is found.
22pub trait TryFromOption: Sized {
23    fn try_from(value: &impl MessageOption) -> Option<Self>;
24}
25
26/// The information model for block options when used in a way that the M flag does not matter.
27///
28/// In serialization, the M bit is unset and ignored (RFC7959: "MUST be set as zero and ignored on
29/// reception").
30struct BlockDataWithoutM {
31    // Validity constraint: using only up to 20 bit
32    blknum: u32,
33    szx: u8,
34}
35
36/// The information model for block options when used in a way that the M flag *does* matter.
37struct BlockDataWithM {
38    coordinates: BlockDataWithoutM,
39    m: bool,
40}
41
42impl core::ops::Deref for BlockDataWithM {
43    type Target = BlockDataWithoutM;
44    fn deref(&self) -> &BlockDataWithoutM {
45        &self.coordinates
46    }
47}
48
49/// Block1 option data (as used in either the request or the response)
50pub struct Block1Data(BlockDataWithM);
51
52/// Request data from a Block2 request
53///
54/// As the M flag is unused in requests, it is not captured in here (and ignored at construction).
55pub struct Block2RequestData(BlockDataWithoutM);
56
57/// Error that occurs when constructing a `Block2RequestData` from a message or an option.
58///
59/// It is singular and contains no details (for there is no usable action from them), but usually
60/// stems from either the option being repeated or having an excessively large value.
61#[derive(Debug)]
62pub struct BadBlock2Option;
63
64const M_BIT: u32 = 0x08;
65const SZX_MASK: u32 = 0x07;
66// Block options are only up to 3 bytes long
67const BLOCK_MAX: u32 = 0xffffff;
68
69impl BlockDataWithoutM {
70    fn from_u32(o: u32) -> Option<Self> {
71        if o > BLOCK_MAX {
72            return None;
73        }
74        Some(Self {
75            szx: (o & SZX_MASK) as u8,
76            blknum: o >> 4,
77        })
78    }
79
80    fn to_u32(&self) -> u32 {
81        (self.blknum << 4) | self.szx as u32
82    }
83
84    /// Size of a single block
85    pub fn size(&self) -> u16 {
86        1 << (4 + self.szx)
87    }
88
89    /// Number of bytes before the indicated block
90    pub fn start(&self) -> u32 {
91        self.size() as u32 * self.blknum
92    }
93}
94
95impl BlockDataWithM {
96    fn from_u32(o: u32) -> Option<Self> {
97        Some(Self {
98            coordinates: BlockDataWithoutM::from_u32(o)?,
99            m: o & M_BIT != 0,
100        })
101    }
102
103    fn to_u32(&self) -> u32 {
104        self.coordinates.to_u32() | if self.m { M_BIT } else { 0 }
105    }
106}
107
108impl Block2RequestData {
109    /// Extract a request block 2 value from a request message.
110    ///
111    /// Absence of the option is not an error and results in the default value to be returned;
112    /// exceeding length or duplicate entries are an error and are indicated by returning an error,
113    /// which should be responded to with a Bad Option error.
114    pub fn from_message(message: &impl ReadableMessage) -> Result<Self, BadBlock2Option> {
115        let mut b2options = message.options().filter(|o| o.number() == option::BLOCK2);
116
117        match b2options.next() {
118            None => Ok(Self::default()),
119            Some(o) => {
120                if b2options.next().is_none() {
121                    Self::from_option(&o)
122                } else {
123                    Err(BadBlock2Option)
124                }
125            }
126        }
127    }
128
129    /// Extract a request block 2 value from a single option. An error is indicated on a malformed
130    /// (ie. overly long) option.
131    ///
132    /// Compared to [Block2RequestData::from_message()], this can easily be packed into a single
133    /// loop that processes all options and fails on unknown critical ones; on the other hand, this
134    /// does not automate the check for duplicate options.
135    ///
136    /// # Panics
137    ///
138    /// In debug mode if the option is not Block2
139    pub fn from_option(option: &impl MessageOption) -> Result<Self, BadBlock2Option> {
140        debug_assert!(option.number() == option::BLOCK2);
141        let o: u32 = option.value_uint().ok_or(BadBlock2Option)?;
142        BlockDataWithoutM::from_u32(o)
143            .map(Self)
144            .ok_or(BadBlock2Option)
145    }
146
147    pub fn to_option_value(&self, more: bool) -> u32 {
148        self.0.to_u32() | if more { 0x08 } else { 0 }
149    }
150
151    /// Size of a single block
152    pub fn size(&self) -> u16 {
153        self.0.size()
154    }
155
156    /// Number of bytes before the indicated block
157    pub fn start(&self) -> usize {
158        self.0.start() as _
159    }
160
161    /// Return a block that has identical .start(), but a block size smaller or equal to the given
162    /// one.
163    ///
164    /// Returns None if the given size is not expressible as a CoAP block (ie. is less than 16).
165    pub fn shrink(mut self, size: u16) -> Option<Self> {
166        while self.size() > size {
167            if self.0.szx == 0 {
168                return None;
169            }
170            self.0.szx -= 1;
171            self.0.blknum *= 2;
172        }
173        Some(self)
174    }
175}
176
177impl Block1Data {
178    /// Number of bytes before the indicated block
179    pub fn start(&self) -> usize {
180        self.0.start() as _
181    }
182
183    pub fn more(&self) -> bool {
184        self.0.m
185    }
186
187    pub fn to_option_value(&self) -> u32 {
188        self.0.to_u32()
189    }
190}
191
192impl Default for Block2RequestData {
193    fn default() -> Self {
194        Self(BlockDataWithoutM { szx: 6, blknum: 0 })
195    }
196}
197
198impl TryFromOption for Block2RequestData {
199    fn try_from(value: &impl MessageOption) -> Option<Self> {
200        if value.number() != coap_numbers::option::BLOCK2 {
201            return None;
202        }
203
204        Self::from_option(value).ok()
205    }
206}
207
208impl TryFromOption for Block1Data {
209    fn try_from(o: &impl MessageOption) -> Option<Self> {
210        if o.number() != coap_numbers::option::BLOCK1 {
211            return None;
212        }
213
214        Some(Self(BlockDataWithM::from_u32(o.value_uint()?)?))
215    }
216}