Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gcoap: Provide link encoder #47

Merged
merged 2 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/coap_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,49 @@ where
}
}

impl<H> crate::gcoap::WithLinkEncoder for GcoapHandler<H>
where
H: coap_handler::Handler + coap_handler::Reporting,
{
fn encode(&self, writer: &mut crate::gcoap::LinkEncoder) {
use coap_handler::Record;
for record in self.0.report() {
writer.write_comma_maybe();
writer.write(b"<");
for pathelement in record.path() {
writer.write(b"/");
writer.write(pathelement.as_ref().as_bytes());
}
writer.write(b">");
if let Some(rel) = record.rel() {
// Not trying to be smart about whether or not we need the quotes
writer.write(b";rel=\"");
writer.write(rel.as_bytes());
writer.write(b"\"");
}
for attr in record.attributes() {
use coap_handler::Attribute::*;
match attr {
Observable => writer.write(b";obs"),
Interface(i) => {
writer.write(b";if=\"");
writer.write(i.as_bytes());
writer.write(b"\"");
}
ResourceType(r) => {
writer.write(b";rt=\"");
writer.write(r.as_bytes());
writer.write(b"\"");
}
// FIXME: deduplicate with what's somewhere in coap-handler-implementations;
// implement remaining items
_ => (),
}
}
}
}
}

/// Blanket implementation for mutex wrapped resources
///
/// This is useful in combination with the defauilt implementation for Option as well.
Expand Down
157 changes: 148 additions & 9 deletions src/gcoap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ use crate::error::NegativeErrorExt;
use core::convert::TryInto;
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use riot_sys::libc::c_void;
use riot_sys::libc;
use riot_sys::{coap_optpos_t, coap_pkt_t, gcoap_listener_t};

use riot_sys::coap_resource_t;

#[cfg(marker_coap_request_ctx_t)]
type HandlerArg4 = riot_sys::coap_request_ctx_t;
#[cfg(not(marker_coap_request_ctx_t))]
type HandlerArg4 = c_void;
type HandlerArg4 = libc::c_void;

/// Give the caller a way of registering Gcoap handlers into the global Gcoap registry inside a
/// callback. When the callback terminates, the registered handlers are deregistered again,
Expand Down Expand Up @@ -113,6 +113,22 @@ where
{
// keeping methods u32 because the sys constants are too
pub fn new(path: &'a core::ffi::CStr, methods: u32, handler: &'a mut H) -> Self {
Self::_new(path, methods, handler, None)
}

fn _new(
path: &'a core::ffi::CStr,
methods: u32,
handler: &'a mut H,
encoder: Option<
unsafe extern "C" fn(
*const riot_sys::coap_resource_t,
*mut libc::c_char,
riot_sys::size_t,
*mut riot_sys::coap_link_encoder_ctx_t,
) -> i32,
>,
) -> Self {
let methods = methods.try_into().unwrap();

SingleHandlerListener {
Expand All @@ -121,18 +137,13 @@ where
path: path.as_ptr() as _,
handler: Some(Self::call_handler),
methods: methods,
context: handler as *mut _ as *mut c_void,
context: handler as *mut _ as *mut libc::c_void,
},
listener: gcoap_listener_t {
resources: 0 as *const _,
resources_len: 0,
next: 0 as *mut _,
// FIXME expose -- or tell people to write their own .wk/c, leave this NULL or even
// no-op (which ain't NULL) and expose the encoding mechanism for extension in an
// own .wk/c writer
//
// Works both for older versions without request_matcher and for current ones
link_encoder: None,
link_encoder: encoder,
..Default::default()
},
}
Expand Down Expand Up @@ -185,6 +196,80 @@ where
}
}

