gmqtt 0.1.1

A no_std, no_alloc MQTTv5 packet parsing library for embedded devices
Documentation
#![cfg_attr(not(test), no_std)]
//! [![github]](https://gitlab.com/genesismobo/gmqtt) [![crates-io]](https://crates.io/crates/gmqtt) [![docs-rs]](https://docs.rs/gmqtt)
//!
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
//!
//! <br>
//! This library provides a way for writing and reading MQTT V5 packets in a `no_std` and `no_alloc` environment
//! <br>
//!
//! # Details
//! - Write a packet to a buffer and read back from it
//! ```
//! use gmqtt::{
//!     control_packet::{
//!         connect::{ConnectProperties, Login},
//!         Connect, Packet,
//!     },
//!     read_packet, write_packet,
//! };
//!
//! const MAX_MQTT_PACKET_SIZE: u32 = 1024;
//!
//! pub fn main() {
//!     // Instantiate a packet with your data
//!     let keep_alive: u16 = 60; // 60 seconds
//!     let login = Login {
//!         username: "username",
//!         password: "password".as_bytes(),
//!     };
//!     let properties = ConnectProperties {
//!         topic_alias_max: Some(0),
//!         max_packet_size: Some(MAX_MQTT_PACKET_SIZE),
//!         ..ConnectProperties::default()
//!     };
//!     let connect_packet = Packet::Connect(Connect {
//!         protocol: gmqtt::Protocol::V5,
//!         clean_start: false,
//!         keep_alive,
//!         client_id: "our client id",
//!         last_will: None,
//!         login: Some(login),
//!         properties: Some(properties),
//!     });
//!
//!     let mut buffer = [0u8; MAX_MQTT_PACKET_SIZE as usize];
//!
//!     // Use the `write_packet` function to write/encode the packet, returning the encoded length
//!     let len = write_packet(&connect_packet, &mut buffer).unwrap();
//!
//!     // Use the `read_packet` function to read/decode the packet.
//!     let read_packet = read_packet(&buffer[..len]).unwrap();
//!
//!     assert_eq!(connect_packet, read_packet);
//! }
//! ```

mod protocol;
mod utils;

use crate::control_packet::PropertyType;
use core::{
    convert::TryFrom,
    fmt::{Debug, Display, Formatter},
    num::NonZeroU16,
};
use utils::write_u16;

pub mod control_packet;

pub use heapless::Vec;
pub use protocol::Protocol;

pub use crate::control_packet::{
    clone_packet, read_packet, subscribe::SubscribeFilter, write_packet,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MqttError {
    /// Passed buffer has insufficient size
    /// It is the caller's responsibility to pass a large enough buffer
    InsufficientBufferSize,

    /// Trying to read/write an invalid length.
    /// Refers to an invalid/corrupted length, rather than a buffer size issue
    InvalidLength,

    /// PacketId cannot be zero
    PacketIdZero,

    /// SubscriptionIdentifier cannot be zero
    SubscriptionIdentifierZero,
    /// Attempted to decode a non-UTF8 string
    InvalidString(core::str::Utf8Error),

    /// UserProperties heapless vector has a limit size of 32 units
    TooManyUserProperties,

    TooManyFilters,
    TooManySubscriptionIdentifiers,
    TooManySubscribeReasonCodes,
    IncorrectPacketFormat,
    PayloadTooLong,
    PayloadRequired,
    MalformedRemainingLength,
    MalformedPacket,
    InsufficientBytes,
    InvalidHeader,
    InvalidPid,
    InvalidProtocol(u8),
    InvalidPropertyByte(u8),
    InvalidPropertyType(PropertyType),
    InvalidConnectReasonCode(u8),
    InvalidReasonCode(u8),
    InvalidRetainForwardRule(u8),
    InvalidQoS(u8),
    InvalidDisconnectReason(u8),
}

impl Display for MqttError {
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        write!(f, "{:?}", self)
    }
}

#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Copy)]
pub struct Pid(NonZeroU16);
impl Pid {
    /// Returns a new `Pid`
    pub fn new(n: u16) -> Self {
        Pid(NonZeroU16::new(n).unwrap())
    }

    /// Get the `Pid` as a raw `u16`.
    pub fn get(self) -> u16 {
        self.0.get()
    }

    pub fn read(buf: &[u8], offset: &mut usize) -> Result<Self, MqttError> {
        let pid = ((buf[*offset] as u16) << 8) | buf[*offset + 1] as u16;
        *offset += 2;
        Self::try_from(pid)
    }

    pub fn write(self, buf: &mut [u8], offset: &mut usize) {
        write_u16(buf, offset, self.get())
    }
}

impl Default for Pid {
    fn default() -> Pid {
        Pid::new(1)
    }
}

impl core::ops::Add<u16> for Pid {
    type Output = Pid;

    /// Adding a `u16` to a `Pid` will wrap around and avoid 0.
    fn add(self, u: u16) -> Pid {
        let n = match self.get().overflowing_add(u) {
            (n, false) => n,
            (n, true) => n + 1,
        };
        Pid(NonZeroU16::new(n).unwrap())
    }
}

impl core::ops::Sub<u16> for Pid {
    type Output = Pid;

    /// Adding a `u16` to a `Pid` will wrap around and avoid 0.
    fn sub(self, u: u16) -> Pid {
        let n = match self.get().overflowing_sub(u) {
            (0, _) => core::u16::MAX,
            (n, false) => n,
            (n, true) => n - 1,
        };
        Pid(NonZeroU16::new(n).unwrap())
    }
}

impl From<Pid> for u16 {
    /// Convert `Pid` to `u16`.
    fn from(p: Pid) -> Self {
        p.0.get()
    }
}

impl TryFrom<u16> for Pid {
    type Error = MqttError;

    /// Convert `u16` to `Pid`. Will fail for value 0.
    fn try_from(u: u16) -> Result<Self, MqttError> {
        match NonZeroU16::new(u) {
            Some(nz) => Ok(Pid(nz)),
            None => Err(MqttError::InvalidPid),
        }
    }
}

/// Quality of service
#[repr(u8)]
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Copy)]
pub enum QoS {
    AtMostOnce = 0,
    AtLeastOnce = 1,
    ExactlyOnce = 2,
}

/// Maps a number to QoS
impl TryFrom<u8> for QoS {
    type Error = MqttError;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(QoS::AtMostOnce),
            1 => Ok(QoS::AtLeastOnce),
            2 => Ok(QoS::ExactlyOnce),
            qos => Err(MqttError::InvalidQoS(qos)),
        }
    }
}

/// Combined `QoS`/`Pid`
///
/// Used only in `Publish` packets
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Copy)]
pub enum QosPid {
    AtMostOnce,
    AtLeastOnce(Pid),
    ExactlyOnce(Pid),
}

impl QosPid {
    /// Extract the `Pid` from a `QosPid`, if any
    pub fn pid(self) -> Option<Pid> {
        match self {
            QosPid::AtMostOnce => None,
            QosPid::AtLeastOnce(p) => Some(p),
            QosPid::ExactlyOnce(p) => Some(p),
        }
    }

    /// Extract the `QoS` from a `QosPid`
    pub fn qos(self) -> QoS {
        match self {
            QosPid::AtMostOnce => QoS::AtMostOnce,
            QosPid::AtLeastOnce(_) => QoS::AtLeastOnce,
            QosPid::ExactlyOnce(_) => QoS::ExactlyOnce,
        }
    }
}