coap-message 0.3.0

Interface to CoAP messages
Documentation
/// Iteration item for option values
///
/// This is the trait of items produced by [`ReadableMessage::options()`].
///
/// An implementation needs to allow the user to get the value as a memory slice. This is trivial
/// for messages that are stored in serialized form; there this can be a fat pointer.
/// Implementations that store options semantically (eg. as a `struct Block { n: usize, m: bool,
/// szx: u8 }`) will typically make their MessageOption large enough to contain serialized options,
/// or heap-allocate for them.
pub trait MessageOption {
    /// Numeric option number
    ///
    /// See [OptionNumber](crate::OptionNumber) on how to interpret them.
    fn number(&self) -> u16;
    /// Obtain the option's raw value
    ///
    /// This can be used directly for options with opaque value semantics; for other semantics, see
    /// the [value_str]() and [value_uint]() helper methods.
    #[doc(alias = "opaque")]
    fn value(&self) -> &[u8];

    /// Obtain the option's value as a text string, or None if the option contains invalid UTF-8.
    ///
    /// Implementations can override this to reduce the string checking overhead if they already
    /// have the value as a string internally.
    #[doc(alias = "string")]
    fn value_str(&self) -> Option<&str> {
        core::str::from_utf8(self.value()).ok()
    }

    /// Obtain the option's value as a number following the `uint` [value
    /// format](https://tools.ietf.org/html/rfc7252#section-3.2), or None if the option is too
    /// long for the requested number size.
    ///
    /// Implementations can override this to reduce conversion overhead if they already have a
    /// numeric value internally as soon as U's type is replaced with an equally capable public num
    /// trait.
    #[doc(alias = "uint")]
    fn value_uint<U>(&self) -> Option<U>
    where
        U: num_traits::sign::Unsigned + num_traits::ops::bytes::FromBytes,
        U::Bytes: Sized + Default,
    {
        let mut bufarray: U::Bytes = Default::default();
        let buf = bufarray.as_mut();
        let buflen = buf.len();
        let val = self.value();
        if val.len() > buflen {
            return None;
        }
        buf[buflen - val.len()..].copy_from_slice(val);
        Some(U::from_be_bytes(&bufarray))
    }
}

/// Marker trait that indicates that ReadableMessage::options are produced in ascending
/// sequence.
///
/// This can be set on most CoAP message backends. Examples of backends where it is not implemented
/// are single-pass reads over in-place decrypted OSCORE messages.
pub trait WithSortedOptions: ReadableMessage {}

/// A CoAP message whose code, options and payload can be read
pub trait ReadableMessage {
    /// See [`Self::code()`]
    type Code: crate::numbers::Code;
    /// Type of an individual option, indiciating its option number and value
    type MessageOption<'a>: MessageOption
    where
        Self: 'a;
    /// See [`Self::options()`]
    type OptionsIter<'a>: Iterator<Item = Self::MessageOption<'a>>
    where
        Self: 'a;

    /// Get the code (request method or response code) of the message
    ///
    /// See [Code](crate::Code) for its details.
    fn code(&self) -> Self::Code;

    /// Produce all options in arbitrary order as an iterator
    ///
    /// They are sorted if the [`WithSortedOptions`] is implemented as well; implementers should
    /// set that trait whenever they can.
    fn options(&self) -> Self::OptionsIter<'_>;

    /// Get the payload set in the message
    ///
    /// This is necessarily empty for messages of some codes.
    fn payload(&self) -> &[u8];
}

// It would be nice to have more type state in here (for headers, last option number and whether
// payload has been set); this is a first step that can easily wrap jnet and maybe gcoap. Taking
// the next step is likely to happen soon, given that jnet coap has already moved to type state.
/// A message that can be written to, creating a CoAP request or response.
///
/// This is the bare minimum a message type needs to provide to generic applications. It is up to
/// the user to ensure this valid sequence of operations:
///
/// * Exactly one call to `set_code`
/// * Any number of calls to `add_option`, with monotonically increasing option numbers
/// * Zero or one call to `set_payload`
///
/// Steps that can reasonably fail at runtime are fallible -- for example, a payload to be set may
/// simply not fit within the message size. Adding options in the wrong sequence is also an
/// expected source, eg. when code paths are triggered that were not tested in that combination.
///
/// Other errors violating the call sequence, such as failure to call `set_code`, or adding an
/// option after the payload has been set, may be implemented in a panic. (When occurring in a
/// fallible operation, the implementation may also choose to report an error instead).
///
/// Failed operations may be retried (eg. with shorter values); the failed attempt must not have an
/// effect on the message.
///
/// Implementations may tolerate erroneous call sequences as long as they can produce messages that
/// are likely to match the caller's expectations -- no need to keep track of usage errors just to
/// produce correct errors. Users may wrap messages in dedicated checkers for more strictness.
pub trait MinimalWritableMessage {
    /// See [`Self::set_code()`]
    type Code: crate::numbers::Code;
    /// See [`Self::add_option()`]
    type OptionNumber: crate::numbers::OptionNumber;

