verdigris 0.1.1

Browser application to explore, learn and debug CoAP
Documentation
/** A writable CoAP message in CoAP-over-WebSocket serialization
 *
 * Writes are append-only, as this stores the message in a Vec<u8> with no intention to reorder
 * fields; for random-access construction consider building a HeapCoAPMessage first and then
 * populating from there.
 *
 * FIXME: This currently does no error checking whatsoever, and will build invalid messages if
 * driven wrong.
 */
#[derive(Debug, Clone)]
pub struct CoAPWSMessageW {
    data: Vec<u8>,
    option_base: u16,
}

use coap_message_utils::option_extension::push_extensions;

impl CoAPWSMessageW {
    pub fn new(token: &[u8]) -> Self {
        assert!(token.len() <= 8);
        let mut data = Vec::with_capacity(token.len() + 2);
        data.push(token.len() as u8);
        data.push(0); // Initially empty, needs to be set with set_code
        data.extend_from_slice(&token);

        Self { data, option_base: 0 }
    }

    pub fn serialize(self) -> Vec<u8> {
        self.data
    }
}

impl coap_message::MinimalWritableMessage for CoAPWSMessageW {
    type Code = u8;
    type OptionNumber = u16;

    fn set_code(&mut self, code: u8) {
        self.data[1] = code;
    }

    fn add_option(&mut self, optnum: u16, value: &[u8]) {
        assert!(value.len() <= u16::MAX.into());
        let delta = optnum - self.option_base;
        self.option_base = optnum;
        let len = value.len() as u16;

        let startindex = self.data.len();
        self.data.push(0); // to be added to:
        self.data[startindex] += push_extensions(&mut self.data, delta) << 4;
        self.data[startindex] += push_extensions(&mut self.data, len);

        self.data.extend_from_slice(value);
    }

    fn set_payload(&mut self, payload: &[u8]) {
        if payload.len() > 0 {
            self.data.push(0xff);
            self.data.extend_from_slice(payload);
        }
    }
}

/** A CoAP message in CoAP-over-WebSocket serialization
 *
 * The object is checked for validity of the header fields (length field, token length), but not
 * for validity of options or payload.
 * */
#[derive(Debug, Clone)]
pub struct CoAPWSMessageR<B: AsRef<[u8]>> {
    // organized as struct to possibly store things like a payload offset to avoid repeated
    // iteration
    data: B,
}

use coap_message_utils::{inmemory::{MessageOption, OptionsIter}, option_iteration::{OptPayloadReader, OptItem}};

impl<B: AsRef<[u8]>> CoAPWSMessageR<B> {
    pub fn new(data: B) -> Result<Self, &'static str> {
        let len_tkl = *data.as_ref().get(0).ok_or("Zero message")?;
        if len_tkl > 8 {
            return Err("Invalid first byte");
        }
        let tkl = len_tkl;
        if data.as_ref().len() < 2 + (tkl as usize) {
            return Err("Message too short for token");
        }

        Ok(Self { data })
    }

    pub fn token(&self) -> &[u8] {
        &self.data.as_ref()[2..2 + (self.data.as_ref()[0] as usize)]
    }

    fn options_and_payload(&self) -> &[u8] {
        &self.data.as_ref()[(self.data.as_ref()[0] as usize) + 2..]
    }
}


// Ideally this would build an InMemoryMessage on demand and just defer to its methods, but that'd
// mean the InMemoryMessage gets built on the stack and can't return the longer-lived OptionsIter
impl<'m, B> coap_message::ReadableMessage<'m> for CoAPWSMessageR<B>
    where B: AsRef<[u8]> + 'm
{
    type Code = u8;
    type MessageOption = MessageOption<'m>;
    type OptionsIter = OptionsIter<'m>;

    fn code(&self) -> u8 {
        self.data.as_ref()[1]
    }

    fn payload(&self) -> &[u8] {
        let empty: &[u8] = &[];

        // ... into which we'll index
        let optpayload = self.options_and_payload();

        OptPayloadReader::new(optpayload)
            .filter(|x| !matches!(x, OptItem::Option { .. }))
            .next()
            .map(|x| if let OptItem::Payload(data) = x {
                // Can't return data itself because the iterator doesn't outlive this function
                // To be replaced when ptr_wrapping_offset_from is stabilized
                let offset = data.as_ptr() as usize - optpayload.as_ptr() as usize;
                &optpayload[offset..]
            } else { panic!("Error before payload") })
            .unwrap_or(&empty)
    }

    fn options(&'m self) -> OptionsIter<'m> {
        OptionsIter(OptPayloadReader::new(self.options_and_payload()))
    }
}