Skip to main content

capfile/format/
pcapng.rs

1//! PCAPNG format parsing
2//!
3//! PCAPNG is the modern packet capture format. It uses a block-based
4//! structure with support for multiple interfaces, metadata, and comments.
5
6use crate::format::pcap::PCAPNG_BYTE_ORDER_MAGIC;
7use crate::Error;
8
9/// PCAPNG block types
10pub mod block_type {
11    /// Section Header Block - defines the capture file format
12    pub const SHB: u32 = 0x0a0d0d0a;
13    /// Interface Description Block - defines network interfaces
14    pub const IDB: u32 = 0x00000001;
15    /// Packet Block - obsolete, use Enhanced Packet Block
16    pub const PB: u32 = 0x00000002;
17    /// Simple Packet Block - obsolete
18    pub const SPB: u32 = 0x00000003;
19    /// Name Resolution Block - maps addresses to names
20    pub const NRB: u32 = 0x00000004;
21    /// Interface Statistics Block - interface statistics
22    pub const ISB: u32 = 0x00000005;
23    /// Enhanced Packet Block - packet data with timestamps
24    pub const EPB: u32 = 0x00000006;
25    /// Custom block type
26    pub const CUSTOM: u32 = 0x00040000;
27}
28
29/// Block type identifier
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum BlockType {
32    SectionHeader,
33    InterfaceDescription,
34    Packet,
35    SimplePacket,
36    NameResolution,
37    InterfaceStatistics,
38    EnhancedPacket,
39    Unknown(u32),
40}
41
42impl From<u32> for BlockType {
43    fn from(value: u32) -> Self {
44        match value {
45            block_type::SHB => BlockType::SectionHeader,
46            block_type::IDB => BlockType::InterfaceDescription,
47            block_type::PB => BlockType::Packet,
48            block_type::SPB => BlockType::SimplePacket,
49            block_type::NRB => BlockType::NameResolution,
50            block_type::ISB => BlockType::InterfaceStatistics,
51            block_type::EPB => BlockType::EnhancedPacket,
52            other => BlockType::Unknown(other),
53        }
54    }
55}
56
57/// Read a u16 from little-endian bytes safely
58fn read_u16(data: &[u8], offset: usize) -> Result<u16, Error> {
59    if data.len() < offset + 2 {
60        return Err(Error::truncated(offset + 2, data.len()));
61    }
62    Ok(u16::from_le_bytes([data[offset], data[offset + 1]]))
63}
64
65/// Read a u32 from little-endian bytes safely
66fn read_u32(data: &[u8], offset: usize) -> Result<u32, Error> {
67    if data.len() < offset + 4 {
68        return Err(Error::truncated(offset + 4, data.len()));
69    }
70    Ok(u32::from_le_bytes([
71        data[offset],
72        data[offset + 1],
73        data[offset + 2],
74        data[offset + 3],
75    ]))
76}
77
78/// Read an i64 from little-endian bytes safely
79fn read_i64(data: &[u8], offset: usize) -> Result<i64, Error> {
80    if data.len() < offset + 8 {
81        return Err(Error::truncated(offset + 8, data.len()));
82    }
83    let bytes: [u8; 8] = [
84        data[offset],
85        data[offset + 1],
86        data[offset + 2],
87        data[offset + 3],
88        data[offset + 4],
89        data[offset + 5],
90        data[offset + 6],
91        data[offset + 7],
92    ];
93    Ok(i64::from_le_bytes(bytes))
94}
95
96/// Read a u64 from little-endian bytes safely
97fn read_u64(data: &[u8], offset: usize) -> Result<u64, Error> {
98    if data.len() < offset + 8 {
99        return Err(Error::truncated(offset + 8, data.len()));
100    }
101    let bytes: [u8; 8] = [
102        data[offset],
103        data[offset + 1],
104        data[offset + 2],
105        data[offset + 3],
106        data[offset + 4],
107        data[offset + 5],
108        data[offset + 6],
109        data[offset + 7],
110    ];
111    Ok(u64::from_le_bytes(bytes))
112}
113
114/// PCAPNG Section Header Block
115#[derive(Debug, Clone)]
116pub struct SectionHeaderBlock {
117    /// Byte-order magic (0x1a2b3c4d)
118    pub byte_order_magic: u32,
119    /// Major version
120    pub version_major: u16,
121    /// Minor version
122    pub version_minor: u16,
123    /// Section length (-1 for unknown)
124    pub section_length: i64,
125}
126
127/// PCAPNG Interface Description Block
128#[derive(Debug, Clone)]
129pub struct InterfaceDescriptionBlock {
130    /// Interface ID
131    pub interface_id: u16,
132    /// Link type (same as pcap)
133    pub link_type: u16,
134    /// Snapshot length
135    pub snap_len: u32,
136}
137
138/// PCAPNG Enhanced Packet Block
139#[derive(Debug, Clone)]
140pub struct EnhancedPacketBlock {
141    /// Interface ID
142    pub interface_id: u32,
143    /// Timestamp high bits (nanoseconds since epoch)
144    pub timestamp_high: u32,
145    /// Timestamp low bits
146    pub timestamp_low: u32,
147    /// Captured length
148    pub captured_length: u32,
149    /// Original length
150    pub original_length: u32,
151    /// Packet data (borrowed from input)
152    pub data: Vec<u8>,
153}
154
155/// PCAPNG Interface Statistics Block
156#[derive(Debug, Clone)]
157pub struct InterfaceStatisticsBlock {
158    /// Interface ID
159    pub interface_id: u32,
160    /// Timestamp (high 32 bits)
161    pub timestamp_high: u32,
162    /// Timestamp (low 32 bits)
163    pub timestamp_low: u32,
164    /// Number of packets received
165    pub packets_received: u64,
166    /// Number of packets dropped
167    pub packets_dropped: u64,
168    /// Number of packets received but discarded
169    pub packets_discarded: u64,
170}
171
172/// Name resolution record type
173#[derive(Debug, Clone, PartialEq)]
174pub enum NameRecord {
175    /// IPv4 address mapping
176    IPv4([u8; 4], String),
177    /// IPv6 address mapping
178    IPv6([u8; 16], String),
179    /// End of records
180    End,
181}
182
183/// PCAPNG Name Resolution Block
184#[derive(Debug, Clone)]
185pub struct NameResolutionBlock {
186    /// Name resolution records
187    pub records: Vec<NameRecord>,
188}
189
190/// PCAPNG block
191#[derive(Debug, Clone)]
192pub enum Block {
193    SectionHeader(SectionHeaderBlock),
194    InterfaceDescription(InterfaceDescriptionBlock),
195    EnhancedPacket(EnhancedPacketBlock),
196    NameResolution(NameResolutionBlock),
197    InterfaceStatistics(InterfaceStatisticsBlock),
198    Unknown,
199}
200
201/// Parse a PCAPNG block
202pub fn parse_block(input: &[u8], offset: usize) -> Result<(Block, usize), Error> {
203    if input.len() < 12 {
204        return Err(Error::truncated(12, input.len()));
205    }
206
207    // Block type (4 bytes)
208    let block_type = read_u32(input, 0)?;
209    // Block length (4 bytes) - includes header and trailer
210    let block_len = read_u32(input, 4)? as usize;
211
212    if block_len < 12 || block_len > input.len() {
213        return Err(Error::parse(
214            offset,
215            format!("Invalid block length: {}", block_len),
216        ));
217    }
218
219    let block_data = &input[8..block_len - 4];
220    let block = match BlockType::from(block_type) {
221        BlockType::SectionHeader => {
222            if block_data.len() < 16 {
223                return Err(Error::truncated(16, block_data.len()));
224            }
225            let byte_order_magic = read_u32(block_data, 0)?;
226            if byte_order_magic != PCAPNG_BYTE_ORDER_MAGIC {
227                return Err(Error::parse(offset, "Invalid byte-order magic".to_string()));
228            }
229            Block::SectionHeader(SectionHeaderBlock {
230                byte_order_magic,
231                version_major: read_u16(block_data, 4)?,
232                version_minor: read_u16(block_data, 6)?,
233                section_length: read_i64(block_data, 8)?,
234            })
235        }
236        BlockType::InterfaceDescription => {
237            if block_data.len() < 8 {
238                return Err(Error::truncated(8, block_data.len()));
239            }
240            Block::InterfaceDescription(InterfaceDescriptionBlock {
241                interface_id: 0, // Will be assigned by reader
242                link_type: read_u16(block_data, 0)?,
243                snap_len: read_u32(block_data, 4)?,
244            })
245        }
246        BlockType::EnhancedPacket => {
247            if block_data.len() < 20 {
248                return Err(Error::truncated(20, block_data.len()));
249            }
250            let captured_len = read_u32(block_data, 12)? as usize;
251            let data_len = captured_len + (4 - captured_len % 4) % 4; // Padding
252
253            if block_data.len() < 20 + data_len {
254                return Err(Error::truncated(20 + data_len, block_data.len()));
255            }
256
257            // Copy packet data to avoid lifetime issues
258            let data = block_data[20..20 + captured_len].to_vec();
259
260            Block::EnhancedPacket(EnhancedPacketBlock {
261                interface_id: read_u32(block_data, 0)?,
262                timestamp_high: read_u32(block_data, 4)?,
263                timestamp_low: read_u32(block_data, 8)?,
264                captured_length: captured_len as u32,
265                original_length: read_u32(block_data, 16)?,
266                data,
267            })
268        }
269        BlockType::NameResolution => {
270            // Name Resolution Block - parse records
271            let mut records = Vec::new();
272            let mut pos = 0;
273
274            while pos + 4 <= block_data.len() {
275                let record_type = read_u16(block_data, pos)?;
276                pos += 2;
277
278                if record_type == 0 {
279                    // End of records
280                    break;
281                }
282
283                let record_len = read_u16(block_data, pos)? as usize;
284                pos += 2;
285
286                if pos + record_len > block_data.len() {
287                    break;
288                }
289
290                match record_type {
291                    1 => {
292                        // IPv4 address record
293                        if record_len >= 4 {
294                            let addr = [
295                                block_data[pos],
296                                block_data[pos + 1],
297                                block_data[pos + 2],
298                                block_data[pos + 3],
299                            ];
300                            let name_start = pos + 4;
301                            let name_end = name_start + record_len - 4;
302                            if name_end <= block_data.len() {
303                                let name =
304                                    String::from_utf8_lossy(&block_data[name_start..name_end])
305                                        .trim_end_matches('\0')
306                                        .to_string();
307                                if !name.is_empty() {
308                                    records.push(NameRecord::IPv4(addr, name));
309                                }
310                            }
311                        }
312                    }
313                    2 => {
314                        // IPv6 address record
315                        if record_len >= 16 {
316                            let mut addr = [0u8; 16];
317                            addr.copy_from_slice(&block_data[pos..pos + 16]);
318                            let name_start = pos + 16;
319                            let name_end = name_start + record_len - 16;
320                            if name_end <= block_data.len() {
321                                let name =
322                                    String::from_utf8_lossy(&block_data[name_start..name_end])
323                                        .trim_end_matches('\0')
324                                        .to_string();
325                                if !name.is_empty() {
326                                    records.push(NameRecord::IPv6(addr, name));
327                                }
328                            }
329                        }
330                    }
331                    _ => {}
332                }
333
334                // Pad to 4-byte boundary
335                let consumed = 4 + record_len;
336                pos += (consumed + 3) & !3;
337            }
338
339            Block::NameResolution(NameResolutionBlock { records })
340        }
341        BlockType::InterfaceStatistics => {
342            // Interface Statistics Block - minimum 24 bytes (interface ID + timestamps + start/end)
343            if block_data.len() < 24 {
344                return Err(Error::truncated(24, block_data.len()));
345            }
346
347            let interface_id = read_u32(block_data, 0)?;
348            let ts_high = read_u32(block_data, 4)?;
349            let ts_low = read_u32(block_data, 8)?;
350
351            // Skip end timestamp (8 bytes) and read optional counters
352            let mut packets_received = 0u64;
353            let mut packets_dropped = 0u64;
354            let mut packets_discarded = 0u64;
355
356            if block_data.len() >= 32 {
357                packets_received = read_u64(block_data, 24)?;
358                packets_dropped = read_u64(block_data, 32)?;
359            }
360            if block_data.len() >= 40 {
361                packets_discarded = read_u64(block_data, 40)?;
362            }
363
364            Block::InterfaceStatistics(InterfaceStatisticsBlock {
365                interface_id,
366                timestamp_high: ts_high,
367                timestamp_low: ts_low,
368                packets_received,
369                packets_dropped,
370                packets_discarded,
371            })
372        }
373        BlockType::Unknown(_) | BlockType::Packet | BlockType::SimplePacket => Block::Unknown,
374    };
375
376    Ok((block, offset + block_len))
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    /// Test Section Header Block parsing
384    #[test]
385    fn test_parse_shb() {
386        // Minimal SHB: block type (4) + block length (4) + byte-order (4) + version (4) + section length (8) + block length (4)
387        let data = vec![
388            // Block type (SHB)
389            0x0a, 0x0d, 0x0d, 0x0a, // Block length (28 bytes)
390            0x1c, 0x00, 0x00, 0x00, // Byte-order magic
391            0x4d, 0x3c, 0x2b, 0x1a, // Version major (2)
392            0x02, 0x00, // Version minor (4)
393            0x04, 0x00, // Section length (-1 = unknown)
394            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Block length (repeated)
395            0x1c, 0x00, 0x00, 0x00,
396        ];
397
398        let (block, _) = parse_block(&data, 0).unwrap();
399        match block {
400            Block::SectionHeader(shb) => {
401                assert_eq!(shb.byte_order_magic, PCAPNG_BYTE_ORDER_MAGIC);
402                assert_eq!(shb.version_major, 2);
403                assert_eq!(shb.version_minor, 4);
404                assert_eq!(shb.section_length, -1);
405            }
406            _ => panic!("Expected SectionHeader block"),
407        }
408    }
409
410    /// Test Interface Description Block parsing
411    #[test]
412    fn test_parse_idb() {
413        // IDB format: [type:4][length:4][link_type:2][pad:2][snap_len:4][options:4][length:4]
414        // Total: 4 + 4 + 2 + 2 + 4 + 4 + 4 = 24 bytes
415        let data = vec![
416            // Block type (IDB = 1)
417            0x01, 0x00, 0x00, 0x00, // Block length (24 = 0x18)
418            0x18, 0x00, 0x00, 0x00, // Link type (Ethernet = 1)
419            0x01, 0x00,
420            // Padding (2 bytes) - IDB requires 4-byte alignment for fields after link_type
421            0x00, 0x00, // Snap len (65535 = 0xFFFF)
422            0xff, 0xff, 0x00, 0x00, // Options (4 bytes, set to 0)
423            0x00, 0x00, 0x00, 0x00, // Block length (repeated)
424            0x18, 0x00, 0x00, 0x00,
425        ];
426
427        assert_eq!(data.len(), 24, "IDB should be exactly 24 bytes");
428
429        let (block, _) = parse_block(&data, 0).unwrap();
430        match block {
431            Block::InterfaceDescription(idb) => {
432                assert_eq!(idb.link_type, 1);
433                assert_eq!(idb.snap_len, 65535);
434            }
435            _ => panic!("Expected InterfaceDescription block"),
436        }
437    }
438
439    /// Test Enhanced Packet Block parsing
440    #[test]
441    fn test_parse_epb() {
442        // EPB: block type + block length + interface id + timestamps + captured len + original len + data + padding + block length
443        let packet_data = vec![0xde, 0xad, 0xbe, 0xef];
444        let block_len = 32 + packet_data.len(); // 20 bytes header + data + padding + 4 block length
445
446        let mut data = vec![
447            // Block type (EPB)
448            0x06, 0x00, 0x00, 0x00,
449            // Block length
450        ];
451        data.extend_from_slice(&(block_len as u32).to_le_bytes());
452
453        // Interface ID
454        data.extend_from_slice(&0u32.to_le_bytes());
455        // Timestamp high
456        data.extend_from_slice(&1000u32.to_le_bytes());
457        // Timestamp low
458        data.extend_from_slice(&500u32.to_le_bytes());
459        // Captured length
460        data.extend_from_slice(&(packet_data.len() as u32).to_le_bytes());
461        // Original length
462        data.extend_from_slice(&(packet_data.len() as u32).to_le_bytes());
463        // Packet data
464        data.extend_from_slice(&packet_data);
465        // Padding (to 4-byte boundary)
466        #[allow(clippy::manual_is_multiple_of)]
467        while data.len() % 4 != 0 {
468            data.push(0);
469        }
470        // Block length (repeated)
471        data.extend_from_slice(&(block_len as u32).to_le_bytes());
472
473        let (block, _) = parse_block(&data, 0).unwrap();
474        match block {
475            Block::EnhancedPacket(epb) => {
476                assert_eq!(epb.interface_id, 0);
477                assert_eq!(epb.timestamp_high, 1000);
478                assert_eq!(epb.timestamp_low, 500);
479                assert_eq!(epb.captured_length, 4);
480                assert_eq!(epb.original_length, 4);
481                assert_eq!(&epb.data, &packet_data);
482            }
483            _ => panic!("Expected EnhancedPacket block"),
484        }
485    }
486
487    /// Test truncated block
488    #[test]
489    fn test_parse_truncated() {
490        let data = vec![0u8; 8]; // Too short
491
492        let result = parse_block(&data, 0);
493        assert!(result.is_err());
494    }
495
496    /// Test invalid block length
497    #[test]
498    fn test_parse_invalid_block_length() {
499        let data = vec![
500            0x0a, 0x0d, 0x0d, 0x0a, // SHB
501            0x00, 0x00, 0x00, 0x00, // Invalid block length (0)
502        ];
503
504        let result = parse_block(&data, 0);
505        assert!(result.is_err());
506    }
507
508    /// Test invalid byte-order magic
509    #[test]
510    fn test_parse_invalid_byte_order() {
511        let data = vec![
512            0x0a, 0x0d, 0x0d, 0x0a, // SHB
513            0x10, 0x00, 0x00, 0x00, // Block length (16)
514            0x00, 0x00, 0x00, 0x00, // Invalid byte-order magic
515            0x02, 0x00, 0x04, 0x00, // Version
516            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Section length
517            0x10, 0x00, 0x00, 0x00, // Block length (repeated)
518        ];
519
520        let result = parse_block(&data, 0);
521        assert!(result.is_err());
522    }
523}