mqtt5_protocol/packet/
pubrec.rs

1use crate::error::{MqttError, Result};
2use crate::packet::{AckPacketHeader, FixedHeader, MqttPacket, PacketType};
3use crate::protocol::v5::properties::Properties;
4use crate::types::ReasonCode;
5use bytes::{Buf, BufMut};
6
7/// MQTT PUBREC packet (`QoS` 2 publish received, part 1)
8#[derive(Debug, Clone)]
9pub struct PubRecPacket {
10    /// Packet identifier
11    pub packet_id: u16,
12    /// Reason code
13    pub reason_code: ReasonCode,
14    /// PUBREC properties (v5.0 only)
15    pub properties: Properties,
16}
17
18impl PubRecPacket {
19    /// Creates a new PUBREC 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 PUBREC 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 PUBREC
54    fn is_valid_pubrec_reason_code(code: ReasonCode) -> bool {
55        matches!(
56            code,
57            ReasonCode::Success
58                | ReasonCode::NoMatchingSubscribers
59                | ReasonCode::UnspecifiedError
60                | ReasonCode::ImplementationSpecificError
61                | ReasonCode::NotAuthorized
62                | ReasonCode::TopicNameInvalid
63                | ReasonCode::PacketIdentifierInUse
64                | ReasonCode::QuotaExceeded
65                | ReasonCode::PayloadFormatInvalid
66        )
67    }
68
69    /// Creates a bebytes header for this packet
70    #[must_use]
71    pub fn create_header(&self) -> AckPacketHeader {
72        AckPacketHeader::create(self.packet_id, self.reason_code)
73    }
74
75    /// Creates a packet from a bebytes header and properties
76    ///
77    /// # Errors
78    ///
79    /// Returns an error if the reason code in the header is invalid
80    pub fn from_header(header: AckPacketHeader, properties: Properties) -> Result<Self> {
81        let reason_code = header.get_reason_code().ok_or_else(|| {
82            MqttError::MalformedPacket(format!(
83                "Invalid PUBREC reason code: 0x{:02X}",
84                header.reason_code
85            ))
86        })?;
87
88        if !Self::is_valid_pubrec_reason_code(reason_code) {
89            return Err(MqttError::MalformedPacket(format!(
90                "Invalid PUBREC reason code: {reason_code:?}"
91            )));
92        }
93
94        Ok(Self {
95            packet_id: header.packet_id,
96            reason_code,
97            properties,
98        })
99    }
100}
101
102impl MqttPacket for PubRecPacket {
103    fn packet_type(&self) -> PacketType {
104        PacketType::PubRec
105    }
106
107    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
108        // Variable header
109        buf.put_u16(self.packet_id);
110
111        // For v5.0, always encode reason code and properties
112        // For v3.1.1, we would skip these if reason_code is Success and no properties
113        if self.reason_code != ReasonCode::Success || !self.properties.is_empty() {
114            buf.put_u8(u8::from(self.reason_code));
115            self.properties.encode(buf)?;
116        }
117
118        Ok(())
119    }
120
121    fn decode_body<B: Buf>(buf: &mut B, _fixed_header: &FixedHeader) -> Result<Self> {
122        // Packet identifier
123        if buf.remaining() < 2 {
124            return Err(MqttError::MalformedPacket(
125                "PUBREC missing packet identifier".to_string(),
126            ));
127        }
128        let packet_id = buf.get_u16();
129
130        // Reason code and properties (optional in v3.1.1, always present in v5.0)
131        let (reason_code, properties) = if buf.has_remaining() {
132            // Read reason code
133            let reason_byte = buf.get_u8();
134            let code = ReasonCode::from_u8(reason_byte).ok_or_else(|| {
135                MqttError::MalformedPacket(format!("Invalid PUBREC reason code: {reason_byte}"))
136            })?;
137
138            if !Self::is_valid_pubrec_reason_code(code) {
139                return Err(MqttError::MalformedPacket(format!(
140                    "Invalid PUBREC reason code: {code:?}"
141                )));
142            }
143
144            // Properties (if present)
145            let props = if buf.has_remaining() {
146                Properties::decode(buf)?
147            } else {
148                Properties::default()
149            };
150
151            (code, props)
152        } else {
153            // v3.1.1 style - no reason code or properties means success
154            (ReasonCode::Success, Properties::default())
155        };
156
157        Ok(Self {
158            packet_id,
159            reason_code,
160            properties,
161        })
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use crate::protocol::v5::properties::PropertyId;
169    use bytes::BytesMut;
170
171    #[test]
172    fn test_pubrec_basic() {
173        let packet = PubRecPacket::new(123);
174
175        assert_eq!(packet.packet_id, 123);
176        assert_eq!(packet.reason_code, ReasonCode::Success);
177        assert!(packet.properties.is_empty());
178    }
179
180    #[test]
181    fn test_pubrec_with_reason() {
182        let packet = PubRecPacket::new_with_reason(456, ReasonCode::QuotaExceeded)
183            .with_reason_string("Quota exceeded for client".to_string());
184
185        assert_eq!(packet.packet_id, 456);
186        assert_eq!(packet.reason_code, ReasonCode::QuotaExceeded);
187        assert!(packet.properties.contains(PropertyId::ReasonString));
188    }
189
190    #[test]
191    fn test_pubrec_encode_decode() {
192        let packet = PubRecPacket::new(789);
193
194        let mut buf = BytesMut::new();
195        packet.encode(&mut buf).unwrap();
196
197        let fixed_header = FixedHeader::decode(&mut buf).unwrap();
198        assert_eq!(fixed_header.packet_type, PacketType::PubRec);
199
200        let decoded = PubRecPacket::decode_body(&mut buf, &fixed_header).unwrap();
201        assert_eq!(decoded.packet_id, 789);
202        assert_eq!(decoded.reason_code, ReasonCode::Success);
203    }
204}