Files
MicrOBU/docs/06-signed-its-vam-ble.md
T

142 lines
8.9 KiB
Markdown
Raw Normal View History

# 06 – Signed ITS messages, VAM and the BLE link (2026-09-23)
What changed when the ESP32-C5 OBU moved onto the colleague's vanetza-idf station, how the pieces
fit together, how it was verified, and what is still open. Hardware checks still to do are in
`TODO.md` ("Signed-TX firmware ...").
## Summary
- **Signing lives on the ESP32-C5.** The authorization ticket's private key is in the board's NVS;
vanetza-idf's security entity signs every secured message there (IEEE 1609.2 / ETSI TS 103 097,
ECDSA NIST P-256). The phone never holds a key and never signs.
- **The phone decides what to send and when.** It builds CAM or VAM (UPER) from its own GNSS/IMU,
hands each message to the board with the flag "signed" or "unsigned", and keeps the board's clock
and position current.
- **Two links, one protocol.** USB-C (native USB Serial/JTAG) or Bluetooth LE, chosen in Settings.
Both carry the colleague's station-link protocol v1 plus one MicrOBU extension for reception.
- **Reception is unchanged for the app.** Every ITS message heard on air reaches the phone, signed
or not, verifiable or not, exactly as with the previous firmware.
- **Demo PKI, not the EU trust list.** Signed messages carry a throwaway chain. Receivers that
verify against the EU trust list drop them; unsigned sending remains available.
## Who does what
| | Phone (app) | ESP32-C5 (obu-firmware) |
|---|---|---|
| CAM / VAM content and UPER encoding | yes | – |
| Send cadence (CAM 1 Hz baseline; VAM per TS 103 300-3 clause 6.4) | yes | – |
| Pseudonym (station ID + MAC, rotated together) | yes | uses the MAC it is configured with |
| Time and position (PoTi) | yes, per new GNSS fix | keeps an ITS clock from it |
| GeoNetworking + BTP headers | – | yes |
| Signing (TS 103 097), certificate handling | – | yes |
| Credentials | ships the demo bundle, provisions it once | stores it in NVS |
| 802.11p radio at 5 900 MHz | – | yes |
| Reception: unwrap GN/BTP, forward | decodes CAM / DENM / SPATEM | yes (all frames) |
## Firmware (obu-firmware)
obu-firmware is now a port of `microbu-esp32c5/firmware`: the colleague's repository, of which
this repository keeps a copy in `microbu-esp32c5/` (their commit cf4b99f plus our V2X2MAP signature
verification; nothing is pushed to their repository). vanetza-idf is taken from
`microbu-esp32c5/external/vanetza-idf`. It builds
with **ESP-IDF 6.0.2 only**: the raw-TX path uses private Wi-Fi driver structures that vanetza-idf
pins to that version. The previous C firmware (IDF 6.1) is backed up as a full flash image in
`firmware-backups/` (gitignored, restore command in its README.txt); its sources stay on disk,
unbuilt. Setup and flashing: `obu-firmware/FLASHING.md`. Design notes and every deviation from the
colleague's code (`MicrOBU:` in the sources): `obu-firmware/NOTES.md`.
Main changes against the colleague's firmware:
- **Raw receive path kept.** vanetza-idf decapsulates strictly and would drop unsigned frames (the
bench car) and anything not signed under the demo root (every RSU). Every captured frame also
goes through the previous firmware's `gn_unwrap.c` and reaches the phone as link opcode
`V2X_RX` (0x85), whose body is the old `SERIAL_MSG_V2X_RX` payload.
- **Unsigned sending kept.** The colleague's station refuses unsecured requests; here they go out
with the previous firmware's `geonet.c` header.
- **Console on UART0** (CH343, COM3 on the bench); the native USB port carries only link frames.
- **BLE pauses advertising while USB is in use** (BLE and ITS-G5 share one RF front end).
- **NVS 80 KB instead of 24 KB**, app at 0x20000. At 24 KB the BLE bond could not be stored and the
phone had to pair on every connection.
- Fixes found on the bench: radio queue drained before the first PoTi (no RX, ~177 queue drops
before); station loop waited 0 ticks at 100 Hz and starved the idle task; 2.4 KB RX buffer moved
off the Wi-Fi task stack; no silent truncation of BLE notifications; ATT MTU 517; serial writes
skipped when no USB host is present.
## Link protocol
Station-link v1 (`microbu-esp32c5/station-link/README.md`): `[opcode][flags][sequence LE][body]`,
little-endian, at most 512 octets. Over USB each message is one `0xAA55` frame of type `0x10`
(the old framing and CRC). Over BLE each message is one GATT value on service
`0000C175-BA5E-4C17-8000-00805F9B34FB` (the README describes a different, Nordic-UART layout; the
firmware is what counts).
| Direction | Message | Used for |
|---|---|---|
| phone → board | `STATION_CONFIGURE` | pseudonym MAC, station type, channel 180, 20 dBm; starts the radio |
| phone → board | `CREDENTIALS_PROVISION` | the demo bundle, once, when the board reports no ticket |
| phone → board | `POTI_UPDATE` | position and ITS time, once per new GNSS fix |
| phone → board | `BTP_DATA_REQUEST` | one CAM (port 2001, psid 36) or VAM (port 2018, psid 638), signed or not |
| board → phone | `RESULT` | answer to a request |
| board → phone | `STATUS` | every second: counters, tickets, signed/refused counts |
| board → phone | `V2X_RX` (0x85, MicrOBU) | every ITS message heard on air |
The app side is `Esp32Link.kt` (session), `StationLink.kt` (codec, pinned by unit tests to bytes
from the colleague's Python implementation), `UsbSerialTransport.kt` and `BleLinkTransport.kt`.
A board still on the previous firmware is recognised by its old heartbeat and keeps working for
CAM over USB.
## Redundancy and recovery
| Situation | What notices | What happens |
|---|---|---|
| USB link dead (board hung, cable) | app watchdog: no frame for 3.5 s (the board's `STATUS` comes every second) | link marked ERROR on the card |
| USB unplugged | Android detach broadcast | port closed; Connect again after re-plugging |
| BLE link lost | BLE supervision timeout (4 s) | app reconnects by itself: 1 s after a drop, then backing off to 30 s if attempts fail |
| Board reset / power cycle | first `STATUS` says "not configured" | app reconfigures (and re-provisions if needed) without user action |
| App closed and reopened | new session | app configures the board again; BLE reconnects with the stored bond, no passkey (confirmed) |
| Phone clock or GNSS time jumping | app tracks the board's clock | PoTi never moves it backwards (except a real correction of ≥ 60 s), so the board does not restart its stack |
| Board firmware wedged | ESP task watchdog (30 s, logs on COM3) | the phone sees it as a dead link (above) |
## App changes
- Settings > Connection > ESP32-C5: **link** USB-C / Bluetooth, **transmit** CAM / VAM, **sign
outgoing messages** (on by default). The connection card, top bar and dashboard show the link in
use, the pairing passkey when needed, and signing counters.
- VAM encoder (`VamUperCodec.kt`, TS 103 300-3 V2.3.1, checked against asn1tools) and the VAM
generation rules (`VamGenerationRules.kt`).
- `GnssTimeSource` keeps the last measured phone-clock error while GNSS time drops out indoors. The
bench phone's clock was 14 minutes fast; falling back to it made every transmitted timestamp
jump by 14 minutes.
- Bluetooth permissions (Android 12+) requested at start-up.
## Credentials (demo PKI)
`app/src/main/assets/demo-chain.vcr`, generated 2026-09-23 with the colleague's `vidf_issue`: root
`6E7D0374FB021901` → AA `B3312F29844299E0` → AT `B80B49387A4C12EB` (two years; psid 36 SSP `010000`,
psid 638 SSP `01`). It is throwaway and not EU-registered; its private key ships with the app on
purpose. The colleague's own demo chain only grants psid 638 and cannot sign CAMs.
## Verification
- **Unit tests** (103): VAM bytes against asn1tools, station-link messages against the colleague's
Python encoder, VAM generation rules.
- **Signatures on air**: `obu-firmware/test/verify_signed_pcap.py` checks a pcap with asn1tools
and OpenSSL, sharing no code with the firmware. Pinger capture of 2026-09-23: 12/12 signed CAMs,
psid 36, signer the demo AT, all signatures valid, chain valid.
- **Third-party stack**: the CiT One receives the signed CAMs (~1 Hz on `v2x/rx/cam`), so its
stack unwraps our envelope. Its MQTT interface exposes no security information, and it forwards
unsigned and unknown-root messages alike, so it cannot tell whether it verified the signature.
- **V2X2MAP (COM10)**: now the colleague's v2x2map 0.3.0 bridge from source with a new
`verify.py` and `--trust demo-chain.vcr`; it shows "signature verified", "SIGNATURE INVALID" or
"not verified" per packet. Signed CAMs and signed VAMs verified live; a one-bit change in a
signed CAM comes out invalid. Launcher: `micrOBU_workspace/v2x-obu-esp32c5/start-v2x2map-signed.bat`.
## Open
- BLE/ITS-G5 coexistence is not measured: does an active BLE connection cost 5.9 GHz reception?
- `time_regression` standing still indoors for several minutes, and board reset recovery over BLE,
after the last fixes.
- The signature's generationTime follows the app's UTC-based `ItsTime`; the colleague's VBS adds
the 5 leap seconds (TAI). Which is right is the open question in `ItsTime.kt`.
- Real EU PKI enrolment/authorisation (TS 102 941) instead of the demo chain.