    /// Error returned when an option can not be added (eg. for lack of space, or because an option
    /// of a higher number or even the payload was already set)
    type AddOptionError: crate::error::RenderableOnMinimal + core::fmt::Debug;
    /// Error returned when setting the payload (eg. for lack of space, or when a message of that
    /// type does not take a payload)
    type SetPayloadError: crate::error::RenderableOnMinimal + core::fmt::Debug;
    /// Error type into which either of the other errors, as well as the errors for conversion of
    /// the `Code` and `OptionNumber`, can be `.into()`ed.
    ///
    /// For many implementations it can make sense to use a single error type for all of those, in
    /// which case the From bounds are trivially fulfilled.
    type UnionError: crate::error::RenderableOnMinimal
        + core::fmt::Debug
        + From<Self::AddOptionError>
        + From<Self::SetPayloadError>
        + From<<Self::Code as crate::numbers::Code>::Error>
        + From<<Self::OptionNumber as crate::numbers::OptionNumber>::Error>;

    /// Set the CoAP code of the message (in a request, that is the request method)
    fn set_code(&mut self, code: Self::Code);

    /// Add an option to the message
    ///
    /// Calls to this method need to happen in ascending numeric sequence.
    ///
    /// The option number is pre-encoded in the [Self::OptionNumber] type. The value is provided in
    /// its serialized form. Under the aspect of [option value
    /// formats](https://tools.ietf.org/html/rfc7252#section-3.2), this adds opaque options (but
    /// may just as well be used for adding options in another format when they are pre-encoded).
    fn add_option(
        &mut self,
        number: Self::OptionNumber,
        value: &[u8],
    ) -> Result<(), Self::AddOptionError>;

    /// Set the payload to the message
    ///
    /// This must be called only once.
    fn set_payload(&mut self, data: &[u8]) -> Result<(), Self::SetPayloadError>;

    /// Copy code, options and payload in from a readable message
    // While this was originally intended for overriding for better optimization, that can only be
    // done with specialization, which will likely require a breaking change.
    fn set_from_message<M>(&mut self, msg: &M) -> Result<(), Self::UnionError>
    where
        M: ReadableMessage + WithSortedOptions,
    {
        use crate::numbers::{Code, OptionNumber};

        self.set_code(Self::Code::new(msg.code().into())?);

        for opt in msg.options() {
            self.add_option(Self::OptionNumber::new(opt.number())?, opt.value())?;
        }
        self.set_payload(msg.payload())?;
        Ok(())
    }

    /// Shortcut for `add_option(self, number, value.as_bytes())`.
    ///
    /// Implementations with type checked options can provide more efficient implementations (ie.
    /// ones that don't need to UTF-8-check when they feed the resulting bytes back into a string
    /// field), but must still accept string options via the generic
    /// [`add_option()`](Self::add_option) method.
    fn add_option_str(
        &mut self,
        number: Self::OptionNumber,
        value: &str,
    ) -> Result<(), Self::AddOptionError> {
        self.add_option(number, value.as_bytes())
    }

    /// Shortcut for `add_option` on a buffer containing the uint encoded value
    ///
    /// Implementations with type checked options can provide more efficient implementations (ie.
    /// ones that don't need to decode the uint when reading it into a uint field), but must still
    /// accept integer options via the generic [`add_option()`](Self::add_option) method.
    fn add_option_uint<U: num_traits::sign::Unsigned + num_traits::ops::bytes::ToBytes>(
        &mut self,
        number: Self::OptionNumber,
        value: U,
    ) -> Result<(), Self::AddOptionError> {
        let value = value.to_be_bytes();
        let mut value = value.as_ref();
        while let Some(&0) = value.first() {
            value = &value[1..];
        }
        self.add_option(number, value)
    }

