coap-handler 0.1.4

Interface to (and simple implementations) of CoAP handlers
Documentation
//! The implementations that used to reside here have been moved to [coap-handler-implementations];
//! its original version is still available here, but users are encouraged to use the later
//! standalone versions.
//!
//! [coap-handler-implementations]: https://docs.rs/coap-handler-implementations/
#![deprecated]
#![allow(deprecated)]
#![allow(
    clippy::needless_lifetimes,
    clippy::useless_conversion,
    clippy::redundant_closure,
    clippy::unwrap_or_else_default,
    clippy::manual_map,
    clippy::unused_unit
)]

use core::convert::TryInto;

use coap_message::{MessageOption, MutableWritableMessage, ReadableMessage};

use crate::helpers::{block2_write_with_cf, codeconvert, Block2RequestData, MaskingUriUpToPath};
use crate::Handler;

use coap_numbers::{code, option};

// Used to store response codes internally until the type of the response message is determined and
// it can be converted into an actual outgoing code
//
// FIXME One way to do this better might be to make the handler already specific to the response
// message type, then the code could be statically converted into whichever code type that uses
// already at extraction time. (Another option would be to have an enum here with the codes we know
// we use, and limit outputs to those that implement an From<OutCode>)
type Code = u8;

/// Backport of <https://github.com/rust-lang/rust/issues/64295>. The contested points about the
/// API do not apply.
trait IterOrderByBackport: Iterator {
    fn cmp_by<I, F>(mut self, other: I, mut cmp: F) -> core::cmp::Ordering
    where
        Self: Sized,
        I: IntoIterator,
        F: FnMut(Self::Item, I::Item) -> core::cmp::Ordering,
    {
        let mut other = other.into_iter();

        loop {
            let x = match self.next() {
                None => {
                    if other.next().is_none() {
                        return core::cmp::Ordering::Equal;
                    } else {
                        return core::cmp::Ordering::Less;
                    }
                }
                Some(val) => val,
            };

            let y = match other.next() {
                None => return core::cmp::Ordering::Greater,
                Some(val) => val,
            };

            match cmp(x, y) {
                core::cmp::Ordering::Equal => (),
                non_eq => return non_eq,
            }
        }
    }
}

impl<T: Iterator> IterOrderByBackport for T {}

/// A resource that unconditionally responds 4.04 Not Found.
///
/// This is a convenience tool for building trees of resources -- rather than special casing the
/// "none found" situation, this handler can be used.
pub struct NeverFound {}

impl Handler for NeverFound {
    type RequestData = ();

    fn extract_request_data(&mut self, _request: &impl ReadableMessage) -> Self::RequestData {
        ()
    }

    fn estimate_length(&mut self, _request: &Self::RequestData) -> usize {
        0
    }

    fn build_response(
        &mut self,
        response: &mut impl MutableWritableMessage,
        _request: Self::RequestData,
    ) {
        response.set_code(codeconvert(code::NOT_FOUND));
        response.set_payload(b"");
    }
}

/// Start building a tree of sub-resources
///
/// While technically this just returns a handler that returns 4.04 unconditionally, it also
/// implemnts HandlerBuilder, and thus can be used like this:
///
/// ```
/// use coap_handler::implementations::*;
/// let handler = new_dispatcher()
///     .at(&["dev", "name"], TypedStaticResponse::from("Demo program"))
///     .at(&["dev", "version"], TypedStaticResponse::from("0.8.15"))
///     ;
/// ```
pub fn new_dispatcher() -> NeverFound {
    NeverFound {}
}

