async_coap/option/
num.rs

1// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15
16use super::*;
17
18/// Type representing a CoAP option number.
19#[derive(Copy, Eq, PartialEq, Hash, Clone, Ord, PartialOrd)]
20pub struct OptionNumber(pub u16);
21
22impl OptionNumber {
23    /// IF_MATCH option.
24    pub const IF_MATCH: OptionNumber = OptionNumber(1);
25
26    /// URI_HOST option.
27    pub const URI_HOST: OptionNumber = OptionNumber(3);
28
29    /// ETAG option.
30    pub const ETAG: OptionNumber = OptionNumber(4);
31
32    /// IF_NONE_MATCH option.
33    pub const IF_NONE_MATCH: OptionNumber = OptionNumber(5);
34
35    /// OBSERVE option.
36    pub const OBSERVE: OptionNumber = OptionNumber(6);
37
38    /// URI_PORT option.
39    pub const URI_PORT: OptionNumber = OptionNumber(7);
40
41    /// LOCATION_PATH option.
42    pub const LOCATION_PATH: OptionNumber = OptionNumber(8);
43
44    /// OSCORE option.
45    pub const OSCORE: OptionNumber = OptionNumber(9);
46
47    /// URI_PATH option.
48    pub const URI_PATH: OptionNumber = OptionNumber(11);
49
50    /// CONTENT_FORMAT option.
51    pub const CONTENT_FORMAT: OptionNumber = OptionNumber(12);
52
53    /// MAX_AGE option.
54    pub const MAX_AGE: OptionNumber = OptionNumber(14);
55
56    /// URI_QUERY option.
57    pub const URI_QUERY: OptionNumber = OptionNumber(15);
58
59    /// ACCEPT option.
60    pub const ACCEPT: OptionNumber = OptionNumber(17);
61
62    /// LOCATION_QUERY option.
63    pub const LOCATION_QUERY: OptionNumber = OptionNumber(20);
64
65    /// BLOCK2 option.
66    pub const BLOCK2: OptionNumber = OptionNumber(23);
67
68    /// BLOCK1 option.
69    pub const BLOCK1: OptionNumber = OptionNumber(27);
70
71    /// SIZE2 option.
72    pub const SIZE2: OptionNumber = OptionNumber(28);
73
74    /// PROXY_URI option.
75    pub const PROXY_URI: OptionNumber = OptionNumber(35);
76
77    /// PROXY_SCHEME option.
78    pub const PROXY_SCHEME: OptionNumber = OptionNumber(39);
79
80    /// SIZE1 option.
81    pub const SIZE1: OptionNumber = OptionNumber(60);
82
83    /// NO_RESPONSE option.
84    pub const NO_RESPONSE: OptionNumber = OptionNumber(258);
85
86    /// Returns true if this option number is critical, false if it is optional.
87    pub fn is_critical(self) -> bool {
88        const FLAG_CRITICAL: u16 = 1;
89        self.0 & FLAG_CRITICAL == FLAG_CRITICAL
90    }
91
92    /// Returns true if this option is "un-safe".
93    pub fn is_un_safe(self) -> bool {
94        const FLAG_UN_SAFE: u16 = 2;
95        self.0 & FLAG_UN_SAFE == FLAG_UN_SAFE
96    }
97
98    /// Returns true if this option is a "no-cache-key" option.
99    pub fn is_no_cache_key(self) -> bool {
100        const FLAG_NO_CACHE_KEY_MASK: u16 = 0x1e;
101        const FLAG_NO_CACHE_KEY_MAGIC: u16 = 0x1c;
102        self.0 & FLAG_NO_CACHE_KEY_MASK == FLAG_NO_CACHE_KEY_MAGIC
103    }
104
105    /// Returns the expected value type for this option number.
106    pub fn option_value_type(self) -> OptionValueType {
107        match self {
108            OptionNumber::IF_MATCH => OptionValueType::Opaque,
109            OptionNumber::URI_HOST => OptionValueType::String,
110            OptionNumber::ETAG => OptionValueType::Opaque,
111            OptionNumber::IF_NONE_MATCH => OptionValueType::Flag,
112            OptionNumber::OBSERVE => OptionValueType::Integer,
113            OptionNumber::URI_PORT => OptionValueType::Integer,
114            OptionNumber::LOCATION_PATH => OptionValueType::String,
115            OptionNumber::OSCORE => OptionValueType::Opaque,
116            OptionNumber::URI_PATH => OptionValueType::String,
117            OptionNumber::CONTENT_FORMAT => OptionValueType::ContentFormat,
118            OptionNumber::MAX_AGE => OptionValueType::Integer,
119            OptionNumber::URI_QUERY => OptionValueType::String,
120            OptionNumber::ACCEPT => OptionValueType::ContentFormat,
121            OptionNumber::LOCATION_QUERY => OptionValueType::String,
122            OptionNumber::BLOCK2 => OptionValueType::Block,
123            OptionNumber::BLOCK1 => OptionValueType::Block,
124            OptionNumber::SIZE2 => OptionValueType::Integer,
125            OptionNumber::PROXY_URI => OptionValueType::String,
126            OptionNumber::PROXY_SCHEME => OptionValueType::String,
127            OptionNumber::SIZE1 => OptionValueType::Integer,
128            OptionNumber::NO_RESPONSE => OptionValueType::Integer,
129            OptionNumber(_) => OptionValueType::Opaque,
130        }
131    }
132
133    /// Returns true if this option is allowed in requests, false if it is prohibited in requests.
134    pub fn is_ok_in_request(self) -> bool {
135        match self {
136            OptionNumber::IF_MATCH => true,
137            OptionNumber::URI_HOST => true,
138            OptionNumber::ETAG => true,
139            OptionNumber::IF_NONE_MATCH => true,
140            OptionNumber::OBSERVE => true,
141            OptionNumber::URI_PORT => true,
142            OptionNumber::LOCATION_PATH => false,
143            OptionNumber::URI_PATH => true,
144            OptionNumber::CONTENT_FORMAT => true,
145            OptionNumber::MAX_AGE => false,
146            OptionNumber::URI_QUERY => true,
147            OptionNumber::ACCEPT => true,
148            OptionNumber::LOCATION_QUERY => false,
149            OptionNumber::BLOCK2 => true,
150            OptionNumber::BLOCK1 => true,
151            OptionNumber::SIZE2 => false,
152            OptionNumber::PROXY_URI => true,
153            OptionNumber::PROXY_SCHEME => true,
154            OptionNumber::SIZE1 => true,
155            OptionNumber::NO_RESPONSE => true,
156
157            // We default to true for unknown options.
158            OptionNumber(_) => true,
159        }
160    }
161
162    /// Returns true if this option is allowed in responses, false if it is prohibited in responses.
163    pub fn is_ok_in_response(self) -> bool {
164        match self {
165            OptionNumber::IF_MATCH => false,
166            OptionNumber::URI_HOST => false,
167            OptionNumber::ETAG => true,
168            OptionNumber::IF_NONE_MATCH => false,
169            OptionNumber::OBSERVE => true,
170            OptionNumber::URI_PORT => false,
171            OptionNumber::LOCATION_PATH => true,
172            OptionNumber::URI_PATH => false,
173            OptionNumber::CONTENT_FORMAT => true,
174            OptionNumber::MAX_AGE => true,
175            OptionNumber::URI_QUERY => false,
176            OptionNumber::ACCEPT => false,
177            OptionNumber::LOCATION_QUERY => true,
178            OptionNumber::BLOCK2 => true,
179            OptionNumber::BLOCK1 => true,
180            OptionNumber::SIZE2 => true,
181            OptionNumber::PROXY_URI => false,
182            OptionNumber::PROXY_SCHEME => false,
183            OptionNumber::SIZE1 => false,
184            OptionNumber::NO_RESPONSE => false,
185
186            // We default to true for unknown options.
187            OptionNumber(_) => true,
188        }
189    }
190
191    /// Returns true if multiple instances of this option are allowed, false if only one instance
192    /// is allowed.
193    pub fn is_repeatable(self) -> bool {
194        match self {
195            OptionNumber::IF_MATCH => true,
196            OptionNumber::URI_HOST => false,
197            OptionNumber::ETAG => true,
198            OptionNumber::IF_NONE_MATCH => false,
199            OptionNumber::OBSERVE => false,
200            OptionNumber::URI_PORT => false,
201            OptionNumber::LOCATION_PATH => true,
202            OptionNumber::URI_PATH => true,
203            OptionNumber::CONTENT_FORMAT => false,
204            OptionNumber::MAX_AGE => false,
205            OptionNumber::URI_QUERY => true,
206            OptionNumber::ACCEPT => false,
207            OptionNumber::LOCATION_QUERY => true,
208            OptionNumber::BLOCK2 => false,
209            OptionNumber::BLOCK1 => false,
210            OptionNumber::SIZE2 => false,
211            OptionNumber::PROXY_URI => false,
212            OptionNumber::PROXY_SCHEME => false,
213            OptionNumber::SIZE1 => false,
214            OptionNumber::NO_RESPONSE => false,
215
216            // We default to true for unknown options.
217            OptionNumber(_) => true,
218        }
219    }
220
221    /// Attempts to return a `Some(&'static str)` containing the name of the option.
222    ///
223    /// If the option number isn't recognized, this method returns `None`.
224    pub fn static_name(self) -> Option<&'static str> {
225        match self {
226            OptionNumber::IF_MATCH => Some("If-Match"),
227            OptionNumber::URI_HOST => Some("Uri-Host"),
228            OptionNumber::ETAG => Some("ETag"),
229            OptionNumber::IF_NONE_MATCH => Some("If-None-Match"),
230            OptionNumber::OBSERVE => Some("Observe"),
231            OptionNumber::URI_PORT => Some("Uri-Port"),
232            OptionNumber::LOCATION_PATH => Some("Location-Path"),
233            OptionNumber::OSCORE => Some("OSCORE"),
234            OptionNumber::URI_PATH => Some("Uri-Path"),
235            OptionNumber::CONTENT_FORMAT => Some("Content-Format"),
236            OptionNumber::MAX_AGE => Some("Max-Age"),
237            OptionNumber::URI_QUERY => Some("Uri-Query"),
238            OptionNumber::ACCEPT => Some("Accept"),
239            OptionNumber::LOCATION_QUERY => Some("Location-Query"),
240            OptionNumber::BLOCK2 => Some("Block2"),
241            OptionNumber::BLOCK1 => Some("Block1"),
242            OptionNumber::SIZE2 => Some("Size2"),
243            OptionNumber::PROXY_URI => Some("Proxy-Uri"),
244            OptionNumber::PROXY_SCHEME => Some("Proxy-Scheme"),
245            OptionNumber::SIZE1 => Some("Size1"),
246            OptionNumber::NO_RESPONSE => Some("No-Response"),
247            _ => None,
248        }
249    }
250
251    /// Writes out the name of this option along with a text debugging description of the value
252    /// associated with this option.
253    pub fn fmt_with_value(self, f: &mut std::fmt::Formatter<'_>, value: &[u8]) -> std::fmt::Result {
254        write!(f, "{}", self)?;
255        match self.option_value_type() {
256            OptionValueType::Opaque | OptionValueType::Flag => {
257                if !value.is_empty() {
258                    f.write_str(":")?;
259                    for b in value {
260                        write!(f, "{:02X}", b)?;
261                    }
262                }
263            }
264            OptionValueType::Integer => {
265                if let Some(i) = try_decode_u32(value) {
266                    write!(f, ":{}", i)?;
267                } else {
268                    f.write_str("ERR")?;
269                }
270            }
271            OptionValueType::Block => {
272                if let Some(i) = try_decode_u32(value) {
273                    write!(f, ":{}", BlockInfo(i))?;
274                } else {
275                    f.write_str("ERR")?;
276                }
277            }
278            OptionValueType::ContentFormat => {
279                if let Some(i) = try_decode_u16(value) {
280                    write!(f, ":{}", ContentFormat(i))?;
281                } else {
282                    f.write_str("ERR")?;
283                }
284            }
285            OptionValueType::String => {
286                if let Ok(s) = std::str::from_utf8(value) {
287                    write!(f, ":{:?}", s)?;
288                } else {
289                    f.write_str("ERR")?;
290                }
291            }
292        }
293
294        Ok(())
295    }
296}
297
298impl core::fmt::Display for OptionNumber {
299    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
300        if let Some(name) = self.static_name() {
301            f.write_str(name)
302        } else {
303            // Write out a descriptive identifier.
304            if self.is_critical() {
305                f.write_str("Opt-")?;
306            } else {
307                f.write_str("Crit-")?;
308            }
309
310            if self.is_un_safe() {
311                f.write_str("UnSafe-")?;
312            }
313
314            if self.is_no_cache_key() {
315                f.write_str("NoCacheKey-")?;
316            }
317
318            write!(f, "{}", self.0)
319        }
320    }
321}
322
323impl core::fmt::Debug for OptionNumber {
324    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
325        write!(f, "{}({})", self.0, self)
326    }
327}
328
329impl core::ops::Add<u16> for OptionNumber {
330    type Output = Self;
331    fn add(self, other: u16) -> Self {
332        OptionNumber(self.0 + other)
333    }
334}
335
336impl core::ops::Sub<OptionNumber> for OptionNumber {
337    type Output = u16;
338    fn sub(self, other: OptionNumber) -> u16 {
339        assert!(self.0 >= other.0);
340        self.0 - other.0
341    }
342}
343
344impl core::cmp::PartialOrd<u16> for OptionNumber {
345    fn partial_cmp(&self, other: &u16) -> Option<core::cmp::Ordering> {
346        Some(self.0.cmp(other))
347    }
348}
349
350impl core::cmp::PartialEq<u16> for OptionNumber {
351    fn eq(&self, other: &u16) -> bool {
352        self.0.eq(other)
353    }
354}
355
356impl Default for OptionNumber {
357    fn default() -> Self {
358        OptionNumber(0)
359    }
360}