use core::convert::TryFrom;
use heapless::Vec;
use num_enum::TryFromPrimitive;
use crate::{utils::*, MqttError};
use super::{PacketType, PropertyType};
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Disconnect<'a> {
pub reason: DisconnectReason,
pub properties: Option<DisconnectProperties<'a>>,
}
#[derive(Debug, Clone, PartialEq, Copy, TryFromPrimitive)]
#[repr(u8)]
pub enum DisconnectReason {
NormalDisconnection = 0x00,
DisconnectWithWillMessage = 0x04,
UnspecifiedError = 0x80,
MalformedPacket = 0x81,
ProtocolError = 0x82,
ImplementationSpecificError = 0x83,
NotAuthorized = 0x87,
ServerBusy = 0x89,
ServerShuttingDown = 0x8B,
KeepAliveTimeout = 0x8D,
SessionTakenOver = 0x8E,
TopicFilterInvalid = 0x8F,
TopicNameInvalid = 0x90,
ReceiveMaximumExceeded = 0x93,
TopicAliasInvalid = 0x94,
PacketTooLarge = 0x95,
MessageRateTooHigh = 0x96,
QuotaExceeded = 0x97,
AdministrativeAction = 0x98,
PayloadFormatInvalid = 0x99,
RetainNotSupported = 0x9A,
QoSNotSupported = 0x9B,
UseAnotherServer = 0x9C,
ServerMoved = 0x9D,
SharedSubscriptionsNotSupported = 0x9E,
ConnectionRateExceeded = 0x9F,
MaximumConnectTime = 0xA0,
SubscriptionIdentifiersNotSupported = 0xA1,
WildcardSubscriptionsNotSupported = 0xA2,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct DisconnectProperties<'a> {
pub session_expiry_interval: Option<u32>,
pub reason_string: Option<&'a str>,
pub user_properties: Vec<(&'a str, &'a str), 8>,
pub server_reference: Option<&'a str>,
}
impl<'a> Disconnect<'a> {
pub fn write(&self, buf: &mut [u8], offset: &mut usize) -> Result<usize, MqttError> {
check_remaining(buf, offset, 2)?;
let header: u8 = 0xE0;
let length: u8;
write_u8(buf, offset, header);
if self.reason == DisconnectReason::NormalDisconnection && self.properties.is_none() {
length = 0x00;
write_u8(buf, offset, length);
} else if let Some(ref properties) = self.properties {
length = 0x02 + properties.len() as u8;
write_u8(buf, offset, length);
write_u8(buf, offset, self.reason as u8);
properties.write(buf, offset)?;
} else {
length = 0x01;
write_u8(buf, offset, length);
write_u8(buf, offset, self.reason as u8);
}
Ok(2 + length as usize)
}
pub fn read(buf: &'a [u8], offset: &mut usize) -> Result<Self, MqttError> {
let packet_identifier = buf[*offset];
if packet_identifier & 0x0F != 0x00 {
return Err(MqttError::MalformedPacket);
}
if packet_identifier >> 4 != PacketType::Disconnect as u8 {
return Err(MqttError::InvalidHeader);
}
*offset += 1;
let remaining_len = buf[*offset];
let packet = match remaining_len {
0 => Self {
reason: DisconnectReason::NormalDisconnection,
properties: None,
},
1 => {
*offset += 1;
let rc = buf[*offset];
let reason = DisconnectReason::try_from(rc)
.map_err(|_| MqttError::InvalidDisconnectReason(rc))?;
Self {
reason,
properties: None,
}
}
_ => {
*offset += 1;
let rc = buf[*offset];
let reason = DisconnectReason::try_from(rc)
.map_err(|_| MqttError::InvalidDisconnectReason(rc))?;
*offset += 1;
let properties = DisconnectProperties::read(buf, offset)?;
Self { reason, properties }
}
};
Ok(packet)
}
}
impl Default for DisconnectReason {
fn default() -> Self {
Self::NormalDisconnection
}
}
impl DisconnectReason {
pub fn reason_str(&self) -> &'static str {
match *self {
DisconnectReason::NormalDisconnection => "Normal Disconnection",
DisconnectReason::DisconnectWithWillMessage => "Disconnect With Will Message",
DisconnectReason::UnspecifiedError => "Unspecified Error",
DisconnectReason::MalformedPacket => "Malformed Packet",
DisconnectReason::ProtocolError => "Protocol Error",
DisconnectReason::ImplementationSpecificError => "Implementation Specific Error",
DisconnectReason::NotAuthorized => "Not Authorized",
DisconnectReason::ServerBusy => "Server Busy",
DisconnectReason::ServerShuttingDown => "Server Shutting Down",
DisconnectReason::KeepAliveTimeout => "Keep Alive Timeout",
DisconnectReason::SessionTakenOver => "Session Taken Over",
DisconnectReason::TopicFilterInvalid => "Topic Filter Invalid",
DisconnectReason::TopicNameInvalid => "Topic Name Invalid",
DisconnectReason::ReceiveMaximumExceeded => "Receive Maximum Exceeded",
DisconnectReason::TopicAliasInvalid => "Topic Alias Invalid",
DisconnectReason::PacketTooLarge => "Packet Too Large",
DisconnectReason::MessageRateTooHigh => "Message Rate Too High",
DisconnectReason::QuotaExceeded => "Quota Exceeded",
DisconnectReason::AdministrativeAction => "Administrative Action",
DisconnectReason::PayloadFormatInvalid => "Payload Format Invalid",
DisconnectReason::RetainNotSupported => "Retain Not Supported",
DisconnectReason::QoSNotSupported => "QoS Not Supported",
DisconnectReason::UseAnotherServer => "Use Another Server",
DisconnectReason::ServerMoved => "Server Moved",
DisconnectReason::SharedSubscriptionsNotSupported => {
"Shared Subscriptions Not Supported"
}
DisconnectReason::ConnectionRateExceeded => "Connection Rate Exceeded",
DisconnectReason::MaximumConnectTime => "Maximum Connect Time",
DisconnectReason::SubscriptionIdentifiersNotSupported => {
"Subscription Identifiers Not Supported"
}
DisconnectReason::WildcardSubscriptionsNotSupported => {
"Wildcard Subscriptions Not Supported"
}
}
}
}
impl<'a> DisconnectProperties<'a> {
fn len(&self) -> usize {
let mut len = 0;
if self.session_expiry_interval.is_some() {
len += 1 + 4;
}
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();
}
if let Some(reference) = &self.server_reference {
len += 1 + 2 + reference.len();
}
len
}
pub fn write(&self, buf: &mut [u8], offset: &mut usize) -> Result<(), MqttError> {
write_length(buf, offset, self.len())?;
if let Some(session_expiry_interval) = self.session_expiry_interval {
write_u8(buf, offset, PropertyType::SessionExpiryInterval as u8);
write_bytes(buf, offset, &session_expiry_interval.to_be_bytes());
}
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());
}
if let Some(server_reference) = self.server_reference {
write_u8(buf, offset, PropertyType::ServerReference as u8);
write_bytes_with_len(buf, offset, server_reference.as_bytes());
}
Ok(())
}
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 property_code = buf[prop_offset];
prop_offset += 1;
cursor += 1;
match super::property(property_code)? {
PropertyType::SessionExpiryInterval => {
properties.session_expiry_interval = Some(read_u32(buf, *offset + cursor));
cursor += 4;
}
PropertyType::ReasonString => {
let reason_string = read_str(buf, &mut prop_offset)?;
cursor += 2 + reason_string.len();
properties.reason_string = Some(reason_string);
}
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)?;
}
PropertyType::ServerReference => {
let server_reference = read_str(buf, &mut prop_offset)?;
cursor += 2 + server_reference.len();
properties.server_reference = Some(server_reference);
}
other => return Err(MqttError::InvalidPropertyType(other)),
}
}
*offset += properties.len();
Ok(Some(properties))
}
}
#[cfg(test)]
mod tests {
use super::{Disconnect, DisconnectProperties, DisconnectReason};
#[test]
fn encode_disconnect() {
let mut buf = [0u8; 2];
let expected = [0xE0, 0x00];
let disconnect = Disconnect::default();
disconnect.write(&mut buf, &mut 0).unwrap();
assert_eq!(expected, buf);
}
#[test]
fn encode_disconnect_with_reason() {
let mut buf = [0u8; 3];
let expected = [0xe0, 0x01, DisconnectReason::UnspecifiedError as u8];
let disconnect = Disconnect {
reason: DisconnectReason::UnspecifiedError,
properties: None,
};
disconnect.write(&mut buf, &mut 0).unwrap();
assert_eq!(expected, buf);
}
#[test]
fn encode_disconnect_with_properties() {
let mut buf = [0u8; 64];
let mut offset = 0;
let expected = [
0xe0, 0x09, DisconnectReason::UnspecifiedError as u8, 0x07, 0x1f,
0x00,
0x04,
b't',
b'e',
b's',
b't',
];
let properties = DisconnectProperties {
reason_string: Some("test"),
..DisconnectProperties::default()
};
let disconnect = Disconnect {
reason: DisconnectReason::UnspecifiedError,
properties: Some(properties),
};
disconnect.write(&mut buf, &mut offset).unwrap();
assert_eq!(&expected, &buf[..offset]);
}
#[test]
fn decode_disconnect() {
let bytes = [0xe0, 0x01, 0x81];
let mut offset = 0;
let expected = Disconnect {
reason: DisconnectReason::MalformedPacket,
properties: None,
};
let disconnect = Disconnect::read(&bytes, &mut offset).unwrap();
assert_eq!(expected, disconnect)
}
#[test]
fn decode_disconnect_with_properties() {
let bytes = [
0xe0, 0x09, DisconnectReason::UnspecifiedError as u8, 0x07, 0x1f,
0x00,
0x04,
b't',
b'e',
b's',
b't',
];
let mut offset = 0;
let properties = DisconnectProperties {
reason_string: Some("test"),
..DisconnectProperties::default()
};
let expected = Disconnect {
reason: DisconnectReason::UnspecifiedError,
properties: Some(properties),
};
let disconnect = Disconnect::read(&bytes, &mut offset).unwrap();
assert_eq!(expected, disconnect)
}
}