# Sniffer board and capture tooling How to put an ESP32-C5 on the ITS-G5 channel as a passive sniffer, pull its captures onto this PC, and check what is on air. The sniffer firmware itself is the third-party `its-g5-receiver-firmware` checkout beside this repo; only the tooling and these notes are ours. | File | What it does | |---|---| | `live_capture.py` | Streams the device's captures into a growing `.pcap` while it runs. The usual choice. | | `dump_pcap.py` | Pulls one capture out of the device's in-memory buffer after the fact. | | `../obu-firmware/test/pcap_gn_tally.py` | Tallies GeoNetworking headers per station over a `.pcap`. | One-time: `pip install pyserial` (present in Python 3.11 on the bench PC, so `py -3.11` works). ## Which port The sniffer firmware's console, and with it the pcap stream, goes out **UART0** - the board's USB-bridge port (a CH343, its own COM number), not the native USB-C port. A board with only one USB-C port cannot be used as a sniffer for this reason. On the bench this has been COM5 and, after a re-enumeration, COM8. ## Live capture (preferred) ```powershell cd capture py -3.11 live_capture.py COM8 ``` It writes `recordings/capture_.pcap` next to itself, flushing after every packet, so the file can be read while it grows. Stop it with Ctrl+C. Use `-o ` to write elsewhere; `recordings/` is gitignored, since captures are large and are data rather than source. Captures taken before 2026-09-14 are still in `its-g5-receiver-firmware/recordings/`; the host tests read both directories. ### The CR insertion, and why captures used to be corrupt ESP-IDF's newlib console converts LF to CRLF on its way out, and that applies to every `0x0a` byte of the **binary** pcap stream, not only to log text. Each inserted CR shifts everything after it, so pcap record headers and captured frames alike come out corrupt, and the file stops being parseable at the first occurrence. Measured on 2026-09-14: a 787 KB capture parsed cleanly for only 82 of about 2000 records, and DENMs appeared on nonsense BTP ports because their payloads contain `0x0a` often. `undo_crlf()` in `live_capture.py` reverses it on the raw stream before any framing, which is exact; afterwards a capture parsed to EOF and DENMs read as port 2002 again. **Captures taken before 2026-09-14 are truncated at their first corrupted record.** Anything measured from them is worth re-checking. `dump_pcap.py` reads the same console and has not been given the same treatment yet. ## Checking a capture ```powershell py -3.11 ..\obu-firmware\test\pcap_gn_tally.py recordings\capture_.pcap ``` One row per station, packet type, BTP port and GN lifetime. For the messages themselves, decode the payloads with `asn1tools` against the modules in `../asn1/` and re-encode them: identical bytes mean the message was read exactly, wrong bytes mean it was not. `obu-firmware/test/check_replay.py` does this over a whole capture. ## Flashing the sniffer firmware From the receiver checkout, with its **pinned** ESP-IDF (not the global 5.5.4 install): ```powershell cd its-g5-receiver-firmware git submodule update --init --recursive .\esp-idf\install.bat Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass .\esp-idf\export.ps1 idf.py set-target esp32c5 idf.py -p COM8 -b 921600 flash ``` Its `sdkconfig` for a bare board (nothing wired) needs SPI Ethernet off, and the pcap destination set to Memory, both under `idf.py menuconfig`. Wired variants have ready-made configs in that checkout: `sdkconfig.proto-w5500`, `sdkconfig.proto-enc28j60`, `sdkconfig.proto-spi-eppp`. To reflash a board that already has a built image, without a toolchain terminal: ```powershell cd its-g5-receiver-firmware\build C:\Espressif\python_env\idf5.5_py3.11_env\Scripts\python.exe -m esptool --chip esp32c5 -p COM8 -b 921600 write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB 0x2000 bootloader/bootloader.bin 0x8000 partition_table/partition-table.bin 0x1e000 ota_data_initial.bin 0x20000 its-g5-receiver-firmware.bin ``` ## Pulling a capture after the fact Only for the Memory destination, and the buffer is small (`SNIFFER_PCAP_MEMORY_SIZE`, 4096 bytes by default) - a smoke test, not a session. In the device console (`idf.py -p COM8 monitor`, exit with Ctrl+T then Ctrl+X): ``` sniffer -P sniffer --stop ``` Then, with the port free: ```powershell py -3.11 dump_pcap.py COM8 ```