Skip to main content

packet_strata/packet/
sctp.rs

1//! SCTP (Stream Control Transmission Protocol) packet header implementation
2//!
3//! This module provides support for parsing and working with SCTP headers as defined in RFC 4960.
4//! SCTP is a transport layer protocol that provides reliable, ordered delivery of messages
5//! with support for multi-streaming and multi-homing.
6//!
7//! # SCTP Common Header Format
8//!
9//! ```text
10//!  0                   1                   2                   3
11//!  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
12//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
13//! |          Source Port          |       Destination Port        |
14//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
15//! |                      Verification Tag                         |
16//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
17//! |                       Checksum (CRC32c)                       |
18//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
19//! ```
20//!
21//! # Key characteristics
22//!
23//! - Common header size: 12 bytes (fixed)
24//! - Verification Tag: identifies the association
25//! - Checksum: CRC32c algorithm (not Adler-32)
26//! - Data is organized in chunks after the common header
27//!
28//! # Examples
29//!
30//! ```
31//! use packet_strata::packet::sctp::SctpHeader;
32//! use packet_strata::packet::HeaderParser;
33//!
34//! // Parse an SCTP packet
35//! let packet = vec![
36//!     0x0B, 0x59, // Source port: 2905
37//!     0x0B, 0x59, // Destination port: 2905
38//!     0x12, 0x34, 0x56, 0x78, // Verification tag
39//!     0x00, 0x00, 0x00, 0x00, // Checksum
40//!     // Chunk data would follow...
41//! ];
42//!
43//! let (header, payload) = SctpHeader::from_bytes(&packet).unwrap();
44//! assert_eq!(header.src_port(), 2905);
45//! assert_eq!(header.dst_port(), 2905);
46//! assert_eq!(header.verification_tag(), 0x12345678);
47//! ```
48
49use std::fmt::{self, Formatter};
50use std::mem;
51
52use zerocopy::byteorder::{BigEndian, U16, U32};
53use zerocopy::{FromBytes, IntoBytes, Unaligned};
54
55use crate::packet::{HeaderParser, PacketHeader};
56
57/// SCTP (Stream Control Transmission Protocol) Header structure as defined in RFC 4960
58///
59/// The SCTP common header is 12 bytes and contains:
60/// - Source port (16 bits)
61/// - Destination port (16 bits)
62/// - Verification tag (32 bits)
63/// - Checksum (32 bits - CRC32c)
64///
65/// Following the common header, SCTP packets contain one or more chunks that carry
66/// control information and user data.
67#[repr(C, packed)]
68#[derive(
69    FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, zerocopy::KnownLayout, zerocopy::Immutable,
70)]
71pub struct SctpHeader {
72    src_port: U16<BigEndian>,
73    dst_port: U16<BigEndian>,
74    verification_tag: U32<BigEndian>,
75    checksum: U32<BigEndian>,
76}
77
78impl SctpHeader {
79    /// Returns the source port number
80    #[inline]
81    pub fn src_port(&self) -> u16 {
82        self.src_port.get()
83    }
84
85    /// Returns the destination port number
86    #[inline]
87    pub fn dst_port(&self) -> u16 {
88        self.dst_port.get()
89    }
90
91    /// Returns the verification tag
92    ///
93    /// The verification tag is used to validate the sender of the packet.
94    /// For INIT chunks, this field is set to 0.
95    #[inline]
96    pub fn verification_tag(&self) -> u32 {
97        self.verification_tag.get()
98    }
99
100    /// Returns the checksum value (CRC32c)
101    #[inline]
102    pub fn checksum(&self) -> u32 {
103        self.checksum.get()
104    }
105
106    /// Returns the length of the SCTP header (always 12 bytes)
107    #[inline]
108    pub fn header_len(&self) -> usize {
109        mem::size_of::<SctpHeader>()
110    }
111
112    /// Validates the SCTP header
113    ///
114    /// Basic validation - more detailed validation would require
115    /// examining the chunk data that follows the header
116    #[inline]
117    pub fn is_valid(&self) -> bool {
118        // SCTP header itself is always valid if it's complete
119        // Chunk validation would be done on the payload
120        true
121    }
122
123    /// Computes the CRC32c checksum for SCTP
124    ///
125    /// SCTP uses CRC32c (Castagnoli) instead of the Internet checksum
126    /// as defined in RFC 4960 Appendix B
127    pub fn compute_checksum(sctp_data: &[u8]) -> u32 {
128        const CRC32C_POLYNOMIAL: u32 = 0x1EDC6F41;
129        const CRC32C_INITIAL: u32 = 0xFFFFFFFF;
130
131        let mut crc = CRC32C_INITIAL;
132
133        // Process the SCTP packet with checksum field set to 0
134        let mut data = sctp_data.to_vec();
135        if data.len() >= 12 {
136            // Set checksum field (bytes 8-11) to 0 for computation
137            data[8..12].copy_from_slice(&[0, 0, 0, 0]);
138        }
139
140        for &byte in &data {
141            crc ^= byte as u32;
142            for _ in 0..8 {
143                if crc & 1 != 0 {
144                    crc = (crc >> 1) ^ CRC32C_POLYNOMIAL;
145                } else {
146                    crc >>= 1;
147                }
148            }
149        }
150
151        !crc
152    }
153
154    /// Verifies the SCTP checksum
155    ///
156    /// Returns true if the checksum is valid
157    pub fn verify_checksum(&self, sctp_data: &[u8]) -> bool {
158        let stored_checksum = self.checksum();
159        let computed = Self::compute_checksum(sctp_data);
160        computed == stored_checksum
161    }
162}
163
164impl PacketHeader for SctpHeader {
165    const NAME: &'static str = "SctpHeader";
166
167    #[inline]
168    fn is_valid(&self) -> bool {
169        self.is_valid()
170    }
171
172    type InnerType = ();
173
174    #[inline]
175    fn inner_type(&self) -> Self::InnerType {}
176}
177
178impl HeaderParser for SctpHeader {
179    type Output<'a> = &'a SctpHeader;
180
181    #[inline]
182    fn into_view<'a>(header: &'a Self, _: &'a [u8]) -> Self::Output<'a> {
183        header
184    }
185}
186
187impl fmt::Display for SctpHeader {
188    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
189        write!(
190            f,
191            "SCTP {} -> {} vtag=0x{:08x}",
192            self.src_port(),
193            self.dst_port(),
194            self.verification_tag()
195        )
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn test_sctp_header_basic() {
205        let header = SctpHeader {
206            src_port: U16::new(3868),
207            dst_port: U16::new(3868),
208            verification_tag: U32::new(0x12345678),
209            checksum: U32::new(0),
210        };
211
212        assert_eq!(header.src_port(), 3868);
213        assert_eq!(header.dst_port(), 3868);
214        assert_eq!(header.verification_tag(), 0x12345678);
215        assert_eq!(header.checksum(), 0);
216        assert_eq!(header.header_len(), 12);
217        assert!(header.is_valid());
218    }
219
220    #[test]
221    fn test_sctp_header_size() {
222        assert_eq!(mem::size_of::<SctpHeader>(), 12);
223        assert_eq!(SctpHeader::FIXED_LEN, 12);
224    }
225
226    #[test]
227    fn test_sctp_parsing_basic() {
228        let packet = create_test_packet();
229
230        let result = SctpHeader::from_bytes(&packet);
231        assert!(result.is_ok());
232
233        let (header, payload) = result.unwrap();
234        assert_eq!(header.src_port(), 2905);
235        assert_eq!(header.dst_port(), 2905);
236        assert_eq!(header.verification_tag(), 0xABCDEF01);
237        assert!(header.is_valid());
238        assert_eq!(payload.len(), 8); // Test payload
239    }
240
241    #[test]
242    fn test_sctp_parsing_too_small() {
243        let packet = vec![0u8; 11]; // Only 11 bytes, need 12
244
245        let result = SctpHeader::from_bytes(&packet);
246        assert!(result.is_err());
247    }
248
249    #[test]
250    fn test_sctp_total_len() {
251        let packet = create_test_packet();
252        let (header, _) = SctpHeader::from_bytes(&packet).unwrap();
253
254        // SCTP header is always 12 bytes (no options in common header)
255        assert_eq!(header.total_len(&packet), 12);
256    }
257
258    #[test]
259    fn test_sctp_from_bytes_with_payload() {
260        let mut packet = Vec::new();
261
262        // SCTP Header
263        packet.extend_from_slice(&9899u16.to_be_bytes()); // Source port
264        packet.extend_from_slice(&9899u16.to_be_bytes()); // Destination port
265        packet.extend_from_slice(&0x11223344u32.to_be_bytes()); // Verification tag
266        packet.extend_from_slice(&0u32.to_be_bytes()); // Checksum (not computed)
267
268        // Add some chunk data as payload
269        let chunk_data = b"SCTP CHUNK DATA";
270        packet.extend_from_slice(chunk_data);
271
272        let result = SctpHeader::from_bytes(&packet);
273        assert!(result.is_ok());
274
275        let (header, payload) = result.unwrap();
276
277        // Verify header fields
278        assert_eq!(header.src_port(), 9899);
279        assert_eq!(header.dst_port(), 9899);
280        assert_eq!(header.verification_tag(), 0x11223344);
281
282        // Verify payload separation
283        assert_eq!(payload.len(), chunk_data.len());
284        assert_eq!(payload, chunk_data);
285    }
286
287    #[test]
288    fn test_sctp_init_chunk() {
289        let mut packet = Vec::new();
290
291        // SCTP Header for INIT chunk
292        packet.extend_from_slice(&5000u16.to_be_bytes()); // Source port
293        packet.extend_from_slice(&5000u16.to_be_bytes()); // Destination port
294        packet.extend_from_slice(&0u32.to_be_bytes()); // Verification tag (0 for INIT)
295        packet.extend_from_slice(&0x12345678u32.to_be_bytes()); // Checksum
296
297        // Simplified INIT chunk
298        let chunk_data = vec![
299            0x01, // Chunk type: INIT
300            0x00, // Chunk flags
301            0x00, 0x10, // Chunk length: 16 bytes
302            0x00, 0x00, 0x00, 0x01, // Initiate tag
303            0x00, 0x00, 0x10, 0x00, // a_rwnd
304            0x00, 0x0A, // Number of outbound streams
305            0x00, 0x0A, // Number of inbound streams
306            0x00, 0x00, 0x00, 0x02, // Initial TSN
307        ];
308        packet.extend_from_slice(&chunk_data);
309
310        let (header, payload) = SctpHeader::from_bytes(&packet).unwrap();
311
312        assert_eq!(header.src_port(), 5000);
313        assert_eq!(header.dst_port(), 5000);
314        assert_eq!(header.verification_tag(), 0); // INIT has verification tag 0
315        assert_eq!(payload.len(), chunk_data.len());
316    }
317
318    #[test]
319    fn test_sctp_multiple_ports() {
320        // Test various common SCTP port combinations
321        let test_cases: Vec<(u16, u16)> = vec![
322            (2905, 2905),   // SCTP default
323            (3868, 3868),   // Diameter
324            (9899, 9899),   // SCTP tunneling
325            (36412, 36412), // S1AP (LTE)
326            (38412, 38412), // NGAP (5G)
327        ];
328
329        for (src, dst) in test_cases {
330            let mut packet = Vec::new();
331            packet.extend_from_slice(&src.to_be_bytes());
332            packet.extend_from_slice(&dst.to_be_bytes());
333            packet.extend_from_slice(&0x11111111u32.to_be_bytes());
334            packet.extend_from_slice(&0x22222222u32.to_be_bytes());
335
336            let (header, _) = SctpHeader::from_bytes(&packet).unwrap();
337            assert_eq!(header.src_port(), src);
338            assert_eq!(header.dst_port(), dst);
339        }
340    }
341
342    #[test]
343    fn test_sctp_checksum_computation() {
344        let mut packet = Vec::new();
345
346        // Create a simple SCTP packet
347        packet.extend_from_slice(&2905u16.to_be_bytes()); // Source port
348        packet.extend_from_slice(&2905u16.to_be_bytes()); // Destination port
349        packet.extend_from_slice(&0x12345678u32.to_be_bytes()); // Verification tag
350        packet.extend_from_slice(&0u32.to_be_bytes()); // Checksum placeholder
351
352        // Add simple chunk data
353        packet.extend_from_slice(&[0x01, 0x00, 0x00, 0x04]); // Simple chunk
354
355        let checksum = SctpHeader::compute_checksum(&packet);
356
357        // Checksum should be computed (implementation is simplified)
358        // In a real implementation, this would use hardware-accelerated CRC32c
359        assert_ne!(checksum, 0);
360    }
361
362    #[test]
363    fn test_sctp_verification_tag_values() {
364        // Test different verification tag values
365        let tags = vec![0x00000000, 0xFFFFFFFF, 0x12345678, 0xABCDEF01];
366
367        for tag in tags {
368            let header = SctpHeader {
369                src_port: U16::new(2905),
370                dst_port: U16::new(2905),
371                verification_tag: U32::new(tag),
372                checksum: U32::new(0),
373            };
374
375            assert_eq!(header.verification_tag(), tag);
376        }
377    }
378
379    #[test]
380    fn test_sctp_wellknown_ports() {
381        // Test well-known SCTP ports
382        let header_diameter = SctpHeader {
383            src_port: U16::new(3868),
384            dst_port: U16::new(3868),
385            verification_tag: U32::new(0),
386            checksum: U32::new(0),
387        };
388        assert_eq!(header_diameter.src_port(), 3868); // Diameter
389
390        let header_s1ap = SctpHeader {
391            src_port: U16::new(36412),
392            dst_port: U16::new(36412),
393            verification_tag: U32::new(0),
394            checksum: U32::new(0),
395        };
396        assert_eq!(header_s1ap.src_port(), 36412); // S1AP
397    }
398
399    // Helper function to create a test SCTP packet
400    fn create_test_packet() -> Vec<u8> {
401        let mut packet = Vec::new();
402
403        // Source port: 2905
404        packet.extend_from_slice(&2905u16.to_be_bytes());
405
406        // Destination port: 2905
407        packet.extend_from_slice(&2905u16.to_be_bytes());
408
409        // Verification tag
410        packet.extend_from_slice(&0xABCDEF01u32.to_be_bytes());
411
412        // Checksum
413        packet.extend_from_slice(&0u32.to_be_bytes());
414
415        // Payload: simple test data
416        packet.extend_from_slice(b"SCTP_TST");
417
418        packet
419    }
420}