mqtt5_protocol/packet/
pingreq.rs

1use crate::error::Result;
2use crate::packet::{FixedHeader, MqttPacket, PacketType};
3use bebytes::BeBytes;
4use bytes::{Buf, BufMut};
5
6/// MQTT PINGREQ packet - complete bebytes implementation
7#[derive(Debug, Clone, Copy, PartialEq, Eq, BeBytes)]
8pub struct PingReqPacket {
9    /// Fixed header type and flags (PINGREQ = 0xC0, remaining length = 0x00)
10    #[bebytes(big_endian)]
11    fixed_header: u16, // 0xC000
12}
13
14impl PingReqPacket {
15    /// The fixed header value for PINGREQ packets
16    pub const FIXED_HEADER: u16 = 0xC000; // PINGREQ (0xC0) + remaining length 0 (0x00)
17}
18
19impl Default for PingReqPacket {
20    fn default() -> Self {
21        Self {
22            fixed_header: Self::FIXED_HEADER,
23        }
24    }
25}
26
27impl MqttPacket for PingReqPacket {
28    fn packet_type(&self) -> PacketType {
29        PacketType::PingReq
30    }
31
32    fn encode_body<B: BufMut>(&self, _buf: &mut B) -> Result<()> {
33        // PINGREQ has no variable header or payload - everything is in fixed header
34        Ok(())
35    }
36
37    fn decode_body<B: Buf>(_buf: &mut B, _fixed_header: &FixedHeader) -> Result<Self> {
38        // PINGREQ has no variable header or payload - everything is in fixed header
39        Ok(Self::default())
40    }
41}
42
43impl PingReqPacket {
44    /// Encode directly to bytes using bebytes
45    #[must_use]
46    pub fn encode_complete(&self) -> Vec<u8> {
47        self.to_be_bytes()
48    }
49
50    /// Decode directly from bytes using bebytes
51    ///
52    /// # Errors
53    ///
54    /// Returns an error if:
55    /// - Insufficient bytes in data
56    /// - Invalid PINGREQ packet structure
57    /// - Fixed header doesn't match expected PINGREQ values
58    pub fn decode_complete(data: &[u8]) -> Result<Self> {
59        let (packet, _consumed) = Self::try_from_be_bytes(data).map_err(|e| {
60            crate::error::MqttError::MalformedPacket(format!("Invalid PINGREQ packet: {e}"))
61        })?;
62
63        // Validate the packet has the correct fixed header
64        if packet.fixed_header != Self::FIXED_HEADER {
65            return Err(crate::error::MqttError::MalformedPacket(format!(
66                "Invalid PINGREQ packet: expected 0x{:04X}, got 0x{:04X}",
67                Self::FIXED_HEADER,
68                packet.fixed_header
69            )));
70        }
71
72        Ok(packet)
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use bytes::BytesMut;
80
81    #[cfg(test)]
82    mod property_tests {
83        use super::*;
84        use proptest::prelude::*;
85
86        proptest! {
87            #[test]
88            fn prop_pingreq_encode_decode_round_trip(_data in any::<u32>()) {
89                let packet = PingReqPacket::default();
90                let bytes = packet.encode_complete();
91                let decoded = PingReqPacket::decode_complete(&bytes).unwrap();
92
93                // Both should represent the same logical packet
94                prop_assert_eq!(bytes, vec![0xC0, 0x00]);
95                prop_assert_eq!(decoded.packet_type(), PacketType::PingReq);
96                prop_assert_eq!(packet, decoded);
97            }
98
99            #[test]
100            fn prop_pingreq_consistent_encoding(_data in any::<u16>()) {
101                let packet1 = PingReqPacket::default();
102                let packet2 = PingReqPacket::default();
103
104                prop_assert_eq!(packet1.encode_complete(), packet2.encode_complete());
105                prop_assert_eq!(packet1, packet2);
106            }
107        }
108    }
109
110    #[test]
111    fn test_pingreq_bebytes_encode_decode() {
112        let packet = PingReqPacket::default();
113
114        // Test bebytes direct encoding
115        let bytes = packet.encode_complete();
116        assert_eq!(bytes.len(), 2);
117        assert_eq!(bytes[0], 0xC0); // PINGREQ packet type
118        assert_eq!(bytes[1], 0x00); // Remaining length = 0
119
120        // Test bebytes direct decoding
121        let decoded = PingReqPacket::decode_complete(&bytes).unwrap();
122        assert_eq!(decoded, packet);
123    }
124
125    #[test]
126    fn test_pingreq_mqtt_packet_interface() {
127        let packet = PingReqPacket::default();
128
129        let mut buf = BytesMut::new();
130        packet.encode(&mut buf).unwrap();
131
132        assert_eq!(buf.len(), 2); // Fixed header only
133        assert_eq!(buf[0], 0xC0); // PINGREQ packet type
134        assert_eq!(buf[1], 0x00); // Remaining length = 0
135
136        let fixed_header = FixedHeader::decode(&mut buf).unwrap();
137        assert_eq!(fixed_header.packet_type, PacketType::PingReq);
138        assert_eq!(fixed_header.remaining_length, 0);
139
140        let decoded = PingReqPacket::decode_body(&mut buf, &fixed_header).unwrap();
141        assert_eq!(decoded, packet);
142    }
143
144    #[test]
145    fn test_pingreq_malformed_data() {
146        // Test with insufficient data
147        let result = PingReqPacket::decode_complete(&[0xC0]);
148        assert!(result.is_err());
149
150        // Test with wrong packet type
151        let result = PingReqPacket::decode_complete(&[0xD0, 0x00]);
152        assert!(result.is_err());
153    }
154}