use serde::{Deserialize, Serialize};
use crate::mqtt_serde::control_packet::{ControlPacketType, MqttControlPacket, MqttPacket};
use crate::mqtt_serde::mqttv5::common::properties::{
encode_properities_hdr, parse_properties_hdr, Property,
};
use crate::mqtt_serde::parser::{
packet_type, parse_packet_id, parse_remaining_length, ParseError, ParseOk,
};
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct MqttSubAck {
pub packet_id: u16,
pub reason_codes: Vec<u8>,
pub properties: Vec<Property>,
}
impl MqttSubAck {
pub fn new(packet_id: u16, reason_codes: Vec<u8>, properties: Vec<Property>) -> Self {
Self {
packet_id,
reason_codes,
properties,
}
}
pub fn new_success(packet_id: u16, subscription_count: usize) -> Self {
let reason_codes = vec![0x00; subscription_count]; Self::new(packet_id, reason_codes, Vec::new())
}
pub fn new_simple(packet_id: u16, reason_codes: Vec<u8>) -> Self {
Self::new(packet_id, reason_codes, Vec::new())
}
pub fn new_granted(packet_id: u16, qos_levels: Vec<u8>) -> Self {
Self::new(packet_id, qos_levels, Vec::new())
}
pub fn new_mixed(packet_id: u16, reason_codes: Vec<u8>, properties: Vec<Property>) -> Self {
Self::new(packet_id, reason_codes, properties)
}
}
impl MqttControlPacket for MqttSubAck {
fn control_packet_type(&self) -> u8 {
ControlPacketType::SUBACK as u8
}
fn variable_header(&self) -> Result<Vec<u8>, ParseError> {
let mut bytes = Vec::new();
#[cfg(feature = "strict-protocol-compliance")]
if self.packet_id == 0 {
return Err(ParseError::ParseError(
"SUBACK packet_id must be > 0".to_string(),
));
}
bytes.extend_from_slice(&self.packet_id.to_be_bytes());
bytes.extend(encode_properities_hdr(&self.properties)?);
Ok(bytes)
}
fn payload(&self) -> Result<Vec<u8>, ParseError> {
#[cfg(feature = "strict-protocol-compliance")]
if self.reason_codes.is_empty() {
return Err(ParseError::ParseError(
"SUBACK payload must contain at least one reason code".to_string(),
));
}
Ok(self.reason_codes.clone())
}
fn from_bytes(buffer: &[u8]) -> Result<ParseOk, ParseError> {
let packet_type = packet_type(buffer)?;
if packet_type != ControlPacketType::SUBACK as u8 {
return Err(ParseError::InvalidPacketType);
}
let (size, vbi_len) = parse_remaining_length(&buffer[1..])?;
let mut offset: usize = 1 + vbi_len;
let total_len = offset + size;
if total_len > buffer.len() {
return Ok(ParseOk::Continue(total_len - buffer.len(), 0));
}
let (packet_id, consumed) =
parse_packet_id(buffer.get(offset..).ok_or(ParseError::BufferTooShort)?)?;
offset += consumed;
#[cfg(feature = "strict-protocol-compliance")]
if packet_id == 0 {
return Err(ParseError::ParseError(
"SUBACK packet_id must be > 0".to_string(),
));
}
let (properties, consumed) = parse_properties_hdr(
buffer
.get(offset..total_len)
.ok_or(ParseError::BufferTooShort)?,
)?;
offset += consumed;
let reason_codes = buffer
.get(offset..total_len)
.ok_or(ParseError::BufferTooShort)?
.to_vec();
if reason_codes.is_empty() {
#[cfg(feature = "strict-protocol-compliance")]
return Err(ParseError::ParseError(
"SUBACK payload must contain at least one reason code".to_string(),
));
}
let suback = MqttSubAck {
packet_id,
reason_codes,
properties,
};
Ok(ParseOk::Packet(MqttPacket::SubAck5(suback), total_len))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_suback_success() {
let suback = MqttSubAck::new_success(1234, 3);
assert_eq!(suback.packet_id, 1234);
assert_eq!(suback.reason_codes, vec![0x00, 0x00, 0x00]);
assert!(suback.properties.is_empty());
let bytes = suback.to_bytes().unwrap();
match MqttSubAck::from_bytes(&bytes).unwrap() {
ParseOk::Packet(MqttPacket::SubAck5(parsed_suback), consumed) => {
assert_eq!(consumed, bytes.len());
assert_eq!(parsed_suback.packet_id, 1234);
assert_eq!(parsed_suback.reason_codes, vec![0x00, 0x00, 0x00]);
assert!(parsed_suback.properties.is_empty());
}
_ => panic!("Expected SUBACK packet"),
}
}
#[test]
fn test_suback_granted_qos() {
let qos_levels = vec![0x00, 0x01, 0x02]; let suback = MqttSubAck::new_granted(5678, qos_levels.clone());
let bytes = suback.to_bytes().unwrap();
match MqttSubAck::from_bytes(&bytes).unwrap() {
ParseOk::Packet(MqttPacket::SubAck5(parsed_suback), _) => {
assert_eq!(parsed_suback.packet_id, 5678);
assert_eq!(parsed_suback.reason_codes, qos_levels);
}
_ => panic!("Expected SUBACK packet"),
}
}
#[test]
fn test_suback_mixed_results() {
let reason_codes = vec![
0x00, 0x01, 0x02, 0x80, 0x83, 0x8F, 0x91, 0x97, ];
let suback = MqttSubAck::new_simple(9999, reason_codes.clone());
let bytes = suback.to_bytes().unwrap();
match MqttSubAck::from_bytes(&bytes).unwrap() {
ParseOk::Packet(MqttPacket::SubAck5(parsed_suback), _) => {
assert_eq!(parsed_suback.packet_id, 9999);
assert_eq!(parsed_suback.reason_codes, reason_codes);
}
_ => panic!("Expected SUBACK packet"),
}
}
#[test]
fn test_suback_with_properties() {
let reason_codes = vec![0x01, 0x02]; let properties = vec![
Property::ReasonString("Subscriptions processed".to_string()),
Property::UserProperty("server".to_string(), "mqtt_broker_v5".to_string()),
];
let suback = MqttSubAck::new(1111, reason_codes.clone(), properties);
let bytes = suback.to_bytes().unwrap();
match MqttSubAck::from_bytes(&bytes).unwrap() {
ParseOk::Packet(MqttPacket::SubAck5(parsed_suback), _) => {
assert_eq!(parsed_suback.packet_id, 1111);
assert_eq!(parsed_suback.reason_codes, reason_codes);
assert_eq!(parsed_suback.properties.len(), 2);
}
_ => panic!("Expected SUBACK packet"),
}
}
#[test]
fn test_suback_single_subscription() {
let suback = MqttSubAck::new_simple(42, vec![0x02]);
let bytes = suback.to_bytes().unwrap();
assert_eq!(bytes[0], 0x90);
match MqttSubAck::from_bytes(&bytes).unwrap() {
ParseOk::Packet(MqttPacket::SubAck5(parsed_suback), _) => {
assert_eq!(parsed_suback.packet_id, 42);
assert_eq!(parsed_suback.reason_codes, vec![0x02]);
}
_ => panic!("Expected SUBACK packet"),
}
}
#[test]
fn test_suback_error_conditions() {
let suback = MqttSubAck::new_simple(0, vec![0x00]);
assert!(suback.to_bytes().is_err());
let suback = MqttSubAck::new_simple(1, Vec::new());
assert!(suback.to_bytes().is_err());
}
#[test]
fn test_suback_all_error_codes() {
let reason_codes = vec![
0x80, 0x83, 0x87, 0x8F, 0x91, 0x97, 0x9E, 0xA1, 0xA2, ];
let suback = MqttSubAck::new_simple(2222, reason_codes.clone());
let bytes = suback.to_bytes().unwrap();
match MqttSubAck::from_bytes(&bytes).unwrap() {
ParseOk::Packet(MqttPacket::SubAck5(parsed_suback), _) => {
assert_eq!(parsed_suback.reason_codes, reason_codes);
}
_ => panic!("Expected SUBACK packet"),
}
}
#[test]
fn test_suback_roundtrip_comprehensive() {
let reason_codes = vec![0x00, 0x01, 0x02, 0x80, 0x8F];
let properties = vec![
Property::ReasonString("Mixed subscription results".to_string()),
Property::UserProperty("timestamp".to_string(), "2025-01-01T12:00:00Z".to_string()),
];
let original_suback = MqttSubAck::new(0x7FFF, reason_codes.clone(), properties);
let bytes = original_suback.to_bytes().unwrap();
match MqttSubAck::from_bytes(&bytes).unwrap() {
ParseOk::Packet(MqttPacket::SubAck5(parsed_suback), consumed) => {
assert_eq!(consumed, bytes.len());
assert_eq!(parsed_suback.packet_id, original_suback.packet_id);
assert_eq!(parsed_suback.reason_codes, original_suback.reason_codes);
assert_eq!(
parsed_suback.properties.len(),
original_suback.properties.len()
);
}
_ => panic!("Expected SUBACK packet"),
}
}
#[test]
fn test_suback_large_subscription_list() {
let reason_codes: Vec<u8> = (0..100)
.map(|i| if i % 3 == 0 { 0x80 } else { (i % 3) as u8 })
.collect();
let suback = MqttSubAck::new_simple(3333, reason_codes.clone());
let bytes = suback.to_bytes().unwrap();
match MqttSubAck::from_bytes(&bytes).unwrap() {
ParseOk::Packet(MqttPacket::SubAck5(parsed_suback), _) => {
assert_eq!(parsed_suback.packet_id, 3333);
assert_eq!(parsed_suback.reason_codes.len(), 100);
assert_eq!(parsed_suback.reason_codes, reason_codes);
}
_ => panic!("Expected SUBACK packet"),
}
}
#[test]
fn test_parse_suback() {
let packet_bytes: [u8; 6] = [0x90, 0x04, 0xa9, 0x8f, 0x00, 0x00];
let result = MqttSubAck::from_bytes(packet_bytes.as_ref());
match result {
Ok(ParseOk::Packet(packet, consumed)) => {
assert_eq!(consumed, 6);
match packet {
MqttPacket::SubAck5(suback) => {
assert_eq!(suback.packet_id, 43407);
assert_eq!(suback.reason_codes, vec![0]);
}
_ => panic!("Invalid packet"),
}
}
_ => panic!("Invalid result"),
}
}
}