#![no_std]
use coap_message::{
Code as _, MinimalWritableMessage, MutableWritableMessage, OptionNumber as _, ReadableMessage,
};
use coap_message_utils::Error;
use coap_numbers::{code, content_format, option};
use core::num::Wrapping;
pub struct BufferHandler<'a, const N: usize>(&'a scroll_ring::Buffer<N>);
impl<'a, const N: usize> BufferHandler<'a, N> {
pub fn new(buffer: &'a scroll_ring::Buffer<N>) -> Self {
Self(buffer)
}
}
#[doc(hidden)]
pub enum RequestedScrollbufPosition {
FromStart,
StartingAt(Wrapping<u32>),
}
#[doc(hidden)]
#[derive(Debug)]
pub enum BuildResponseError<SE: coap_message::error::RenderableOnMinimal + core::fmt::Debug> {
Own(Error),
Stack(SE),
}
impl<SE: coap_message::error::RenderableOnMinimal + core::fmt::Debug>
coap_message::error::RenderableOnMinimal for BuildResponseError<SE>
{
type Error<IE: coap_message::error::RenderableOnMinimal + core::fmt::Debug> = Error;
fn render<M: MinimalWritableMessage>(
self,
message: &mut M,
) -> Result<(), Self::Error<M::UnionError>> {
match self {
BuildResponseError::Stack(se) => se
.render(message)
.map_err(|_| coap_message_utils::Error::internal_server_error())?,
BuildResponseError::Own(e) => e
.render(message)
.map_err(|_| coap_message_utils::Error::internal_server_error())?,
}
Ok(())
}
}
use RequestedScrollbufPosition::*;
impl<'a, const N: usize> coap_handler::Handler for BufferHandler<'a, N> {
type RequestData = RequestedScrollbufPosition;
type ExtractRequestError = Error;
type BuildResponseError<M: MinimalWritableMessage> = BuildResponseError<M::UnionError>;
fn extract_request_data<M: ReadableMessage>(
&mut self,
req: &M,
) -> Result<RequestedScrollbufPosition, Error> {
use coap_message::MessageOption;
use coap_message_utils::OptionsExt;
req.options()
.filter(|o| {
!(o.number() == option::ACCEPT
&& o.value_uint::<u16>()
== Some(content_format::from_str("application/cbor-seq").unwrap()))
})
.ignore_elective_others()?;
Ok(match req.code().into() {
code::GET => FromStart,
code::FETCH => StartingAt(Wrapping({
let payload = req.payload();
let (start, bytes_consumed) = match payload.get(0) {
Some(i @ 0..=23) => (Some((*i).into()), 1),
Some(24) => (payload.get(1).map(|&n| n.into()), 2),
Some(25) => (
payload
.get(1..3)
.map(|b| u16::from_be_bytes(b.try_into().unwrap()))
.map(u32::from),
3,
),
Some(26) => (
payload
.get(1..5)
.map(|b| u32::from_be_bytes(b.try_into().unwrap())),
5,
),
Some(_) => return Err(Error::bad_request_with_rbep(0)),
None => return Err(Error::bad_request()),
};
let Some(start) = start else {
return Err(Error::bad_request_with_rbep(payload.len()));
};
if bytes_consumed != payload.len() {
return Err(Error::bad_request_with_rbep(bytes_consumed));
}
start
})),
_ => return Err(Error::method_not_allowed()),
})
}
fn estimate_length(&mut self, _: &<Self as coap_handler::Handler>::RequestData) -> usize {
1100
}
fn build_response<M: MutableWritableMessage>(
&mut self,
res: &mut M,
mode: RequestedScrollbufPosition,
) -> Result<(), Self::BuildResponseError<M>> {
res.set_code(M::Code::new(code::CONTENT).map_err(|e| BuildResponseError::Stack(e.into()))?);
res.add_option_uint(
M::OptionNumber::new(option::CONTENT_FORMAT)
.map_err(|e| BuildResponseError::Stack(e.into()))?,
content_format::from_str("application/cbor-seq").unwrap(),
)
.map_err(|e| BuildResponseError::Stack(e.into()))?;
let len = res.available_space() - 1;
let msg_buf = res
.payload_mut_with_len(len)
.map_err(|e| BuildResponseError::Stack(e.into()))?;
assert!(len > 8);
msg_buf[0] = 0x1a; msg_buf[5] = 0x59; let data_area = &mut msg_buf[8..];
let (cursor, bytes) = match mode {
FromStart => match self.0.read_earliest(data_area) {
Ok((cursor, bytes)) => (cursor, bytes),
_ => {
return Err(BuildResponseError::Own(
Error::service_unavailable().with_max_age(0),
));
}
},
StartingAt(n) => match self.0.read_from_cursor(n, data_area) {
Ok(bytes) => (n, bytes),
Err(scroll_ring::ReadErr::BufferUnavailable) => {
return Err(BuildResponseError::Own(
Error::service_unavailable().with_max_age(0),
));
}
Err(scroll_ring::ReadErr::DataUnavailable) => {
return Err(BuildResponseError::Own(Error::bad_request()));
}
},
};
let bytes_written = core::cmp::min(bytes, data_area.len());
msg_buf[1..5].copy_from_slice(&cursor.0.to_be_bytes());
msg_buf[6..8].copy_from_slice(&(bytes_written as u16).to_be_bytes());
res.truncate(8 + bytes_written)
.map_err(|e| BuildResponseError::Stack(e.into()))?;
Ok(())
}
}
#[doc(hidden)]
pub struct BufferHandlerRecord(());
impl<'a, const N: usize> coap_handler::Reporting for BufferHandler<'a, N> {
type Record<'b> = BufferHandlerRecord where Self: 'b;
type Reporter<'b> = core::iter::Once<BufferHandlerRecord> where Self: 'b;
fn report(&self) -> Self::Reporter<'_> {
core::iter::once(BufferHandlerRecord(()))
}
}
impl<'a> coap_handler::Record for BufferHandlerRecord {
type PathElement = &'static &'static str;
type PathElements = core::iter::Empty<&'static &'static str>;
type Attributes = core::iter::Once<coap_handler::Attribute>;
fn path(&self) -> Self::PathElements {
core::iter::empty()
}
fn rel(&self) -> Option<&str> {
None
}
fn attributes(&self) -> Self::Attributes {
core::iter::once(coap_handler::Attribute::Interface(
"tag:riot-os.org,2021:ser-out",
))
}
}