1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! 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_utils::Error;
use coap_message::{Code, MinimalWritableMessage};
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(())
    }
}