verdigris 0.2.1

Browser application to explore, learn and debug CoAP
//! Tools around coap-message that may once go standalone but are tried out here

use coap_message::MinimalWritableMessage;
use coap_message::{OptionNumber, FromOtherOption};

// Sure we could make this all type based with apply_single_option(self, ...) -> Self::Tail?
// It's "just" that the type would need a const nextoption, and the zipper construction would need
// to const evaluate them to not have exploding tail versions.

/// A generator of ordered options
///
/// After construction, this can be used with [apply_to_message] to sequentially write all its
/// options into a message (with [apply_to_message_limited] stopping the process halfway).
///
/// Implementors need to provide the [apply_single_option] and [next_option] functions. If a
/// user of the trait prefers to use them over the abovementioned convenience functions, be sure to
/// only call `apply_single_option` when indicated by `next_option`.
///
/// Specialization options are still being explored; ideally, that'd work without introducing any
/// additional orthogonal function names (apply_{to_message{,_limit},single_option}_shortcut or
/// that like), and even better the result type in that case should be `Result<(), !>`.
pub trait OptionFeed<O: OptionNumber> {
    /// Append all the options in the feed to the message.
    ///
    /// This is the main application of OptionFeed.
    ///
    /// It leaves the feed drained.
    fn apply_to_message<M: MinimalWritableMessage>(&mut self, message: &mut M) -> Result<(), <<M as MinimalWritableMessage>::OptionNumber as TryFrom<u16>>::Error> {
        while self.next_option().is_some() {
            self.apply_single_option(message)?;
        }
        Ok(())
    }

    /// Append options to in the feed to the message up to and including a maximum option number.
    fn apply_to_message_limited<M: MinimalWritableMessage>(&mut self, message: &mut M, maxopt: u16) -> Result<(), <<M as MinimalWritableMessage>::OptionNumber as TryFrom<u16>>::Error> {
        while self.next_option().map(|next| next <= maxopt) == Some(true) {
            self.apply_single_option(message)?;
        }
        Ok(())
    }

    /// Append any number of options to the message from this feed as long as their number is as
    /// advertised in [next_option]
    ///
    /// ### Panics
    ///
    /// This may panic if next_option() already indicated that there is no next option in this
    /// feed.
    fn apply_single_option<M: MinimalWritableMessage>(&mut self, message: &mut M) -> Result<(), <M::OptionNumber as FromOtherOption<O>>::Error>;

    /// Option number the feed would write next to the message.
    ///
    /// Returning None indicates that no options are added to the message.
    ///
    /// Implementors may be pessimistic here and give a number even if they then don't actually
    /// produce an option on that number.
    fn next_option(&self) -> Option<u16>;
}

use core::marker::PhantomData;

// The PhantomData here is not strictly essential I think, but should make it easier to get clearer
// error messages when the conflict is between the builder and the new option, and not between the
// builder's previous content and the new option
pub struct OptionFeedBuilder<O: OptionNumber, F: OptionFeed<O>>(F, PhantomData<O>);

impl<O: OptionNumber> OptionFeedBuilder<O, NoOptions> {
    pub fn new() -> Self {
        OptionFeedBuilder(NoOptions, PhantomData)
    }
}

impl<O: OptionNumber + Clone, F: OptionFeed<O>> OptionFeedBuilder<O, F> {
    pub fn add_option<D: AsRef<[u8]>>(self, number: O, value: D) -> OptionFeedBuilder<O, impl OptionFeed<O>> {
        OptionFeedBuilder(Zipper(self.0, OpaqueOption::new(number, value), PhantomData), PhantomData)
    }

    pub fn add_option_uint<Ux: coap_message::numtraits::Ux>(self, number: O, value: Ux) -> OptionFeedBuilder<O, impl OptionFeed<O>> {
        OptionFeedBuilder(Zipper(self.0, UintOption::new(number, value), PhantomData), PhantomData)
    }

    pub fn finish(self) -> F {
        self.0
    }
}


pub struct NoOptions;

