#![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,
}
}
}