use core::convert::{TryFrom, TryInto};
use heapless::Vec;
use crate::{utils::*, MqttError};
use super::{property, PropertyType};
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct ConnAck<'a> {
pub session_present: bool,
pub code: ConnectReasonCode,
pub properties: Option<ConnAckProperties<'a>>,
}
impl<'a> ConnAck<'a> {
pub fn new(code: ConnectReasonCode, session_present: bool) -> Self {
ConnAck {
code,
session_present,
properties: None,
}
}
pub fn read(buf: &'a [u8], offset: &mut usize) -> Result<Self, MqttError> {
let flags = buf[*offset];
*offset += 1;
let return_code = buf[*offset];
*offset += 1;
Ok(ConnAck {
session_present: (flags & 0b1 == 1),
code: return_code.try_into()?,
properties: ConnAckProperties::read(buf, offset)?,
})
}
pub fn write(&self, buf: &mut [u8], offset: &mut usize) -> Result<usize, MqttError> {
let header: u8 = 0x20;
let mut flags: u8 = 0b0000_0000;
if self.session_present {
flags |= 0b1;
};
let return_code = self.code as u8;
check_remaining(buf, offset, self.len())?;
write_u8(buf, offset, header);
let write_len = write_length(buf, offset, self.len())? + 1;
write_u8(buf, offset, flags);
write_u8(buf, offset, return_code);
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 = 1 + 1;
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 => {
len += 1;
}
}
len
}
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Default)]
pub struct ConnAckProperties<'a> {
pub session_expiry_interval: Option<u32>,
pub assigned_client_identifier: Option<&'a str>,
pub server_keep_alive: Option<u16>,
pub authentication_method: Option<&'a str>,
pub authentication_data: Option<&'a [u8]>,
pub response_information: Option<&'a str>,
pub server_reference: Option<&'a str>,
pub reason_string: Option<&'a str>,
pub receive_max: Option<u16>,
pub topic_alias_max: Option<u16>,
pub max_qos: Option<u8>,
pub retain_available: Option<u8>,
pub user_properties: Vec<(&'a str, &'a str), 32>,
pub max_packet_size: Option<u32>,
pub wildcard_subscription_available: Option<u8>,
pub subscription_identifiers_available: Option<u8>,
pub shared_subscription_available: Option<u8>,
}
impl<'a> ConnAckProperties<'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::AssignedClientIdentifier => {
let assigned_client_identifier = read_str(buf, &mut prop_offset)?;
cursor += 2 + assigned_client_identifier.len();
properties.assigned_client_identifier = Some(assigned_client_identifier);
}
PropertyType::AuthenticationData => {
let authentication_data = read_bytes(buf, &mut prop_offset)?;
cursor += 2 + authentication_data.len();
properties.authentication_data = Some(authentication_data);
}
PropertyType::AuthenticationMethod => {
let authentication_method = read_str(buf, &mut prop_offset)?;
cursor += 2 + authentication_method.len();
properties.authentication_method = Some(authentication_method);
}
PropertyType::MaximumPacketSize => {
properties.max_packet_size = Some(read_u32(buf, *offset + cursor));
cursor += 4;
}
PropertyType::MaximumQos => {
properties.max_qos = Some(buf[*offset + cursor]);
cursor += 1;
}
PropertyType::ReasonString => {
let reason_string = read_str(buf, &mut prop_offset)?;
cursor += 2 + reason_string.len();
properties.reason_string = Some(reason_string);
}
PropertyType::ReceiveMaximum => {
properties.receive_max = Some(read_u16(buf, *offset + cursor));
cursor += 2;
}
PropertyType::ResponseInformation => {
let response_information = read_str(buf, &mut prop_offset)?;
cursor += 2 + response_information.len();
properties.response_information = Some(response_information);
}
PropertyType::RetainAvailable => {
properties.retain_available = Some(buf[*offset + cursor]);
cursor += 1;
}
PropertyType::ServerKeepAlive => {
properties.server_keep_alive = Some(read_u16(buf, *offset + cursor));
cursor += 2;
}
PropertyType::ServerReference => {
let server_reference = read_str(buf, &mut prop_offset)?;
cursor += 2 + server_reference.len();
properties.server_reference = Some(server_reference);
}
PropertyType::SessionExpiryInterval => {
properties.session_expiry_interval = Some(read_u32(buf, *offset + cursor));
cursor += 4;
}
PropertyType::SharedSubscriptionAvailable => {
properties.shared_subscription_available = Some(buf[*offset + cursor]);
cursor += 1;
}
PropertyType::SubscriptionIdentifierAvailable => {
properties.subscription_identifiers_available = Some(buf[*offset + cursor]);
cursor += 1;
}
PropertyType::TopicAliasMaximum => {
properties.topic_alias_max = Some(read_u16(buf, *offset + cursor));
cursor += 2;
}
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::WildcardSubscriptionAvailable => {
properties.wildcard_subscription_available = Some(buf[*offset + cursor]);
cursor += 1;
}
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(assigned_client_identifier) = self.assigned_client_identifier {
write_u8(buf, offset, PropertyType::AssignedClientIdentifier as u8);
write_bytes_with_len(buf, offset, assigned_client_identifier.as_bytes());
}
if let Some(authentication_data) = self.authentication_data {
write_u8(buf, offset, PropertyType::AuthenticationData as u8);
write_bytes_with_len(buf, offset, authentication_data);
}
if let Some(authentication_method) = self.authentication_method {
write_u8(buf, offset, PropertyType::AuthenticationMethod as u8);
write_bytes_with_len(buf, offset, authentication_method.as_bytes());
}
if let Some(max_packet_size) = self.max_packet_size {
write_u8(buf, offset, PropertyType::MaximumPacketSize as u8);
write_bytes(buf, offset, &max_packet_size.to_be_bytes());
}
if let Some(max_qos) = self.max_qos {
write_u8(buf, offset, PropertyType::MaximumQos as u8);
write_u8(buf, offset, max_qos);
}
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());
}
if let Some(receive_max) = self.receive_max {
write_u8(buf, offset, PropertyType::ReceiveMaximum as u8);
write_u16(buf, offset, receive_max);
}
if let Some(response_information) = self.response_information {
write_u8(buf, offset, PropertyType::ResponseInformation as u8);
write_bytes_with_len(buf, offset, response_information.as_bytes());
}
if let Some(retain_available) = self.retain_available {
write_u8(buf, offset, PropertyType::RetainAvailable as u8);
write_u8(buf, offset, retain_available);
}
if let Some(server_keep_alive) = self.server_keep_alive {
write_u8(buf, offset, PropertyType::ServerKeepAlive as u8);
write_u16(buf, offset, server_keep_alive);
}
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());
}
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(shared_subscription_available) = self.shared_subscription_available {
write_u8(buf, offset, PropertyType::SharedSubscriptionAvailable as u8);
write_u8(buf, offset, shared_subscription_available);
}
if let Some(topic_alias_max) = self.topic_alias_max {
write_u8(buf, offset, PropertyType::TopicAliasMaximum as u8);
write_u16(buf, offset, topic_alias_max);
}
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(wildcard_subscription_available) = self.wildcard_subscription_available {
write_u8(
buf,
offset,
PropertyType::WildcardSubscriptionAvailable as u8,
);
write_u8(buf, offset, wildcard_subscription_available);
}
if let Some(subscription_identifiers_available) = self.subscription_identifiers_available {
write_u8(
buf,
offset,
PropertyType::SubscriptionIdentifierAvailable as u8,
);
write_u8(buf, offset, subscription_identifiers_available);
}
Ok(())
}
fn len(&self) -> usize {
let mut len = 0;
if self.session_expiry_interval.is_some() {
len += 1 + 4;
}
if self.receive_max.is_some() {
len += 1 + 2;
}
if self.max_qos.is_some() {
len += 1 + 1;
}
if self.retain_available.is_some() {
len += 1 + 1;
}
if self.max_packet_size.is_some() {
len += 1 + 4;
}
if self.topic_alias_max.is_some() {
len += 1 + 2;
}
if self.wildcard_subscription_available.is_some() {
len += 1 + 1;
}
if self.subscription_identifiers_available.is_some() {
len += 1 + 1;
}
if self.shared_subscription_available.is_some() {
len += 1 + 1;
}
if self.server_keep_alive.is_some() {
len += 1 + 2;
}
if let Some(reason) = &self.reason_string {
len += 1 + 2 + reason.len();
}
if let Some(id) = &self.assigned_client_identifier {
len += 1 + 2 + id.len();
}
if let Some(info) = &self.response_information {
len += 1 + 2 + info.len();
}
if let Some(reference) = &self.server_reference {
len += 1 + 2 + reference.len();
}
if let Some(authentication_method) = &self.authentication_method {
len += 1 + 2 + authentication_method.len();
}
if let Some(authentication_data) = &self.authentication_data {
len += 1 + 2 + authentication_data.len();
}
for (key, value) in self.user_properties.iter() {
len += 1 + 2 + key.len() + 2 + value.len();
}
len
}
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, Copy)]
#[repr(u8)]
pub enum ConnectReasonCode {
Success = 0,
UnspecifiedError = 0x80,
MalformedPacket = 0x81,
ProtocolError = 0x82,
ImplementationSpecificError = 0x83,
UnsupportedProtocolVersion = 0x84,
ClientIdentifierNotValid = 0x85,
BadUserNamePassword = 0x86,
NotAuthorized = 0x87,
ServerUnavailable = 0x88,
ServerBusy = 0x89,
Banned = 0x8A,
BadAuthenticationMethod = 0x8C,
TopicNameInvalid = 0x90,
PacketTooLarge = 0x95,
QuotaExceeded = 0x97,
PayloadFormatInvalid = 0x99,
RetainNotSupported = 0x9A,
QoSNotSupported = 0x9B,
UseAnotherServer = 0x9C,
ServerMoved = 0x9D,
ConnectionRateExceeded = 0x9F,
}
impl TryFrom<u8> for ConnectReasonCode {
type Error = MqttError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
let code = match value {
0 => ConnectReasonCode::Success,
128 => ConnectReasonCode::UnspecifiedError,
129 => ConnectReasonCode::MalformedPacket,
130 => ConnectReasonCode::ProtocolError,
131 => ConnectReasonCode::ImplementationSpecificError,
132 => ConnectReasonCode::UnsupportedProtocolVersion,
133 => ConnectReasonCode::ClientIdentifierNotValid,
134 => ConnectReasonCode::BadUserNamePassword,
135 => ConnectReasonCode::NotAuthorized,
136 => ConnectReasonCode::ServerUnavailable,
137 => ConnectReasonCode::ServerBusy,
138 => ConnectReasonCode::Banned,
140 => ConnectReasonCode::BadAuthenticationMethod,
144 => ConnectReasonCode::TopicNameInvalid,
149 => ConnectReasonCode::PacketTooLarge,
151 => ConnectReasonCode::QuotaExceeded,
153 => ConnectReasonCode::PayloadFormatInvalid,
154 => ConnectReasonCode::RetainNotSupported,
155 => ConnectReasonCode::QoSNotSupported,
156 => ConnectReasonCode::UseAnotherServer,
157 => ConnectReasonCode::ServerMoved,
159 => ConnectReasonCode::ConnectionRateExceeded,
code => return Err(MqttError::InvalidConnectReasonCode(code)),
};
Ok(code)
}
}
#[cfg(test)]
mod test {
use super::{ConnAck, ConnAckProperties, ConnectReasonCode};
fn sample<'a>() -> ConnAck<'a> {
let properties = ConnAckProperties {
session_expiry_interval: Some(1234),
receive_max: Some(432),
max_qos: Some(2),
retain_available: Some(1),
max_packet_size: Some(100),
assigned_client_identifier: Some("test"),
topic_alias_max: Some(456),
reason_string: Some("test"),
user_properties: heapless::Vec::<_, 32>::from_slice(&[("test", "test")]).unwrap(),
wildcard_subscription_available: Some(1),
subscription_identifiers_available: Some(1),
shared_subscription_available: Some(0),
server_keep_alive: Some(1234),
response_information: Some("test"),
server_reference: Some("test"),
authentication_method: Some("test"),
authentication_data: Some(&[1, 2, 3, 4]),
};
ConnAck {
session_present: false,
code: ConnectReasonCode::Success,
properties: Some(properties),
}
}
fn sample_bytes() -> Vec<u8> {
vec![
0x20, 0x57, 0x00, 0x00, 0x54, 0x12, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, 0x16, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04, 0x15, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, 0x27, 0x00, 0x00, 0x00, 0x64, 0x24, 0x02, 0x1f, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, 0x21, 0x01, 0xb0, 0x1a, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, 0x25, 0x01, 0x13, 0x04, 0xd2, 0x1c, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, 0x11, 0x00, 0x00, 0x04, 0xd2, 0x2a, 0x00, 0x22, 0x01, 0xc8, 0x26, 0x00, 0x04, 0x74, 0x65, 0x73, 0x74, 0x00, 0x04, 0x74, 0x65, 0x73,
0x74, 0x28, 0x01, 0x29, 0x01, ]
}
#[test]
fn connack_len() {
let expected = 87;
let actual = sample().len();
assert_eq!(expected, actual);
}
#[test]
fn connack_encode() {
let expected = sample_bytes();
let mut buf = [0u8; 89];
sample().write(&mut buf, &mut 0).unwrap();
assert_eq!(expected.as_slice(), buf);
}
#[test]
fn connack_decode() {
let bytes = &sample_bytes()[2..];
let expected = sample();
let actual = ConnAck::read(bytes, &mut 0).unwrap();
assert_eq!(expected, actual);
}
#[test]
fn connack_props_encode() {
let expected = &sample_bytes()[4..];
let mut buf = [0u8; 85];
sample()
.properties
.unwrap()
.write(&mut buf, &mut 0)
.unwrap();
assert_eq!(expected.len(), buf.len());
assert_eq!(expected, buf);
}
#[test]
fn connack_props_decode() {
let bytes = &sample_bytes()[4..];
let expected = sample().properties.unwrap();
let actual = ConnAckProperties::read(bytes, &mut 0).unwrap().unwrap();
assert_eq!(expected, actual);
}
}