coap_message_utils/option_processing.rs
1use crate::option_value::{Block2RequestData, TryFromOption};
2use crate::Error;
3use coap_message::MessageOption;
4use coap_numbers::option;
5
6/// Extensions implemented for any [MessageOption] iterator to enable simple and direct use
7///
8/// As an extension trait, this is not meant to be implemented (in fact, Rust's rules prohibit this
9/// from being implemented anywhere else after this crate's `impl<T, O> OptionsExt<O> for T`), but
10/// can be .
11///
12/// This will typically be used by filtering down a message's options, e.g. like this:
13///
14/// ```
15/// # struct ReqData {
16/// # block2: coap_message_utils::option_value::Block2RequestData,
17/// # }
18///
19/// use coap_message::{MessageOption, ReadableMessage};
20/// use coap_message_utils::{Error, OptionsExt};
21///
22/// fn process_message(req: &impl ReadableMessage) -> Result<ReqData, Error>
23/// {
24/// let mut block2 = None;
25///
26/// req.options()
27/// .take_block2(&mut block2)
28/// .filter(|o| {
29/// match o.number() {
30/// // my own option => my own behavior
31/// _ => true
32/// }
33/// })
34/// .ignore_elective_others()
35/// ?;
36///
37/// let block2 = block2.unwrap_or_default();
38/// Ok(ReqData { block2 })
39/// }
40/// ```
41// No need to seal this: the `for T` implementation already ensures that nobody implements it.
42pub trait OptionsExt<O: MessageOption>: Iterator<Item = O> {
43 /// Remove Uri-Host option from the iterator
44 ///
45 /// Note that not processing this may be inappropriate for security reasons, especially with
46 /// security models that otherwise require DNS rebinding protection.
47 fn ignore_uri_host(self) -> impl Iterator<Item = O>;
48
49 /// Remove Uri-Query options from the iterator
50 ///
51 /// Note that this is *not* something that should simply be placed in a handler; it should only
52 /// be used if the definition of the resource's interface explicitly allows the implementation
53 /// to ignore unknown query parameters.
54 fn ignore_uri_query(self) -> impl Iterator<Item = O>;
55
56 /// Exhaust the iterator, successfully if no critical options are present, or indicating an
57 /// error if critical options were not processed before.
58 fn ignore_elective_others(self) -> Result<(), Error>;
59
60 /// Store the first matching and parsable option into `out`, and return the iterator over the
61 /// remaining options.
62 ///
63 /// Unparsable or duplicate options are simply left in the output and rely on their criticality
64 /// to cause an error when eventually `.ignore_elective_others()` is called. When using this
65 /// mechanism to parse a non-critical option that should not be ignored on parsing errors, that
66 /// option should not implement [`TryFromOption`] on `Self`, but rather on `Result<Self,
67 /// ParsingError>`, and `if Some(Err(_)) = out` after the options have been exhausted, the
68 /// error needs to be raised.
69 fn take_into<'a, T: TryFromOption>(self, out: &'a mut Option<T>) -> impl Iterator<Item = O>
70 where
71 Self: 'a;
72
73 /// Set out to the parsed value of the found Block2 option, and return an iterator over the
74 /// remaining options.
75 ///
76 /// Unparsable or repeated Block2 options are left in the output, leaving the error to show up
77 /// in the eventual ignore_elective_others call.
78 ///
79 /// Note that this is merely a pre-typed version of take_into (and not deprecated yet because
80 /// it's a convenient shortcut to spelling out the `None`'s type).
81 fn take_block2<'a>(self, out: &'a mut Option<Block2RequestData>) -> impl Iterator<Item = O>
82 where
83 Self: 'a;
84
85 /// Call a function (that typically cranks some path state machine) on every (valid) Uri-Path
86 /// option in an iterator, hiding them from further iteration.
87 ///
88 /// Error handling of the UTF8 decoding is done by not removing invalid options from the
89 /// iterator, thus leaving them for an eventual ignore_elective_others.
90 fn take_uri_path<F: FnMut(&str)>(self, f: F) -> impl Iterator<Item = O>;
91}
92
93impl<T, O> OptionsExt<O> for T
94where
95 T: Iterator<Item = O>,
96 O: MessageOption,
97{
98 fn ignore_uri_host(self) -> impl Iterator<Item = O> {
99 self.filter(|o| o.number() != option::URI_HOST)
100 }
101
102 fn ignore_uri_query(self) -> impl Iterator<Item = O> {
103 self.filter(|o| o.number() != option::URI_QUERY)
104 }
105
106 fn ignore_elective_others(mut self) -> Result<(), Error> {
107 match self.find(|o| option::get_criticality(o.number()) == option::Criticality::Critical) {
108 Some(o) => Err(Error::bad_option(o.number())),
109 None => Ok(()),
110 }
111 }
112
113 fn take_into<'a, T2: 'a + TryFromOption>(
114 self,
115 out: &'a mut Option<T2>,
116 ) -> impl Iterator<Item = O>
117 where
118 T: 'a,
119 {
120 self.filter(move |o| {
121 if out.is_none() {
122 if let Some(o) = T2::try_from(o) {
123 *out = Some(o);
124 return false;
125 }
126 }
127 true
128 })
129 }
130
131 fn take_block2<'a>(self, out: &'a mut Option<Block2RequestData>) -> impl Iterator<Item = O>
132 where
133 Self: 'a,
134 {
135 self.take_into(out)
136 }
137
138 fn take_uri_path<F: FnMut(&str)>(self, mut f: F) -> impl Iterator<Item = O> {
139 self.filter(move |o| {
140 if o.number() == option::URI_PATH {
141 if let Ok(s) = core::str::from_utf8(o.value()) {
142 f(s);
143 return false;
144 }
145 }
146 true
147 })
148 }
149}