/// The [OptionFeed] implementation that represents the empty set of options
impl<O: OptionNumber> OptionFeed<O> for NoOptions {
    fn apply_single_option<M: MinimalWritableMessage>(&mut self, _message: &mut M) -> Result<(), <M::OptionNumber as FromOtherOption<O>>::Error> {
        panic!("Called despite None as next_option")
    }

    fn next_option(&self) -> Option<u16> {
        None
    }
}

/// A single opaque option
// This is a candidate for adding `, const N: u16` as soon as min_const_generics are stable
struct OpaqueOption<O: OptionNumber + Clone, D: AsRef<[u8]>>(Option<(O, D)>);

impl<O: OptionNumber + Clone, D: AsRef<[u8]>> OpaqueOption<O, D> {
    fn new(number: O, value: D) -> Self {
        OpaqueOption(Some((number, value)))
    }
}

impl<O: OptionNumber + Clone, D: AsRef<[u8]>> OptionFeed<O> for OpaqueOption<O, D> {
    fn apply_single_option<M: MinimalWritableMessage>(&mut self, message: &mut M) -> Result<(), <M::OptionNumber as FromOtherOption<O>>::Error> {
        let (num, val) = self.0.take().expect("Feed exhausted");
        let num = M::OptionNumber::convert_from_other(num)?;
        message.add_option(num, val.as_ref());
        Ok(())
    }

    fn next_option(&self) -> Option<u16> {
        self.0.as_ref().map(|s| s.0.clone().into())
    }
}


/// A single numeric option
///
/// As the underlying message may have a more efficient add_option_uint, this stores the number in
/// Rust representation and only serializes it with the message.
struct UintOption<O: OptionNumber + Clone, Ux: coap_message::numtraits::Ux>(Option<(O, Ux)>);

impl<O: OptionNumber + Clone, Ux: coap_message::numtraits::Ux> UintOption<O, Ux> {
    fn new(number: O, value: Ux) -> Self {
        UintOption(Some((number, value)))
    }
}

impl<O: OptionNumber + Clone, Ux: coap_message::numtraits::Ux> OptionFeed<O> for UintOption<O, Ux> {
    fn apply_single_option<M: MinimalWritableMessage>(&mut self, message: &mut M) -> Result<(), <M::OptionNumber as FromOtherOption<O>>::Error> {
        let (num, val) = self.0.take().expect("Feed exhausted");
        let num = M::OptionNumber::convert_from_other(num)?;
        message.add_option_uint(num, val);
        Ok(())
    }

    fn next_option(&self) -> Option<u16> {
        self.0.as_ref().map(|s| s.0.clone().into())
    }
}


pub struct Zipper<O: OptionNumber, F1: OptionFeed<O>, F2: OptionFeed<O>>(F1, F2, PhantomData<O>);

impl<O: OptionNumber, F1: OptionFeed<O>, F2: OptionFeed<O>> OptionFeed<O> for Zipper<O, F1, F2> {
    fn apply_single_option<M: MinimalWritableMessage>(&mut self, message: &mut M) -> Result<(), <M::OptionNumber as FromOtherOption<O>>::Error> {
        match (self.0.next_option(), self.1.next_option()) {
            (None, None) => panic!("Out of options"),
            (None, Some(_)) => self.1.apply_single_option(message)?,
            (Some(_), None) => self.0.apply_single_option(message)?,
            (Some(x), Some(y)) if x == y => {
                self.0.apply_single_option(message)?;
                self.1.apply_single_option(message)?;
            }
            (Some(x), Some(y)) if x < y => self.0.apply_single_option(message)?,
            (Some(_), Some(_)) => self.1.apply_single_option(message)?,
        }
        Ok(())
    }

    fn next_option(&self) -> Option<u16> {
        match (self.0.next_option(), self.1.next_option()) {
            (None, None) => None,
            (None, Some(y)) => Some(y),
            (Some(x), None) => Some(x),
            (Some(x), Some(y)) if x <= y => Some(x),
            (Some(_), Some(y)) => Some(y),
        }
    }
}