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 217 218 219 220 221 222 223 224 225 226 227 228 229
//! Tools to easily pull options from a [coap_message::ReadableMessage].
//!
//! This will typically be used by filtering down a message's options, e.g. like this:
//!
//! ```
//! # struct ReqData {
//! # block2: coap_handler_implementations::helpers::Block2RequestData,
//! # }
//!
//! use coap_message::{MessageOption, ReadableMessage};
//! use crate::coap_handler_implementations::option_processing::OptionsExt;
//! # use crate::coap_handler_implementations::option_processing::CriticalOptionsRemain;
//!
//! fn process_message(req: &impl ReadableMessage) -> Result<ReqData, CriticalOptionsRemain>
//! {
//! let mut block2 = None;
//!
//! req.options()
//! .take_block2(&mut block2)
//! .filter(|o| {
//! match o.number() {
//! // my own option => my own behavior
//! _ => true
//! }
//! })
//! .ignore_elective_others()
//! ?;
//!
//! let block2 = block2.unwrap_or_default();
//! Ok(ReqData { block2 })
//! }
//! ```
// FIXME: This is still not used heavily internally, as it lacks facilities to take single options
// (and the old code would only largely profit if it could tat) -- and that's not provided yet
// because of undecidedness around type_alias_impl_trait.
use crate::helpers::Block2RequestData;
use coap_message::MessageOption;
use coap_numbers::option;
/// Error type for [OptionsExt::ignore_elective_others()]
// Could be made to contain a private error detail that is the first critical number, conditional
// on a default feature, and accessible only using an accessor whose presence is conditional on
// that default feature.
#[derive(Debug)]
pub struct CriticalOptionsRemain(());
/// Extensions implemented for any [MessageOption] iterator to enable simple and direct use
///
/// See module level documentation for examples.
// No need to seal this: the `for T` implementation already ensures that nobody implements it.
pub trait OptionsExt<O: MessageOption>: Iterator<Item = O> {
type IgnoredUriHost: Iterator<Item = O> + Sized;
/// Remove Uri-Host option from the iterator
///
/// Note that not processing this may be inappropriate for security reasons, especially with
/// security models that otherwise require DNS rebinding protection.
fn ignore_uri_host(self) -> Self::IgnoredUriHost;
type IgnoredUriQuery: Iterator<Item = O> + Sized;
/// Remove Uri-Query options from the iterator
///
/// Note that this is *not* something that should simply be placed in a handler; it should only
/// be used if the definition of the resource's interface explicitly allows the implementation
/// to ignore unknown query parameters.
fn ignore_uri_query(self) -> Self::IgnoredUriQuery;
/// Exhaust the iterator, successfully if no critical options are present, or indicating an
/// error if critical options were not processed before.
fn ignore_elective_others(self) -> Result<(), CriticalOptionsRemain>;
type WithoutBlock2<'a>: Iterator<Item = O>;
/// Set out to the parsed value of the found Block2 option, and return an iterator over the
/// remaining options.
///
/// Unparsable or repeated Block2 options are left in the output, leaving the error to show up
/// in the eventual ignore_elective_others call.
fn take_block2(self, out: &mut Option<Block2RequestData>) -> Self::WithoutBlock2<'_>;
type WithoutUriPath<'a, F: FnMut(&str)>: Iterator<Item = O>;
/// Call a function (that typically cranks some path state machine) on every (valid) Uri-Path
/// option in an iterator, hiding them from further iteration.
///
/// Error handling of the UTF8 decoding is done by not removing invalid options from the
/// iterator, thus leaving them for an eventual ignore_elective_others.
fn take_uri_path<'a, F: FnMut(&str)>(self, f: F) -> Self::WithoutUriPath<'a, F>;
}
#[cfg(not(feature = "nontrivial_option_processing"))]
mod aux {
//! Helpers for while we don't allow type_alias_impl_trait everywhere
//!
//! These should go away once type_alias_impl_trait can be assumed.
use super::*;
pub struct BlockPusher<'a, O: MessageOption, I: Iterator<Item = O>> {
pub(super) instream: I,
pub(super) out: &'a mut Option<Block2RequestData>,
}
impl<'a, O: MessageOption, I: Iterator<Item = O>> Iterator for BlockPusher<'a, O, I> {
type Item = O;
// Note that this is effectively a copy of the take_block2 implementation in the
// nontrivial_option_processing case.
//
// This can go away once the type_alias_impl_trait feature can be assumed.
fn next(&mut self) -> Option<O> {
while let Some(o) = self.instream.next() {
if matches!(o.number(), option::BLOCK2) && self.out.is_none() {
if let Ok(o) = Block2RequestData::from_option(&o) {
*self.out = Some(o);
continue;
}
}
return Some(o);
}
None
}
}
pub struct UriPusher<O: MessageOption, I: Iterator<Item = O>, F: FnMut(&str)> {
pub(super) instream: I,
pub(super) f: F,
}
impl<O: MessageOption, I: Iterator<Item = O>, F: FnMut(&str)> Iterator for UriPusher<O, I, F> {
type Item = O;
// Note that this is effectively a copy of the take_uri_path implementation in the
// nontrivial_option_processing case.
//
// This can go away once the type_alias_impl_trait feature can be assumed.
fn next(&mut self) -> Option<O> {
while let Some(o) = self.instream.next() {
if o.number() == option::URI_PATH {
if let Ok(s) = core::str::from_utf8(o.value()) {
(self.f)(s);
continue;
}
}
return Some(o);
}
None
}
}
}
impl<T, O> OptionsExt<O> for T
where
T: Iterator<Item = O>,
O: MessageOption,
{
type IgnoredUriHost = core::iter::Filter<Self, fn(&O) -> bool>;
fn ignore_uri_host(self) -> Self::IgnoredUriHost {
fn keep_option<O: MessageOption>(o: &O) -> bool {
o.number() != option::URI_HOST
}
self.filter(keep_option)
}
type IgnoredUriQuery = core::iter::Filter<Self, fn(&O) -> bool>;
fn ignore_uri_query(self) -> Self::IgnoredUriQuery {
fn keep_option<O: MessageOption>(o: &O) -> bool {
o.number() != option::URI_QUERY
}
self.filter(keep_option)
}
fn ignore_elective_others(mut self) -> Result<(), CriticalOptionsRemain> {
match self.find(|o| option::get_criticality(o.number()) == option::Criticality::Critical) {
Some(_) => Err(CriticalOptionsRemain(())),
None => Ok(()),
}
}
// See BlockPusher comments
#[cfg(not(feature = "nontrivial_option_processing"))]
type WithoutBlock2<'a> = aux::BlockPusher<'a, O, Self>;
#[cfg(not(feature = "nontrivial_option_processing"))]
fn take_block2(self, out: &mut Option<Block2RequestData>) -> Self::WithoutBlock2<'_> {
aux::BlockPusher {
instream: self,
out,
}
}
#[cfg(feature = "nontrivial_option_processing")]
type WithoutBlock2<'a> = impl Iterator<Item = O>;
#[cfg(feature = "nontrivial_option_processing")]
fn take_block2(self, out: &mut Option<Block2RequestData>) -> Self::WithoutBlock2<'_> {
// Beware that this is copied in BlockPusher
self.filter(move |o| {
if matches!(o.number(), option::BLOCK2) && out.is_none() {
if let Ok(o) = Block2RequestData::from_option(o) {
*out = Some(o);
return false;
}
}
true
})
}
#[cfg(not(feature = "nontrivial_option_processing"))]
type WithoutUriPath<'a, F: FnMut(&str)> = aux::UriPusher<O, Self, F>;
#[cfg(not(feature = "nontrivial_option_processing"))]
fn take_uri_path<'a, F: FnMut(&str)>(self, f: F) -> Self::WithoutUriPath<'a, F> {
aux::UriPusher { instream: self, f }
}
#[cfg(feature = "nontrivial_option_processing")]
type WithoutUriPath<'a, F: FnMut(&str)> = impl Iterator<Item = O>;
#[cfg(feature = "nontrivial_option_processing")]
fn take_uri_path<'a, F: FnMut(&str)>(self, mut f: F) -> Self::WithoutUriPath<'a, F> {
// Beware that this is copied in UriPusher
self.filter(move |o| {
if o.number() == option::URI_PATH {
if let Ok(s) = core::str::from_utf8(o.value()) {
f(s);
return false;
}
}
true
})
}
}