Skip to main content

rumqttc/mqttbytes/v5/
pubrel.rs

1use super::{
2    Error, FixedHeader, PropertyType, len_len, length, property, read_mqtt_string, read_u8,
3    read_u16, write_mqtt_string, write_remaining_length,
4};
5use bytes::{Buf, BufMut, Bytes, BytesMut};
6
7/// Return code in `PubRel`
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[repr(u8)]
10pub enum PubRelReason {
11    Success = 0,
12    PacketIdentifierNotFound = 146,
13}
14
15/// `QoS2` Publish release, in response to PUBREC packet
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct PubRel {
18    pub pkid: u16,
19    pub reason: PubRelReason,
20    pub properties: Option<PubRelProperties>,
21}
22
23impl PubRel {
24    #[must_use]
25    pub const fn new(pkid: u16, properties: Option<PubRelProperties>) -> Self {
26        Self {
27            pkid,
28            reason: PubRelReason::Success,
29            properties,
30        }
31    }
32
33    #[must_use]
34    pub fn size(&self) -> usize {
35        // If there are no properties during success, sending reason code is optional
36        if self.reason == PubRelReason::Success && self.properties.is_none() {
37            return 4;
38        }
39
40        let len = self.len();
41        let remaining_len_size = len_len(len);
42
43        1 + remaining_len_size + len
44    }
45
46    fn len(&self) -> usize {
47        let mut len = 2 + 1; // pkid + reason
48
49        // The Reason Code and Property Length can be omitted if the Reason Code is 0x00 (Success)
50        // and there are no Properties. In this case the PUBREL has a Remaining Length of 2.
51        // <https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901144>
52        if self.reason == PubRelReason::Success && self.properties.is_none() {
53            return 2;
54        }
55
56        if let Some(p) = &self.properties {
57            let properties_len = p.len();
58            let properties_len_len = len_len(properties_len);
59            len += properties_len_len + properties_len;
60        } else {
61            len += 1;
62        }
63
64        len
65    }
66
67    pub fn read(fixed_header: FixedHeader, mut bytes: Bytes) -> Result<Self, Error> {
68        let variable_header_index = fixed_header.header_len;
69        bytes.advance(variable_header_index);
70        let pkid = read_u16(&mut bytes)?;
71        if fixed_header.remaining_len == 2 {
72            return Ok(Self {
73                pkid,
74                reason: PubRelReason::Success,
75                properties: None,
76            });
77        }
78
79        let ack_reason = read_u8(&mut bytes)?;
80        if fixed_header.remaining_len < 4 {
81            return Ok(Self {
82                pkid,
83                reason: reason(ack_reason)?,
84                properties: None,
85            });
86        }
87
88        let properties = PubRelProperties::read(&mut bytes)?;
89        let puback = Self {
90            pkid,
91            reason: reason(ack_reason)?,
92            properties,
93        };
94
95        Ok(puback)
96    }
97
98    pub fn write(&self, buffer: &mut BytesMut) -> Result<usize, Error> {
99        let len = self.len();
100        buffer.put_u8(0x62);
101        let count = write_remaining_length(buffer, len)?;
102        buffer.put_u16(self.pkid);
103
104        // If there are no properties during success, sending reason code is optional
105        if self.reason == PubRelReason::Success && self.properties.is_none() {
106            return Ok(4);
107        }
108
109        buffer.put_u8(code(self.reason));
110
111        if let Some(p) = &self.properties {
112            p.write(buffer)?;
113        } else {
114            write_remaining_length(buffer, 0)?;
115        }
116
117        Ok(1 + count + len)
118    }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct PubRelProperties {
123    pub reason_string: Option<String>,
124    pub user_properties: Vec<(String, String)>,
125}
126
127impl PubRelProperties {
128    fn len(&self) -> usize {
129        let mut len = 0;
130
131        if let Some(reason) = &self.reason_string {
132            len += 1 + 2 + reason.len();
133        }
134
135        for (key, value) in &self.user_properties {
136            len += 1 + 2 + key.len() + 2 + value.len();
137        }
138
139        len
140    }
141
142    pub fn read(bytes: &mut Bytes) -> Result<Option<Self>, Error> {
143        let mut reason_string = None;
144        let mut user_properties = Vec::new();
145
146        let (properties_len_len, properties_len) = length(bytes.iter())?;
147        bytes.advance(properties_len_len);
148        if properties_len == 0 {
149            return Ok(None);
150        }
151
152        let mut cursor = 0;
153        // read until cursor reaches property length. properties_len = 0 will skip this loop
154        while cursor < properties_len {
155            let prop = read_u8(bytes)?;
156            cursor += 1;
157
158            match property(prop)? {
159                PropertyType::ReasonString => {
160                    let reason = read_mqtt_string(bytes)?;
161                    cursor += 2 + reason.len();
162                    reason_string = Some(reason);
163                }
164                PropertyType::UserProperty => {
165                    let key = read_mqtt_string(bytes)?;
166                    let value = read_mqtt_string(bytes)?;
167                    cursor += 2 + key.len() + 2 + value.len();
168                    user_properties.push((key, value));
169                }
170                _ => return Err(Error::InvalidPropertyType(prop)),
171            }
172        }
173
174        Ok(Some(Self {
175            reason_string,
176            user_properties,
177        }))
178    }
179
180    pub fn write(&self, buffer: &mut BytesMut) -> Result<(), Error> {
181        let len = self.len();
182        write_remaining_length(buffer, len)?;
183
184        if let Some(reason) = &self.reason_string {
185            buffer.put_u8(PropertyType::ReasonString as u8);
186            write_mqtt_string(buffer, reason);
187        }
188
189        for (key, value) in &self.user_properties {
190            buffer.put_u8(PropertyType::UserProperty as u8);
191            write_mqtt_string(buffer, key);
192            write_mqtt_string(buffer, value);
193        }
194
195        Ok(())
196    }
197}
198
199/// Connection return code type
200fn reason(num: u8) -> Result<PubRelReason, Error> {
201    num.try_into()
202}
203
204const fn code(reason: PubRelReason) -> u8 {
205    reason as u8
206}
207
208impl TryFrom<u8> for PubRelReason {
209    type Error = Error;
210
211    fn try_from(value: u8) -> Result<Self, Self::Error> {
212        match value {
213            0 => Ok(Self::Success),
214            146 => Ok(Self::PacketIdentifierNotFound),
215            _ => Err(Error::InvalidConnectReturnCode(value)),
216        }
217    }
218}
219
220#[cfg(test)]
221mod test {
222    use super::super::test::{USER_PROP_KEY, USER_PROP_VAL};
223    use super::*;
224    use bytes::BytesMut;
225    use pretty_assertions::assert_eq;
226
227    #[test]
228    fn length_calculation() {
229        let mut dummy_bytes = BytesMut::new();
230        // Use user_properties to pad the size to exceed ~128 bytes to make the
231        // remaining_length field in the packet be 2 bytes long.
232        let pubrel_props = PubRelProperties {
233            reason_string: None,
234            user_properties: vec![(USER_PROP_KEY.into(), USER_PROP_VAL.into())],
235        };
236
237        let pubrel_pkt = PubRel::new(1, Some(pubrel_props));
238
239        let size_from_size = pubrel_pkt.size();
240        let size_from_write = pubrel_pkt.write(&mut dummy_bytes).unwrap();
241        let size_from_bytes = dummy_bytes.len();
242
243        assert_eq!(size_from_write, size_from_bytes);
244        assert_eq!(size_from_size, size_from_bytes);
245    }
246
247    #[test]
248    fn reason_code_cast_matches_spec() {
249        assert_eq!(PubRelReason::Success as u8, 0);
250        assert_eq!(PubRelReason::PacketIdentifierNotFound as u8, 146);
251    }
252
253    #[test]
254    fn reason_and_code_round_trip() {
255        let values = [
256            (0, PubRelReason::Success),
257            (146, PubRelReason::PacketIdentifierNotFound),
258        ];
259
260        for (raw, parsed) in values {
261            assert_eq!(reason(raw).unwrap(), parsed);
262            assert_eq!(code(parsed), raw);
263        }
264    }
265
266    #[test]
267    fn reason_invalid_code_errors() {
268        assert!(matches!(
269            reason(42),
270            Err(Error::InvalidConnectReturnCode(42))
271        ));
272    }
273}