Skip to main content

ant_quic/constrained/
header.rs

1// Copyright 2024 Saorsa Labs Ltd.
2//
3// This Saorsa Network Software is licensed under the General Public License (GPL), version 3.
4// Please see the file LICENSE-GPL, or visit <http://www.gnu.org/licenses/> for the full text.
5//
6// Full details available at https://saorsalabs.com/licenses
7
8//! Packet header format for the constrained protocol
9//!
10//! The constrained protocol uses a minimal 5-byte header designed for low-MTU transports:
11//!
12//! ```text
13//!  0       1       2       3       4
14//! +-------+-------+-------+-------+-------+
15//! |  CID (16b)    | SEQ   | ACK   | FLAGS |
16//! +-------+-------+-------+-------+-------+
17//! ```
18//!
19//! This compares favorably to QUIC's minimum ~20 byte headers.
20
21use super::types::{ConnectionId, ConstrainedError, PacketFlags, SequenceNumber};
22
23/// Minimum header size in bytes
24pub const HEADER_SIZE: usize = 5;
25
26/// Constrained protocol packet header
27///
28/// A compact 5-byte header containing all information needed for reliable delivery.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct ConstrainedHeader {
31    /// Connection identifier (2 bytes)
32    pub connection_id: ConnectionId,
33    /// Sequence number for this packet (1 byte)
34    pub seq: SequenceNumber,
35    /// Acknowledgment number (cumulative) (1 byte)
36    pub ack: SequenceNumber,
37    /// Packet flags (1 byte)
38    pub flags: PacketFlags,
39}
40
41impl ConstrainedHeader {
42    /// Create a new header with the specified fields
43    pub const fn new(
44        connection_id: ConnectionId,
45        seq: SequenceNumber,
46        ack: SequenceNumber,
47        flags: PacketFlags,
48    ) -> Self {
49        Self {
50            connection_id,
51            seq,
52            ack,
53            flags,
54        }
55    }
56
57    /// Create a SYN header for connection initiation
58    pub fn syn(connection_id: ConnectionId) -> Self {
59        Self {
60            connection_id,
61            seq: SequenceNumber::new(0),
62            ack: SequenceNumber::new(0),
63            flags: PacketFlags::SYN,
64        }
65    }
66
67    /// Create a SYN-ACK header for connection response
68    pub fn syn_ack(connection_id: ConnectionId, ack: SequenceNumber) -> Self {
69        Self {
70            connection_id,
71            seq: SequenceNumber::new(0),
72            ack,
73            flags: PacketFlags::SYN_ACK,
74        }
75    }
76
77    /// Create an ACK-only header
78    pub fn ack(connection_id: ConnectionId, seq: SequenceNumber, ack: SequenceNumber) -> Self {
79        Self {
80            connection_id,
81            seq,
82            ack,
83            flags: PacketFlags::ACK,
84        }
85    }
86
87    /// Create a DATA header
88    pub fn data(connection_id: ConnectionId, seq: SequenceNumber, ack: SequenceNumber) -> Self {
89        Self {
90            connection_id,
91            seq,
92            ack,
93            flags: PacketFlags::DATA.union(PacketFlags::ACK),
94        }
95    }
96
97    /// Create a FIN header for connection close
98    pub fn fin(connection_id: ConnectionId, seq: SequenceNumber, ack: SequenceNumber) -> Self {
99        Self {
100            connection_id,
101            seq,
102            ack,
103            flags: PacketFlags::FIN.union(PacketFlags::ACK),
104        }
105    }
106
107    /// Create a RST header for connection reset
108    pub fn reset(connection_id: ConnectionId) -> Self {
109        Self {
110            connection_id,
111            seq: SequenceNumber::new(0),
112            ack: SequenceNumber::new(0),
113            flags: PacketFlags::RST,
114        }
115    }
116
117    /// Create a PING header for keep-alive
118    pub fn ping(connection_id: ConnectionId, seq: SequenceNumber) -> Self {
119        Self {
120            connection_id,
121            seq,
122            ack: SequenceNumber::new(0),
123            flags: PacketFlags::PING,
124        }
125    }
126
127    /// Create a PONG header in response to ping
128    pub fn pong(connection_id: ConnectionId, ack: SequenceNumber) -> Self {
129        Self {
130            connection_id,
131            seq: SequenceNumber::new(0),
132            ack,
133            flags: PacketFlags::PONG,
134        }
135    }
136
137    /// Serialize header to bytes
138    ///
139    /// Returns a 5-byte array containing the serialized header.
140    pub fn to_bytes(&self) -> [u8; HEADER_SIZE] {
141        let cid_bytes = self.connection_id.to_bytes();
142        [
143            cid_bytes[0],
144            cid_bytes[1],
145            self.seq.value(),
146            self.ack.value(),
147            self.flags.value(),
148        ]
149    }
150
151    /// Deserialize header from bytes
152    ///
153    /// Returns an error if the slice is too short.
154    pub fn from_bytes(bytes: &[u8]) -> Result<Self, ConstrainedError> {
155        if bytes.len() < HEADER_SIZE {
156            return Err(ConstrainedError::PacketTooSmall {
157                expected: HEADER_SIZE,
158                actual: bytes.len(),
159            });
160        }
161
162        Ok(Self {
163            connection_id: ConnectionId::from_bytes([bytes[0], bytes[1]]),
164            seq: SequenceNumber::new(bytes[2]),
165            ack: SequenceNumber::new(bytes[3]),
166            flags: PacketFlags::new(bytes[4]),
167        })
168    }
169
170    /// Check if this is a SYN packet
171    pub const fn is_syn(&self) -> bool {
172        self.flags.is_syn()
173    }
174
175    /// Check if this is a SYN-ACK packet
176    pub const fn is_syn_ack(&self) -> bool {
177        self.flags.is_syn() && self.flags.is_ack()
178    }
179
180    /// Check if this has the ACK flag
181    pub const fn is_ack(&self) -> bool {
182        self.flags.is_ack()
183    }
184
185    /// Check if this is a FIN packet
186    pub const fn is_fin(&self) -> bool {
187        self.flags.is_fin()
188    }
189
190    /// Check if this is a RST packet
191    pub const fn is_rst(&self) -> bool {
192        self.flags.is_rst()
193    }
194
195    /// Check if this is a DATA packet
196    pub const fn is_data(&self) -> bool {
197        self.flags.is_data()
198    }
199
200    /// Check if this is a PING packet
201    pub const fn is_ping(&self) -> bool {
202        self.flags.is_ping()
203    }
204
205    /// Check if this is a PONG packet
206    pub const fn is_pong(&self) -> bool {
207        self.flags.is_pong()
208    }
209}
210
211impl std::fmt::Display for ConstrainedHeader {
212    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213        write!(
214            f,
215            "[{} {} {} {}]",
216            self.connection_id, self.seq, self.ack, self.flags
217        )
218    }
219}
220
221/// A complete packet with header and optional payload
222#[derive(Debug, Clone)]
223pub struct ConstrainedPacket {
224    /// Packet header
225    pub header: ConstrainedHeader,
226    /// Packet payload (empty for control packets)
227    pub payload: Vec<u8>,
228}
229
230impl ConstrainedPacket {
231    /// Create a new packet with header and payload
232    pub fn new(header: ConstrainedHeader, payload: Vec<u8>) -> Self {
233        Self { header, payload }
234    }
235
236    /// Create a control packet (no payload)
237    pub fn control(header: ConstrainedHeader) -> Self {
238        Self {
239            header,
240            payload: Vec::new(),
241        }
242    }
243
244    /// Create a data packet
245    pub fn data(
246        connection_id: ConnectionId,
247        seq: SequenceNumber,
248        ack: SequenceNumber,
249        payload: Vec<u8>,
250    ) -> Self {
251        Self {
252            header: ConstrainedHeader::data(connection_id, seq, ack),
253            payload,
254        }
255    }
256
257    /// Total size of the packet (header + payload)
258    pub fn total_size(&self) -> usize {
259        HEADER_SIZE + self.payload.len()
260    }
261
262    /// Serialize the complete packet to bytes
263    pub fn to_bytes(&self) -> Vec<u8> {
264        let mut bytes = Vec::with_capacity(self.total_size());
265        bytes.extend_from_slice(&self.header.to_bytes());
266        bytes.extend_from_slice(&self.payload);
267        bytes
268    }
269
270    /// Deserialize a packet from bytes
271    pub fn from_bytes(bytes: &[u8]) -> Result<Self, ConstrainedError> {
272        let header = ConstrainedHeader::from_bytes(bytes)?;
273        let payload = if bytes.len() > HEADER_SIZE {
274            bytes[HEADER_SIZE..].to_vec()
275        } else {
276            Vec::new()
277        };
278        Ok(Self { header, payload })
279    }
280
281    /// Check if this packet has a payload
282    pub fn has_payload(&self) -> bool {
283        !self.payload.is_empty()
284    }
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290
291    #[test]
292    fn test_header_serialization() {
293        let header = ConstrainedHeader::new(
294            ConnectionId::new(0x1234),
295            SequenceNumber::new(10),
296            SequenceNumber::new(5),
297            PacketFlags::DATA.union(PacketFlags::ACK),
298        );
299
300        let bytes = header.to_bytes();
301        assert_eq!(bytes.len(), HEADER_SIZE);
302        assert_eq!(bytes[0], 0x12); // CID high byte
303        assert_eq!(bytes[1], 0x34); // CID low byte
304        assert_eq!(bytes[2], 10); // SEQ
305        assert_eq!(bytes[3], 5); // ACK
306        assert_eq!(bytes[4], 0x12); // DATA | ACK
307
308        let restored = ConstrainedHeader::from_bytes(&bytes).unwrap();
309        assert_eq!(restored, header);
310    }
311
312    #[test]
313    fn test_header_from_bytes_too_short() {
314        let result = ConstrainedHeader::from_bytes(&[1, 2, 3]);
315        assert!(result.is_err());
316        match result {
317            Err(ConstrainedError::PacketTooSmall { expected, actual }) => {
318                assert_eq!(expected, HEADER_SIZE);
319                assert_eq!(actual, 3);
320            }
321            _ => panic!("Expected PacketTooSmall error"),
322        }
323    }
324
325    #[test]
326    fn test_syn_header() {
327        let header = ConstrainedHeader::syn(ConnectionId::new(0xABCD));
328        assert!(header.is_syn());
329        assert!(!header.is_ack());
330        assert_eq!(header.seq, SequenceNumber::new(0));
331    }
332
333    #[test]
334    fn test_syn_ack_header() {
335        let header = ConstrainedHeader::syn_ack(ConnectionId::new(0xABCD), SequenceNumber::new(1));
336        assert!(header.is_syn());
337        assert!(header.is_ack());
338        assert!(header.is_syn_ack());
339        assert_eq!(header.ack, SequenceNumber::new(1));
340    }
341
342    #[test]
343    fn test_data_header() {
344        let header = ConstrainedHeader::data(
345            ConnectionId::new(0x1234),
346            SequenceNumber::new(5),
347            SequenceNumber::new(3),
348        );
349        assert!(header.is_data());
350        assert!(header.is_ack());
351        assert!(!header.is_syn());
352    }
353
354    #[test]
355    fn test_fin_header() {
356        let header = ConstrainedHeader::fin(
357            ConnectionId::new(0x1234),
358            SequenceNumber::new(10),
359            SequenceNumber::new(8),
360        );
361        assert!(header.is_fin());
362        assert!(header.is_ack());
363    }
364
365    #[test]
366    fn test_reset_header() {
367        let header = ConstrainedHeader::reset(ConnectionId::new(0x1234));
368        assert!(header.is_rst());
369        assert!(!header.is_ack());
370    }
371
372    #[test]
373    fn test_ping_pong_headers() {
374        let ping = ConstrainedHeader::ping(ConnectionId::new(0x1234), SequenceNumber::new(5));
375        assert!(ping.is_ping());
376        assert!(!ping.is_pong());
377
378        let pong = ConstrainedHeader::pong(ConnectionId::new(0x1234), SequenceNumber::new(5));
379        assert!(pong.is_pong());
380        assert!(!pong.is_ping());
381    }
382
383    #[test]
384    fn test_header_display() {
385        let header = ConstrainedHeader::data(
386            ConnectionId::new(0xABCD),
387            SequenceNumber::new(10),
388            SequenceNumber::new(5),
389        );
390        let display = format!("{}", header);
391        assert!(display.contains("ABCD"));
392        assert!(display.contains("SEQ:10"));
393        assert!(display.contains("ACK|DATA"));
394    }
395
396    #[test]
397    fn test_packet_serialization() {
398        let packet = ConstrainedPacket::data(
399            ConnectionId::new(0x1234),
400            SequenceNumber::new(5),
401            SequenceNumber::new(3),
402            b"Hello".to_vec(),
403        );
404
405        assert_eq!(packet.total_size(), HEADER_SIZE + 5);
406        assert!(packet.has_payload());
407
408        let bytes = packet.to_bytes();
409        assert_eq!(bytes.len(), HEADER_SIZE + 5);
410        assert_eq!(&bytes[HEADER_SIZE..], b"Hello");
411
412        let restored = ConstrainedPacket::from_bytes(&bytes).unwrap();
413        assert_eq!(restored.header, packet.header);
414        assert_eq!(restored.payload, packet.payload);
415    }
416
417    #[test]
418    fn test_control_packet() {
419        let packet = ConstrainedPacket::control(ConstrainedHeader::syn(ConnectionId::new(0x1234)));
420        assert!(!packet.has_payload());
421        assert_eq!(packet.total_size(), HEADER_SIZE);
422    }
423
424    #[test]
425    fn test_packet_from_bytes_header_only() {
426        let header = ConstrainedHeader::ack(
427            ConnectionId::new(0x1234),
428            SequenceNumber::new(1),
429            SequenceNumber::new(0),
430        );
431        let bytes = header.to_bytes();
432
433        let packet = ConstrainedPacket::from_bytes(&bytes).unwrap();
434        assert_eq!(packet.header, header);
435        assert!(packet.payload.is_empty());
436    }
437}