/// Trait implemented by default on all handlers that lets the user stack them using a builder-like
/// syntax.
///
/// Note that the resulting ForkingRequestData<ForkingRequestData<...>,()> enums that might look
/// wasteful on paper are optimized into the minimum necessary size since
/// <https://github.com/rust-lang/rust/pull/45225>. They are, however, suboptimal when it comes to
/// how many times the options are read.
pub trait HandlerBuilder<'a, OldRD>
where
    Self: Handler + Sized,
{
    /// Divert requests arriving at `path` into the given `handler`.
    ///
    /// The handler will not *not* see the Uri-Path (and Uri-Host, as this builder doesn't do
    /// virtual hosting yet) options any more; see the top-level module documentation on Options
    /// Hiding for rationale.
    fn at<H>(self, path: &'a [&'a str], handler: H) -> ForkingHandler<'a, H, Self>
    where
        H: Handler + Sized;
}

pub struct ForkingHandler<'a, H1, H2> {
    h1: H1,
    h2: H2,

    // I'd like to have a closure in here, and that'd almost work as a type D: Fn(&Message<Bin>)
    // -> bool, but I can't write at()... -> ForkingHandler<impl ..., H, Self> in the trait's
    // signature.
    h1_condition: &'a [&'a str],
}

/// Tagged-union container for ForkingHandler
pub enum ForkingRequestData<RD1, RD2> {
    First(RD1),
    Second(RD2),
}

impl<'a, RD1, H1, RD2, H2> Handler for ForkingHandler<'a, H1, H2>
where
    H1: Handler<RequestData = RD1>,
    H2: Handler<RequestData = RD2>,
{
    type RequestData = ForkingRequestData<RD1, RD2>;

    fn extract_request_data(&mut self, request: &impl ReadableMessage) -> Self::RequestData {
        let expected_path = self.h1_condition.iter().map(|s| s.as_bytes());
        let actual_path = request.options().filter(|o| o.number() == option::URI_PATH);

        if IterOrderByBackport::cmp_by(expected_path, actual_path, |e, a| e.cmp(a.value()))
            == core::cmp::Ordering::Equal
        {
            let masked = MaskingUriUpToPath(request);
            ForkingRequestData::First(self.h1.extract_request_data(&masked))
        } else {
            ForkingRequestData::Second(self.h2.extract_request_data(request))
        }
    }

    fn estimate_length(&mut self, request: &Self::RequestData) -> usize {
        match request {
            ForkingRequestData::First(r) => self.h1.estimate_length(r),
            ForkingRequestData::Second(r) => self.h2.estimate_length(r),
        }
    }

    fn build_response(
        &mut self,
        response: &mut impl MutableWritableMessage,
        request: Self::RequestData,
    ) {
        match request {
            ForkingRequestData::First(r) => self.h1.build_response(response, r),
            ForkingRequestData::Second(r) => self.h2.build_response(response, r),
        }
    }
}

impl<'a, OldRD, OldH> HandlerBuilder<'a, OldRD> for OldH
where
    Self: Handler<RequestData = OldRD> + Sized,
{
    fn at<H>(self, path: &'a [&'a str], handler: H) -> ForkingHandler<'a, H, Self> {
        ForkingHandler {
            h1: handler,
            h2: self,
            h1_condition: path,
        }
    }
}

pub struct TypedStaticResponse {
    pub payload: &'static [u8],
    pub contentformat: &'static [u8], // pre-encoded
}

impl From<&'static str> for TypedStaticResponse {
    fn from(s: &'static str) -> Self {
        TypedStaticResponse {
            payload: s.as_bytes(),
            contentformat: &[], // 0; text/plain;charset=utf-8
        }
    }
}

impl Handler for TypedStaticResponse {
    type RequestData = Code;

    fn extract_request_data(&mut self, request: &impl ReadableMessage) -> Self::RequestData {
        match request.code().into() {
            code::GET => (),
            _ => return code::METHOD_NOT_ALLOWED,
        };

        for o in request.options() {
            match o.number() {
                option::ACCEPT => {
                    if o.value() != self.contentformat {
                        return code::UNSUPPORTED_CONTENT_FORMAT;
                    }
                }
                o if option::get_criticality(o) == option::Criticality::Critical => {
                    return code::BAD_OPTION;
                }
                _ => {}
            }
        }

        code::CONTENT
    }

