coap-handler-implementations 0.4.2

Simple implementations of CoAP handlers
Documentation
//! 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::BadOption;
//!
//! fn process_message(req: &impl ReadableMessage) -> Result<ReqData, BadOption>
//! {
//!     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()]
///
/// This currently contains the number that failed; this may be extended to make that optional for
/// slimmer error processing (in that case, it'll become a ZST).
///
/// It is designed to be usable also with elective options (eg. as the error type of processing
/// one), but currently has no publicly usable options for doing that.
#[derive(Debug)]
pub struct BadOption {
    unprocessed_option: u16,
}

impl crate::helpers::Renderable for BadOption {
    fn render(&self, message: &mut impl coap_message::MinimalWritableMessage) {
        let special_code = match self.unprocessed_option {
            coap_numbers::option::ACCEPT => Some(coap_numbers::code::NOT_ACCEPTABLE),
            coap_numbers::option::PROXY_URI | coap_numbers::option::PROXY_SCHEME => {
                Some(coap_numbers::code::PROXYING_NOT_SUPPORTED)
            }
            coap_numbers::option::CONTENT_FORMAT => {
                Some(coap_numbers::code::UNSUPPORTED_CONTENT_FORMAT)
            }
            _ => None,
        };
        if let Some(code) = special_code {
            message.set_code(crate::helpers::codeconvert(code));
            message.set_payload(b"");
            return;
        }

        message.set_code(crate::helpers::codeconvert(coap_numbers::code::BAD_OPTION));
        if let Some(n) = coap_numbers::content_format::from_str("application/cbor") {
            message.add_option_uint(
                crate::helpers::optconvert(coap_numbers::option::CONTENT_FORMAT),
                n,
            );
        }
        // Encoding for {-8: 0x0000}
        let mut buf = [161, 39, 25, 0, 0];
        buf[3..5].copy_from_slice(&self.unprocessed_option.to_be_bytes());
        message.set_payload(&buf);
    }
}

/// 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<(), BadOption>;

    type WithoutBlock2<'a>: Iterator<Item = O>
    where
        Self: 'a;
    /// 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<'a>(self, out: &'a mut Option<Block2RequestData>) -> Self::WithoutBlock2<'a>
    where
        Self: 'a;

    type WithoutUriPath<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<F: FnMut(&str)>(self, f: F) -> Self::WithoutUriPath<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<(), BadOption> {
        match self.find(|o| option::get_criticality(o.number()) == option::Criticality::Critical) {
            Some(o) => Err(BadOption {
                unprocessed_option: o.number(),
            }),
            None => Ok(()),
        }
    }

    // See BlockPusher comments
    #[cfg(not(feature = "nontrivial_option_processing"))]
    type WithoutBlock2<'a> = aux::BlockPusher<'a, O, Self> where T: 'a;
    #[cfg(not(feature = "nontrivial_option_processing"))]
    fn take_block2<'a>(self, out: &'a mut Option<Block2RequestData>) -> Self::WithoutBlock2<'a>
    where
        T: 'a,
    {
        aux::BlockPusher {
            instream: self,
            out,
        }
    }

    #[cfg(feature = "nontrivial_option_processing")]
    type WithoutBlock2<'a> = impl Iterator<Item = O> + 'a where Self: 'a;
    #[cfg(feature = "nontrivial_option_processing")]
    fn take_block2<'a>(self, out: &'a mut Option<Block2RequestData>) -> Self::WithoutBlock2<'a>
    where
        Self: 'a,
    {
        // 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<F: FnMut(&str)> = aux::UriPusher<O, Self, F>;
    #[cfg(not(feature = "nontrivial_option_processing"))]
    fn take_uri_path<F: FnMut(&str)>(self, f: F) -> Self::WithoutUriPath<F> {
        aux::UriPusher { instream: self, f }
    }

    #[cfg(feature = "nontrivial_option_processing")]
    type WithoutUriPath<F: FnMut(&str)> = impl Iterator<Item = O>;
    #[cfg(feature = "nontrivial_option_processing")]
    fn take_uri_path<F: FnMut(&str)>(self, mut f: F) -> Self::WithoutUriPath<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
        })
    }
}