mqtt5_protocol/packet/
pubrel.rs

1use crate::error::{MqttError, Result};
2use crate::packet::{FixedHeader, MqttPacket, PacketType};
3use crate::protocol::v5::properties::Properties;
4use crate::types::ReasonCode;
5use bytes::{Buf, BufMut};
6
7/// MQTT PUBREL packet (`QoS` 2 publish release, part 2)
8#[derive(Debug, Clone)]
9pub struct PubRelPacket {
10    /// Packet identifier
11    pub packet_id: u16,
12    /// Reason code
13    pub reason_code: ReasonCode,
14    /// PUBREL properties (v5.0 only)
15    pub properties: Properties,
16}
17
18impl PubRelPacket {
19    /// Creates a new PUBREL packet
20    #[must_use]
21    pub fn new(packet_id: u16) -> Self {
22        Self {
23            packet_id,
24            reason_code: ReasonCode::Success,
25            properties: Properties::default(),
26        }
27    }
28
29    /// Creates a new PUBREL packet with a reason code
30    #[must_use]
31    pub fn new_with_reason(packet_id: u16, reason_code: ReasonCode) -> Self {
32        Self {
33            packet_id,
34            reason_code,
35            properties: Properties::default(),
36        }
37    }
38
39    /// Sets the reason string
40    #[must_use]
41    pub fn with_reason_string(mut self, reason: String) -> Self {
42        self.properties.set_reason_string(reason);
43        self
44    }
45
46    /// Adds a user property
47    #[must_use]
48    pub fn with_user_property(mut self, key: String, value: String) -> Self {
49        self.properties.add_user_property(key, value);
50        self
51    }
52
53    /// Validates the reason code for PUBREL
54    fn is_valid_pubrel_reason_code(code: ReasonCode) -> bool {
55        matches!(
56            code,
57            ReasonCode::Success | ReasonCode::PacketIdentifierNotFound
58        )
59    }
60}
61
62impl MqttPacket for PubRelPacket {
63    fn packet_type(&self) -> PacketType {
64        PacketType::PubRel
65    }
66
67    fn flags(&self) -> u8 {
68        0x02 // PUBREL must have flags = 0x02
69    }
70
71    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
72        // Variable header
73        buf.put_u16(self.packet_id);
74
75        // For v5.0, always encode reason code and properties
76        // For v3.1.1, we would skip these if reason_code is Success and no properties
77        if self.reason_code != ReasonCode::Success || !self.properties.is_empty() {
78            buf.put_u8(u8::from(self.reason_code));
79            self.properties.encode(buf)?;
80        }
81
82        Ok(())
83    }
84
85    fn decode_body<B: Buf>(buf: &mut B, fixed_header: &FixedHeader) -> Result<Self> {
86        // Validate flags
87        if fixed_header.flags != 0x02 {
88            return Err(MqttError::MalformedPacket(format!(
89                "Invalid PUBREL flags: expected 0x02, got 0x{:02X}",
90                fixed_header.flags
91            )));
92        }
93
94        // Packet identifier
95        if buf.remaining() < 2 {
96            return Err(MqttError::MalformedPacket(
97                "PUBREL missing packet identifier".to_string(),
98            ));
99        }
100        let packet_id = buf.get_u16();
101
102        // Reason code and properties (optional in v3.1.1, always present in v5.0)
103        let (reason_code, properties) = if buf.has_remaining() {
104            // Read reason code
105            let reason_byte = buf.get_u8();
106            let code = ReasonCode::from_u8(reason_byte).ok_or_else(|| {
107                MqttError::MalformedPacket(format!("Invalid PUBREL reason code: {reason_byte}"))
108            })?;
109
110            if !Self::is_valid_pubrel_reason_code(code) {
111                return Err(MqttError::MalformedPacket(format!(
112                    "Invalid PUBREL reason code: {code:?}"
113                )));
114            }
115
116            // Properties (if present)
117            let props = if buf.has_remaining() {
118                Properties::decode(buf)?
119            } else {
120                Properties::default()
121            };
122
123            (code, props)
124        } else {
125            // v3.1.1 style - no reason code or properties means success
126            (ReasonCode::Success, Properties::default())
127        };
128
129        Ok(Self {
130            packet_id,
131            reason_code,
132            properties,
133        })
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use crate::protocol::v5::properties::PropertyId;
141    use bytes::BytesMut;
142
143    #[test]
144    fn test_pubrel_basic() {
145        let packet = PubRelPacket::new(123);
146
147        assert_eq!(packet.packet_id, 123);
148        assert_eq!(packet.reason_code, ReasonCode::Success);
149        assert!(packet.properties.is_empty());
150        assert_eq!(packet.flags(), 0x02);
151    }
152
153    #[test]
154    fn test_pubrel_with_reason() {
155        let packet = PubRelPacket::new_with_reason(456, ReasonCode::PacketIdentifierNotFound)
156            .with_reason_string("Packet ID not found".to_string());
157
158        assert_eq!(packet.packet_id, 456);
159        assert_eq!(packet.reason_code, ReasonCode::PacketIdentifierNotFound);
160        assert!(packet.properties.contains(PropertyId::ReasonString));
161    }
162
163    #[test]
164    fn test_pubrel_encode_decode() {
165        let packet = PubRelPacket::new(789);
166
167        let mut buf = BytesMut::new();
168        packet.encode(&mut buf).unwrap();
169
170        let fixed_header = FixedHeader::decode(&mut buf).unwrap();
171        assert_eq!(fixed_header.packet_type, PacketType::PubRel);
172        assert_eq!(fixed_header.flags, 0x02);
173
174        let decoded = PubRelPacket::decode_body(&mut buf, &fixed_header).unwrap();
175        assert_eq!(decoded.packet_id, 789);
176        assert_eq!(decoded.reason_code, ReasonCode::Success);
177    }
178
179    #[test]
180    fn test_pubrel_invalid_flags() {
181        let mut buf = BytesMut::new();
182        buf.put_u16(123);
183
184        let fixed_header = FixedHeader::new(PacketType::PubRel, 0x00, 2); // Wrong flags
185        let result = PubRelPacket::decode_body(&mut buf, &fixed_header);
186        assert!(result.is_err());
187    }
188}