Signed packets. A GeoNetworking Basic Header NextHeader of 2 means a TS 103 097 (IEEE 1609.2) envelope follows, with the Common Header inside it. gn_unwrap_its rejected all of these, and most real traffic is signed: the 2026-08-17 capture holds 157 signed frames from 15 source MACs against 2 unsecured stations. It now opens a COER-encoded signedData, or a bare unsecuredData, and parses the inner packet as before. The inner packet comes first inside tbsData, so the certificate and signature are never parsed, and the signature is not verified - the firmware has no trust store. Such messages reach the phone with the new V2X_RX flags bit1, signed but not verified. The app reads only bit0 and is unaffected until it learns the flag. Encrypted payloads, nested signing and the legacy v1.2.1 envelope are still rejected. All 157 recorded signed frames have the layout this reads, in all three COER length forms, and asn1tools decodes every envelope to the same inner packet. Payload bounds. Every frame recorded through the ESP32-C5's promiscuous RX, about 15 000 of them, ends in 8 bytes that are not part of the 802.11 frame and not a valid FCS. obu-firmware reads frames through the same API and took the rest of the frame as the message, so it forwarded those 8 bytes to the phone after every message. UPER decoders stop where the message ends, so nothing visibly broke, but the bytes cost serial bandwidth and 8 bytes of the DENM's headroom, and they stayed attached wherever raw payloads were stored or passed on. The payload is now exactly what the Common Header's payload-length field declares, which is also what separates a signed message from its signature. A frame longer than main.c's 800-byte capture buffer is now reported as truncated instead of being forwarded cut off, and counted as an oversize drop through the new serial_link_note_oversize_drop, as it was when the cut-off frame failed serial_link's size check. Host tests in obu-firmware/test/host build the firmware sources unmodified with MSYS2 gcc; `make` runs all three. - test_chain: frames from the firmware's TX code checked byte by byte against EN 302 636-4-1 and parsed back, including hand-built signed frames, the payload-length rule, the RX trailer, and every truncation length against a no-access guard page. 1731 checks, 0 failures. - test_replay and check_replay.py: all 15 145 recorded frames through gn_unwrap_its, cut to 800 bytes as on the board, and re-derived independently in Python with the envelope decoded by asn1tools. They agree on every record; 15 131 accepted, 157 of them signed. 11 043 of the 11 106 distinct messages re-encode byte-identically. The other 63 fail the same way with the old 8 bytes put back, so the boundary is not the cause: 5 are our own CAMs from before the 2026-08-20 yawRateConfidence fix, and the rest, from other stations, are a follow-up in TODO.md. - fuzz_gn_unwrap: random edits of every recorded frame, each run against the guard page. 50 000 000 iterations, no crash. obu-firmware/test/pcap_gn_tally.py tallies GeoNetworking header fields per station over captures; it is how the other stations' lifetimes were measured. TODO.md collects what is still open, including the on-air check for this change: it builds on IDF 6.1 but has not been flashed.
104 lines
6.3 KiB
Markdown
104 lines
6.3 KiB
Markdown
# 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/<capture>.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).
|