1use anyhow::{bail, Context, Result};
5use std::path::Path;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct PcapPacket<'a> {
9 pub ts_sec: u32,
10 pub ts_usec: u32,
12 pub data: &'a [u8],
14}
15
16const GLOBAL_HDR_LEN: usize = 24;
17const RECORD_HDR_LEN: usize = 16;
18
19#[derive(Clone, Debug)]
21pub struct Pcap {
22 bytes: Vec<u8>,
23 header: Header,
24}
25
26impl Pcap {
27 pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
30 let header = Header::parse(&bytes)?;
31 Ok(Self { bytes, header })
32 }
33
34 pub fn packets(&self) -> Packets<'_> {
35 self.header.packets(&self.bytes)
36 }
37}
38
39pub fn read_pcap(path: impl AsRef<Path>) -> Result<Pcap> {
42 let path = path.as_ref();
43 let bytes = std::fs::read(path).with_context(|| format!("read {}", path.display()))?;
44 Pcap::from_bytes(bytes)
45}
46
47pub fn parse_pcap(d: &[u8]) -> Result<Packets<'_>> {
52 Ok(Header::parse(d)?.packets(d))
53}
54
55#[derive(Clone, Copy, Debug)]
57struct Header {
58 read_u32: fn([u8; 4]) -> u32,
59 nano: bool,
60}
61
62impl Header {
63 fn parse(d: &[u8]) -> Result<Self> {
64 if d.len() < GLOBAL_HDR_LEN {
65 bail!("pcap too short");
66 }
67 let (le, nano) = match u32::from_le_bytes(bytes_at(d, 0)) {
68 0xa1b2_c3d4 => (true, false),
69 0xd4c3_b2a1 => (false, false),
70 0xa1b2_3c4d => (true, true),
71 0x4d3c_b2a1 => (false, true),
72 m => bail!(
73 "not a classic pcap file (magic {m:08x}); if pcapng, convert: tcpdump -r in -w out"
74 ),
75 };
76 let read_u32 = if le {
77 u32::from_le_bytes
78 } else {
79 u32::from_be_bytes
80 };
81 Ok(Self { read_u32, nano })
82 }
83
84 fn packets(self, d: &[u8]) -> Packets<'_> {
85 Packets {
86 header: self,
87 rest: &d[GLOBAL_HDR_LEN..],
88 }
89 }
90}
91
92#[derive(Clone, Debug)]
93pub struct Packets<'a> {
94 header: Header,
95 rest: &'a [u8],
96}
97
98impl<'a> Iterator for Packets<'a> {
99 type Item = PcapPacket<'a>;
100
101 fn next(&mut self) -> Option<PcapPacket<'a>> {
102 let d = self.rest;
103 if d.len() < RECORD_HDR_LEN {
104 return None;
105 }
106 let rd = self.header.read_u32;
107 let ts_sec = rd(bytes_at(d, 0));
108 let mut ts_usec = rd(bytes_at(d, 4));
109 let caplen = rd(bytes_at(d, 8)) as usize;
110 let data = d[RECORD_HDR_LEN..].get(..caplen)?;
111 if self.header.nano {
112 ts_usec /= 1000;
113 }
114 self.rest = &d[RECORD_HDR_LEN + caplen..];
115 Some(PcapPacket {
116 ts_sec,
117 ts_usec,
118 data,
119 })
120 }
121}
122
123fn bytes_at(d: &[u8], off: usize) -> [u8; 4] {
124 [d[off], d[off + 1], d[off + 2], d[off + 3]]
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 fn file(magic: u32, le: bool, records: &[(u32, u32, &[u8])]) -> Vec<u8> {
132 let w = |v: u32| if le { v.to_le_bytes() } else { v.to_be_bytes() };
133 let mut d = Vec::new();
134 d.extend_from_slice(&magic.to_le_bytes());
135 d.extend_from_slice(&[0u8; 20]);
136 for (sec, frac, data) in records {
137 d.extend_from_slice(&w(*sec));
138 d.extend_from_slice(&w(*frac));
139 d.extend_from_slice(&w(data.len() as u32));
140 d.extend_from_slice(&w(data.len() as u32));
141 d.extend_from_slice(data);
142 }
143 d
144 }
145
146 fn packets(d: &[u8]) -> Result<Vec<PcapPacket<'_>>> {
147 Ok(parse_pcap(d)?.collect())
148 }
149
150 #[test]
151 fn little_endian_records_come_back_in_order() {
152 let d = file(0xa1b2_c3d4, true, &[(1, 2, &[0xaa, 0xbb]), (3, 4, &[0xcc])]);
153 let p = packets(&d).unwrap();
154 assert_eq!(
155 p,
156 [
157 PcapPacket {
158 ts_sec: 1,
159 ts_usec: 2,
160 data: &[0xaa, 0xbb]
161 },
162 PcapPacket {
163 ts_sec: 3,
164 ts_usec: 4,
165 data: &[0xcc]
166 },
167 ]
168 );
169 }
170
171 #[test]
172 fn big_endian_magic_selects_big_endian_headers() {
173 let d = file(0xd4c3_b2a1, false, &[(0x0102_0304, 7, &[1, 2, 3])]);
174 let p = packets(&d).unwrap();
175 assert_eq!((p[0].ts_sec, p[0].ts_usec), (0x0102_0304, 7));
176 assert_eq!(p[0].data, [1, 2, 3]);
177 }
178
179 #[test]
180 fn nanosecond_files_report_microseconds() {
181 let d = file(0xa1b2_3c4d, true, &[(9, 123_456_789, &[0])]);
182 let p = packets(&d).unwrap();
183 assert_eq!(p[0].ts_usec, 123_456);
184 let d = file(0x4d3c_b2a1, false, &[(9, 5_000, &[0])]);
185 assert_eq!(packets(&d).unwrap()[0].ts_usec, 5);
186 }
187
188 #[test]
189 fn truncated_last_record_is_dropped() {
190 let mut d = file(0xa1b2_c3d4, true, &[(1, 1, &[1, 2, 3, 4])]);
191 d.pop();
192 assert!(packets(&d).unwrap().is_empty());
193 }
194
195 #[test]
196 fn pcapng_and_short_files_are_rejected() {
197 let mut d = vec![0u8; 24];
198 d[..4].copy_from_slice(&0x0a0d_0d0au32.to_le_bytes());
199 assert!(parse_pcap(&d).unwrap_err().to_string().contains("tcpdump"));
200 assert!(parse_pcap(&d[..10]).is_err());
201 }
202
203 #[test]
204 fn an_owned_file_walks_the_same_packets() {
205 let d = file(0xa1b2_c3d4, true, &[(1, 2, &[0xaa]), (3, 4, &[0xbb, 0xcc])]);
206 let pcap = Pcap::from_bytes(d.clone()).unwrap();
207 assert_eq!(pcap.packets().collect::<Vec<_>>(), packets(&d).unwrap());
208 assert!(Pcap::from_bytes(vec![0; 24]).is_err());
209 }
210}