gmqtt 0.1.1

A no_std, no_alloc MQTTv5 packet parsing library for embedded devices
Documentation
use core::convert::TryFrom;

use heapless::Vec;

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

use super::{property, PropertyType};

/// A PUBACK packet is the response to a PUBLISH packet with QoS 1.
///
/// [MQTT5 SPECIFICATION](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901121)
#[derive(Debug, Clone, PartialEq)]
pub struct PubAck<'a> {
    /// Packet Identifier from the PUBLISH packet that is being acknowledged.
    pub pid: Pid,

    /// Byte 3 in the Variable Header is the PUBACK Reason Code.
    pub reason: PubAckReason,
    pub properties: Option<PubAckProperties<'a>>,
}

impl<'a> PubAck<'a> {
    pub fn new(pid: Pid) -> Self {
        PubAck {
            pid,
            reason: PubAckReason::Success,
            properties: None,
        }
    }

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

        let reason = PubAckReason::try_from(buf[*offset])?;

        // Move offset to properties_len field
        *offset += 1;

        Ok(PubAck {
            pid,
            reason,
            properties: PubAckProperties::read(buf, offset)?,
        })
    }

    pub fn write(&self, buf: &mut [u8], offset: &mut usize) -> Result<usize, MqttError> {
        // PubAck (0x40);
        let header: u8 = 0x40;

        check_remaining(buf, offset, self.len())?;

        write_u8(buf, offset, header);
        let write_len = write_length(buf, offset, self.len())? + 1;
        self.pid.write(buf, offset);
        write_u8(buf, offset, self.reason as u8);

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

        Ok(write_len)
    }

    fn len(&self) -> usize {
        let mut len = 2 + 1; // pkid + reason

        // If there are no properties, sending reason code is optional
        if self.reason == PubAckReason::Success && self.properties.is_none() {
            return 2;
        }

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

        len
    }
}

/// [MQTT5 SPECIFICATION]([MQTT5 SPECIFICATION](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901125))
#[derive(Debug, Clone, PartialEq, Default)]
pub struct PubAckProperties<'a> {
    /// `31 (0x1F)` Byte, Identifier of the Reason String.
    ///
    /// Followed by the UTF-8 Encoded String representing the reason associated with this response.
    pub reason_string: Option<&'a str>,

    /// `38 (0x26)` Byte, Identifier of the User Property.
    ///
    /// Followed by UTF-8 String Pair.
    pub user_properties: Vec<(&'a str, &'a str), 32>,
}
impl<'a> PubAckProperties<'a> {
    pub fn read(buf: &'a [u8], offset: &mut usize) -> Result<Option<Self>, MqttError> {
        let mut properties = Self::default();

        // NOTE: mosquitto 2.0.0 broker not send properties len
        if buf.len() <= *offset {
            return Ok(None);
        }

        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::ReasonString => {
                    let reason = read_str(buf, &mut prop_offset)?;
                    cursor += 2 + reason.len();
                    properties.reason_string = Some(reason);
                }

                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())?;
        if let Some(reason_string) = self.reason_string {
            write_u8(buf, offset, PropertyType::ReasonString as u8);
            write_bytes_with_len(buf, offset, reason_string.as_bytes());
        }

        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;

        if let Some(reason) = &self.reason_string {
            len += 1 + 2 + reason.len();
        }

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

        len
    }
}

/// Return code in connack
/// [MQTT5 SPECIFICATION](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901124)
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum PubAckReason {
    /// The message is accepted. Publication of the QoS 1 message proceeds.
    Success = 0,

    /// The message is accepted but there are no subscribers.
    /// This is sent only by the Server.
    /// If the Server knows that there are no matching subscribers, it MAY use this Reason Code instead of 0x00 (Success).
    NoMatchingSubscribers = 16,

    /// The receiver does not accept the publish but either does not want to reveal the reason, or it does not match one of the other values.
    UnspecifiedError = 128,

    /// The PUBLISH is valid but the receiver is not willing to accept it.
    ImplementationSpecificError = 131,

    /// The PUBLISH is not authorized.
    NotAuthorized = 135,

    /// The Topic Name is not malformed, but is not accepted by this Client or Server.
    TopicNameInvalid = 144,

    /// The Packet Identifier is already in use. This might indicate a mismatch in the Session State between the Client and Server.
    PacketIdentifierInUse = 145,

    /// An implementation or administrative imposed limit has been exceeded.
    QuotaExceeded = 151,

    /// The payload format does not match the specified Payload Format Indicator.
    PayloadFormatInvalid = 153,
}

