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 { u16::from_be_bytes([b[0], b[1]]) } else { u16::from_le_bytes([b[0], b[1]]) })
54}
55
56fn read_u32(data: &[u8], offset: usize, big_endian: bool) -> Option<u32> {
57 if offset + 4 > data.len() {
58 return None;
59 }
60 let b = &data[offset..offset + 4];
61 Some(if big_endian {
62 u32::from_be_bytes([b[0], b[1], b[2], b[3]])
63 } else {
64 u32::from_le_bytes([b[0], b[1], b[2], b[3]])
65 })
66}
67
68fn read_u64(data: &[u8], offset: usize, big_endian: bool) -> Option<u64> {
69 if offset + 8 > data.len() {
70 return None;
71 }
72 let b = &data[offset..offset + 8];
73 Some(if big_endian {
74 u64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
75 } else {
76 u64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
77 })
78}
79
80pub fn parse_pcapng(data: &[u8]) -> Result<Vec<(u32, u32, Vec<u8>)>, String> {
90 if data.len() < 12 {
91 return Err("file too short for PCAPng".into());
92 }
93
94 let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
95 if magic != SHB_MAGIC {
96 return Err(format!("not a PCAPng file (magic: 0x{magic:08x})"));
97 }
98
99 let mut pos = 0usize;
100 let mut big_endian = false;
101 let mut interfaces: Vec<Interface> = Vec::new();
102 let mut packets: Vec<(u32, u32, Vec<u8>)> = Vec::new();
103
104 while pos + 8 <= data.len() {
105 let block_type = read_u32(data, pos, big_endian).ok_or("failed to read block type")?;
109 let block_len_raw = read_u32(data, pos + 4, false).ok_or("failed to read block length")?;
113 let block_len = if block_type == BLOCK_SHB {
114 block_len_raw
115 } else {
116 read_u32(data, pos + 4, big_endian).ok_or("failed to read block length")?
117 };
118
119 if block_len < 12 || pos + block_len as usize > data.len() {
120 break;
121 }
122
123 let block_data = &data[pos..pos + block_len as usize];
124
125 match block_type {
126 BLOCK_SHB => {
127 if block_len < 16 {
129 pos += block_len as usize;
130 continue;
131 }
132 let bom =
133 u32::from_le_bytes([block_data[8], block_data[9], block_data[10], block_data[11]]);
134 big_endian = match bom {
135 BYTE_ORDER_MAGIC => false,
136 BYTE_ORDER_MAGIC_SWAPPED => true,
137 _ => return Err(format!("invalid byte-order magic: 0x{bom:08x}")),
138 };
139 interfaces.clear();
141 }
142
143 BLOCK_IDB => {
144 if block_len < 20 {
146 pos += block_len as usize;
147 continue;
148 }
149 let link_type = read_u16(block_data, 8, big_endian).unwrap_or(1);
150 let mut ts_resol = 6u8; let mut ts_offset = 0u64;
152
153 let opts_start = 16usize;
154 let opts_end = block_len as usize - 4;
155 let mut opos = opts_start;
156 while opos + 4 <= opts_end {
157 let opt_code = read_u16(block_data, opos, big_endian).unwrap_or(0);
158 let opt_len = read_u16(block_data, opos + 2, big_endian).unwrap_or(0) as usize;
159 opos += 4;
160 if opt_code == 0 {
161 break; }
163 if opos + opt_len <= opts_end {
164 match opt_code {
165 9 => {
166 if opt_len >= 1 {
168 ts_resol = block_data[opos];
169 }
170 }
171 14 => {
172 if opt_len >= 8 {
174 ts_offset = read_u64(block_data, opos, big_endian).unwrap_or(0);
175 }
176 }
177 _ => {}
178 }
179 }
180 opos += (opt_len + 3) & !3;
182 }
183
184 interfaces.push(Interface { link_type, ts_resol, ts_offset });
185 }
186
187 BLOCK_EPB => {
188 if block_len < 32 {
195 pos += block_len as usize;
196 continue;
197 }
198 let iface_id = read_u32(block_data, 8, big_endian).unwrap_or(0) as usize;
199 let ts_high = read_u32(block_data, 12, big_endian).unwrap_or(0) as u64;
200 let ts_low = read_u32(block_data, 16, big_endian).unwrap_or(0) as u64;
201 let cap_len = read_u32(block_data, 20, big_endian).unwrap_or(0) as usize;
202 let ts = (ts_high << 32) | ts_low;
203
204 let iface = interfaces
205 .get(iface_id)
206 .cloned()
207 .unwrap_or(Interface { link_type: 1, ts_resol: 6, ts_offset: 0 });
208
209 if iface.link_type != 1 {
210 pos += block_len as usize;
211 continue;
212 }
213
214 let data_start = 28usize;
215 if data_start + cap_len <= block_len as usize - 4 {
216 let pkt_data = block_data[data_start..data_start + cap_len].to_vec();
217 let (ts_sec, ts_usec) = iface.ts_to_secs_usecs(ts);
218 packets.push((ts_sec, ts_usec, pkt_data));
219 }
220 }
221
222 BLOCK_OPB => {
223 if block_len < 32 {
231 pos += block_len as usize;
232 continue;
233 }
234 let iface_id = read_u16(block_data, 8, big_endian).unwrap_or(0) as usize;
235 let ts_high = read_u32(block_data, 12, big_endian).unwrap_or(0) as u64;
236 let ts_low = read_u32(block_data, 16, big_endian).unwrap_or(0) as u64;
237 let cap_len = read_u32(block_data, 20, big_endian).unwrap_or(0) as usize;
238 let ts = (ts_high << 32) | ts_low;
239
240 let iface = interfaces
241 .get(iface_id)
242 .cloned()
243 .unwrap_or(Interface { link_type: 1, ts_resol: 6, ts_offset: 0 });
244
245 if iface.link_type != 1 {
246 pos += block_len as usize;
247 continue;
248 }
249
250 let data_start = 28usize;
251 if data_start + cap_len <= block_len as usize - 4 {
252 let pkt_data = block_data[data_start..data_start + cap_len].to_vec();
253 let (ts_sec, ts_usec) = iface.ts_to_secs_usecs(ts);
254 packets.push((ts_sec, ts_usec, pkt_data));
255 }
256 }
257
258 BLOCK_SPB => {
259 if block_len < 16 {
261 pos += block_len as usize;
262 continue;
263 }
264 let orig_len = read_u32(block_data, 8, big_endian).unwrap_or(0) as usize;
265 let cap_len = orig_len.min(block_len as usize - 16);
266 let iface = interfaces
267 .first()
268 .cloned()
269 .unwrap_or(Interface { link_type: 1, ts_resol: 6, ts_offset: 0 });
270
271 if iface.link_type == 1 && cap_len > 0 {
272 let pkt_data = block_data[12..12 + cap_len].to_vec();
273 packets.push((0, 0, pkt_data));
274 }
275 }
276
277 _ => {
278 }
280 }
281
282 pos += block_len as usize;
283 }
284
285 Ok(packets)
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 fn pad4(len: usize) -> usize {
293 (len + 3) & !3
294 }
295
296 fn build_pcapng_le(frames: &[&[u8]]) -> Vec<u8> {
299 let mut buf = Vec::new();
300
301 let shb_len: u32 = 28;
303 buf.extend_from_slice(&BLOCK_SHB.to_le_bytes());
304 buf.extend_from_slice(&shb_len.to_le_bytes());
305 buf.extend_from_slice(&BYTE_ORDER_MAGIC.to_le_bytes());
306 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());
310
311 let idb_len: u32 = 20;
313 buf.extend_from_slice(&BLOCK_IDB.to_le_bytes());
314 buf.extend_from_slice(&idb_len.to_le_bytes());
315 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());
319
320 for frame in frames {
321 let padded = pad4(frame.len());
322 let epb_len = 32 + padded as u32;
323 buf.extend_from_slice(&BLOCK_EPB.to_le_bytes());
324 buf.extend_from_slice(&epb_len.to_le_bytes());
325 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);
331 buf.extend(std::iter::repeat(0u8).take(padded - frame.len()));
332 buf.extend_from_slice(&epb_len.to_le_bytes());
333 }
334
335 buf
336 }
337
338 #[test]
339 fn rejects_bad_magic() {
340 let mut file = build_pcapng_le(&[]);
341 file[0] = 0x00;
342 assert!(parse_pcapng(&file).is_err());
343 }
344
345 #[test]
346 fn parses_single_packet() {
347 let frame = [0xAAu8; 40];
348 let file = build_pcapng_le(&[&frame]);
349
350 let packets = parse_pcapng(&file).unwrap();
351 assert_eq!(packets.len(), 1);
352 let (ts_sec, ts_usec, data) = &packets[0];
353 assert_eq!(*ts_sec, 1);
354 assert_eq!(*ts_usec, 0);
355 assert_eq!(data.as_slice(), &frame[..]);
356 }
357
358 #[test]
359 fn parses_multiple_packets_with_unaligned_length() {
360 let frame_a = [0x11u8; 7]; let frame_b = [0x22u8; 60];
362 let file = build_pcapng_le(&[&frame_a, &frame_b]);
363
364 let packets = parse_pcapng(&file).unwrap();
365 assert_eq!(packets.len(), 2);
366 assert_eq!(packets[0].2, frame_a.to_vec());
367 assert_eq!(packets[1].2, frame_b.to_vec());
368 }
369
370 #[test]
371 fn empty_capture_yields_no_packets() {
372 let file = build_pcapng_le(&[]);
373 let packets = parse_pcapng(&file).unwrap();
374 assert!(packets.is_empty());
375 }
376}