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],
)
}