#![cfg_attr(feature = "downcast", allow(unsafe_code))]
use super::*;
use crate::option_extension::option_header_len;
#[derive(Clone)]
pub struct MessageMut<EM: MessageBufferMut> {
pub(crate) encoded: EM,
pub(crate) latest: u16,
pub(crate) end: usize,
pub(crate) payload_start: Option<usize>,
}
#[cfg(feature = "downcast")]
fn type_id_of_emv_of_lml<T: 'static + MessageBufferMut>(
_val: &LifetimesMatterLittle<T>,
) -> core::any::TypeId {
core::any::TypeId::of::<MessageMut<T>>()
}
impl<EM: MessageBufferMut> core::fmt::Debug for MessageMut<EM> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("MessageMut")
.field("MessageBufferMut impl", &core::any::type_name::<EM>())
.field("encoded.code", &self.encoded.code())
.field("encoded.tail", &self.encoded.tail())
.field("latest", &self.latest)
.field("end", &self.end)
.field("payload_start", &self.payload_start)
.finish()
}
}
#[cfg(feature = "defmt")]
impl<EM: MessageBufferMut> defmt::Format for MessageMut<EM> {
fn format(&self, f: defmt::Formatter<'_>) {
defmt::write!(
f,
"MessageMut {{ MessageBufferMut impl {=str}, code {=u8}, tail {=[u8]}, latest {=u16}, end {}, payload_start {} }}",
&core::any::type_name::<EM>(),
self.encoded.code(),
self.encoded.tail(),
self.latest,
self.end,
self.payload_start
);
}
}
impl<'a> MessageMut<SliceBufferMut<'a>> {
pub fn new_in_slice(code: &'a mut u8, tail: &'a mut [u8]) -> Self {
MessageMut {
encoded: SliceBufferMut::new(code, tail),
latest: 0,
end: 0,
payload_start: None,
}
}
pub fn new_from_encoded_slice(
code: &'a mut u8,
tail: &'a mut [u8],
) -> Result<Self, ParsingError> {
let em = SliceBufferMut::new(code, tail);
let read_only = Message::new(em);
read_only.into_mutable()
}
}
impl<T: MessageBufferMut> MessageMut<T> {
pub fn new_empty(encoded: T) -> Self {
MessageMut {
encoded,
latest: 0,
end: 0,
payload_start: None,
}
}
#[cfg(feature = "downcast")]
pub fn downcast_from<M: MinimalWritableMessage>(generic: &mut M) -> Option<&mut Self> {
let (reference, type_id) = generic.with_static_type_annotation()?.into_inner();
let our_static = T::static_mut_variant()?;
let our_id = type_id_of_emv_of_lml(&our_static);
if type_id != our_id {
return None;
}
let ptr = reference as *mut M as *mut Self;
Some(unsafe { &mut *ptr })
}
}
impl<EM: MessageBufferMut> MessageMut<EM> {
fn options_end(&self) -> usize {
self.payload_start.map_or(self.end, |s| s - 1)
}
#[allow(clippy::missing_panics_doc, reason = "backed by type invariants")]
pub fn insert_option(&mut self, number: u16, data: &[u8]) -> Result<(), WriteError> {
use crate::{
option_extension::encode_extensions,
option_iteration::{OptItem, OptPayloadReader},
};
let Ok(data_len) = u16::try_from(data.len()) else {
return Err(WriteError::OutOfSpace);
};
let mut insertion_point = 0;
let mut previous_option = 0u16;
let mut next_option = 0u16;
let mut next_len = None;
let options_end = self.options_end();
let tail = self.encoded.tail_mut();
if self.latest <= number {
insertion_point = options_end;
previous_option = self.latest;
} else {
let mut opt_iter = OptPayloadReader::new(tail);
loop {
match opt_iter.next() {
Some(OptItem::Option { number: num, data }) => {
if num <= number {
let (slice, base) = opt_iter.destruct();
insertion_point = tail.len() - slice.len();
previous_option = base;
opt_iter = OptPayloadReader::new_from(slice, base);
continue;
}
next_option = num;
next_len = Some(
u16::try_from(data.len()).expect("Guaranteed by OptPayloadReader"),
);
break;
}
_ => unreachable!("encoding invariant"),
}
}
}
let delta = number.checked_sub(previous_option).expect("Checked above");
let new_encoded = encode_extensions(delta, data_len);
let new_encoded = new_encoded.as_ref();
let current_encoded_len = option_header_len(tail[insertion_point]);
let encoding_length_delta;
let next_encoded;
let added_len = if let Some(next_len) = next_len {
let next_delta = next_option - number;
let next_encoded_tmp = encode_extensions(next_delta, next_len);
encoding_length_delta = usize::from(current_encoded_len)
.checked_sub(next_encoded_tmp.as_ref().len())
.expect("Encoding length can only shrink");
next_encoded = Some(next_encoded_tmp);
new_encoded.len() - encoding_length_delta + data.len()
} else {
next_encoded = None;
encoding_length_delta = 0;
new_encoded.len() + data.len()
};
if self.end + added_len > tail.len() {
return Err(WriteError::OutOfSpace);
}
let src = insertion_point + encoding_length_delta..self.end;
self.encoded
.tail_mut()
.copy_within(src, insertion_point + added_len);
if let Some(payload_start) = self.payload_start {
self.payload_start = Some(payload_start + added_len);
}
self.encoded
.tail_mut()
.get_mut(insertion_point..insertion_point + new_encoded.len())
.expect("Remaining length has been checked")
.copy_from_slice(new_encoded);
insertion_point += new_encoded.len();
self.encoded
.tail_mut()
.get_mut(insertion_point..insertion_point + data.len())
.expect("Remaining length has been checked")
.copy_from_slice(data);
insertion_point += data.len();
if let Some(next_encoded) = next_encoded {
self.encoded
.tail_mut()
.get_mut(insertion_point..insertion_point + next_encoded.as_ref().len())
.expect("Remaining length has been checked")
.copy_from_slice(next_encoded.as_ref());
} else {
self.latest = number;
}
self.end += added_len;
Ok(())
}
#[expect(
clippy::cast_sign_loss,
reason = "differences are checked by construction"
)]
#[expect(
clippy::cast_possible_wrap,
reason = "usize values stem from actual sizes"
)]
fn remove_option_at(
tail: &mut [u8],
index: usize,
end: usize,
previous_option: u16,
) -> Result<isize, InvariantViolated> {
use crate::{
option_extension::encode_extensions,
option_iteration::{OptItem, OptPayloadReader},
};
let mut opt_iter = OptPayloadReader::new_from(&tail[index..], previous_option);
let Some(OptItem::Option { number, data }) = opt_iter.next() else {
unreachable!("caller promised that opt0on is there")
};
let next_opt = match opt_iter.next().ok_or(InvariantViolated)? {
OptItem::Option { number, data } => Some((number, data)),
OptItem::Payload(_) => None,
OptItem::Error(_) => return Err(InvariantViolated),
};
let current_encoded_len = isize::from(option_header_len(tail[index]));
let encoding_length_delta;
let next_encoded;
let len_removed = if let Some((next_number, next_data)) = next_opt {
let next_data_len = u16::try_from(next_data.len()).map_err(|_| InvariantViolated)?;
let current_next_delta = next_number - number;
let current_next_encoded = encode_extensions(current_next_delta, next_data_len);
let current_next_encoded_len = current_next_encoded.as_ref().len() as isize;
let next_delta = next_number - previous_option;
let next_encoded_tmp = encode_extensions(next_delta, next_data_len);
encoding_length_delta =
next_encoded_tmp.as_ref().len() as isize - current_next_encoded_len;
next_encoded = Some(next_encoded_tmp);
current_encoded_len - encoding_length_delta + data.len() as isize
} else {
next_encoded = None;
encoding_length_delta = 0;
current_encoded_len + data.len() as isize
};
if tail.len() < (end as isize - len_removed) as usize {
return Err(InvariantViolated);
}
if end.saturating_sub(index + current_encoded_len as usize + data.len()) != 0 {
let src =
index + current_encoded_len as usize + data.len() + encoding_length_delta as usize
..end;
tail.copy_within(src, index + encoding_length_delta as usize);
}
if let Some(next_encoded) = next_encoded {
tail.get_mut(index..index + next_encoded.as_ref().len())
.expect("checked above")
.copy_from_slice(next_encoded.as_ref());
}
Ok(len_removed)
}
#[expect(
clippy::cast_sign_loss,
reason = "differences are checked by construction"
)]
#[expect(
clippy::cast_possible_wrap,
reason = "usize values stem from actual sizes"
)]
pub fn retain_options<P>(&mut self, predicate: P)
where
P: Fn(u16, &[u8]) -> bool,
{
use crate::option_iteration::{OptItem, OptPayloadReader};
if self.latest == 0 {
return;
}
let tail = self.encoded.tail_mut();
let mut opt_iter = OptPayloadReader::new(tail);
let mut previous_option = 0u16;
let mut opt_index = 0;
loop {
match opt_iter.next() {
Some(OptItem::Option { number, data }) => {
let (slice, _) = opt_iter.destruct();
if predicate(number, data) {
previous_option = number;
opt_index = tail.len() - slice.len();
opt_iter = OptPayloadReader::new_from(slice, number);
continue;
}
let Ok(len_removed) =
Self::remove_option_at(tail, opt_index, self.end, previous_option)
else {
unreachable!(
"Internal invariant violated (bug in coap-message-implementations)"
);
};
self.end = (self.end as isize - len_removed) as usize;
let options_end = if let Some(payload_start) = self.payload_start {
let new = (payload_start as isize - len_removed) as usize;
self.payload_start = Some(new);
new - 1
} else {
self.end
};
if opt_index == options_end {
self.latest = previous_option;
return;
}
opt_iter = OptPayloadReader::new_from(&tail[opt_index..], previous_option);
}
None | Some(OptItem::Payload(_)) => return,
Some(OptItem::Error(_)) => unreachable!(
"Options not encoded as expected (bug in coap-message-implementations)"
),
}
}
}
pub fn reset(&mut self) {
self.latest = 0;
self.end = 0;
self.payload_start = None;
*self.encoded.code_mut() = 0;
}
pub fn untruncate(&mut self, mut added_len: usize) -> Result<(), WriteError> {
if self.payload_start.is_none() {
added_len += 1;
}
if self.encoded.tail().len() < self.end + added_len {
return Err(WriteError::OutOfSpace);
}
if self.payload_start.is_none() {
self.encoded.tail_mut()[self.end] = 0xff;
self.payload_start = Some(self.end + 1);
}
self.end += added_len;
Ok(())
}
pub fn finish(self) -> (usize, EM) {
(self.end, self.encoded)
}
}
struct InvariantViolated;
impl<EM: MessageBufferMut> coap_message::ReadableMessage for MessageMut<EM> {
type Code = u8;
type MessageOption<'b>
= MessageOption<'b>
where
Self: 'b;
type OptionsIter<'b>
= OptionsIter<'b>
where
Self: 'b;
fn code(&self) -> u8 {
self.encoded.code()
}
fn payload(&self) -> &[u8] {
match self.payload_start {
None => &[],
Some(start) => &self.encoded.tail()[start..self.end],
}
}
fn options(&self) -> <Self as coap_message::ReadableMessage>::OptionsIter<'_> {
OptionsIter(
crate::option_iteration::OptPayloadReader::new(&self.encoded.tail()[..self.end]),
None,
)
}
}
impl<EM: MessageBufferMut> coap_message::WithSortedOptions for MessageMut<EM> {}
impl<EM: MessageBufferMut> MinimalWritableMessage for MessageMut<EM> {
type Code = u8;
type OptionNumber = u16;
type UnionError = WriteError;
type AddOptionError = WriteError;
type SetPayloadError = WriteError;
fn set_code(&mut self, code: u8) {
*self.encoded.code_mut() = code;
}
fn add_option(&mut self, number: u16, data: &[u8]) -> Result<(), WriteError> {
let delta = number
.checked_sub(self.latest)
.ok_or(WriteError::OutOfSequence)?;
self.latest = number;
let encoded = crate::option_extension::encode_extensions(delta, data.len() as u16);
let encoded = encoded.as_ref();
let added_len = encoded.len() + data.len();
let option_cursor;
let eventual_end = self.end + added_len;
if let Some(payload_start) = self.payload_start {
if added_len > self.encoded.tail().len() - self.end {
return Err(WriteError::OutOfSpace);
}
option_cursor = payload_start - 1;
let src = option_cursor..self.end;
self.encoded
.tail_mut()
.copy_within(src, option_cursor + added_len);
self.payload_start = Some(payload_start + added_len);
} else {
option_cursor = self.end;
}
self.encoded
.tail_mut()
.get_mut(option_cursor..option_cursor + encoded.len())
.ok_or(WriteError::OutOfSpace)?
.copy_from_slice(encoded);
let option_cursor = option_cursor + encoded.len();
self.encoded
.tail_mut()
.get_mut(option_cursor..option_cursor + data.len())
.ok_or(WriteError::OutOfSpace)?
.copy_from_slice(data);
self.end = eventual_end;
Ok(())
}
fn set_payload(&mut self, payload: &[u8]) -> Result<(), WriteError> {
if self.payload_start.is_some() {
return Err(WriteError::OutOfSequence);
}
if !payload.is_empty() {
*self
.encoded
.tail_mut()
.get_mut(self.end)
.ok_or(WriteError::OutOfSpace)? = 0xff;
let start = self.end + 1;
self.end = start + payload.len();
self.encoded
.tail_mut()
.get_mut(start..self.end)
.ok_or(WriteError::OutOfSpace)?
.copy_from_slice(payload);
self.payload_start = Some(start);
}
Ok(())
}
#[cfg(feature = "downcast")]
fn with_static_type_annotation(
&mut self,
) -> Option<coap_message::helpers::RefMutWithStaticType<'_, Self>> {
let em_static = EM::static_mut_variant()?;
Some(unsafe {
coap_message::helpers::RefMutWithStaticType::new(
self,
type_id_of_emv_of_lml(&em_static),
)
})
}
#[inline]
#[allow(refining_impl_trait_reachable)]
fn promote_to_mutable_writable_message(&mut self) -> Option<&mut Self> {
Some(self)
}
}
impl<EM: MessageBufferMut> MutableWritableMessage for MessageMut<EM> {
fn available_space(&self) -> usize {
self.encoded.tail().len() - self.options_end()
}
fn payload_mut_with_len(&mut self, len: usize) -> Result<&mut [u8], WriteError> {
if len == 0 {
self.truncate(0)?;
return Ok(&mut []);
}
let start = match self.payload_start {
None => {
self.encoded.tail_mut()[self.end] = 0xff;
self.end + 1
}
Some(payload_start) => payload_start,
};
let end = start + len;
let end = end.clamp(0, self.encoded.tail().len());
self.payload_start = Some(start);
self.end = end;
self.encoded
.tail_mut()
.get_mut(start..end)
.ok_or(WriteError::OutOfSpace)
}
fn truncate(&mut self, len: usize) -> Result<(), WriteError> {
match (len, self.payload_start) {
(0, Some(payload_start)) => {
self.end = payload_start - 1;
self.payload_start = None;
}
(0, None) => {}
(len, Some(payload_start)) if self.end - payload_start >= len => {
self.end = payload_start + len;
}
_ => return Err(WriteError::OutOfSpace),
}
Ok(())
}
fn mutate_options<F>(&mut self, mut f: F)
where
F: FnMut(u16, &mut [u8]),
{
let optend = self.options_end();
let mut slice = &mut self.encoded.tail_mut()[..optend];
let mut option_base = 0;
while !slice.is_empty() {
let delta_len = slice[0];
slice = &mut slice[1..];
if delta_len == 0xff {
break;
}
let mut delta = u16::from(delta_len) >> 4;
let mut len = u16::from(delta_len) & 0x0f;
let new_len = {
#[allow(clippy::redundant_slicing)]
let mut readable = &slice[..];
crate::option_extension::take_extension(&mut delta, &mut readable)
.expect("Invalid encoded option in being-written message");
crate::option_extension::take_extension(&mut len, &mut readable)
.expect("Invalid encoded option in being-written message");
readable.len()
};
let trim = slice.len() - new_len;
slice = &mut slice[trim..];
option_base += delta;
let len = len.into();
f(option_base, &mut slice[..len]);
slice = &mut slice[len..];
}
}
}