# 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, kept beside this one, gitignored). 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.