1const SHB_MAGIC: u32 = 0x0A0D_0D0A;
12const BYTE_ORDER_MAGIC: u32 = 0x1A2B_3C4D;
13const BYTE_ORDER_MAGIC_SWAPPED: u32 = 0x4D3C_2B1A;
14
15const BLOCK_SHB: u32 = 0x0A0D_0D0A;
16const BLOCK_IDB: u32 = 0x0000_0001;
17const BLOCK_OPB: u32 = 0x0000_0002; const BLOCK_SPB: u32 = 0x0000_0003;
19const BLOCK_EPB: u32 = 0x0000_0006;
20
21#[derive(Debug, Clone)]
24struct Interface {
25 link_type: u16,
26 ts_resol: u8,
30 ts_offset: u64,
31}
32
33impl Interface {
34 fn ts_to_secs_usecs(&self, ts: u64) -> (u32, u32) {
35 let resol = self.ts_resol;
36 let (sec, frac) = if resol == 6 {
37 (ts / 1_000_000, ts % 1_000_000)
38 } else if resol == 9 {
39 (ts / 1_000_000_000, (ts % 1_000_000_000) / 1_000)
40 } else {
41 let factor = 10u64.pow(resol as u32);
42 (ts / factor, (ts % factor) * 1_000_000 / factor)
43 };
44 ((sec + self.ts_offset) as u32, frac as u32)
45 }
46}
47
48fn read_u16(data: &[u8], offset: usize, big_endian: bool) -> Option<u16> {
49 if offset + 2 > data.len() {
50 return None;
51 }
52 let b = &data[offset..offset + 2];
53 Some(if big_endian {
54 u16::from_be_bytes([b[0], b[1]])
55 } else {
56 u16::from_le_bytes([b[0], b[1]])
57 })
58}
59
60fn read_u32(data: &[u8], offset: usize, big_endian: bool) -> Option<u32> {
61 if offset + 4 > data.len() {
62 return None;
63 }
64 let b = &data[offset..offset + 4];
65 Some(if big_endian {
66 u32::from_be_bytes([b[0], b[1], b[2], b[3]])
67 } else {
68 u32::from_le_bytes([b[0], b[1], b[2], b[3]])
69 })
70}
71
72fn read_u64(data: &[u8], offset: usize, big_endian: bool) -> Option<u64> {
73 if offset + 8 > data.len() {
74 return None;
75 }
76 let b = &data[offset..offset + 8];
77 Some(if big_endian {
78 u64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
79 } else {
80 u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
81 })
82}
83
84pub fn parse_pcapng(data: &[u8]) -> Result<Vec<(u32, u32, Vec<u8>)>, String> {
94 if data.len() < 12 {
95 return Err("file too short for PCAPng".into());
96 }
97
98 let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
99 if magic != SHB_MAGIC {
100 return Err(format!("not a PCAPng file (magic: 0x{magic:08x})"));
101 }
102
103 let mut pos = 0usize;
104 let mut big_endian = false;
105 let mut interfaces: Vec<Interface> = Vec::new();
106 let mut packets: Vec<(u32, u32, Vec<u8>)> = Vec::new();
107
108 while pos + 8 <= data.len() {
109 let block_type = read_u32(data, pos, big_endian).ok_or("failed to read block type")?;
113 let block_len_raw = read_u32(data, pos + 4, false).ok_or("failed to read block length")?;
117 let block_len = if block_type == BLOCK_SHB {
118 block_len_raw
119 } else {
120 read_u32(data, pos + 4, big_endian).ok_or("failed to read block length")?
121 };
122
123 if block_len < 12 || pos + block_len as usize > data.len() {
124 break;
125 }
126
127 let block_data = &data[pos..pos + block_len as usize];
128
129 match block_type {
130 BLOCK_SHB => {
131 if block_len < 16 {
133 pos += block_len as usize;
134 continue;
135 }
136 let bom = u32::from_le_bytes([
137 block_data[8],
138 block_data[9],
139 block_data[10],
140 block_data[11],
141 ]);
142 big_endian = match bom {
143 BYTE_ORDER_MAGIC => false,
144 BYTE_ORDER_MAGIC_SWAPPED => true,
145 _ => return Err(format!("invalid byte-order magic: 0x{bom:08x}")),
146 };
147 interfaces.clear();
149 }
150
151 BLOCK_IDB => {
152 if block_len < 20 {
154 pos += block_len as usize;
155 continue;
156 }
157 let link_type = read_u16(block_data, 8, big_endian).unwrap_or(1);
158 let mut ts_resol = 6u8; let mut ts_offset = 0u64;
160
161 let opts_start = 16usize;
162 let opts_end = block_len as usize - 4;
163 let mut opos = opts_start;
164 while opos + 4 <= opts_end {
165 let opt_code = read_u16(block_data, opos, big_endian).unwrap_or(0);
166 let opt_len = read_u16(block_data, opos + 2, big_endian).unwrap_or(0) as usize;
167 opos += 4;
168 if opt_code == 0 {
169 break; }
171 if opos + opt_len <= opts_end {
172 match opt_code {
173 9 => {
174 if opt_len >= 1 {
176 ts_resol = block_data[opos];
177 }
178 }
179 14
180 if opt_len >= 8 => {
182 ts_offset = read_u64(block_data, opos, big_endian).unwrap_or(0);
183 }
184 _ => {}
185 }
186 }
187 opos += (opt_len + 3) & !3;
189 }
190
191 interfaces.push(Interface {
192 link_type,
193 ts_resol,
194 ts_offset,
195 });
196 }
197
198 BLOCK_EPB => {
199 if block_len < 32 {
206 pos += block_len as usize;
207 continue;
208 }
209 let iface_id = read_u32(block_data, 8, big_endian).unwrap_or(0) as usize;
210 let ts_high = read_u32(block_data, 12, big_endian).unwrap_or(0) as u64;
211 let ts_low = read_u32(block_data, 16, big_endian).unwrap_or(0) as u64;
212 let cap_len = read_u32(block_data, 20, big_endian).unwrap_or(0) as usize;
213 let ts = (ts_high << 32) | ts_low;
214
215 let iface = interfaces.get(iface_id).cloned().unwrap_or(Interface {
216 link_type: 1,
217 ts_resol: 6,
218 ts_offset: 0,
219 });
220
221 if iface.link_type != 1 {
222 pos += block_len as usize;
223 continue;
224 }
225
226 let data_start = 28usize;
227 if data_start + cap_len <= block_len as usize - 4 {
228 let pkt_data = block_data[data_start..data_start + cap_len].to_vec();
229 let (ts_sec, ts_usec) = iface.ts_to_secs_usecs(ts);
230 packets.push((ts_sec, ts_usec, pkt_data));
231 }
232 }
233
234 BLOCK_OPB => {
235 if block_len < 32 {
243 pos += block_len as usize;
244 continue;
245 }
246 let iface_id = read_u16(block_data, 8, big_endian).unwrap_or(0) as usize;
247 let ts_high = read_u32(block_data, 12, big_endian).unwrap_or(0) as u64;
248 let ts_low = read_u32(block_data, 16, big_endian).unwrap_or(0) as u64;
249 let cap_len = read_u32(block_data, 20, big_endian).unwrap_or(0) as usize;
250 let ts = (ts_high << 32) | ts_low;
251
252 let iface = interfaces.get(iface_id).cloned().unwrap_or(Interface {
253 link_type: 1,
254 ts_resol: 6,
255 ts_offset: 0,
256 });
257
258 if iface.link_type != 1 {
259 pos += block_len as usize;
260 continue;
261 }
262
263 let data_start = 28usize;
264 if data_start + cap_len <= block_len as usize - 4 {
265 let pkt_data = block_data[data_start..data_start + cap_len].to_vec();
266 let (ts_sec, ts_usec) = iface.ts_to_secs_usecs(ts);
267 packets.push((ts_sec, ts_usec, pkt_data));
268 }
269 }
270
271 BLOCK_SPB => {
272 if block_len < 16 {
274 pos += block_len as usize;
275 continue;
276 }
277 let orig_len = read_u32(block_data, 8, big_endian).unwrap_or(0) as usize;
278 let cap_len = orig_len.min(block_len as usize - 16);
279 let iface = interfaces.first().cloned().unwrap_or(Interface {
280 link_type: 1,
281 ts_resol: 6,
282 ts_offset: 0,
283 });
284
285 if iface.link_type == 1 && cap_len > 0 {
286 let pkt_data = block_data[12..12 + cap_len].to_vec();
287 packets.push((0, 0, pkt_data));
288 }
289 }
290
291 _ => {
292 }
294 }
295
296 pos += block_len as usize;
297 }
298
299 Ok(packets)
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305
306 fn pad4(len: usize) -> usize {
307 (len + 3) & !3
308 }
309
310 fn build_pcapng_le(frames: &[&[u8]]) -> Vec<u8> {
313 let mut buf = Vec::new();
314
315 let shb_len: u32 = 28;
317 buf.extend_from_slice(&BLOCK_SHB.to_le_bytes());
318 buf.extend_from_slice(&shb_len.to_le_bytes());
319 buf.extend_from_slice(&BYTE_ORDER_MAGIC.to_le_bytes());
320 buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&(-1i64).to_le_bytes()); buf.extend_from_slice(&shb_len.to_le_bytes());
324
325 let idb_len: u32 = 20;
327 buf.extend_from_slice(&BLOCK_IDB.to_le_bytes());
328 buf.extend_from_slice(&idb_len.to_le_bytes());
329 buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&65535u32.to_le_bytes()); buf.extend_from_slice(&idb_len.to_le_bytes());
333
334 for frame in frames {
335 let padded = pad4(frame.len());
336 let epb_len = 32 + padded as u32;
337 buf.extend_from_slice(&BLOCK_EPB.to_le_bytes());
338 buf.extend_from_slice(&epb_len.to_le_bytes());
339 buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&1_000_000u32.to_le_bytes()); buf.extend_from_slice(&(frame.len() as u32).to_le_bytes()); buf.extend_from_slice(&(frame.len() as u32).to_le_bytes()); buf.extend_from_slice(frame);
345 buf.extend(std::iter::repeat_n(0u8, padded - frame.len()));
346 buf.extend_from_slice(&epb_len.to_le_bytes());
347 }
348
349 buf
350 }
351
352 #[test]
353 fn rejects_bad_magic() {
354 let mut file = build_pcapng_le(&[]);
355 file[0] = 0x00;
356 assert!(parse_pcapng(&file).is_err());
357 }
358
359 #[test]
360 fn parses_single_packet() {
361 let frame = [0xAAu8; 40];
362 let file = build_pcapng_le(&[&frame]);
363
364 let packets = parse_pcapng(&file).unwrap();
365 assert_eq!(packets.len(), 1);
366 let (ts_sec, ts_usec, data) = &packets[0];
367 assert_eq!(*ts_sec, 1);
368 assert_eq!(*ts_usec, 0);
369 assert_eq!(data.as_slice(), &frame[..]);
370 }
371
372 #[test]
373 fn parses_multiple_packets_with_unaligned_length() {
374 let frame_a = [0x11u8; 7]; let frame_b = [0x22u8; 60];
376 let file = build_pcapng_le(&[&frame_a, &frame_b]);
377
378 let packets = parse_pcapng(&file).unwrap();
379 assert_eq!(packets.len(), 2);
380 assert_eq!(packets[0].2, frame_a.to_vec());
381 assert_eq!(packets[1].2, frame_b.to_vec());
382 }
383
384 #[test]
385 fn empty_capture_yields_no_packets() {
386 let file = build_pcapng_le(&[]);
387 let packets = parse_pcapng(&file).unwrap();
388 assert!(packets.is_empty());
389 }
390}