# TODO Engineering to-do list. The reviewer-facing open items live in `docs/01-requirements-traceability.md` ("Open items"); this file is the working list behind them. ## Waiting on hardware ### Over-the-air check of the GN lifetime fix (added 2026-09-11) `geonet.c` now writes GN lifetime `0x05` (1 s) instead of `0x83`, which decoded to 3200 s. Changed in both `obu-firmware` and `obu-cam-transmistter`. Both still build (IDF 6.1 / 5.5.4), and the compiled `geonet_wrap_shb` stores the new byte, but it has not been seen on air yet. Nothing else reads this byte (`gn_unwrap.c` ignores it, the app never sees GN headers), so the app does not need updating alongside the firmware. Needs: the phone with the app, the OBU ESP32-C5, and a **second** ESP32-C5 running `its-g5-receiver-firmware` to capture with. - [ ] Flash `obu-firmware` (see `obu-firmware/FLASHING.md`). - [ ] Connect the phone, let it send CAMs, and confirm the CAM Pinger's `tx fail` counter stays 0. - [ ] Capture with the receiver into `its-g5-receiver-firmware/recordings/`. - [ ] Run `python obu-firmware/test/pcap_gn_tally.py its-g5-receiver-firmware/recordings/.pcap`. The rows for the phone's pseudonym MACs must show SHB, port 2001, lifetime `0x05`, exactly like every other station's CAMs. - [ ] While the phone is connected: real-station CAMs/DENMs still reach the app (RX path unchanged). Partial check possible with one board and no phone: flash it, `idf.py -p COMx monitor`, and look for `OCB @ 5900 MHz - TX/RX armed`. That proves the new build boots and brings the radio up, not that it transmits correctly. ### obu-cam-transmistter yawRateConfidence fix (added 2026-09-11) Its `cam.c` (compiled into that firmware) wrote `yawRateConfidence` as 3 bits / 7 instead of 4 bits / unavailable(8), the bug the app fixed on 2026-08-20. Fixed in it and in obu-firmware's reference copy; asn1tools now decodes the CAM and re-encodes it byte-identically, and it builds on IDF 5.5.4. No board runs this firmware right now (the production OBU runs obu-firmware), so this only matters if it is flashed again: - [ ] After flashing it: capture, run `pcap_gn_tally.py`, and decode the CAM payload with asn1tools (`py -3.11`, modules in `asn1/`). ### Signed-message reception and exact payloads (added 2026-09-11) obu-firmware's `gn_unwrap.c` now unwraps TS 103 097 signed packets (signature not verified, reported as V2X_RX flags bit1) and cuts every message to the length its header declares, dropping the 8 bytes the chip's RX appends to each frame, which were forwarded to the phone until now. Verified on the host (`obu-firmware/test/host`: chain, replay of all recordings against asn1tools, 50M-iteration fuzz) and built on IDF 6.1, but not flashed: the production OBU still runs the 2026-09-10 build. Needs the OBU with this build, the phone, and signed traffic - real vehicles or RSUs, since the bench CiT One sends unsigned. A second ESP32 running the receiver firmware is optional, but shows what was on air at the time. - [ ] Flash obu-firmware (this also carries the GN lifetime fix above). - [ ] Near signed traffic: signed CAMs/DENMs appear in the app, and a simultaneous capture shows them on air (`pcap_gn_tally.py` lists them as `secured`). - [ ] Unsigned bench traffic still decodes in the app as before (messages now arrive 8 bytes shorter). - [ ] The heartbeat's oversize counter still counts over-long messages (e.g. road SPATEMs). ## Set up host testing - [x] Install MSYS2 UCRT64 gcc (done 2026-09-11: gcc 16.2.0, GNU Make 4.4.1; chosen over WSL, vanetza is not going to be built). Setup and the PATH gotcha: `obu-firmware/test/host/README.md`. - [x] Host round-trip test `obu-firmware/test/host/test_chain.c` (`geonet_wrap_shb` -> `dot11p_build_frame` -> `gn_unwrap_its`, byte-checked against the standard). Done 2026-09-11: 491 checks, 0 failed. Run `make` in that folder before flashing any firmware fix. - [x] Replay of the recorded captures (`test_replay.c` + `check_replay.py`, independent asn1tools check). Done 2026-09-11: C and Python agree on all 15 145 records. - [x] Mutation fuzzer `fuzz_gn_unwrap.c`, inputs against a no-access guard page. Done 2026-09-11: 50 000 000 iterations, no crash. `make` runs a 2 000 000-iteration pass every time. ## Firmware ideas from the vanetza review (2026-09-11, not started) Suggested order after the host tests exist: - [x] **Read secured packets (GN NextHeader=2) without verifying them.** Done 2026-09-11 in `gn_unwrap.c`, host-verified; flagged to the phone as V2X_RX flags bit1. On-air check under "Waiting on hardware". - [ ] **Forward the full GeoBroadcast area**: shape (circle/rectangle/ellipse), DistanceB, angle, appended to the V2X_RX prefix behind a capability bit. Port vanetza's `geonet/areas.cpp` `inside_or_at_border` to the app, which currently treats every area as a circle. - [ ] **RX filtering before the serial link**: duplicate detection for GBC (last 8 sequence numbers per source, as vanetza does), drop our own frames, reject GN version != 1. - [ ] **Read the DCC-MCO field** (the 4 "reserved" bytes of an SHB header): neighbours' channel busy ratio for free. - [ ] **Minimum TX gap in firmware** as a DCC safety net (vanetza reactive table: 60 ms relaxed ... 460 ms restrictive), with a CBR estimate in the heartbeat. - [ ] **Generic V2X_TX message** (BTP port, SHB/GBC, traffic class, lifetime, area) so the phone can send DENM and VAM without reflashing. Consider QoS Data frames: vanetza's Cohda receive path drops non-QoS ones. Dropped: building vanetza as a GN/BTP oracle. Real captures (`pcap_gn_tally.py`), the host round-trip test and `asn1tools` for UPER cover what it would have checked. ## Follow-ups found 2026-09-11 - [ ] **App: show the signed flag.** `V2xRxFrame.parse` in `SerialFrame.kt` only reads bit0 of the flags byte; read bit1 (signed, not verified) and show it where messages are listed. - [ ] **Messages that do not decode with asn1tools.** In the recordings, 56 from the CiT One (`aa:f8:76:7d:bd:ad`: 54 CAMs of 245 bytes, 2 DENMs of 402 bytes) and one 218-byte CAM from `6e:94:03:1b:05:26` fail against `cam_1_4_1`/`denm_1_3_1` + `cdd_1_3_1_1`, with or without the old trailing bytes. A newer module version on the sender, or a sender bug; check what the app's decoders make of them (`check_replay.py` lists the records).