riot_coap_handler_demos/
ps.rs

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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
//! Handlers for process exploration in RIOT

use coap_handler_implementations::{
    helpers::MaskingUriUpToPath, wkc, TypeHandler, TypeRenderable, TypeRequestData,
};
use coap_message_utils::{Error, OptionsExt};
use core::fmt::Debug;
use riot_wrappers::thread::KernelPID;

struct AllProcesses;

impl TypeRenderable for AllProcesses {
    type Get = Self;
    type Put = ();
    type Post = ();

    fn get(&mut self) -> Result<Self::Get, u8> {
        Ok(AllProcesses)
    }
}

impl minicbor::Encode<()> for AllProcesses {
    fn encode<W: minicbor::encode::Write>(
        &self,
        e: &mut minicbor::Encoder<W>,
        _ctx: &mut (),
    ) -> Result<(), minicbor::encode::Error<W::Error>> {
        e.begin_array()?;
        for pid in KernelPID::all_pids() {
            if pid.status().is_err() {
                // Serializing an absent one would fail -- can still race and fail, but that's the
                // nature of `ps`.
                continue;
            }
            e.encode(&OneProcess(pid))?;
        }
        e.end()?;
        Ok(())
    }
}

#[derive(Copy, Clone)]
struct OneProcess(KernelPID);

impl TypeRenderable for OneProcess {
    type Get = Self;
    type Put = ();
    type Post = ();

    fn get(&mut self) -> Result<Self::Get, u8> {
        Ok(*self)
    }
}

impl<C> minicbor::Encode<C> for OneProcess {
    fn encode<W: minicbor::encode::Write>(
        &self,
        e: &mut minicbor::Encoder<W>,
        _ctx: &mut C,
    ) -> Result<(), minicbor::encode::Error<W::Error>> {
        let pid = self.0;
        let stats = pid.stack_stats().ok();
        let status = pid
            .status()
            .map_err(|_| minicbor::encode::Error::message("Process just vanished"))?;

        #[derive(minicbor::Encode)]
        #[cbor(array)]
        struct Record<'a> {
            #[n(0)]
            pidnum: i16,
            #[cbor(n(1), encode_with = "debug_string")]
            status: riot_wrappers::thread::Status,
            #[n(2)]
            name: Option<&'a str>,
            #[n(3)]
            size: Option<usize>,
            #[n(4)]
            free: Option<usize>,
        }

        e.encode(Record {
            pidnum: pid.into(),
            status,
            name: pid.get_name(),
            size: stats.as_ref().map(|s| s.size()),
            free: stats.as_ref().map(|s| s.free()),
        })?;
        Ok(())
    }
}

/// Wrapper for Status (or anything else) that uses the Debug implementation to serialize a string
/// of it
///
/// (Of course, someone could just as well implement Serialize for the original type, but it seems
/// fair to assume this can't be generally assumed to be).
///
/// ## Panics
///
/// Use this only to wrap entities whose Debug output is no more than 16 bytes long!
fn debug_string<C, T: Debug, W: minicbor::encode::Write>(
    v: &T,
    e: &mut minicbor::Encoder<W>,
    _ctx: &mut C,
) -> Result<(), minicbor::encode::Error<W::Error>> {
    // The simple implementation should be this -- but that produces invalid CBOR in serde_cbor
    // 0.11.1, and getting updates there is tricky.
    // serializer.collect_str(&format_args!("{:?}", self.0))

    let mut s = heapless::String::<16>::new();
    use core::fmt::Write;
    write!(s, "{:?}", v).expect("Overflow");
    e.str(&s)?;
    Ok(())
}

/// Build a handler that will report a summary of the current processes in CBOR form
///
/// The precise format is subject to change, but currently produces an array of process records
/// each with the process ID, its state in text form, and the name (or null), followed by
/// stack size and stack free size (trailing unknown / inapplicable elements left out):
///
/// ```json
///
/// [[1, 'Pending', 'idle', 8192, 7592],
///  [2, 'Sleeping', 'main', 12288, 11044],
///  [3, 'ReceiveBlocked', 'ipv6', 8192, 6352],
///  [4, 'ReceiveBlocked', 'udp', 8192, 6928],
///  [5, 'Running', 'coap', 8276, 6604],
///  [6, 'ReceiveBlocked', 'gnrc_netdev_tap', 8192, 5716]]
/// ```
pub fn ps() -> impl coap_handler::Handler {
    TypeHandler::new_minicbor_0_24(AllProcesses)
}

