coap-message-demos 0.5.0

Demos of the coap-message ecosystem
Documentation
//! This module demonstrates the use of [coap_handler_implementations::TypeRenderable]

use coap_handler::{Attribute, Handler, Reporting};
use coap_handler_implementations::{
    DeleteRenderable, FetchRenderable, GetRenderable, HandlerBuilder, IPatchRenderable,
    PostRenderable, PutRenderable, ReportingHandlerBuilder, TypeHandler, TypeRenderable,
    new_dispatcher, with_get_put_fetch,
};
use coap_message_utils::Error;
use core::default::Default;
use heapless::{String, Vec};

/// A demo struct with several members, all of which implement minicbor traits.
///
/// Using TypeRenderable, a CoAP GETter and SETter are implemented. To avoid being boring, both
/// do some checks: The setter rejects attempts to write HTML, and the getter refuses to answer if
/// the "hidden" property is true.
// Not going through minicbor because that has no implementations for heapless or vice versa
#[derive(minicbor::Decode, minicbor::Encode, Clone)]
#[cbor(map)]
pub struct MyCBOR {
    #[n(0)]
    hidden: bool,
    #[n(1)]
    number: usize,
    #[cbor(n(2), with = "minicbor_adapters")]
    label: String<32>,
    #[cbor(n(3), with = "minicbor_adapters")]
    list: Vec<usize, 16>,
}

impl Default for MyCBOR {
    fn default() -> Self {
        Self {
            hidden: false,
            number: 32,
            label: "Hello".try_into().unwrap(),
            list: Vec::from_slice(&[1, 2, 3]).expect("More than 3 entries allocated"),
        }
    }
}

impl GetRenderable for MyCBOR {
    type Get = MyCBOR;

    fn get(&mut self) -> Result<MyCBOR, Error> {
        if self.hidden {
            return Err(Error::forbidden());
        }
        Ok(self.clone())
    }
}

impl PutRenderable for MyCBOR {
    fn put(&mut self, new: &MyCBOR) -> Result<(), Error> {
        if new.label.contains('<') {
            // No HTML injection please ;-)
            return Err(Error::bad_request());
        }
        *self = new.clone();
        Ok(())
    }
    type Put = MyCBOR;
}

// Doing Fetch is a bit harder because there's no trivial (mini)cbor derivations for it.
//
// Also, there is choice involved: We choose for this demo to fetch by sending a map key and
// obtaining the respective value.

pub struct MyCborView {
    index: u8,
    // We have to copy it around because we can't pass a lifetime'd thing out of FetchOut
    // (typed_resource could be bent to make it work, but then TryingThroughMutex ceases to be
    // possible), but due to when TypeHandler renders FETCH, that's just a short-lived copy on
    // the stack that, given we only read later, might even be optimized away if the compiler makes
    // maximum use of the mutex's Release semantics.
    data: MyCBOR,
}

impl<C> minicbor::encode::Encode<C> for MyCborView {
    fn encode<W: minicbor::encode::Write>(
        &self,
        e: &mut minicbor::Encoder<W>,
        _ctx: &mut C,
    ) -> Result<(), minicbor::encode::Error<W::Error>> {
        match self.index {
            0 => e.encode(self.data.hidden),
            1 => e.encode(self.data.number),
            2 => e.encode(self.data.label.as_str()),
            3 => e.encode(self.data.list.as_slice()),
            _ => e.encode(()),
        }?;
        Ok(())
    }
}

impl FetchRenderable for MyCBOR {
    type FetchIn = u8;
    type FetchOut = MyCborView;

    fn fetch(&mut self, index: &Self::FetchIn) -> Result<Self::FetchOut, Error> {
        let data = self.clone();
        if data.hidden && *index >= 2 {
            // It'd be fun if we could also do granular reveal at GET, but that's more derive
            // hacking.
            return Err(Error::forbidden());
        }
        Ok(MyCborView {
            index: *index,
            data,
        })
    }
}

/// Build a handler that gives access to a single [MyCBOR] object
///
/// This can be built with on no_std, and thus has no external synchronization -- the MyCBOR is
/// owned by the handler, and can only be accessed through the CoAP interface. For an alternative,
/// see [double_cbor_with_access]().
pub fn single_cbor_tree() -> impl Handler + Reporting {
    let cbor: MyCBOR = Default::default();

    new_dispatcher()
        .at_with_attributes(
            &["cbor"],
            &[Attribute::Ct(60)],
            TypeHandler::new_minicbor_2(with_get_put_fetch(cbor)),
        )
        .with_wkc()
}