    fn estimate_length(&mut self, request: &Self::RequestData) -> usize {
        match *request {
            code::CONTENT => {
                2 /* small option offset plus payload marker */ + self.payload.len() + self.contentformat.len()
            }
            _ => 0,
        }
    }

    fn build_response(
        &mut self,
        response: &mut impl MutableWritableMessage,
        request: Self::RequestData,
    ) {
        response.set_code(codeconvert(request));
        let mut p = &b""[..];
        if request == code::CONTENT {
            if let Ok(cfopt) = option::CONTENT_FORMAT.try_into() {
                response.add_option(cfopt, self.contentformat);
            }
            p = self.payload;
        }
        response.set_payload(p);
    }
}

/// Information a SimpleRenderable needs to carry from request to response.
// Newtype wrapper to avoid exposing Block2RequestData
pub struct SimpleRenderableData(Result<Block2RequestData, Code>);

/// A simplified Handler trait that can react to GET requests and will render to a fmt::Write
/// object with blockwise backing.
///
/// Anything that implements it (which includes plain &str, for example) can be packed into a
/// [SimpleRendered] to form a Handler.
pub trait SimpleRenderable {
    fn render<W>(&mut self, writer: &mut W)
    where
        W: core::fmt::Write;

    /// If something is returned, GETs with differing Accept options will be rejecected, and the
    /// value will be set in responses.
    fn content_format(&self) -> Option<u16> {
        None
    }
}

#[derive(Debug, Copy, Clone)]
pub struct SimpleRendered<T: SimpleRenderable>(pub T);

impl<T> Handler for SimpleRendered<T>
where
    T: SimpleRenderable,
{
    type RequestData = SimpleRenderableData;

    fn extract_request_data(&mut self, request: &impl ReadableMessage) -> Self::RequestData {
        let expected_accept = self.0.content_format();

        let mut block2 = Ok(None);

        for o in request.options() {
            match o.number() {
                coap_numbers::option::ACCEPT => {
                    if expected_accept.is_some() && o.value_uint() != expected_accept {
                        return SimpleRenderableData(Err(coap_numbers::code::NOT_ACCEPTABLE));
                    }
                }
                coap_numbers::option::BLOCK2 => {
                    block2 = match block2 {
                        Err(e) => Err(e),
                        Ok(Some(_)) => Err(coap_numbers::code::BAD_REQUEST),
                        Ok(None) => Block2RequestData::from_option(&o)
                            .map(|o| Some(o))
                            .map_err(|()| code::BAD_REQUEST),
                    }
                }
                o if option::get_criticality(o) == option::Criticality::Critical => {
                    return SimpleRenderableData(Err(code::BAD_OPTION));
                }
                _ => (),
            }
        }

        let reqdata = match request.code().into() {
            code::GET => block2.map(|o| o.unwrap_or_else(Default::default)),
            _ => Err(code::METHOD_NOT_ALLOWED),
        };
        SimpleRenderableData(reqdata)
    }

    fn estimate_length(&mut self, _request: &Self::RequestData) -> usize {
        1280 - 40 - 4 // does this correclty calculate the IPv6 minimum MTU?
    }

    fn build_response(
        &mut self,
        response: &mut impl MutableWritableMessage,
        request: Self::RequestData,
    ) {
        let block2data = match request.0 {
            Ok(x) => x,
            Err(c) => {
                response.set_code(codeconvert(c));
                response.set_payload(b"");
                return;
            }
        };

        let cf = self.0.content_format();
        response.set_code(codeconvert(code::CONTENT));
        block2_write_with_cf(block2data, response, |w| self.0.render(w), cf);
    }
}

// FIXME This and the TypedStaticResponse::from(s) both provide incompelte solutions: This provides
// blockwising, TypedStaticResponse provides a content format
impl<'a> SimpleRenderable for &'a str {
    fn render<W>(&mut self, writer: &mut W)
    where
        W: core::fmt::Write,
    {
        writer
            .write_str(self)
            .expect("The backend of SimpleRenderable supports infallible writing");
    }
}

use serde::Serialize;
use serde_cbor;

