coap-handler-implementations 0.4.2

Simple implementations of CoAP handlers
Documentation
//! Module containing the [TypeHandler] handler and the [TypeRenderable]
//! trait, along with the [TypeSerializer] helper trait and its corresponding implementations
//! for various serde(ish) (de)serializers.

use crate::helpers::{block2_write_with_cf, codeconvert, Block2RequestData, Renderable};
use crate::option_processing::{BadOption, OptionsExt};
use crate::Code;
use coap_handler::Handler;
use coap_message::{MessageOption, MutableWritableMessage, ReadableMessage};
use coap_numbers::{code, option};
use core::marker::PhantomData;

use serde::Serialize;

/// A simple Handler trait that supports GET, POST and/or PUT on a data structure that supports
/// serde.
///
/// A TypeRenderable implementation can be turned into a [Handler] by wrapping it in
/// [TypeHandler::new].
pub trait TypeRenderable {
    type Get;
    type Post;
    type Put;

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

/// Keeping them hidden to stay flexible; they don't need to be named for their'e either default or
/// their users have aliases.
mod sealed {
    pub trait TypeSerializer {
        const CF: Option<u16>;
    }

    pub struct SerdeCBORSerialization;
    pub struct MiniCBORSerialization;
}
use sealed::*;

impl TypeSerializer for SerdeCBORSerialization {
    const CF: Option<u16> = coap_numbers::content_format::from_str("application/cbor");
}
impl TypeSerializer for MiniCBORSerialization {
    const CF: Option<u16> = coap_numbers::content_format::from_str("application/cbor");
}

/// 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 [TypeRenderable]) 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 TypeHandler<H, S: TypeSerializer = SerdeCBORSerialization>
where
    H: TypeRenderable,
{
    handler: H,
    _phantom: PhantomData<S>,
}

impl<H, S> TypeHandler<H, S>
where
    H: TypeRenderable,
    S: TypeSerializer,
{
    fn check_get_options(
        request: &impl ReadableMessage,
    ) -> Result<Block2RequestData, TypeRequestData> {
        let mut block2 = None;

        request
            .options()
            .take_block2(&mut block2)
            .filter(|o| {
                if o.number() == option::ACCEPT {
                    if let Some(cf) = S::CF {
                        // If they differ, we'll keep the option for later failing
                        o.value_uint() != Some(cf)
                    } else {
                        // We don't know any content format, so we keep the option in the iterator
                        // to fail later
                        true
                    }
                } else {
                    true
                }
            })
            .ignore_elective_others()
            .map_err(|o| TypeRequestData(TypeRequestDataE::BadOption(o)))?;

        Ok(block2.unwrap_or_default())
    }

    fn check_delete_options(request: &impl ReadableMessage) -> Result<(), TypeRequestData> {
        request
            .options()
            .ignore_elective_others()
            .map_err(|o| TypeRequestData(TypeRequestDataE::BadOption(o)))
    }

    fn check_postput_options(request: &impl ReadableMessage) -> Result<(), TypeRequestData> {
        let mut cf = Ok(());

        request
            .options()
            .filter(|o| {
                if o.number() == option::CONTENT_FORMAT
                    && (S::CF.is_none() || o.value_uint() != S::CF)
                {
                    cf = Err(TypeRequestData(Done(Code(
                        coap_numbers::code::UNSUPPORTED_CONTENT_FORMAT,
                    ))));
                }
                // It's not a critical option so we don't really have to filter it out
                true
            })
            .ignore_elective_others()
            .map_err(|o| TypeRequestData(TypeRequestDataE::BadOption(o)))?;

        cf
    }
}

impl<H> TypeHandler<H, SerdeCBORSerialization>
where
    H: TypeRenderable,
    H::Get: for<'de> serde::Serialize,
    H::Post: for<'de> serde::Deserialize<'de>,
    H::Put: for<'de> serde::Deserialize<'de>,
{
    pub fn new(handler: H) -> Self {
        TypeHandler {
            handler,
            _phantom: PhantomData,
        }
    }
}

impl<H> TypeHandler<H, MiniCBORSerialization>
where
    H: TypeRenderable,
    H::Get: for<'de> minicbor::Encode<()>,
    H::Post: for<'de> minicbor::Decode<'de, ()>,
    H::Put: for<'de> minicbor::Decode<'de, ()>,
{
    pub fn new_minicbor(handler: H) -> Self {
        TypeHandler {
            handler,
            _phantom: PhantomData,
        }
    }
}

/// Data carried around between a request and its response for [TypeHandler]s
pub struct TypeRequestData(TypeRequestDataE);

enum TypeRequestDataE {
    Get(Block2RequestData), // GET to be processed later, but all request opions were in order
    Done(Code), // All done, just a response to emit -- if POST/PUT has been processed, or GET had a bad accept/option
    BadOption(BadOption),
}
use self::TypeRequestDataE::{Done, Get};

// FIXME for all the below: deduplicate (but not sure how, without HKTs)

impl<H> Handler for TypeHandler<H, SerdeCBORSerialization>
where
    H: TypeRenderable,
    H::Get: for<'de> serde::Serialize,
    H::Post: for<'de> serde::Deserialize<'de>,
    H::Put: for<'de> serde::Deserialize<'de>,
{
    type RequestData = TypeRequestData;

    fn extract_request_data(&mut self, request: &impl ReadableMessage) -> Self::RequestData {
        TypeRequestData(match request.code().into() {
            code::DELETE => match Self::check_delete_options(request) {
                Err(e) => return e,
                Ok(()) => Done(Code(self.handler.delete())),
            },
            code::GET => match Self::check_get_options(request) {
                Err(e) => return e,
                Ok(block) => Get(block),
            },
            code::POST => {
                if let Err(e) = Self::check_postput_options(request) {
                    return 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(Code(self.handler.post(&p))),
                    Err(_) => Done(Code(code::BAD_REQUEST)),
                }
            }
            code::PUT => {
                if let Err(e) = Self::check_postput_options(request) {
                    return e;
                }

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

    fn estimate_length(&mut self, request: &Self::RequestData) -> usize {
        match &request.0 {
            Done(_) => 4,
            TypeRequestDataE::BadOption(_) => 20, // FIXME: a rough estimation
            Get(block) => (block.size() + 25).into(), // FIXME: hard-coded copied over from block2_write_with_cf's estimated overhead
        }
    }

    fn build_response(
        &mut self,
        response: &mut impl MutableWritableMessage,
        request: Self::RequestData,
    ) {
        match request.0 {
            Done(r) => r.render(response),
            TypeRequestDataE::BadOption(cor) => cor.render(response),
            Get(block2) => {
                let repr = self.handler.get();
                match repr {
                    Err(e) => {
                        Code(e).render(response);
                    }
                    Ok(repr) => {
                        response.set_code(codeconvert(code::CONTENT));
                        match block2_write_with_cf(
                            block2,
                            response,
                            |win| repr.serialize(&mut serde_cbor::ser::Serializer::new(win)),
                            SerdeCBORSerialization::CF,
                        ) {
                            Ok(()) => (),
                            Err(_) => {
                                // FIXME: Rewind all the written options
                                response.set_code(codeconvert(code::INTERNAL_SERVER_ERROR));
                            }
                        }
                    }
                }
            }
        }
    }
}

impl<H> Handler for TypeHandler<H, MiniCBORSerialization>
where
    H: TypeRenderable,
    H::Get: for<'de> minicbor::Encode<()>,
    H::Post: for<'de> minicbor::Decode<'de, ()>,
    H::Put: for<'de> minicbor::Decode<'de, ()>,
{
    type RequestData = TypeRequestData;

    fn extract_request_data(&mut self, request: &impl ReadableMessage) -> Self::RequestData {
        TypeRequestData(match request.code().into() {
            code::DELETE => match Self::check_delete_options(request) {
                Err(e) => return e,
                Ok(()) => Done(Code(self.handler.delete())),
            },
            code::GET => match Self::check_get_options(request) {
                Err(e) => return e,
                Ok(block) => Get(block),
            },
            code::POST => {
                if let Err(e) = Self::check_postput_options(request) {
                    return e;
                }

                let parsed: Result<H::Post, _> = minicbor::decode(request.payload());
                match parsed {
                    Ok(p) => Done(Code(self.handler.post(&p))),
                    Err(_) => Done(Code(code::BAD_REQUEST)),
                }
            }
            code::PUT => {
                if let Err(e) = Self::check_postput_options(request) {
                    return e;
                }

                let parsed: Result<H::Put, _> = minicbor::decode(request.payload());
                match parsed {
                    Ok(p) => Done(Code(self.handler.put(&p))),
                    Err(_) => Done(Code(code::BAD_REQUEST)),
                }
            }
            _ => Done(Code(code::METHOD_NOT_ALLOWED)),
        })
    }

    fn estimate_length(&mut self, request: &Self::RequestData) -> usize {
        match &request.0 {
            Done(_) => 4,
            TypeRequestDataE::BadOption(_) => 20, // FIXME: a rough estimation
            Get(block) => (block.size() + 25).into(), // FIXME: hard-coded copied over from block2_write_with_cf's estimated overhead
        }
    }

    fn build_response(
        &mut self,
        response: &mut impl MutableWritableMessage,
        request: Self::RequestData,
    ) {
        match request.0 {
            Done(r) => r.render(response),
            TypeRequestDataE::BadOption(cor) => cor.render(response),
            Get(block2) => {
                let repr = self.handler.get();
                match repr {
                    Err(e) => {
                        Code(e).render(response);
                    }
                    Ok(repr) => {
                        response.set_code(codeconvert(code::CONTENT));
                        match block2_write_with_cf(
                            block2,
                            response,
                            |win| minicbor::encode(&repr, win),
                            MiniCBORSerialization::CF,
                        ) {
                            Ok(()) => (),
                            Err(_) => {
                                // FIXME: Rewind all the written options
                                response.set_code(codeconvert(code::INTERNAL_SERVER_ERROR));
                            }
                        }
                    }
                }
            }
        }
    }
}