# Host-side tests for obu-firmware **Status (2026-09-11):** the chain test, the capture replay and the fuzzer are written and pass; results under "Running". Toolchain: MSYS2 UCRT64 gcc, Option B below (chosen and installed 2026-09-11). `geonet.c`, `dot11p.c` and `gn_unwrap.c` include nothing but standard C headers, so they compile unmodified on a PC. That makes three things possible without a board: a TX -> RX round trip through our own code, replaying real captures through the RX parser, and fuzzing the parser that reads untrusted radio bytes. ESP-IDF only builds `main/` (and `components/`), so nothing under `test/` ever affects the firmware image. ## Layout ``` obu-firmware/ ├── main/ firmware sources; the tests compile these directly, never copies └── test/ ├── pcap_gn_tally.py GN header fields per station over .pcap captures (Python only) └── host/ ├── README.md this file ├── Makefile `make`: builds ../../main/{geonet,dot11p,gn_unwrap}.c + tests, runs them ├── test_chain.c geonet_wrap_shb -> dot11p_build_frame -> gn_unwrap_its, byte-checked ├── test_util.c/.h shared: guard page, crash report, pcap read/write ├── test_replay.c every recorded frame through gn_unwrap_its, results to a TSV ├── check_replay.py re-derives each result independently, then asn1tools on the messages ├── fuzz_gn_unwrap.c mutation fuzzer; each input ends against a no-access guard page, so │ an over-read faults (no ASan on MinGW) └── build/ compiler output; already ignored by the repo's `build/` rule ``` The repo's `vanetza/` folder is a gitignored reading copy only; it is not built. `check_replay.py` reads the IEEE 1609.2 ASN.1 modules from it (they carry no licence header, so they are not copied into `asn1/`). Without it, signed frames are still replayed but not checked independently. ## Running From PowerShell, with MSYS2 on PATH for the session (Option B step 3): ```powershell $env:PATH = "C:\msys64\ucrt64\bin;C:\msys64\usr\bin;$env:PATH" cd C:\Users\Ashin\AndroidStudioProjects\MicrOBU\obu-firmware\test\host make ``` `make` runs three things (`make chain`, `make replay`, `make fuzz` run one). A non-zero exit, or a `CRASH ... during: ` line (from the fuzzer, followed by the input as hex), is a failure. - **chain** (`test_chain.c`): frames built by the firmware's own TX code, checked byte by byte against EN 302 636-4-1 and parsed back. Covers the CAM layout (non-QoS and QoS), Source Position Vector edges, output-buffer bounds, a 512-byte payload through `main.c`'s buffer sizes, hand-built GeoBroadcast frames in all three shapes, signed frames in all three COER length forms plus a top-level unsecuredData, one-byte mutations that must be rejected or accepted, the Common Header's payload length as the message boundary, the 8-byte RX trailer, and every truncation length of each frame against the guard page. `pcap_gn_tally.py` then reads the frames back as a second parser; `build/test_chain.pcap` opens in Wireshark too. 2026-09-11: 1731 checks, 0 failed. - **replay** (`test_replay.c` + `check_replay.py`): every record in `its-g5-receiver-firmware/recordings/*.pcap` through `gn_unwrap_its`, each cut to `main.c`'s 800-byte capture buffer as on the board. `check_replay.py` re-derives each result on its own (its own GN/BTP parse; the security envelope decoded by asn1tools from the IEEE 1609.2 modules), compares record by record, then decodes and re-encodes every distinct message with asn1tools - a byte-identical re-encode is only possible when the message was cut at exactly the right byte. Needs `py -3.11` with asn1tools. 2026-09-11: 15 145 records, 15 131 accepted (10 831 CAM, 4 300 DENM; 157 of them signed); C and Python agree on every record; 11 043 of the 11 106 distinct messages re-encode byte-identically. The other 63 fail the same way with the old 8 trailing bytes put back, so the boundary is not the cause: 5 are our own CAMs from before the 2026-08-20 yawRateConfidence fix, 56 come from the CiT One and 1 from another station (see `TODO.md`), and 1 uses an extension asn1tools cannot re-encode. - **fuzz** (`fuzz_gn_unwrap.c`): random edits of every recorded frame, each run against the guard page; an over-read crashes, an accepted payload outside its input fails. Default 2 000 000 iterations (about 2 s); `make fuzz FUZZ_ITER=50000000 FUZZ_SEED=7` for a longer run. 2026-09-11: 50 000 000 iterations, no crash. Not covered: `main.c` (serial prefix parsing, queues) and `serial_link.c`, which need ESP-IDF; and the phone's encoder, whose bytes are opaque here (asn1tools and the app's golden test cover it). ## Option A (not used): WSL2 + Ubuntu 24.04 Kept as the fallback if AddressSanitizer or libFuzzer are ever needed; Option B has neither. 1. In **PowerShell as Administrator**: ```powershell wsl --install -d Ubuntu-24.04 ``` Reboot if it asks. Ubuntu then opens and asks for a Linux username and password (separate from the Windows account). Checked 2026-09-11: Hyper-V is already running on this PC, so no BIOS change should be needed. If the install says virtualization is disabled, enable Intel VT-x / AMD SVM in the BIOS. 2. Confirm it is WSL **2**: `wsl -l -v` should list `Ubuntu-24.04` with VERSION `2`. 3. Inside Ubuntu, the compilers: ```bash sudo apt update sudo apt install -y build-essential clang cmake ninja-build git pkg-config python3 ``` 4. Smoke test: the firmware sources compile on the host (expect no output): ```bash cd /mnt/c/Users/Ashin/AndroidStudioProjects/MicrOBU/obu-firmware/test/host cc -std=c11 -Wall -Wextra -fsyntax-only ../../main/geonet.c ../../main/dot11p.c ../../main/gn_unwrap.c ``` ## Option B (chosen): native Windows gcc via MSYS2 Enough for the round-trip test, the capture replay and the guard-page fuzzer. No libFuzzer and no AddressSanitizer with MinGW gcc, which is why the fuzzer uses a guard page instead. 1. In PowerShell: `winget install -e --id MSYS2.MSYS2` (installs to `C:\msys64`). 2. Open **MSYS2 UCRT64** from the Start menu and run `pacman -Syu`. If the window closes, reopen it and run `pacman -Syu` again. Then: ```bash pacman -S --needed mingw-w64-ucrt-x86_64-gcc make ``` 3. **`C:\msys64\ucrt64\bin` must be on PATH.** Calling `C:\msys64\ucrt64\bin\gcc.exe` by its full path alone exits 1 with no message, because gcc's compiler stages load their DLLs from that folder. `make` lives on the MSYS side, in `C:\msys64\usr\bin`. Either work inside the **MSYS2 UCRT64** shell, which has both, or put them on PATH for the current session: ```powershell $env:PATH = "C:\msys64\ucrt64\bin;C:\msys64\usr\bin;$env:PATH" # PowerShell ``` ```bash export PATH=/c/msys64/ucrt64/bin:/c/msys64/usr/bin:$PATH # Git Bash ``` Adding them to the user PATH permanently also works; it was deliberately not done by setup. 4. Smoke test (expect no output): ```powershell cd C:\Users\Ashin\AndroidStudioProjects\MicrOBU\obu-firmware\test\host gcc -std=c11 -Wall -Wextra -fsyntax-only ../../main/geonet.c ../../main/dot11p.c ../../main/gn_unwrap.c ``` Installed on this PC 2026-09-11: MSYS2 20260611, gcc 16.2.0 (UCRT64), GNU Make 4.4.1. All three firmware sources compile with `-std=c11 -O2 -Wall -Wextra -Wpedantic` and no warnings. ## Line endings The repo runs with `core.autocrlf=true`, so Windows checkouts have CRLF line endings. C compilers don't care; shell scripts run from WSL do (`bash: $'\r': command not found`). When the first `.sh` file lands here, add `*.sh text eol=lf` to a root `.gitattributes` (none exists yet).