Files
MicrOBU/TODO.md
T
Ashin Walpola 73d4477f1e Move the capture tooling into the repo
live_capture.py and dump_pcap.py were untracked files inside the third-party
its-g5-receiver-firmware checkout, so the tools every on-air measurement depends
on were versioned nowhere and would vanish with a fresh clone of that project.
They are ours rather than that project's, so they now live in capture/, with the
setup notes rewritten as its README: which port the sniffer speaks on and why,
how to capture, how to check a capture, and how to flash the sniffer board.

live_capture.py carries the fix made on 2026-09-14. The sniffer's 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, so every inserted CR shifts the rest
of the stream. A 787 KB capture parsed cleanly for only 82 of about 2000
records, and DENMs turned up on nonsense BTP ports because their payloads carry
0x0a often. undo_crlf() reverses it on the raw stream before any framing, which
is exact; afterwards a capture parsed to EOF and DENMs read as port 2002.
Captures taken before that date are truncated at their first corrupted record,
so anything measured from them is worth re-checking.

capture/recordings/ is gitignored, since captures are data rather than source.
The host tests now read both that directory and the older one in the receiver
checkout, so no capture has to be moved while it is being written.

dump_pcap.py reads the same console and still needs the same treatment; that is
recorded in TODO.md.
2026-09-14 13:38:38 +02:00

8.0 KiB

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.

  • 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).
  • Capture with the receiver (COM8) into its-g5-receiver-firmware/recordings/.
  • 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.
  • Real-station CAMs/DENMs/SPATEM still reach the app (logcat: handleCamUper, handleDenmUper, handleSpatUper all decoding, 2026-09-14).
  • 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:

  • 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.

  • Flash obu-firmware (done 2026-09-14, COM3).
  • 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

  • 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.
  • 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.
  • 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.
  • 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:

  • 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

  • Capture tooling moved into this repo (capture/), with the CR-insertion fix. The sniffer's 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() reverses it on the raw stream before framing; afterwards a capture parsed to EOF and DENMs read as port 2002. Every capture taken before 2026-09-14 is truncated at its first corrupted record - re-measure anything derived from them.
  • capture/dump_pcap.py reads the same console and still needs the same treatment.
  • 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).