coap-message-demos 0.5.0

Demos of the coap-message ecosystem
Documentation
//! Logging demo application
//!
//! This contains three components, from the most universal to the most demo-like:
//!
//! * A ringbuffered implementation of the [log] interface (based on [scroll-ring])
//! * A way of accessing messages logged into this from CoAP
//! * A way of injecting arbitrary log messages through CoAP
//!
//! It's implemented in a rather stupid-but-straightforward fashion; a better implementation would
//! would likely use [defmt](https://docs.rs/defmt/).
//!
//!
//! There is no separate handler builder for all the implemented handlers; see the top-level
//! [crate::full_application_tree] builder for one that includes everything from here.

use coap_message::{Code, MinimalWritableMessage};
use coap_message_utils::Error;
use coap_numbers::code;

/// A ring-buffered log structure
pub struct Log {
    data: scroll_ring::Buffer<1024>,
}

impl Log {
    pub fn new() -> Self {
        Self {
            data: Default::default(),
        }
    }

    pub fn init(&'static self) -> Result<(), log::SetLoggerError> {
        log::set_logger(self)?;
        log::set_max_level(log::LevelFilter::Info);
        Ok(())
    }

    /// Allocate a log in static memory, and set it up
    pub fn start_once() -> &'static Self {
        use static_cell::StaticCell;
        static S: StaticCell<Log> = StaticCell::new();

        let s = S.init_with(|| Self::new());
        s.init()
            .expect("No other log must be set before calling start_once");
        s
    }

    /// Obtain a [coap_handler::Handler] implementation from the Log
    ///
    /// This is exposed as a separate type rather than on Log in order to reuse the same handler
    /// implementation for more than just the log.
    pub fn handler(&self) -> coap_scroll_ring_server::BufferHandler<'_, 1024> {
        coap_scroll_ring_server::BufferHandler::new(&self.data)
    }
}

impl log::Log for Log {
    fn enabled(&self, metadata: &log::Metadata) -> bool {
        metadata.level() <= log::Level::Info
    }

    fn log(&self, record: &log::Record) {
        if self.enabled(record.metadata()) {
            // This is terribly lock-unlock-y; not doing anything about it because both structured
            // logging and getting-an-actual-lock-on-something are better options -- this is, after
            // all, primarily emulating what stdout does.
            struct WriteWrapper<'a>(&'a scroll_ring::Buffer<1024>);
            impl<'a> core::fmt::Write for WriteWrapper<'a> {
                fn write_str(&mut self, s: &str) -> core::fmt::Result {
                    self.0.write(s.as_bytes());
                    Ok(())
                }
            }

            use core::fmt::Write;
            writeln!(
                WriteWrapper(&self.data),
                "{} {}",
                record.level(),
                record.args()
            )
            .unwrap();
        }
    }

    fn flush(&self) {}
}

/// CoAP handler that accepts POST requests and dumps the request's plain-text payload into the log
/// at the configured log level
pub struct LogMessagePostHandler {
    level: log::Level,
}

impl LogMessagePostHandler {
    pub fn new_info() -> Self {
        Self {
            level: log::Level::Info,
        }
    }

    pub fn new_warn() -> Self {
        Self {
            level: log::Level::Warn,
        }
    }
}

impl coap_handler::Handler for LogMessagePostHandler {
    type ExtractRequestError = Error;
    type BuildResponseError<M: MinimalWritableMessage> = M::UnionError;
    type RequestData = ();

    fn extract_request_data<M: coap_message::ReadableMessage>(
        &mut self,
        request: &M,
    ) -> Result<Self::RequestData, Error> {
        use coap_message_utils::OptionsExt;

        match request.code().into() {
            code::POST => (),
            _ => Err(Error::method_not_allowed())?,
        }

        request.options().ignore_elective_others()?;

        let payload = core::str::from_utf8(request.payload())
            .map_err(|e| Error::bad_request_with_rbep(e.valid_up_to()))?;

        use log::log;
        log!(self.level, "{}", payload);
        Ok(())
    }
    fn estimate_length(&mut self, _: &Self::RequestData) -> usize {
        8
    }
    fn build_response<M: coap_message::MutableWritableMessage>(
        &mut self,
        response: &mut M,
        _request: (),
    ) -> Result<(), M::UnionError> {
        response.set_code(M::Code::new(code::CHANGED)?);
        Ok(())
    }
}