/// A simple Handler trait that supports GET, POST and/or PUT on a data structure that supports
/// serde.
///
/// A SimpleCBORHandler implementation can be turned into a [Handler] by wrapping it in
/// [SimpleCBORWrapper::new].
pub trait SimpleCBORHandler {
    // Does it really make sense to constrain them? Seems so, as the only practical thing this
    // trait is used for is later derivation, and that'd fail if any of those isn't satisified, so
    // rather specifying them here for good error messages.
    type Get: for<'de> serde::Serialize;
    type Post: for<'de> serde::Deserialize<'de>;
    type Put: for<'de> serde::Deserialize<'de>;

    fn get(&mut self) -> Result<Self::Get, Code> {
        Err(code::METHOD_NOT_ALLOWED)
    }
    fn post(&mut self, _representation: &Self::Post) -> Code {
        code::METHOD_NOT_ALLOWED
    }
    fn put(&mut self, _representation: &Self::Put) -> Code {
        code::METHOD_NOT_ALLOWED
    }
}

/// Wrapper for resource handlers that are implemented in terms of GETting, POSTing or PUTting
/// objects in CBOR format.
///
/// The wrapper handles all encoding and decoding, options processing (ignoring the critical
/// Uri-Path option under the assumption that that has been already processed by an underlying
/// request router), the corresponding errors and block-wise GETs.
///
/// More complex handlers (eg. implementing additional representations, or processing query
/// aprameters into additional data available to the [SimpleCBORHandler]) can be built by
/// forwarding to this (where any critical but already processed options would need to be masked
/// from the message's option) or taking inspiration from it.
pub struct SimpleCBORWrapper<H: SimpleCBORHandler> {
    handler: H,
}

impl<H: SimpleCBORHandler> SimpleCBORWrapper<H> {
    pub fn new(handler: H) -> Self {
        SimpleCBORWrapper { handler }
    }

    fn check_get_options(request: &impl ReadableMessage) -> Result<(), Code> {
        for o in request.options() {
            match o.number().into() {
                option::ACCEPT => {
                    if o.value_uint() != Some(60u8) {
                        return Err(code::UNSUPPORTED_CONTENT_FORMAT);
                    }
                }
                o if option::get_criticality(o) == option::Criticality::Critical => {
                    return Err(code::BAD_OPTION);
                }
                _ => {}
            }
        }

        Ok(())
    }

    fn check_postput_options(request: &impl ReadableMessage) -> Result<(), Code> {
        for o in request.options() {
            match o.number().into() {
                option::CONTENT_FORMAT => {
                    if o.value_uint() != Some(60u8) {
                        return Err(code::NOT_ACCEPTABLE);
                    }
                }
                o if option::get_criticality(o) == option::Criticality::Critical => {
                    return Err(code::BAD_OPTION);
                }
                _ => {}
            }
        }

        Ok(())
    }
}

