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 MqttPubAck {
pub packet_id: u16,
pub reason_code: u8,
pub properties: Vec<Property>,
}
impl MqttPubAck {
pub fn new(packet_id: u16, reason_code: u8, properties: Vec<Property>) -> Self {
Self {
packet_id,
reason_code,
properties,
}
}
}
impl MqttControlPacket for MqttPubAck {
fn control_packet_type(&self) -> u8 {
ControlPacketType::PUBACK as u8
}
fn variable_header(&self) -> Result<Vec<u8>, ParseError> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&self.packet_id.to_be_bytes());
if self.reason_code == 0x00 && self.properties.is_empty() {
return Ok(bytes);
}
bytes.push(self.reason_code);
bytes.extend(encode_properities_hdr(&self.properties)?);
Ok(bytes)
}
fn payload(&self) -> Result<Vec<u8>, ParseError> {
Ok(Vec::new())
}
fn from_bytes(buffer: &[u8]) -> Result<ParseOk, ParseError> {
let packet_type = packet_type(buffer)?;
if packet_type != ControlPacketType::PUBACK 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;
let reason_code = if size > 2 {
let code = *buffer.get(offset).ok_or(ParseError::BufferTooShort)?;
offset += 1;
code
} else {
0x00
};
let (properties, consumed) = if offset < total_len {
parse_properties_hdr(
buffer
.get(offset..total_len)
.ok_or(ParseError::BufferTooShort)?,
)?
} else {
(vec![], 0)
};
offset += consumed;
let packet = MqttPacket::PubAck5(MqttPubAck {
packet_id,
reason_code,
properties,
});
if offset != total_len {
return Err(ParseError::InternalError(format!(
"Inconsistent offset {} != total: {}",
offset, total_len
)));
}
Ok(ParseOk::Packet(packet, offset))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mqtt_serde::control_packet::MqttPacket;
use hex;
#[test]
fn test_puback_serde_roundtrip_success() {
let puback_original = MqttPubAck::new(42, 0x00, vec![]);
let bytes = puback_original.to_bytes().unwrap();
assert_eq!(bytes, vec![0x40, 0x02, 0x00, 0x2A]);
let ParseOk::Packet(MqttPacket::PubAck5(puback_deserialized), consumed) =
MqttPubAck::from_bytes(&bytes).unwrap()
else {
panic!("Failed to deserialize");
};
assert_eq!(puback_original, puback_deserialized);
assert_eq!(consumed, 4);
}
#[test]
fn test_puback_serde_roundtrip_with_properties() {
let properties = vec![Property::ReasonString("Not authorized".to_string())];
let puback_original = MqttPubAck::new(100, 0x87, properties);
let bytes = puback_original.to_bytes().unwrap();
println!("Serialized PUBACK with properties: {}", hex::encode(&bytes));
let ParseOk::Packet(MqttPacket::PubAck5(puback_deserialized), consumed) =
MqttPubAck::from_bytes(&bytes).unwrap()
else {
panic!("Failed to deserialize");
};
assert_eq!(puback_original, puback_deserialized);
assert!(consumed > 4); }
#[test]
fn test_parse_minimal_puback() {
let bytes = vec![0x40, 0x02, 0x00, 0x2C]; let ParseOk::Packet(MqttPacket::PubAck5(puback_deserialized), _consumed) =
MqttPubAck::from_bytes(&bytes).unwrap()
else {
panic!("Failed to deserialize");
};
assert_eq!(puback_deserialized.packet_id, 44);
assert_eq!(puback_deserialized.reason_code, 0x00); assert!(puback_deserialized.properties.is_empty());
}
#[test]
fn test_puback() {
let bytes = hex::decode("400400021000").unwrap();
let bytes_len = bytes.len();
let res: Result<ParseOk, ParseError> = MqttPubAck::from_bytes(&bytes);
match res {
Ok(ParseOk::Packet(MqttPacket::PubAck5(puback), consumed)) => {
assert_eq!(puback.packet_id, 2);
assert_eq!(puback.reason_code, 0x10);
assert_eq!(puback.properties, vec![]);
assert_eq!(consumed, bytes_len);
}
_ => panic!("Expected PubAck"),
}
}
}