SatCat5
file_pcap.cc
1 // Copyright 2024-2025 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
6 #include <hal_posix/file_pcap.h>
8 #include <satcat5/log.h>
9 
13 using satcat5::log::Log;
15 
16 // Set debugging verbosity level (0/1/2)
17 constexpr unsigned DEBUG_VERBOSE = 0;
18 
19 // Global instance of PosixTimer.
20 // (All are equivalent and have no internal state.)
21 static satcat5::util::PosixTimer posix_timer;
22 
23 // Magic-numbers for PCAP:
24 constexpr u32 BLK_PCAP_HDR1_BE = 0xA1B2C3D4;
25 constexpr u32 BLK_PCAP_HDR2_BE = 0xA1B23C4D;
26 constexpr u32 BLK_PCAP_HDR1_LE = 0xD4C3B2A1;
27 constexpr u32 BLK_PCAP_HDR2_LE = 0x4D3CB2A1;
28 
29 // Magic-numbers for PCAPNG (Section 11.1):
30 constexpr u32 BLK_PCAPNG_IDB = 1;
31 constexpr u32 BLK_PCAPNG_SPB = 3;
32 constexpr u32 BLK_PCAPNG_EPB = 6;
33 constexpr u32 BLK_PCAPNG_SHB = 0x0A0D0D0Au;
34 constexpr u32 PCAPNG_MAGIC_BE = 0x1A2B3C4D;
35 
36 // Calculate zero-padding for word-aligned PCAPNG fields.
37 constexpr inline unsigned word_pad(unsigned len)
38  { return (-len) % 4; }
39 
40 ReadPcap::ReadPcap(const char* filename)
41  : satcat5::io::ArrayRead(m_buff, 0)
42  , m_file(0)
43  , m_mode_be(false)
44  , m_mode_ng(false)
45  , m_mode_pc(false)
46  , m_trim(0)
47 {
48  if (filename) open(filename);
49 }
50 
51 void ReadPcap::open(const char* filename) {
52  if (DEBUG_VERBOSE > 0)
53  Log(satcat5::log::DEBUG, "ReadPcap::open");
54 
55  // Open the specified file.
56  m_file.open(filename);
57 
58  // Reset parser state.
59  m_mode_be = false;
60  m_mode_ng = false;
61  m_mode_pc = false;
62  m_trim = 0;
63 
64  // Read first word to detect format...
65  u32 magic = m_file.read_u32();
66  if (magic == BLK_PCAPNG_SHB) {
67  // PCAPNG format, read the rest of the SHB.
68  m_mode_ng = true;
69  pcapng_shb(); // Read header.
70  } else if (magic == BLK_PCAP_HDR1_BE || magic == BLK_PCAP_HDR2_BE) {
71  // PCAP format, big-endian.
72  m_mode_be = true;
73  pcap_hdr(); // Read header
74  } else if (magic == BLK_PCAP_HDR1_LE || magic == BLK_PCAP_HDR2_LE) {
75  // PCAP format, little-endian.
76  m_mode_be = false;
77  pcap_hdr(); // Read header
78  } else {
79  // Invalid file or unsupported format.
80  m_file.close();
81  Log(satcat5::log::ERROR, "ReadPcap: Invalid file");
82  }
83 
84  // If this is a valid file, attempt to read the first data packet.
85  if (m_mode_ng || m_mode_pc) read_finalize();
86 }
87 
89  // Done with current frame, clear the working buffer.
90  read_reset(0);
91 
92  // Get ready to start reading the next frame.
93  // Keep reading PCAP records or PCAPNG blocks, one at a time, until
94  // we find a valid data packet or reach the end of the input file.
95  if (m_mode_pc) {
96  while (m_file.get_read_ready() && !pcap_dat()) {}
97  } else if (m_mode_ng) {
98  while (m_file.get_read_ready() && !pcapng_blk()) {}
99  }
100 }
101 
102 void ReadPcap::pcap_hdr() {
103  if (DEBUG_VERBOSE > 1)
104  Log(satcat5::log::DEBUG, "ReadPcap::pcap_hdr");
105 
106  // Read the file header (Section 4).
107  // (Note we've already read the "magic number".)
108  u16 major = file_rd16();
109  u16 minor = file_rd16();
110  m_file.read_consume(12);
111  u32 type = file_rd32();
112 
113  // Only version 2.4 is supported.
114  // If FCS mode is enabled ("f" bit is set), note the FCS length.
115  if (major == 2 && minor == 4) {
116  m_mode_pc = true;
117  if (type & 0x10000000) m_trim = (type >> 29);
118  }
119 }
120 
121 bool ReadPcap::pcap_dat() {
122  if (DEBUG_VERBOSE > 1)
123  Log(satcat5::log::DEBUG, "ReadPcap::pcap_dat");
124 
125  // Read the "packet record" header (Section 5).
126  m_file.read_consume(8); // Skip timestamp
127  u32 clen = file_rd32(); // Captured packet length
128  u32 olen = file_rd32(); // Original packet length
129 
130  // Take further action?
131  if (clen <= m_trim || olen <= m_trim) {
132  // Abort on end-of-file or invalid length.
133  m_file.close();
134  } else if (olen <= clen && clen <= sizeof(m_buff)) {
135  // Copy normal packets to the working buffer.
136  m_file.read_bytes(clen, m_buff);
137  read_reset(olen - m_trim);
138  } else {
139  // Skip if truncated or larger than our working buffer.
140  m_file.read_consume(clen);
141  }
142 
143  // Did we read some data successfully?
144  return get_read_ready() > 0;
145 }
146 
147 bool ReadPcap::pcapng_blk() {
148  // Read the block type and parse accordingly...
149  switch (file_rd32()) {
150  case BLK_PCAPNG_IDB: pcapng_idb(); break;
151  case BLK_PCAPNG_SPB: pcapng_spb(); break;
152  case BLK_PCAPNG_EPB: pcapng_epb(); break;
153  case BLK_PCAPNG_SHB: pcapng_shb(); break;
154  default: pcapng_skip(); break;
155  }
156 
157  // Did we read some data successfully?
158  return get_read_ready() > 0;
159 }
160 
161 void ReadPcap::pcapng_shb() {
162  if (DEBUG_VERBOSE > 0)
163  Log(satcat5::log::DEBUG, "ReadPcap::pcapng_shb");
164 
165  // Section Header Block (SHB), Section 4.1.
166  // Read the "block total length" and the "byte-order magic".
167  u32 len = m_file.read_u32();
168  u32 bom = m_file.read_u32();
169 
170  // Detect byte-order and reinterpret length accordingly.
171  m_mode_be = (bom == PCAPNG_MAGIC_BE);
172  if (!m_mode_be) len = __builtin_bswap32(len);
173 
174  // Discard the rest of this block.
175  if (len > 12) m_file.read_consume(len - 12);
176 }
177 
178 void ReadPcap::pcapng_idb() {
179  if (DEBUG_VERBOSE > 1)
180  Log(satcat5::log::DEBUG, "ReadPcap::pcapng_idb");
181 
182  // Interface Description Block (IDB), Section 4.2.
183  // Read block length and discard up to the Options field.
184  u32 blen = file_rd32();
185  m_file.read_consume(8);
186  // TODO: Filter by LinkType?
187 
188  // Read the concatenated options (Section 3.5).
189  u32 rdpos = 16;
190  while (rdpos + 8 < blen) {
191  // Read type and length.
192  u16 opt_typ = file_rd16();
193  u16 opt_len = file_rd16();
194  rdpos += 4;
195  // End of options? (opt_endofopt = 0)
196  if (opt_typ == 0) break;
197  // Parse selected options and ignore all others.
198  if (opt_typ == 13 && opt_len == 1) {
199  m_trim = m_file.read_u8(); // "if_fcslen"
200  m_file.read_consume(3);
201  rdpos += 4;
202  } else {
203  unsigned pad_len = opt_len + word_pad(opt_len);
204  m_file.read_consume(pad_len);
205  rdpos += pad_len;
206  }
207  }
208 
209  // Discard up to the start of the next block.
210  m_file.read_consume(blen - rdpos);
211 }
212 
213 void ReadPcap::pcapng_spb() {
214  if (DEBUG_VERBOSE > 1)
215  Log(satcat5::log::DEBUG, "ReadPcap::pcapng_spb");
216 
217  // Simple Packet Block (SPB), Section 4.4.
218  u32 blen = file_rd32(); // Block total length
219  u32 olen = file_rd32(); // Original packet length
220  u32 plen = blen - 16; // Size of packet data field
221 
222  // Take further action?
223  if (blen < 16 || plen <= m_trim || olen <= m_trim) {
224  // Abort on end-of-file or invalid length.
225  m_file.close();
226  } else if (olen <= plen && olen <= sizeof(m_buff)) {
227  // Copy normal packets to the working buffer.
228  m_file.read_bytes(olen, m_buff);
229  read_reset(olen - m_trim);
230  // Discard zero-pad and end-of-block footer.
231  m_file.read_consume(4 + plen - olen);
232  } else {
233  // Skip if truncated or larger than our working buffer.
234  m_file.read_consume(4 + plen);
235  }
236 }
237 
238 void ReadPcap::pcapng_epb() {
239  if (DEBUG_VERBOSE > 1)
240  Log(satcat5::log::DEBUG, "ReadPcap::pcapng_epb");
241 
242  // Enhanced Packet Block (SPB), Section 4.3.
243  // TODO: Support multi-interface captures and filter by interface ID?
244  u32 blen = file_rd32(); // Block total length
245  m_file.read_consume(12); // Discard interface ID and timestamp.
246  u32 clen = file_rd32(); // Captured packet length
247  u32 olen = file_rd32(); // Original packet length
248 
249  // Take further action?
250  if (clen <= m_trim || olen <= m_trim) {
251  // Abort on end-of-file or invalid length.
252  m_file.close();
253  } else if (olen <= clen && olen <= sizeof(m_buff)) {
254  // Copy normal packets to the working buffer.
255  m_file.read_bytes(olen, m_buff);
256  read_reset(olen - m_trim);
257  // Discard zero-pad, options, and end-of-block footer.
258  m_file.read_consume(blen - olen - 28);
259  } else {
260  // Skip if truncated or larger than our working buffer.
261  m_file.read_consume(blen - 28);
262  }
263 }
264 
265 void ReadPcap::pcapng_skip() {
266  if (DEBUG_VERBOSE > 1)
267  Log(satcat5::log::DEBUG, "ReadPcap::pcapng_skip");
268 
269  // Skip unknown blocks using core header, Section 3.1.
270  u32 blen = file_rd32(); // Block total length
271  if (blen > 8) m_file.read_consume(blen - 8);
272 }
273 
275  : satcat5::io::ArrayWrite(m_buff, sizeof(m_buff))
276  , m_file(0)
277  , m_pass(0)
278  , m_mode_ng(pcapng)
279  , m_interface_count(0)
280  , m_interface_next(0)
281 {
282  m_clock.set(posix_timer.gps());
283 }
284 
285 void WritePcap::open(const char* filename, u16 linktype) {
286  if (DEBUG_VERBOSE > 0)
287  Log(satcat5::log::DEBUG, "WritePcap::open");
288 
289  // Open the designated file.
290  m_file.open(filename);
291 
292  // Write the PCAP or PCAPNG header...
293  if (m_mode_ng) {
294  // Write PCAPNG-SHB block (Section 4.1).
295  m_file.write_u32(BLK_PCAPNG_SHB); // Block type
296  m_file.write_u32(32); // Block total length
297  m_file.write_u32(PCAPNG_MAGIC_BE); // Byte-Order Magic
298  m_file.write_u32(0x00010000); // Version 1.0
299  m_file.write_u64(-1ull); // Section length disabled
300  m_file.write_u32(0); // Options (none)
301  m_file.write_u32(32); // Block total length (again)
302  // Write PCAPNG-IDB block for the default capture interface.
303  m_interface_count = 0;
304  add_interface("Default", linktype);
305  } else {
306  // Write the legacy PCAP header (Section 4).
307  m_file.write_u32(BLK_PCAP_HDR1_BE); // Magic number
308  m_file.write_u32(0x00020004); // Version 2.4
309  m_file.write_u32(0); // Reserved
310  m_file.write_u32(0); // Reserved
311  m_file.write_u32(SATCAT5_PCAP_BUFFSIZE); // SnapLen
312  m_file.write_u16(0); // FCS not included
313  m_file.write_u16(linktype); // LinkType
314  }
315 }
316 
317 u32 WritePcap::add_interface(const char* name, u16 linktype) {
318  // Calculate length of block, including label.
319  unsigned block_len = 24, name_len = 0, name_pad = 0;
320  if (name) {
321  name_len = strlen(name);
322  name_pad = word_pad(name_len);
323  block_len += 4 + name_len + name_pad;
324  }
325  // Write PCAPNG-IDB block (Section 4.2).
326  m_file.write_u32(BLK_PCAPNG_IDB); // Block type
327  m_file.write_u32(block_len); // Block total length
328  m_file.write_u16(linktype); // LinkType
329  m_file.write_u16(0); // Reserved
330  m_file.write_u32(SATCAT5_PCAP_BUFFSIZE); // SnapLen
331  if (name_len) { // Add "if_name" option?
332  // Write type/length/value (Section 3.5)
333  m_file.write_u16(2); // Type = 2 (Figure 13)
334  m_file.write_u16(name_len); // Length = Variable
335  m_file.write_bytes(name_len, name); // Value
336  while (name_pad--) m_file.write_u8(0); // Zero-pad?
337  }
338  m_file.write_u32(0); // End of options
339  m_file.write_u32(block_len); // Block total length (again)
340  return m_interface_count++;
341 }
342 
344  // Timestamp is measured in microseconds since UNIX epoch.
345  constexpr u64 GPS2UNIX = 315964800000000ull;
346  u64 unix_usec = 1000 * m_clock.now() + GPS2UNIX;
347 
348  // Forward event to parent class and note frame length.
349  // (If overflow flag is set, original packet size is unknown.)
351  u32 clen = written_len(); // Captured length
352  u32 olen = ok ? clen : UINT32_MAX; // Original length
353 
354  if (DEBUG_VERBOSE > 1)
355  Log(satcat5::log::DEBUG, "WritePcap::write").write10(clen);
356 
357  // If passthrough mode is enabled, copy data now.
358  // Note: Packet is saved to disk even if recipient drops it.
359  if (m_pass && !ok) {
360  Log(satcat5::log::WARNING, "WritePcap: Passthrough dropped (oversize).");
361  } else if (m_pass) {
362  m_pass->write_bytes(written_len(), m_buff);
363  m_pass->write_finalize();
364  }
365 
366  // Write the buffered packet contents...
367  if (m_mode_ng) {
368  // Calculate packet length including zero-pad.
369  u32 plen = clen + word_pad(clen);
370  memset(m_buff + clen, 0, plen - clen);
371  // Write the PCAPNG-EPB block.
372  m_file.write_u32(BLK_PCAPNG_EPB); // Block type
373  m_file.write_u32(36 + plen); // Block total length
374  m_file.write_u32(m_interface_next); // Interface ID
375  m_file.write_u64(unix_usec); // Timestamp
376  m_file.write_u32(clen); // Captured packet length
377  m_file.write_u32(olen); // Original packet length
378  m_file.write_bytes(plen, m_buff); // Packet data
379  m_file.write_u32(0); // Options (none)
380  m_file.write_u32(36 + plen); // Block total length (again)
381  // Reset default interface-ID for the next packet.
382  m_interface_next = 0;
383  } else {
384  // Write the legacy PCAP packet record.
385  m_file.write_u32(unix_usec / 1000000ull); // Timestamp (sec)
386  m_file.write_u32(unix_usec % 1000000ull); // Timestamp (usec)
387  m_file.write_u32(clen); // Captured packet length
388  m_file.write_u32(olen); // Original packet length
389  m_file.write_bytes(clen, m_buff); // Packet data
390  }
391  return m_file.write_finalize();
392 }
393 
394 WritePcapInterface::WritePcapInterface(WritePcap* pcap, const char* label, u16 type)
395  : WriteableRedirect(pcap)
396  , m_pcap(pcap)
397  , m_id(pcap ? pcap->add_interface(label, type) : 0)
398 {
399  // Nothing else to initialize.
400 }
401 
403  if (!m_pcap) return false;
404  m_pcap->set_interface(m_id);
405  return m_pcap->write_finalize();
406 }
void set(s64 gps)
Set current GPS time.
Definition: datetime.cc:54
s64 now() const
Current time as milliseconds since GPS epoch.
Definition: datetime.h:193
Ephemeral Readable interface for a simple array.
Definition: io_readable.h:206
unsigned get_read_ready() const override
How many bytes can be read without blocking?
Definition: io_readable.cc:273
void read_reset(unsigned len)
Reset read position to the start of the backing array, and set the readable length to the specified v...
Definition: io_readable.cc:277
Ephemeral Writeable interface for a simple array.
Definition: io_writeable.h:127
unsigned written_len() const
Report total length after write_finalize() is called.
Definition: io_writeable.h:151
bool write_finalize() override
Mark end of frame and release temporary working data.
void open(const char *filename, unsigned len=0)
Open the specified file to read the next frame.
Definition: file_io.cc:103
bool read_bytes(unsigned nbytes, void *dst)
Read 0 or more bytes into a buffer.
Definition: file_io.cc:132
unsigned get_read_ready() const override
How many bytes can be read without blocking?
Definition: file_io.cc:127
bool read_consume(unsigned nbytes)
Read and discard 0 or more bytes.
Definition: file_io.cc:142
void write_bytes(unsigned nbytes, const void *src)
Write 0 or more bytes from a buffer.
Definition: file_io.cc:59
bool write_finalize() override
Mark end of frame and release temporary working data.
Definition: file_io.cc:64
void open(const char *filename)
Open the specified file.
Definition: file_io.cc:33
Read packet stream from a file.
Definition: file_pcap.h:43
void open(const char *filename)
Open the specified file.
Definition: file_pcap.cc:51
void read_finalize() override
Override end-of-packet handling.
Definition: file_pcap.cc:88
u8 read_u8()
One of many functions for reading integer/floating point values, see details.
Definition: io_readable.cc:44
Store packet stream to a file.
Definition: file_pcap.h:88
u32 add_interface(const char *name, u16 type=LINKTYPE_ETHERNET)
Write a new interface description block (PCAPNG only).
Definition: file_pcap.cc:317
void open(const char *filename, u16 type=LINKTYPE_ETHERNET)
Open the specified file, and optionally specify LinkType.
Definition: file_pcap.cc:285
void set_interface(u32 id)
Set interface ID number for the next call to write_finalize.
Definition: file_pcap.h:114
WritePcap(bool pcapng=true)
Create the capture object and set PCAP or PCAPNG mode.
Definition: file_pcap.cc:274
bool write_finalize() override
Override end-of-packet handling.
Definition: file_pcap.cc:343
Helper object for writing capture files with multiple sources.
Definition: file_pcap.h:143
WritePcapInterface(satcat5::io::WritePcap *pcap, const char *label, u16 type=LINKTYPE_ETHERNET)
Constructor adds a new interface to a packet-capture.
Definition: file_pcap.cc:394
bool write_finalize() override
Override end-of-packet handling.
Definition: file_pcap.cc:402
virtual void write_bytes(unsigned nbytes, const void *src)
Write 0 or more bytes from a buffer.
void write_u8(u8 data)
One of many functions for writing integer/floating point values, see details.
Definition: io_writeable.cc:20
virtual bool write_finalize()
Mark end of frame and release temporary working data.
Wrapper class for forwarding writes to another object.
Definition: io_writeable.h:218
The Log class creates and formats one log message.
Definition: log.h:195
Log & write10(s32 val)
Print integer as a decimal value with no leading zeros.
Definition: log.cc:261
Timer object using ctime::clock().
Definition: posix_utils.h:172
The TimeRef API provides access to a monotonic time-counter.
Definition: timeref.h:142
File I/O for packet capture files (PCAP, PCAPNG)
Diagnostic logging to UART and/or Ethernet ports.
Miscellaneous POSIX wrappers (e.g., heap allocation, log to console...)