pub enum SimpleCBORRequestData {
    Get,        // Nothing has been processed, decision deferred to build_response stage
    Done(Code), // All done, just a response to emit -- if POST/PUT has been processed, or GET had a bad accept/option
}
use self::SimpleCBORRequestData::{Done, Get};
impl<H> Handler for SimpleCBORWrapper<H>
where
    H: SimpleCBORHandler,
    H::Get: serde::Serialize,
    H::Post: for<'de> serde::Deserialize<'de>,
    H::Put: for<'de> serde::Deserialize<'de>,
{
    type RequestData = SimpleCBORRequestData;

    fn extract_request_data(&mut self, request: &impl ReadableMessage) -> Self::RequestData {
        match request.code().into() {
            code::GET => {
                if let Err(e) = Self::check_get_options(request) {
                    return Done(e);
                }

                Get
            }
            code::POST => {
                if let Err(e) = Self::check_postput_options(request) {
                    return Done(e);
                }

                // FIXME: allow getting a mutable payload here, which may be hard for general
                // Message but usually cheap for GNRC-based.
                let parsed: Result<H::Post, _> =
                    serde_cbor::de::from_slice_with_scratch(request.payload(), &mut []);
                match parsed {
                    Ok(p) => Done(self.handler.post(&p)),
                    Err(_) => Done(code::BAD_REQUEST),
                }
            }
            code::PUT => {
                if let Err(e) = Self::check_postput_options(request) {
                    return Done(e);
                }

                let parsed: Result<H::Put, _> =
                    serde_cbor::de::from_slice_with_scratch(request.payload(), &mut []);
                match parsed {
                    Ok(p) => Done(self.handler.put(&p)),
                    Err(_) => Done(code::BAD_REQUEST),
                }
            }
            _ => Done(code::METHOD_NOT_ALLOWED),
        }
    }

    fn estimate_length(&mut self, request: &Self::RequestData) -> usize {
        match request {
            Done(_) => 4,
            Get => 1280 - 40 - 4, // does this correclty calculate the IPv6 minimum MTU?
        }
    }

    fn build_response(
        &mut self,
        response: &mut impl MutableWritableMessage,
        request: Self::RequestData,
    ) {
        match request {
            Done(r) => {
                response.set_code(codeconvert(r));
                response.set_payload(b"");
            }
            Get => {
                let repr = self.handler.get();
                match repr {
                    Err(e) => {
                        response.set_code(codeconvert(e));
                        response.set_payload(b"");
                    }
                    Ok(repr) => {
                        response.set_code(codeconvert(code::CONTENT));
                        // FIXME: This needs to be rewound on overly-long errors
                        if let Ok(cfopt) = option::CONTENT_FORMAT.try_into() {
                            response.add_option_uint(cfopt, 60u8);
                        }

                        let (written, available) = {
                            // FIXME An API that just says how much payload I may use would be
                            // better here.
                            let len = response.available_space() - 1;
                            // FIXME This is a bad estimate; enhance using better knowledge of CBOR
                            // encoding or just go full block-wise. (Trimming at 1KiB payload
                            // because the usize::MAX unbounded impls would yield is clearly too
                            // much)
                            let len = len.min(1024);
                            let payload = &mut response.payload_mut_with_len(len);
                            let mut win = windowed_infinity::WindowedInfinity::new(payload, 0);

                            match repr.serialize(&mut serde_cbor::ser::Serializer::new(&mut win)) {
                                Ok(()) => (win.get_cursor().try_into().ok(), payload.len()),
                                Err(_) => (None, 0),
                            }
                        };
                        match written {
                            Some(w) if w <= available => {
                                response.truncate(w);
                            }
                            _ => {
                                response.set_code(codeconvert(code::INTERNAL_SERVER_ERROR));
                                response.truncate(0);
                            }
                        }
                    }
                }
            }
        }
    }
}

/// An easy way to have resources that may or may not be there in a tree, considering that Handler
/// is not object safe and thus, `if let Some(x) { all = all.at(...)` won't work.
///
/// This returns 4.04 Not Found if the inner handler is absent, and otherwise forwards request and
/// response building.
impl<H> Handler for Option<H>
where
    H: Handler,
{
    type RequestData = Option<H::RequestData>;

    fn extract_request_data(&mut self, request: &impl ReadableMessage) -> Self::RequestData {
        match self {
            None => None,
            Some(h) => Some(h.extract_request_data(request)),
        }
    }

    fn estimate_length(&mut self, request: &Self::RequestData) -> usize {
        match (self, request) {
            (Some(h), Some(r)) => h.estimate_length(r),
            _ => 1,
        }
    }

    fn build_response(
        &mut self,
        response: &mut impl MutableWritableMessage,
        request: Self::RequestData,
    ) {
        // Not using the match-2-tuple pattern of above b/c of
        // "error[E0009]: cannot bind by-move and by-ref in the same pattern"
        if let Some(h) = self {
            if let Some(r) = request {
                return h.build_response(response, r);
            }
        }

        response.set_code(codeconvert(code::NOT_FOUND));
        response.set_payload(b"");
    }
}