# 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. Confirmed on air 2026-09-14. 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. - [x] Flash `obu-firmware` (done 2026-09-14 on COM3; flash backed up first to `Documents/micrOBU_workspace/firmware-backups/COM3-2026-09-14-before-secured-rx.bin`). - [x] Capture with the receiver (COM8) into `its-g5-receiver-firmware/recordings/`. - [x] `pcap_gn_tally.py` on capture_20260914_132126.pcap: our station sends SHB, port 2001, lifetime `0x05`, same as both bench stations. It was `0x83` in the August captures. - [x] Real-station CAMs/DENMs/SPATEM still reach the app (logcat: `handleCamUper`, `handleDenmUper`, `handleSpatUper` all decoding, 2026-09-14). - [x] Our own CAMs decode on air: 397 frames from station 999999 decode with asn1tools and re-encode byte-identically. - [ ] Confirm the CAM Pinger card's `tx fail` / oversize / CRC counters are 0 (needs a look at the phone; not readable from the PC). 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. Since 2026-09-14 the spare board on COM10 runs it as a bench beacon: - [x] Done 2026-09-14: flashed on COM10 and captured on COM8. All 72 CAMs from station 195936478 (0x0BADC0DE) decode with asn1tools and re-encode byte-identically, so the 4-bit yawRateConfidence is right on air. COM10 now runs this beacon rather than obu-firmware - reflash it if the spare is needed as an OBU again. ### 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 flashed to the production OBU on 2026-09-14. The remaining gap is signed traffic to receive: 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. - [x] Flash obu-firmware (done 2026-09-14, COM3). - [x] Unsigned bench traffic still decodes in the app, with messages now cut to their declared length (CAM, DENM and SPATEM all decoding in logcat after the flash). - [ ] 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`). NOT possible at this bench: the CiT One transmits unsigned (`ItsGnSecurity = 0`) and nothing else here signs. Needs a drive past real RSUs, the CiT One switched to signed mode if its API allows, or a replay firmware on a spare board that re-transmits the recorded signed frames. - [ ] The heartbeat's oversize counter still counts over-long messages. Not exercised at the bench: the SPATEMs here are ~340 bytes on air, far below the cap. ## 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-14 - [ ] **Commit the `live_capture.py` fix in the receiver repo.** Its console inserts a CR before every LF, which also hits every 0x0a byte of the binary pcap stream, shifting pcap record headers and frames. A 787 KB capture parsed cleanly for only 82 of ~2000 records, and DENMs showed up on nonsense BTP ports. `undo_crlf()` now reverses it on the raw stream before framing; afterwards a capture parsed to EOF and DENMs read as port 2002. This is the "occasional byte inserted mid-frame" in the older recordings, so **every capture taken before 2026-09-14 is truncated at its first corrupted record** - re-measure anything derived from them. The change is in the `its-g5-receiver-firmware` repo, uncommitted. - [ ] **Do not open COM3's console while the phone is attached.** Opening it toggles DTR/RTS on the CH343 and resets the OBU, which drops the phone's USB link and needs a manual Connect. ## 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).