Skip to main content

capfile/format/
pcap.rs

1//! PCAP format parsing
2//!
3//! PCAP is the legacy packet capture format. It has a simple structure
4//! with a global header followed by packet records.
5
6use crate::Error;
7
8/// PCAP file magic numbers
9pub const PCAP_MAGIC: u32 = 0xa1b2c3d4;
10pub const PCAP_MAGIC_SWAPPED: u32 = 0xd4c3b2a1;
11pub const PCAP_MAGIC_NANO: u32 = 0xa1b23c4d;
12pub const PCAP_MAGIC_NANO_SWAPPED: u32 = 0x4d3cb2a1;
13
14/// PCAPNG byte-order magic
15pub const PCAPNG_BYTE_ORDER_MAGIC: u32 = 0x1a2b3c4d;
16
17/// Common bitmasks for protocol fields
18pub mod mask {
19    /// IPv4 don't fragment flag
20    pub const IPV4_DF: u16 = 0x4000;
21    /// IPv4 more fragments flag
22    pub const IPV4_MF: u16 = 0x2000;
23    /// IPv4 fragment offset mask
24    pub const IPV4_FRAG_OFFSET: u16 = 0x1fff;
25
26    /// DNS QR (query/response) flag
27    pub const DNS_QR: u16 = 0x8000;
28    /// DNS opcode mask
29    pub const DNS_OPCODE: u16 = 0x7800;
30    /// DNS RCODE mask
31    pub const DNS_RCODE: u16 = 0x000f;
32}
33
34/// Read a u16 from little-endian bytes safely
35fn read_u16(data: &[u8], offset: usize) -> Result<u16, Error> {
36    if data.len() < offset + 2 {
37        return Err(Error::truncated(offset + 2, data.len()));
38    }
39    Ok(u16::from_le_bytes([data[offset], data[offset + 1]]))
40}
41
42/// Read a u32 from little-endian bytes safely
43fn read_u32(data: &[u8], offset: usize) -> Result<u32, Error> {
44    if data.len() < offset + 4 {
45        return Err(Error::truncated(offset + 4, data.len()));
46    }
47    Ok(u32::from_le_bytes([
48        data[offset],
49        data[offset + 1],
50        data[offset + 2],
51        data[offset + 3],
52    ]))
53}
54
55/// Read an i32 from little-endian bytes safely
56fn read_i32(data: &[u8], offset: usize) -> Result<i32, Error> {
57    if data.len() < offset + 4 {
58        return Err(Error::truncated(offset + 4, data.len()));
59    }
60    Ok(i32::from_le_bytes([
61        data[offset],
62        data[offset + 1],
63        data[offset + 2],
64        data[offset + 3],
65    ]))
66}
67
68/// Link types as defined in pcap
69pub mod link_type {
70    pub const LINKTYPE_ETHERNET: u16 = 1;
71    pub const LINKTYPE_RAW: u16 = 101;
72    pub const LINKTYPE_IPV4: u16 = 228;
73    pub const LINKTYPE_IPV6: u16 = 229;
74}
75
76/// PCAP global header (24 bytes)
77#[derive(Debug, Clone, Copy)]
78#[repr(C)]
79pub struct PcapHeader {
80    /// Magic number (determines byte order and timestamp precision)
81    pub magic: u32,
82    /// Major version number
83    pub version_major: u16,
84    /// Minor version number
85    pub version_minor: u16,
86    /// Timestamp timezone (unused in practice)
87    pub thiszone: i32,
88    /// Timestamp accuracy (unused in practice)
89    pub sigfigs: u32,
90    /// Maximum packet length
91    pub snaplen: u32,
92    /// Link layer type
93    pub network: u32,
94}
95
96/// Timestamp representation in pcap
97#[derive(Debug, Clone, Copy)]
98pub struct PcapTimestamp {
99    /// Seconds since epoch
100    pub secs: u32,
101    /// Microseconds or nanoseconds since secs
102    pub usecs: u32,
103}
104
105impl PcapTimestamp {
106    /// Convert to nanoseconds since epoch
107    pub fn to_ns(&self, is_nano: bool) -> u64 {
108        let frac = if is_nano {
109            self.usecs
110        } else {
111            self.usecs * 1000
112        };
113        (self.secs as u64) * 1_000_000_000 + frac as u64
114    }
115}
116
117/// PCAP packet header (16 bytes)
118#[derive(Debug, Clone, Copy)]
119#[repr(C)]
120pub struct PcapPacketHeader {
121    /// Timestamp seconds
122    pub ts_sec: u32,
123    /// Timestamp microseconds
124    pub ts_usec: u32,
125    /// Length of packet data in file
126    pub incl_len: u32,
127    /// Original length of packet
128    pub orig_len: u32,
129}
130
131impl PcapHeader {
132    /// Parse pcap header from bytes
133    pub fn parse(input: &[u8]) -> Result<(Self, &[u8]), Error> {
134        if input.len() < 24 {
135            return Err(Error::truncated(24, input.len()));
136        }
137
138        let magic = read_u32(input, 0)?;
139        let version_major = read_u16(input, 4)?;
140        let version_minor = read_u16(input, 6)?;
141        let thiszone = read_i32(input, 8)?;
142        let sigfigs = read_u32(input, 12)?;
143        let snaplen = read_u32(input, 16)?;
144        let network = read_u32(input, 20)?;
145
146        let header = PcapHeader {
147            magic,
148            version_major,
149            version_minor,
150            thiszone,
151            sigfigs,
152            snaplen,
153            network,
154        };
155
156        // Validate magic number
157        match header.magic {
158            PCAP_MAGIC | PCAP_MAGIC_SWAPPED | PCAP_MAGIC_NANO | PCAP_MAGIC_NANO_SWAPPED => {}
159            _ => return Err(Error::InvalidMagic(header.magic)),
160        }
161
162        // Validate version
163        if header.version_major != 2 {
164            return Err(Error::InvalidVersion(header.version_major));
165        }
166
167        Ok((header, &input[24..]))
168    }
169
170    /// Check if bytes are swapped (big-endian)
171    pub fn is_swapped(&self) -> bool {
172        matches!(self.magic, PCAP_MAGIC_SWAPPED | PCAP_MAGIC_NANO_SWAPPED)
173    }
174
175    /// Check if timestamps are in nanoseconds
176    pub fn is_nano(&self) -> bool {
177        matches!(self.magic, PCAP_MAGIC_NANO | PCAP_MAGIC_NANO_SWAPPED)
178    }
179}
180
181/// Parse a PCAP packet header from bytes
182pub fn parse_packet_header(input: &[u8]) -> Result<(PcapPacketHeader, &[u8]), Error> {
183    if input.len() < 16 {
184        return Err(Error::truncated(16, input.len()));
185    }
186
187    let header = PcapPacketHeader {
188        ts_sec: read_u32(input, 0)?,
189        ts_usec: read_u32(input, 4)?,
190        incl_len: read_u32(input, 8)?,
191        orig_len: read_u32(input, 12)?,
192    };
193
194    Ok((header, &input[16..]))
195}
196
197/// Parse a complete PCAP packet (header + data)
198pub fn parse_packet(input: &[u8]) -> Result<(&[u8], &[u8]), Error> {
199    let (header, rest) = parse_packet_header(input)?;
200    let data_len = header.incl_len as usize;
201
202    if rest.len() < data_len {
203        return Err(Error::truncated(data_len, rest.len()));
204    }
205
206    let (data, remaining) = rest.split_at(data_len);
207    Ok((data, remaining))
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    /// Test PCAP header parsing with valid magic number
215    #[test]
216    fn test_parse_pcap_header_valid() {
217        // PCAP magic: 0xa1b2c3d4, version 2.4, Ethernet
218        let data = vec![
219            0xd4, 0xc3, 0xb2, 0xa1, // magic (little-endian)
220            0x02, 0x00, // version_major = 2
221            0x04, 0x00, // version_minor = 4
222            0x00, 0x00, 0x00, 0x00, // thiszone = 0
223            0x00, 0x00, 0x00, 0x00, // sigfigs = 0
224            0xff, 0xff, 0x00, 0x00, // snaplen = 65535
225            0x01, 0x00, 0x00, 0x00, // network = 1 (Ethernet)
226        ];
227
228        let (header, rest) = PcapHeader::parse(&data).unwrap();
229        assert_eq!(header.magic, PCAP_MAGIC);
230        assert_eq!(header.version_major, 2);
231        assert_eq!(header.version_minor, 4);
232        assert_eq!(header.network, 1);
233        assert!(!header.is_swapped());
234        assert!(!header.is_nano());
235        assert!(rest.is_empty());
236    }
237
238    /// Test PCAP header parsing with nanosecond precision
239    #[test]
240    fn test_parse_pcap_header_nano() {
241        let data = vec![
242            0x4d, 0x3c, 0xb2, 0xa1, // magic (nano, little-endian)
243            0x02, 0x00, // version_major = 2
244            0x04, 0x00, // version_minor = 4
245            0x00, 0x00, 0x00, 0x00, // thiszone = 0
246            0x00, 0x00, 0x00, 0x00, // sigfigs = 0
247            0xff, 0xff, 0x00, 0x00, // snaplen = 65535
248            0x01, 0x00, 0x00, 0x00, // network = 1 (Ethernet)
249        ];
250
251        let (header, _) = PcapHeader::parse(&data).unwrap();
252        assert_eq!(header.magic, PCAP_MAGIC_NANO);
253        assert!(header.is_nano());
254    }
255
256    /// Test PCAP header parsing with invalid magic
257    #[test]
258    fn test_parse_pcap_header_invalid_magic() {
259        let data = vec![
260            0x00, 0x00, 0x00, 0x00, // invalid magic
261            0x02, 0x00, // version_major = 2
262            0x04, 0x00, // version_minor = 4
263            0x00, 0x00, 0x00, 0x00, // thiszone = 0
264            0x00, 0x00, 0x00, 0x00, // sigfigs = 0
265            0xff, 0xff, 0x00, 0x00, // snaplen = 65535
266            0x01, 0x00, 0x00, 0x00, // network = 1 (Ethernet)
267        ];
268
269        let result = PcapHeader::parse(&data);
270        assert!(result.is_err());
271    }
272
273    /// Test PCAP header parsing with truncated data
274    #[test]
275    fn test_parse_pcap_header_truncated() {
276        let data = vec![0u8; 10]; // Too short (need 24 bytes)
277
278        let result = PcapHeader::parse(&data);
279        assert!(result.is_err());
280    }
281
282    /// Test packet header parsing
283    #[test]
284    fn test_parse_packet_header() {
285        // Packet: ts=1234567890.123456, len=64, orig=64 (little-endian)
286        let data = vec![
287            0xd2, 0x02, 0x96, 0x49, // ts_sec = 1234567890 (little-endian: 0x499602d2)
288            0x40, 0xe2, 0x01, 0x00, // ts_usec = 123456 (little-endian: 0x0001e240 = 123456)
289            0x40, 0x00, 0x00, 0x00, // incl_len = 64 (little-endian)
290            0x40, 0x00, 0x00, 0x00, // orig_len = 64 (little-endian)
291        ];
292
293        let (header, rest) = parse_packet_header(&data).unwrap();
294        assert_eq!(header.ts_sec, 1234567890);
295        assert_eq!(header.ts_usec, 123456);
296        assert_eq!(header.incl_len, 64);
297        assert_eq!(header.orig_len, 64);
298        assert!(rest.is_empty());
299    }
300
301    /// Test packet header with truncated data
302    #[test]
303    fn test_parse_packet_header_truncated() {
304        let data = vec![0u8; 10]; // Too short
305
306        let result = parse_packet_header(&data);
307        assert!(result.is_err());
308    }
309
310    /// Test packet parsing
311    #[test]
312    fn test_parse_packet() {
313        let data = vec![
314            // Packet header
315            0xd2, 0x02, 0x96, 0x49, // ts_sec
316            0xe8, 0x01, 0x00, 0x00, // ts_usec
317            0x04, 0x00, 0x00, 0x00, // incl_len = 4
318            0x04, 0x00, 0x00, 0x00, // orig_len = 4
319            // Packet data
320            0xde, 0xad, 0xbe, 0xef,
321        ];
322
323        let (data_out, _rest) = parse_packet(&data).unwrap();
324        assert_eq!(data_out, &[0xde, 0xad, 0xbe, 0xef]);
325    }
326
327    /// Test packet parsing with truncated data
328    #[test]
329    fn test_parse_packet_truncated() {
330        let data = vec![
331            0xd2, 0x02, 0x96, 0x49, 0xe8, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00,
332            0x00, // incl_len = 16
333            0x10, 0x00, 0x00, 0x00, // But only 2 bytes of data
334            0xde, 0xad,
335        ];
336
337        let result = parse_packet(&data);
338        assert!(result.is_err());
339    }
340}