use core::convert::{TryFrom, TryInto};
use coap_message::{ReadableMessage, MutableWritableMessage, MessageOption};
use coap_numbers::option;
pub(crate) fn codeconvert<O: TryFrom<u8>>(code: u8) -> O {
code
.try_into()
.map_err(|_| "Responde type can't even express hard-coded code")
.unwrap()
}
pub(crate) fn optconvert<O: TryFrom<u16>>(option: u16) -> O {
option
.try_into()
.map_err(|_| "Response type can't even express Content-Format")
.unwrap()
}
pub struct Block2RequestData {
blknum: u32,
szx: u8,
}
impl Block2RequestData {
pub fn from_message<'a>(message: &'a impl ReadableMessage<'a>) -> Result<Self, ()>
{
let mut b2options = message
.options()
.filter(|o| o.number() == option::BLOCK2);
match b2options.next() {
None => Ok(Self::default()),
Some(o) => {
if let None = b2options.next() {
Self::from_option(&o)
} else {
Err(())
}
}
}
}
pub fn from_option<'a>(option: &'a impl MessageOption) -> Result<Self, ()> {
debug_assert!(option.number() == option::BLOCK2);
let o: u32 = option.value_uint().ok_or(())?;
if o >= 0x1000000 {
return Err(());
}
Ok(Self {
szx: (o as u8) & 0x7,
blknum: o >> 4,
})
}
pub fn size(&self) -> u16 {
1 << (4 + self.szx)
}
pub fn before(&self) -> u32 {
self.size() as u32 * self.blknum
}
pub fn shrink(mut self, size: u16) -> Option<Self> {
while self.size() > size {
if self.szx == 0 {
return None;
}
self.szx -= 1;
self.blknum *= 2;
}
Some(self)
}
}
impl Default for Block2RequestData {
fn default() -> Self {
Self { szx: 6, blknum: 0 }
}
}
pub fn block2_write<F, R>(block2: Block2RequestData, response: &mut impl MutableWritableMessage, f: F) -> R
where
F: FnOnce(&mut BlockWriter) -> R,
{
let estimated_option_size = 25; let payload_budget = response.available_space() - estimated_option_size;
let block2 = block2
.shrink(payload_budget as u16)
.expect("Tiny buffer allocated");
response.add_option(optconvert(option::ETAG), &[0, 0, 0, 0, 0, 0, 0, 0]);
response.add_option_uint(
optconvert(option::BLOCK2),
(block2.blknum << 4) | block2.szx as u32,
);
let (cursor, etag, ret) = {
let full_payload = response.payload_mut();
let mut writer = BlockWriter {
data: &mut full_payload[..block2.size() as usize],
cursor: -(block2.before() as isize),
etag: 0,
};
let ret = f(&mut writer);
(writer.cursor, writer.etag, ret)
};
let set_more;
if cursor > block2.size() as isize {
response.truncate(block2.size() as usize);
set_more = true;
} else if cursor < 0 {
unimplemented!("Report out-of-band seek");
} else {
response.truncate(cursor as usize);
set_more = false;
}
response.mutate_options(|optnum, value| {
match optnum.into() {
option::ETAG => {
use byteorder::ByteOrder;
byteorder::NetworkEndian::write_u64(value, etag);
},
option::BLOCK2 if set_more => {
value[value.len() - 1] |= 0x08;
},
_ => (),
};
});
ret
}
pub struct BlockWriter<'a> {
data: &'a mut [u8],
cursor: isize,
etag: u64,
}
impl<'a> BlockWriter<'a> {
fn new(data: &'a mut [u8], cursor: isize) -> Self {
let etag = 0;
Self { data, cursor, etag }
}
fn bytes_in_buffer(&self) -> Option<usize> {
if self.cursor < 0 {
None
} else {
Some(::core::cmp::min(self.cursor as usize, self.data.len()))
}
}
fn cursor(&self) -> isize {
self.cursor
}
fn etag(&self) -> u64 {
self.etag
}
fn did_overflow(&self) -> bool {
self.cursor > self.data.len() as isize
}
}
impl<'a> ::core::fmt::Write for BlockWriter<'a> {
fn write_str(&mut self, s: &str) -> ::core::fmt::Result {
let mut s = s.as_bytes();
self.etag = crc::crc64::update(self.etag, &crc::crc64::ECMA_TABLE, s);
if self.cursor >= self.data.len() as isize {
self.cursor += s.len() as isize;
return Ok(());
}
if self.cursor < -(s.len() as isize) {
self.cursor += s.len() as isize;
return Ok(());
}
if self.cursor < 0 {
s = &s[(-self.cursor) as usize..];
self.cursor = 0;
}
let mut s_to_copy = s;
if self.cursor as usize + s.len() > self.data.len() {
let copy_bytes = self.data.len() - self.cursor as usize;
s_to_copy = &s[..copy_bytes]
}
self.data[self.cursor as usize..self.cursor as usize + s_to_copy.len()]
.copy_from_slice(s_to_copy);
self.cursor += s.len() as isize;
Ok(())
}
}