Skip to main content

dcp/binary/
hbtp.rs

1//! HBTP (Hyper Binary Transport Protocol) header - 20 bytes.
2
3use crate::DCPError;
4
5/// HBTP magic number: "HBTP" in ASCII
6pub const HBTP_MAGIC: u32 = 0x48425450;
7
8/// HBTP header - 20 bytes
9#[repr(C, packed)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct HbtpHeader {
12    /// Magic: 0x48425450 ("HBTP")
13    pub magic: u32,
14    /// Protocol version
15    pub version: u8,
16    /// Message type
17    pub msg_type: u8,
18    /// Flags (compression, priority, etc.)
19    pub flags: u16,
20    /// Multiplexed stream ID
21    pub stream_id: u32,
22    /// Payload length
23    pub length: u32,
24    /// CRC32 checksum of header fields (excluding checksum itself)
25    pub checksum: u32,
26}
27
28impl HbtpHeader {
29    /// Size of the header in bytes
30    pub const SIZE: usize = 20;
31
32    /// Create a new HBTP header with computed checksum
33    pub fn new(version: u8, msg_type: u8, flags: u16, stream_id: u32, length: u32) -> Self {
34        let mut header = Self {
35            magic: HBTP_MAGIC,
36            version,
37            msg_type,
38            flags,
39            stream_id,
40            length,
41            checksum: 0,
42        };
43        header.checksum = header.compute_checksum();
44        header
45    }
46
47    /// Compute CRC32 checksum of header fields (excluding checksum)
48    pub fn compute_checksum(&self) -> u32 {
49        let bytes = self.as_bytes();
50        // Checksum covers first 16 bytes (everything except the checksum field)
51        crc32fast::hash(&bytes[..16])
52    }
53
54    /// Verify the checksum is correct
55    pub fn verify_checksum(&self) -> bool {
56        self.checksum == self.compute_checksum()
57    }
58
59    /// Parse header from bytes - O(1) via pointer cast
60    #[inline(always)]
61    pub fn from_bytes(bytes: &[u8]) -> Result<&Self, DCPError> {
62        if bytes.len() < Self::SIZE {
63            return Err(DCPError::InsufficientData);
64        }
65        // SAFETY: We've verified the slice is at least 20 bytes
66        let header = unsafe { &*(bytes.as_ptr() as *const Self) };
67        if header.magic != HBTP_MAGIC {
68            return Err(DCPError::InvalidMagic);
69        }
70        Ok(header)
71    }
72
73    /// Parse header from bytes and verify checksum
74    pub fn from_bytes_verified(bytes: &[u8]) -> Result<&Self, DCPError> {
75        let header = Self::from_bytes(bytes)?;
76        if !header.verify_checksum() {
77            return Err(DCPError::ChecksumMismatch);
78        }
79        Ok(header)
80    }
81
82    /// Serialize header to bytes - zero allocation
83    #[inline(always)]
84    pub fn as_bytes(&self) -> &[u8] {
85        // SAFETY: HbtpHeader is repr(C, packed) and has no padding
86        unsafe { std::slice::from_raw_parts(self as *const Self as *const u8, Self::SIZE) }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_header_size() {
96        assert_eq!(std::mem::size_of::<HbtpHeader>(), 20);
97    }
98
99    #[test]
100    fn test_header_round_trip() {
101        let header = HbtpHeader::new(1, 2, 0x0100, 12345, 4096);
102        let bytes = header.as_bytes();
103        let parsed = HbtpHeader::from_bytes(bytes).unwrap();
104
105        // Copy values from packed struct before comparing
106        let magic = parsed.magic;
107        let version = parsed.version;
108        let msg_type = parsed.msg_type;
109        let flags = parsed.flags;
110        let stream_id = parsed.stream_id;
111        let length = parsed.length;
112
113        assert_eq!(magic, HBTP_MAGIC);
114        assert_eq!(version, 1);
115        assert_eq!(msg_type, 2);
116        assert_eq!(flags, 0x0100);
117        assert_eq!(stream_id, 12345);
118        assert_eq!(length, 4096);
119        assert!(parsed.verify_checksum());
120    }
121
122    #[test]
123    fn test_checksum_verification() {
124        let header = HbtpHeader::new(1, 2, 0, 0, 100);
125        assert!(header.verify_checksum());
126
127        // Corrupt the header
128        let mut bytes = header.as_bytes().to_vec();
129        bytes[4] ^= 0xFF; // Flip version byte
130        let corrupted = HbtpHeader::from_bytes(&bytes).unwrap();
131        assert!(!corrupted.verify_checksum());
132    }
133
134    #[test]
135    fn test_invalid_magic() {
136        let bytes = [0u8; 20];
137        assert_eq!(HbtpHeader::from_bytes(&bytes), Err(DCPError::InvalidMagic));
138    }
139
140    #[test]
141    fn test_insufficient_data() {
142        let bytes = [0u8; 10];
143        assert_eq!(
144            HbtpHeader::from_bytes(&bytes),
145            Err(DCPError::InsufficientData)
146        );
147    }
148}