/// Build a handler similar to the [`ps()`] built, but as a tree: Its root resource will report the
/// process list as `ps` does, but there will be additional per-PID resources for the processes
/// below it.
///
/// Unlike `ps`, this is not to be used with `.at()`, but with `.below()`.
///
/// (It can not be used as the root handler or `.below(&[])` because of the different ways trailing
/// slashes work in an empty and non-empyt prefix in CoAP and URI resolution; practically, this is
/// rarely a concern and could be addressed with a type parameter if needed. If you still want to
/// use it that way, an easy fix is to change the internal ROOT_NAME to something non-empty like
/// "all").
///
/// # Open issues
///
/// The output stability caveat of [`ps()`] applies.
///
/// In this implementation, the individual processes are *not* shown during resource discovery.
/// To conform with best practices around HATEOS and gradual reveal, the representation of the
/// entry resource should have explicit URI reference pointers to the individual process resources
/// (rather than letting the user guess that they can use the PID number as the final path
/// component).
///
/// This should be mitigated either by providing a Link Format representation in addition to the
/// CBOR one, or by using an integrated format like CoRAL.
pub fn ps_tree() -> impl coap_handler::Handler + coap_handler::Reporting {
    const ROOT_NAME: &str = "";

    // FIXME: This needs a lot of automation to be viable for manual use; in particular, we're
    // taking a shortcut knowing that it's all TypeRequestData and no POST/PUT is supported
    // (so we can manually select a single RequestData implementation rather than have it
    // enum-dynamic-dispatch into variants, for which I'd be curious whether the compiler would
    // manage to deduplicate them)

    #[derive(Copy, Clone)]
    enum PathState {
        Empty,
        Root,
        Id(KernelPID),
        Derailed,
    }

    use PathState::*;

    impl PathState {
        fn feed(&mut self, segment: &str) {
            *self = match (*self, segment, segment.parse()) {
                (Empty, ROOT_NAME, _) => Root,
                (Empty, _, Ok(n)) => KernelPID::new(n).map(|pid| Id(pid)).unwrap_or(Derailed),
                _ => Derailed,
            };
        }
    }

    struct PsTree;

    impl coap_handler::Handler for PsTree {
        type RequestData = (PathState, TypeRequestData);

        type ExtractRequestError = Error;
        type BuildResponseError<M: coap_message::MinimalWritableMessage> = Error;

        fn extract_request_data<M: coap_message::ReadableMessage>(
            &mut self,
            m: &M,
        ) -> Result<Self::RequestData, Self::ExtractRequestError> {
            let mut res = Empty;

            // Ignoring output as we don't do *any* responding on our own.
            let _ = m
                .options()
                .take_uri_path(|p| res.feed(p))
                .ignore_elective_others();

            Ok((
                res,
                TypeHandler::new_minicbor_0_24(AllProcesses)
                    .extract_request_data(&MaskingUriUpToPath(m))?,
            ))
        }
        fn estimate_length(&mut self, rd: &Self::RequestData) -> usize {
            match rd.0 {
                // Could be long
                Root => 1025,
                // Probably not so long
                Id(_) => 200,
                // Maybe an error message
                _ => 20,
            }
        }
        fn build_response<M: coap_message::MutableWritableMessage>(
            &mut self,
            m: &mut M,
            rd: Self::RequestData,
        ) -> Result<(), Error> {
            match rd.0 {
                // FIXME enhance error propagation
                Root => TypeHandler::new_minicbor_0_24(AllProcesses)
                    .build_response(m, rd.1)
                    .map_err(|_| Error::internal_server_error())?,
                Id(n) => TypeHandler::new_minicbor_0_24(OneProcess(n))
                    .build_response(m, rd.1)
                    .map_err(|_| Error::internal_server_error())?,
                _ => return Err(Error::not_found()),
            };

            Ok(())
        }
    }

    wkc::ConstantSingleRecordReport::new_with_path(
        PsTree,
        &[coap_handler::Attribute::Title("Process list")],
        &[ROOT_NAME],
    )
}