diff --git a/Cargo.toml b/Cargo.toml index d1ec0417..8c2c5bc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,14 +35,14 @@ rand_core_06 = { package = "rand_core", version = "^0.6" } # For nimble UUID parsing and some debug implementations hex = { version = "^0.4.3", default-features = false } -coap-numbers = { version = "^0.2.0", optional = true } +coap-numbers = "^0.2.0" embedded-graphics = "0.6" -# Originally disabled by default because they were not published yet; now -# disabled by default we're moving toward opt-in modules anyway. -coap-message = { version = "^0.2.3", optional = true } -coap-handler = { version = "^0.1.4", optional = true } +coap-message-0-2 = { package = "coap-message", version = "^0.2.3" } +coap-message-0-3 = { package = "coap-message", version = "^0.3.0" } +coap-handler-0-1 = { package = "coap-handler", version = "^0.1.4" } +coap-handler-0-2 = { package = "coap-handler", version = "^0.2.0" } embedded-nal = { version = "0.6.0", optional = true } embedded-nal-tcpextensions = { version = "0.1", optional = true } pin-utils = "0.1" @@ -69,10 +69,8 @@ panic_handler_format = [] # only affects that single thread. panic_handler_crash = [] -# If these are present, traits for the respective optional dependencies will be -# implemented. -with_coap_message = ["coap-message" ] -with_coap_handler = ["coap-handler", "coap-numbers", "with_coap_message"] +with_coap_message = [] +with_coap_handler = [] with_embedded_nal = ["embedded-nal", "embedded-nal-tcpextensions"] # See msg::v2 documentation. Enabling this exposes components not under semver diff --git a/src/coap_handler/mod.rs b/src/coap_handler/mod.rs new file mode 100644 index 00000000..65d33a7f --- /dev/null +++ b/src/coap_handler/mod.rs @@ -0,0 +1,8 @@ +//! This module provides a wrappers around a coap_handler::Handler in different versions, all of +//! which can be registered at a RIOT GcoapHandler. + +pub mod v0_1; +pub mod v0_2; + +#[deprecated(note = "Use through the v0_1 module.")] +pub use v0_1::*; diff --git a/src/coap_handler.rs b/src/coap_handler/v0_1.rs similarity index 88% rename from src/coap_handler.rs rename to src/coap_handler/v0_1.rs index 3123d5ae..e64bfbbe 100644 --- a/src/coap_handler.rs +++ b/src/coap_handler/v0_1.rs @@ -1,8 +1,11 @@ -/// This module provides a wrapper around a coap_handler::Handler that can be registered at a RIOT -/// GcoapHandler. +//! This module provides a wrapper around a coap_handler::Handler that can be registered at a RIOT +//! GcoapHandler. + use core::convert::TryInto; -use coap_message::{MutableWritableMessage, ReadableMessage}; +use coap_handler_0_1::{Attribute, Handler, Record, Reporting}; + +use coap_message_0_2::{MutableWritableMessage, ReadableMessage}; use crate::coap_message::ResponseMessage; use crate::gcoap::PacketBuffer; @@ -11,11 +14,11 @@ use crate::gcoap::PacketBuffer; /// to register it through a [crate::gcoap::SingleHandlerListener]. pub struct GcoapHandler(pub H) where - H: coap_handler::Handler; + H: Handler; impl crate::gcoap::Handler for GcoapHandler where - H: coap_handler::Handler, + H: Handler, { fn handle(&mut self, pkt: &mut PacketBuffer) -> isize { let request_data = self.0.extract_request_data(pkt); @@ -27,10 +30,9 @@ where impl crate::gcoap::WithLinkEncoder for GcoapHandler where - H: coap_handler::Handler + coap_handler::Reporting, + H: 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"<"); @@ -46,7 +48,7 @@ where writer.write(b"\""); } for attr in record.attributes() { - use coap_handler::Attribute::*; + use Attribute::*; match attr { Observable => writer.write(b";obs"), Interface(i) => { @@ -71,9 +73,9 @@ where /// Blanket implementation for mutex wrapped resources /// /// This is useful in combination with the defauilt implementation for Option as well. -impl<'b, H> coap_handler::Handler for &'b crate::mutex::Mutex +impl<'b, H> Handler for &'b crate::mutex::Mutex where - H: coap_handler::Handler, + H: Handler, { type RequestData = Option; diff --git a/src/coap_handler/v0_2.rs b/src/coap_handler/v0_2.rs new file mode 100644 index 00000000..d1fcb59c --- /dev/null +++ b/src/coap_handler/v0_2.rs @@ -0,0 +1,146 @@ +//! This module provides a wrapper around a coap_handler::Handler that can be registered at a RIOT +//! GcoapHandler. + +use core::convert::TryInto; + +use coap_handler_0_2::{Attribute, Handler, Record, Reporting}; + +use coap_message_0_3::{ + error::RenderableOnMinimal, + MinimalWritableMessage, + MutableWritableMessage, + ReadableMessage, +}; + +use crate::coap_message::ResponseMessage; +use crate::gcoap::PacketBuffer; + +/// Adapter to get a [crate::gcoap::Handler] from a more generic [coap_handler::Handler], typically +/// to register it through a [crate::gcoap::SingleHandlerListener]. +pub struct GcoapHandler(pub H) +where + H: Handler; + +impl crate::gcoap::Handler for GcoapHandler +where + H: Handler, +{ + fn handle(&mut self, pkt: &mut PacketBuffer) -> isize { + let request_data = self.0.extract_request_data(pkt); + let mut lengthwrapped = ResponseMessage::new(pkt); + match request_data { + Ok(r) => { + if let Err(e) = self.0.build_response(&mut lengthwrapped, r) { + lengthwrapped.rewind(); + if let Err(_e2) = e.render(&mut lengthwrapped) { + // Render double fault + lengthwrapped.rewind(); + lengthwrapped.set_code(coap_numbers::code::INTERNAL_SERVER_ERROR); + } + } + } + Err(e) => { + if let Err(_e2) = e.render(&mut lengthwrapped) { + // Render double fault + lengthwrapped.rewind(); + lengthwrapped.set_code(coap_numbers::code::INTERNAL_SERVER_ERROR); + } + } + } + lengthwrapped.finish() + } +} + +impl crate::gcoap::WithLinkEncoder for GcoapHandler +where + H: Handler + Reporting, +{ + fn encode(&self, writer: &mut crate::gcoap::LinkEncoder) { + 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 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. +impl<'b, H> Handler for &'b crate::mutex::Mutex +where + H: Handler, +{ + type RequestData = Option; + + type ExtractRequestError = H::ExtractRequestError; + type BuildResponseError = H::BuildResponseError; + + fn extract_request_data( + &mut self, + request: &M, + ) -> Result { + self.try_lock() + .map(|mut h| h.extract_request_data(request)) + .transpose() + } + + fn estimate_length(&mut self, request: &Self::RequestData) -> usize { + if let Some(r) = request { + if let Some(mut s) = self.try_lock() { + return s.estimate_length(r); + } + } + + 1 + } + + fn build_response( + &mut self, + response: &mut M, + request: Self::RequestData, + ) -> Result<(), Self::BuildResponseError> { + if let Some(r) = request { + if let Some(mut s) = self.try_lock() { + return s.build_response(response, r); + } + } + + response.set_code( + coap_numbers::code::SERVICE_UNAVAILABLE + .try_into() + .map_err(|_| "Message type can't even exprss Service Unavailable") + .unwrap(), + ); + Ok(()) + } +} diff --git a/src/coap_message.rs b/src/coap_message/impl_0_2.rs similarity index 52% rename from src/coap_message.rs rename to src/coap_message/impl_0_2.rs index 6a97b0ea..af0fc110 100644 --- a/src/coap_message.rs +++ b/src/coap_message/impl_0_2.rs @@ -1,19 +1,12 @@ -/// This module implements coap_message::ReadableMessage for, and a wrapper that provides -/// coap_message::WritableMessage around RIOT's coap_pkt_t. -use crate::gcoap::{PacketBuffer, PacketBufferOptIter}; -use coap_message::{ +use coap_message_0_2::{ + MessageOption, MinimalWritableMessage, MutableWritableMessage, ReadableMessage, WithSortedOptions, }; -pub struct MessageOption<'a> { - number: u16, - value: &'a [u8], -} - -impl<'a> coap_message::MessageOption for MessageOption<'a> { +impl<'a> MessageOption for super::MessageOption<'a> { fn number(&self) -> u16 { self.number } @@ -23,27 +16,14 @@ impl<'a> coap_message::MessageOption for MessageOption<'a> { } } -pub struct OptionsIterator<'a>(PacketBufferOptIter<'a>); -impl<'a> Iterator for OptionsIterator<'a> { - type Item = MessageOption<'a>; - - fn next(&mut self) -> Option { - let (opt_num, slice) = self.0.next()?; - Some(MessageOption { - number: opt_num, - value: slice, - }) - } -} - -impl WithSortedOptions for PacketBuffer { +impl WithSortedOptions for super::PacketBuffer { // valid because gcoap just reads options from the message where they are stored in sequence } -impl ReadableMessage for PacketBuffer { +impl ReadableMessage for super::PacketBuffer { type Code = u8; - type OptionsIter<'a> = OptionsIterator<'a>; - type MessageOption<'a> = MessageOption<'a>; + type OptionsIter<'a> = super::OptionsIterator<'a>; + type MessageOption<'a> = super::MessageOption<'a>; fn code(&self) -> Self::Code { self.get_code_raw() @@ -54,35 +34,11 @@ impl ReadableMessage for PacketBuffer { } fn options(&self) -> Self::OptionsIter<'_> { - OptionsIterator(self.opt_iter()) - } -} - -pub struct ResponseMessage<'a> { - message: &'a mut PacketBuffer, - payload_written: Option, -} - -impl<'a> ResponseMessage<'a> { - pub fn new(buf: &'a mut PacketBuffer) -> Self { - // Can't really err; FIXME ensure that such a check won't affect ROM too much - buf.resp_init(5 << 5).unwrap(); - - ResponseMessage { - message: buf, - payload_written: None, - } - } - - pub fn finish(&self) -> isize { - self.message.get_length(match self.payload_written { - None => 0, - Some(x) => x + 1, - }) as isize + super::OptionsIterator(self.opt_iter()) } } -impl<'a> MinimalWritableMessage for ResponseMessage<'a> { +impl<'a> MinimalWritableMessage for super::ResponseMessage<'a> { type Code = u8; type OptionNumber = u16; @@ -105,7 +61,7 @@ impl<'a> MinimalWritableMessage for ResponseMessage<'a> { } } -impl<'a> MutableWritableMessage for ResponseMessage<'a> { +impl<'a> MutableWritableMessage for super::ResponseMessage<'a> { fn available_space(&self) -> usize { self.message.payload().len() } diff --git a/src/coap_message/impl_0_3.rs b/src/coap_message/impl_0_3.rs new file mode 100644 index 00000000..7b0c1730 --- /dev/null +++ b/src/coap_message/impl_0_3.rs @@ -0,0 +1,132 @@ +use coap_message_0_3::{ + error::RenderableOnMinimal, + Code, + MessageOption, + MinimalWritableMessage, + MutableWritableMessage, + ReadableMessage, + WithSortedOptions, +}; + +use crate::error::NumericError; + +/// Thin wrapper around NumericError that can render and satisfies all conversion requirements +#[derive(Debug)] +pub struct Error(NumericError); + +impl From for Error { + fn from(e: NumericError) -> Error { + Error(e) + } +} + +impl From for Error { + fn from(e: core::convert::Infallible) -> Error { + match e {} + } +} + +impl RenderableOnMinimal for Error { + type Error = IE; + fn render(self, msg: &mut M) -> Result<(), M::UnionError> { + msg.set_code(Code::new(coap_numbers::code::INTERNAL_SERVER_ERROR)?); + Ok(()) + } +} + +impl<'a> MessageOption for super::MessageOption<'a> { + fn number(&self) -> u16 { + self.number + } + + fn value(&self) -> &[u8] { + self.value + } +} + +impl WithSortedOptions for super::PacketBuffer { + // valid because gcoap just reads options from the message where they are stored in sequence +} + +impl ReadableMessage for super::PacketBuffer { + type Code = u8; + type OptionsIter<'a> = super::OptionsIterator<'a>; + type MessageOption<'a> = super::MessageOption<'a>; + + fn code(&self) -> Self::Code { + self.get_code_raw() + } + + fn payload(&self) -> &[u8] { + self.payload() + } + + fn options(&self) -> Self::OptionsIter<'_> { + super::OptionsIterator(self.opt_iter()) + } +} + +impl<'a> MinimalWritableMessage for super::ResponseMessage<'a> { + type Code = u8; + type OptionNumber = u16; + + type AddOptionError = Error; + type SetPayloadError = Error; + type UnionError = Error; + + fn set_code(&mut self, code: Self::Code) { + self.message.set_code_raw(code); + } + + fn add_option(&mut self, number: Self::OptionNumber, value: &[u8]) -> Result<(), Error> { + if self.payload_written.is_some() { + return Err(NumericError::from_constant(riot_sys::EINVAL as _).into()); + } + self.message.opt_add_opaque(number.into(), value)?; + Ok(()) + } + + fn set_payload(&mut self, data: &[u8]) -> Result<(), Error> { + self.payload_mut_with_len(data.len())?.copy_from_slice(data); + Ok(()) + } +} + +impl<'a> MutableWritableMessage for super::ResponseMessage<'a> { + fn available_space(&self) -> usize { + self.message.payload().len() + } + + fn payload_mut_with_len(&mut self, len: usize) -> Result<&mut [u8], Error> { + self.payload_written = Some(len); + let payload = self.message.payload_mut(); + if let Some((pm, pl)) = payload.get_mut(..len + 1).and_then(<[u8]>::split_first_mut) { + *pm = 0xff; + Ok(pl) + } else { + Err(NumericError::from_constant(riot_sys::EINVAL as _).into()) + } + } + + fn truncate(&mut self, len: usize) -> Result<(), Error> { + if self.payload_written.is_none() { + // payload() will not even return anything sensible yet + return Err(NumericError::from_constant(riot_sys::EINVAL as _).into()); + } + let pl_len = self.message.payload().len() - 1; + if len > pl_len { + return Err(NumericError::from_constant(riot_sys::EINVAL as _).into()); + } + self.payload_written = Some(len); + Ok(()) + } + + fn mutate_options(&mut self, mut callback: F) + where + F: FnMut(Self::OptionNumber, &mut [u8]), + { + for (opt_num, slice) in self.message.opt_iter_mut() { + callback(opt_num.into(), slice); + } + } +} diff --git a/src/coap_message/mod.rs b/src/coap_message/mod.rs new file mode 100644 index 00000000..5140e54a --- /dev/null +++ b/src/coap_message/mod.rs @@ -0,0 +1,55 @@ +//! This module implements [coap_message::ReadableMessage] for, and a wrapper that provides +//! [coap_message::WritableMessage] around RIOT's coap_pkt_t. + +mod impl_0_2; +mod impl_0_3; + +use crate::gcoap::{PacketBuffer, PacketBufferOptIter}; + +pub struct MessageOption<'a> { + number: u16, + value: &'a [u8], +} + +pub struct OptionsIterator<'a>(PacketBufferOptIter<'a>); +impl<'a> Iterator for OptionsIterator<'a> { + type Item = MessageOption<'a>; + + fn next(&mut self) -> Option { + let (opt_num, slice) = self.0.next()?; + Some(MessageOption { + number: opt_num, + value: slice, + }) + } +} + +pub struct ResponseMessage<'a> { + /// Note that this is a slightly weird version of PacketBuffer, where opt_finish is never + /// called, and .payload() perpetually reports the payload marker as part of the payload. + message: &'a mut PacketBuffer, + payload_written: Option, +} + +impl<'a> ResponseMessage<'a> { + pub fn new(buf: &'a mut PacketBuffer) -> Self { + // Can't really err; FIXME ensure that such a check won't affect ROM too much + buf.resp_init(5 << 5).unwrap(); + + ResponseMessage { + message: buf, + payload_written: None, + } + } + + pub(crate) fn rewind(&mut self) { + self.message.resp_init(5 << 5).unwrap(); + } + + pub fn finish(&self) -> isize { + self.message.get_length(match self.payload_written { + None => 0, + Some(x) => x + 1, + }) as isize + } +} diff --git a/src/lib.rs b/src/lib.rs index f922a16b..97e5826c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,7 @@ )] // Primarily for documentation, see feature docs #![cfg_attr(feature = "actual_never_type", feature(never_type))] -#![cfg_attr(feature = "nightly_docs", feature(fundamental))] +#![cfg_attr(feature = "nightly_docs", feature(fundamental, doc_auto_cfg))] /// riot-sys is re-exported here as it is necessary in some places when using it to get values (eg. /// in [error::NumericError::from_constant]). It is also used in macros such as [static_command!]. @@ -143,9 +143,9 @@ pub mod rwlock; #[cfg(feature = "set_panic_handler")] mod panic; -#[cfg(feature = "with_coap_handler")] +#[cfg(riot_module_gcoap)] pub mod coap_handler; -#[cfg(feature = "with_coap_message")] +#[cfg(riot_module_gcoap)] pub mod coap_message; #[cfg(riot_module_sock)]