/// Build a handler that gives access to a single [MyCBOR] object on two paths, and to the rest of
/// the application
///
/// As the MyCBOR object is now stored in an Arc and referenced through a Mutex, it can be in two
/// places in the tree at the same time, and even be accessed by the application at the same time.
#[cfg(feature = "std")]
pub fn double_cbor_with_access() -> (
    impl Handler + Reporting,
    std::sync::Arc<std::sync::Mutex<MyCBOR>>,
) {
    let cbor: std::sync::Arc<std::sync::Mutex<MyCBOR>> = Default::default();

    let handler = new_dispatcher()
        .at_with_attributes(
            &["cbor", "1"],
            &[],
            TypeHandler::new_minicbor_2(with_get_put_fetch(TryingThroughMutex(cbor.clone()))),
        )
        .at_with_attributes(
            &["cbor", "2"],
            &[],
            TypeHandler::new_minicbor_2(with_get_put_fetch(TryingThroughMutex(cbor.clone()))),
        )
        .with_wkc();

    (handler, cbor)
}

/// Helper struct that accesses a TypeRenderable intrough an `Arc<Mutex<_>>`, thus allowing easiy
/// simultaneous access.
///
/// When implementing SimpelCBORHandler, it does not wait for the lock, but rather fails with a
/// 5.03 Service Unavailable that usually prompts the client to retry. Note that this will not
/// happen ever if the items are just accessed through different paths on the same handler, and
/// neither will be if they are only ever locked outside the CoAP server's main loop. (And even
/// then, unless they're locked for long, it's very unlikely to be hit by chance).
///
/// TBD: Send a "Max-Age: 0" option along to indicate that the client can try again right away
/// rather than wait the usual 60 seconds.
///
/// TBD: This may be a nice addition to the [coap_handler] crate in general (but needs the
/// introduction of a `std` feature there).
#[cfg(feature = "std")]
pub struct TryingThroughMutex<T>(pub std::sync::Arc<std::sync::Mutex<T>>);

#[cfg(feature = "std")]
impl<T: TypeRenderable> TypeRenderable for TryingThroughMutex<T> {}

#[cfg(feature = "std")]
impl<T: GetRenderable> GetRenderable for TryingThroughMutex<T> {
    type Get = T::Get;

    fn get(&mut self) -> Result<Self::Get, Error> {
        self.0
            .try_lock()
            .map_err(|_| Error::service_unavailable())?
            .get()
    }
}

#[cfg(feature = "std")]
impl<T: PutRenderable> PutRenderable for TryingThroughMutex<T> {
    type Put = T::Put;

    fn put(&mut self, new: &Self::Put) -> Result<(), Error> {
        self.0
            .try_lock()
            .map(|mut s| s.put(new))
            .unwrap_or(Err(Error::service_unavailable()))
    }
}

#[cfg(feature = "std")]
impl<T: PostRenderable> PostRenderable for TryingThroughMutex<T> {
    type PostIn = T::PostIn;
    type PostOut = T::PostOut;

    fn post(&mut self, request: &Self::PostIn) -> Result<Self::PostOut, Error> {
        self.0
            .try_lock()
            .map(|mut s| s.post(request))
            .unwrap_or(Err(Error::service_unavailable()))
    }
}

#[cfg(feature = "std")]
impl<T: DeleteRenderable> DeleteRenderable for TryingThroughMutex<T> {}

#[cfg(feature = "std")]
impl<T: FetchRenderable> FetchRenderable for TryingThroughMutex<T> {
    type FetchIn = T::FetchIn;
    type FetchOut = T::FetchOut;

    fn fetch(&mut self, request: &Self::FetchIn) -> Result<Self::FetchOut, Error> {
        self.0
            .try_lock()
            .map(|mut s| s.fetch(request))
            .unwrap_or(Err(Error::service_unavailable()))
    }
}

#[cfg(feature = "std")]
impl<T: IPatchRenderable> IPatchRenderable for TryingThroughMutex<T> {
    type IPatch = T::IPatch;

    fn ipatch(&mut self, new: &Self::IPatch) -> Result<(), Error> {
        self.0
            .try_lock()
            .map(|mut s| s.ipatch(new))
            .unwrap_or(Err(Error::service_unavailable()))
    }
}