gmqtt 0.1.1

A no_std, no_alloc MQTTv5 packet parsing library for embedded devices
Documentation
use heapless::Vec;

use crate::{utils::*, MqttError, Pid};

use super::{property, PropertyType};

/// An UNSUBSCRIBE packet is sent by the Client to the Server, to unsubscribe from topics.
///
/// [MQTT5 SPECIFICATION](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901179)
#[derive(Debug, Clone, PartialEq)]
pub struct Unsubscribe<'a> {
    /// Packet identifier
    pub pid: Pid,

    /// The Payload for the UNSUBSCRIBE packet contains the list of Topic Filters that the Client wishes to unsubscribe from.
    /// The Topic Filters in an UNSUBSCRIBE packet MUST be UTF-8 Encoded Strings
    pub filters: Vec<&'a str, 32>,
    pub properties: Option<UnsubscribeProperties<'a>>,
}

impl<'a> Unsubscribe<'a> {
    pub fn new(pid: Pid, filters: heapless::Vec<&'a str, 32>) -> Result<Self, MqttError> {
        Ok(Unsubscribe {
            pid,
            filters,
            properties: None,
        })
    }

    pub fn read(buf: &'a [u8], offset: &mut usize) -> Result<Self, MqttError> {
        let pid = Pid::read(buf, offset)?;

        let properties = UnsubscribeProperties::read(buf, offset)?;

        // move offset to filters field
        *offset += 1;

        let remaining_len = &buf[*offset..].len();

        let mut filters = heapless::Vec::<&'a str, 32>::new();
        let mut cursor = 0;
        while cursor < *remaining_len {
            let filter = read_str(&buf[*offset..], &mut cursor)?;
            filters
                .push(filter)
                .map_err(|_| MqttError::TooManyFilters)?;
        }

        let unsubscribe = Unsubscribe {
            pid,
            filters,
            properties,
        };

        Ok(unsubscribe)
    }

    pub fn write(&self, buf: &mut [u8], offset: &mut usize) -> Result<usize, MqttError> {
        check_remaining(buf, offset, self.len() + 1)?;
        // Unsubscribe (0xA2)

        let header: u8 = 0xA2;
        write_u8(buf, offset, header);

        let write_len = write_length(buf, offset, self.len())? + 1;
        self.pid.write(buf, offset);

        match &self.properties {
            Some(properties) => properties.write(buf, offset)?,
            None => write_u8(buf, offset, 0),
        }

        for filter in &self.filters {
            write_bytes_with_len(buf, offset, filter.as_bytes());
        }

        Ok(write_len)
    }

    fn len(&self) -> usize {
        // Packet id + length of filters (unlike subscribe, this just a string.
        // Hence 2 is prefixed for len per filter)
        let mut len = 2 + self.filters.iter().fold(0, |s, t| 2 + s + t.len());

        if let Some(properties) = &self.properties {
            let properties_len = properties.len();
            let properties_len_len = len_len(properties_len);
            len += properties_len_len + properties_len;
        } else {
            // just 1 byte representing 0 len
            len += 1;
        }

        len
    }
}

/// [MQTT5 SPECIFICATION](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901182)
#[derive(Debug, Clone, PartialEq, Default)]
pub struct UnsubscribeProperties<'a> {
    /// `38 (0x26)` Byte, Identifier of the User Property.
    ///
    /// Followed by a UTF-8 String Pair.
    pub user_properties: Vec<(&'a str, &'a str), 32>,
}

impl<'a> UnsubscribeProperties<'a> {
    pub fn read(buf: &'a [u8], offset: &mut usize) -> Result<Option<Self>, MqttError> {
        let mut properties = Self::default();

        let properties_len = buf[*offset];
        if properties_len == 0 {
            return Ok(None);
        }

        let mut cursor: usize = 1;

        while cursor < properties_len.into() {
            let mut prop_offset = *offset + cursor;
            let prop = buf[prop_offset];

            prop_offset += 1;
            cursor += 1;
            match property(prop)? {
                PropertyType::UserProperty => {
                    let key = read_str(buf, &mut prop_offset)?;
                    let value = read_str(buf, &mut prop_offset)?;
                    cursor += 2 + key.len() + 2 + value.len();
                    properties
                        .user_properties
                        .push((key, value))
                        .map_err(|_| MqttError::TooManyUserProperties)?;
                }

                prop => return Err(MqttError::InvalidPropertyType(prop)),
            }
        }

        *offset += properties.len();
        Ok(Some(properties))
    }

    pub fn write(&self, buf: &mut [u8], offset: &mut usize) -> Result<(), MqttError> {
        write_length(buf, offset, self.len())?;

        for (key, value) in self.user_properties.iter() {
            write_u8(buf, offset, PropertyType::UserProperty as u8);
            write_bytes_with_len(buf, offset, key.as_bytes());
            write_bytes_with_len(buf, offset, value.as_bytes());
        }

        Ok(())
    }

    fn len(&self) -> usize {
        let mut len = 0;

        for (key, value) in self.user_properties.iter() {
            len += 1 + 2 + key.len() + 2 + value.len();
        }

        len
    }
}

#[cfg(test)]
mod tests {
    use super::{Unsubscribe, UnsubscribeProperties};
    use crate::Pid;

    fn sample<'a>() -> Unsubscribe<'a> {
        let properties = UnsubscribeProperties {
            user_properties: heapless::Vec::<_, 32>::from_slice(&[("test", "test")]).unwrap(),
        };

        Unsubscribe {
            pid: Pid::new(10),
            filters: heapless::Vec::<_, 32>::from_slice(&["hello", "world"]).unwrap(),
            properties: Some(properties),
        }
    }

    fn sample_bytes() -> Vec<u8> {
        vec![
            0xa2, // packet type
            0x1e, // remaining len
            0x00, 0x0a, // pkid
            0x0d, // properties len
            0x26, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, 0x00, 0x04, 0x74, 0x65, 0x73,
            0x74, // user properties
            0x00, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, // filter 1
            0x00, 0x05, 0x77, 0x6f, 0x72, 0x6c, 0x64, // filter 2
        ]
    }

    #[test]
    fn unsub_decode() {
        let bytes = &sample_bytes()[2..];
        let expected = sample();
        let actual = Unsubscribe::read(bytes, &mut 0).unwrap();
        assert_eq!(expected, actual);
    }

    #[test]
    fn unsub_encode() {
        let expected = sample_bytes();
        let mut buf = [0u8; 32];
        sample().write(&mut buf, &mut 0).unwrap();

        assert_eq!(expected.as_slice(), buf);
    }

    fn sample2<'a>() -> Unsubscribe<'a> {
        Unsubscribe {
            pid: Pid::new(10),
            filters: heapless::Vec::<_, 32>::from_slice(&["hello"]).unwrap(),
            properties: None,
        }
    }

    fn sample2_bytes() -> Vec<u8> {
        vec![
            0xa2, // packet type
            0x0a, // remaining len
            0x00, 0x0a, // pkid
            0x00, // properties len
            0x00, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, // filter  1
        ]
    }

    #[test]
    fn unsub_decode2() {
        let bytes = &sample2_bytes()[2..];
        let expected = sample2();
        let actual = Unsubscribe::read(bytes, &mut 0).unwrap();
        assert_eq!(expected, actual);
    }

    #[test]
    fn unsub_encode2() {
        let expected = sample2_bytes();
        let mut buf = [0u8; 12];
        sample2().write(&mut buf, &mut 0).unwrap();

        assert_eq!(expected.as_slice(), buf);
    }
}