impl TryFrom<u8> for PubAckReason {
    type Error = MqttError;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        let code = match value {
            0 => PubAckReason::Success,
            16 => PubAckReason::NoMatchingSubscribers,
            128 => PubAckReason::UnspecifiedError,
            131 => PubAckReason::ImplementationSpecificError,
            135 => PubAckReason::NotAuthorized,
            144 => PubAckReason::TopicNameInvalid,
            145 => PubAckReason::PacketIdentifierInUse,
            151 => PubAckReason::QuotaExceeded,
            153 => PubAckReason::PayloadFormatInvalid,
            code => return Err(MqttError::InvalidReasonCode(code)),
        };

        Ok(code)
    }
}

#[cfg(test)]
mod tests {
    use crate::Pid;

    use super::{PubAck, PubAckProperties, PubAckReason};

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

        PubAck {
            pid: Pid::new(42),
            reason: PubAckReason::NoMatchingSubscribers,
            properties: Some(properties),
        }
    }

    fn sample_bytes() -> Vec<u8> {
        vec![
            0x40, // payload type
            0x18, // remaining length
            0x00, 0x2a, // packet id
            0x10, // reason
            0x14, // properties len
            0x1f, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, // reason_string
            0x26, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, 0x00, 0x04, 0x74, 0x65, 0x73,
            0x74, // user properties
        ]
    }

    #[test]
    fn puback_len() {
        let expected = 24;
        let actual = sample().len();
        assert_eq!(expected, actual);
    }
    #[test]
    fn puback_encode() {
        let expected = sample_bytes();
        let mut buf = [0u8; 26];
        sample().write(&mut buf, &mut 0).unwrap();

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

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

    #[test]
    fn puback_props_encode() {
        let expected = &sample_bytes()[5..];
        let mut buf = [0u8; 21];
        sample()
            .properties
            .unwrap()
            .write(&mut buf, &mut 0)
            .unwrap();
        assert_eq!(expected.len(), buf.len());

        assert_eq!(expected, buf);
    }

    #[test]
    fn puback_props_decode() {
        let bytes = &sample_bytes()[5..];
        let expected = sample().properties.unwrap();
        let actual = PubAckProperties::read(bytes, &mut 0).unwrap().unwrap();
        assert_eq!(expected, actual);
    }

    fn sample2<'a>() -> PubAck<'a> {
        PubAck {
            pid: Pid::new(42),
            reason: PubAckReason::NoMatchingSubscribers,
            properties: None,
        }
    }

    fn sample_bytes2() -> Vec<u8> {
        vec![
            0x40, // payload type
            0x04, // remaining length
            0x00, 0x2a, // packet id
            0x10, // reason
            0x00, // properties len
        ]
    }

    #[test]
    fn puback_encode2() {
        let expected = sample_bytes2();
        let mut buf = [0u8; 6];
        sample2().write(&mut buf, &mut 0).unwrap();

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

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

    #[test]
    fn puback_decode_no_properties_len() {
        let bytes = [64, 3, 0, 39, 16];

        let packet = crate::read_packet(&bytes).unwrap();

        assert!(matches!(packet, crate::control_packet::Packet::PubAck(_)));
    }

    #[ignore = "This case is handled in read_packet function"]
    #[test]
    fn puback_decode_no_properties_len_success() {
        let bytes = [0x40, 0x02, 0x00, 0x0A];

        let packet = crate::read_packet(&bytes).unwrap();

        assert!(matches!(packet, crate::control_packet::Packet::PubAck(_)));
    }
}