    /// Auxiliary function for converting `Self::Code::Error`
    ///
    /// This should really not be needed, but serves well in allowing coap-request-imlementations
    /// to convert errors found during writing into their RequestUnionError that can be returned.
    fn convert_code_error(e: <Self::Code as crate::numbers::Code>::Error) -> Self::UnionError {
        Self::UnionError::from(e)
    }
    /// Auxiliary function for converting `Self::OptionNumber::Error`
    ///
    /// This should really not be needed, but serves well in allowing coap-request-imlementations
    /// to convert errors found during writing into their RequestUnionError that can be returned.
    fn convert_option_number_error(
        e: <Self::OptionNumber as crate::numbers::OptionNumber>::Error,
    ) -> Self::UnionError {
        Self::UnionError::from(e)
    }
    /// Auxiliary function for converting [`Self::AddOptionError`]
    ///
    /// This should really not be needed, but serves well in allowing coap-request-imlementations
    /// to convert errors found during writing into their RequestUnionError that can be returned.
    fn convert_add_option_error(e: Self::AddOptionError) -> Self::UnionError {
        Self::UnionError::from(e)
    }
    /// Auxiliary function for converting [`Self::SetPayloadError`]
    ///
    /// This should really not be needed, but serves well in allowing coap-request-imlementations
    /// to convert errors found during writing into their RequestUnionError that can be returned.
    fn convert_set_payload_error(e: Self::SetPayloadError) -> Self::UnionError {
        Self::UnionError::from(e)
    }
}

/// A message that allows later manipulation of a once set payload, and later truncation.
///
/// This is a bit of an unsorted bag that needs further cleanup (FIXME) -- most of this is
/// motivated by block-wise and write-in-place. Might need a bit of reshape, possibly following the
/// [Rust RFC 2884](https://github.com/rust-lang/rfcs/pull/2884).
///
/// The available_space is mainly needed for applications that want to use up the last byte by not
/// zero-padding the Block2 option to its szx=0 equivalent.
///
/// Can that be efficiently be replaced with something like this, and can it be optimized down to
/// the hand-written counting-of-option-bytes that's involved in the use of available_space?
///
/// ```ignore
/// let mut m = allocated_message;
/// for szx in 6..0 {
///     snap = m.snapshot();
///     m.add_option(BLOCK2, ...);
///     m.add_option(..., ...);
///
///     if let Ok(_) = m.write_payload(|p| {
///         if (p.len() < 1 << (4 + szx)) {
///             return Err(());
///         }
///
///         let written = write_block(...);
///
///         Ok(written)
///     }) {
///         break;
///     } else {
///         m = m.revert_to(snap);
///     }
/// } else {
///     panic!("Allocated space doesn't even suffice for 16 byte payload");
/// }
/// ```
///
pub trait MutableWritableMessage: MinimalWritableMessage {
    /// Number of bytes available for additional options, payload marker and payload
    fn available_space(&self) -> usize;

    /// Memory-map `len` bytes of the payload for writing
    ///
    /// If a payload has been set previously, that payload will be available in the slice; in that
    /// case, the caller must make sure to not exceed its length.
    ///
    /// If no payload has been set previously, and the requested length exceeds the available
    /// buffer space, the longest possible payload should be mapped.
    fn payload_mut_with_len(&mut self, len: usize) -> Result<&mut [u8], Self::SetPayloadError>;

    /// Truncate an already-set payload to the given length; that payload must have been written to
    /// before using [`MinimalWritableMessage::set_payload`], or with a suitable [`MutableWritableMessage::payload_mut_with_len`] call.
    fn truncate(&mut self, len: usize) -> Result<(), Self::SetPayloadError>;

    /// Apply a callback to all options in sequence
    ///
    /// This is a possibly inefficient but generic way achieve "allocate first, set when done"
    /// pattern typically found for options like ETag.
    fn mutate_options<F>(&mut self, callback: F)
    where
        F: FnMut(Self::OptionNumber, &mut [u8]);
}

/// Marker trait that relaxes [MinimalWritableMessage]'s sequence requirements.
///
/// This indicates that the sequence of calling [`set_code()`](MinimalWritableMessage::set_code),
/// [`add_option()`](MinimalWritableMessage::add_option) and
/// [`set_payload()`](MinimalWritableMessage::set_payload) is not fixed. The sequence of calls only
/// has meaning in that later `set_code()` and `set_payload()` calls override earlier ones, and
/// that `add_option()` on the same option number are stored in their sequence of addition.
// FIXME: Look into whether there's any implementation where it'd make sense to only have some of
// the relaxation but not all (eg. all options must be out, then comes the code).
pub trait SeekWritableMessage {
    // FIXME: Provide a more generic set_from_message that does not demand
    // WithSortedOptions. It can even have just the same code.
}