unsafe extern "C" fn link_encoder<H: WithLinkEncoder>(
resource: *const riot_sys::coap_resource_t,
buf: *mut libc::c_char,
buf_len: riot_sys::size_t,
ctx: *mut riot_sys::coap_link_encoder_ctx_t,
) -> i32 {
// We're a SingleHandlerListener, therefore we only have a single resource and can
// back-track to Self
// (But we don't need this)
/*
let self_ = unsafe {
&*((resource as *const u8)
.offset(-(memoffset::offset_of!(SingleHandlerListener<H>, resource) as isize))
as *const SingleHandlerListener<H>)
};
*/

let h: &H = unsafe { &*((*resource).context as *const _) };

let buf = buf as *mut u8; // cast away signedness of char
let mut buf = if buf.is_null() {
None
} else {
Some(core::slice::from_raw_parts_mut(buf, buf_len as _))
};

link_encoder_safe(h, buf, unsafe { &mut *ctx })
}

fn link_encoder_safe<H: WithLinkEncoder>(
h: &H,
mut buf: Option<&mut [u8]>,
ctx: &mut riot_sys::coap_link_encoder_ctx_t,
) -> i32 {
let mut writer = LinkEncoder::new(buf.as_deref_mut(), ctx);
h.encode(&mut writer);
let written = writer.written();
drop(writer);

if let Some(buf) = buf.as_ref() {
if written > buf.len() {
// An odd way to say .rfind(), but there's no such function on slices
if let Some((i, _)) = buf.windows(2).enumerate().rfind(|(_, x)| x == b",<") {
// Knowing the syntax of the produced data, we find a point at which we can
// salvage some records. (The rest is lost for lack of a blockwise-enabled
// interface).
i as _
} else {
-1
}
} else {
written as _
}
} else {
written as _
}
}


impl<'a, H> SingleHandlerListener<'a, H>
where
H: 'a + Handler + WithLinkEncoder,
{
/// Like [`new()`], but utilizing that the handler is also [WithLinkEncoder] and can thus influence
/// what is reported when the default .well-known/core handler is queried.
pub fn new_with_link_encoder(
path: &'a core::ffi::CStr,
methods: u32,
handler: &'a mut H,
) -> Self {
Self::_new(path, methods, handler, Some(link_encoder::<H>))
}
}

impl<'a, H> ListenerProvider for SingleHandlerListener<'a, H>
where
H: 'a + Handler,
Expand All @@ -204,6 +289,60 @@ pub trait Handler {
fn handle(&mut self, pkt: &mut PacketBuffer) -> isize;
}

/// The message buffer of a .well-known/core file in appication/link-format, as it is passed to a
/// [WithLinkEncoder] handler.
pub struct LinkEncoder<'a> {
cursor: usize,
buffer: Option<&'a mut [u8]>,
context: &'a mut riot_sys::coap_link_encoder_ctx_t,
}

impl<'a> LinkEncoder<'a> {
fn new(
buffer: Option<&'a mut [u8]>,
context: &'a mut riot_sys::coap_link_encoder_ctx_t,
) -> Self {
Self {
cursor: 0,
buffer,
context,
}
}

fn written(&self) -> usize {
self.cursor
}

/// Emit a comma, except the first time this is called
///
/// (This is the separator of the records of application/link-format; RIOT's )
pub fn write_comma_maybe(&mut self) {
const COAP_LINK_FLAG_INIT_RESLIST: u16 = riot_sys::COAP_LINK_FLAG_INIT_RESLIST as _;
if self.context.flags & COAP_LINK_FLAG_INIT_RESLIST != 0 {
self.context.flags = self.context.flags & !COAP_LINK_FLAG_INIT_RESLIST;
return;
}
self.write(b",");
}

/// Emit arbitrary bytes
pub fn write(&mut self, data: &[u8]) {
if let Some(buffer) = self.buffer.as_mut() {
if self.cursor <= buffer.len() {
let usable = data.len().min(buffer.len() - self.cursor);
buffer[self.cursor..self.cursor + usable].copy_from_slice(&data[..usable]);
}
self.cursor += data.len();
} else {
self.cursor += data.len();
}
}
}

pub trait WithLinkEncoder {
fn encode(&self, buf: &mut LinkEncoder);
}

use riot_sys::{
coap_get_total_hdr_len,
coap_opt_add_opaque,
Expand Down