obu-firmware builds against vanetza-idf from microbu-esp32c5/external, but that tree was gitignored, so a clone of this repository could not build the firmware it ships. It is now committed here as ordinary files in its own folder, microbu-esp32c5/: the colleague's commit cf4b99f plus the V2X2MAP bridge's signature verification (--trust) used on the bench. Nothing is fetched from or pushed to the colleague's repository; this repository and its remotes carry everything. The folder's own .gitignore keeps build output, downloaded components and private key material out, as it did there; the committed file set is identical to that repository's tracked files. The ESP32-C5 is still flashed from obu-firmware/, which only takes vanetza-idf from microbu-esp32c5/, so the two stay separate folders. FLASHING.md says how to take a newer version of the colleague's tree (copy it over the folder, rebuild, test, commit).
462 lines
25 KiB
Markdown
462 lines
25 KiB
Markdown
# VRU ITS-S station-internal link (phone ⇄ micrOBU)
|
||
|
||
Project interface **IF-PROJ-NF-BLE-001** made concrete, plus its DEC-VBS-004
|
||
companions. One VRU ITS-S (ETSI EN 302 665 clause 4.5.1) is split over two
|
||
physical units at the NF-SAP (DEC-VBS-003):
|
||
|
||
| Unit | Hosts |
|
||
|---|---|
|
||
| Phone (app) | VRU basic service, VAM encode/decode, HMI, PoTi, credential manager (TS 102 941 client) |
|
||
| micrOBU (ESP32-C5, this firmware) | BTP-B, GeoNetworking SHB/GBC, SN-SAP security entity (TS 103 097 signing, ticket pool, identifier change, SN-DECAP), ITS-G5 access |
|
||
|
||
Everything that crosses the link is a standards-defined primitive
|
||
(BTP-DATA.request/indication of TS 102 723-11 / TS 103 836-5-1 Annex A; the
|
||
PoTi data set of EN 302 890-2 Tables 2 and 3; the SF-SAP identifier-change
|
||
primitives of TS 102 723-9 Tables 10 to 21; the legacy/reference MF-SET parameter
|
||
semantics of TS 103 175 Table 11) or
|
||
one of three project-defined management messages (station configuration,
|
||
credential provisioning with a `VCR1` bundle, status). No SN-, MN- or MI-SAP
|
||
primitive crosses the link (ACT-015).
|
||
|
||
The control/reference transport is the micrOBU's wired native USB Serial/JTAG
|
||
port. Encrypted BLE GATT is an experimental alternative and shall not replace
|
||
the wired architecture until ACT-021's controlled coexistence experiment shows
|
||
that concurrent BLE does not disturb C5 PHY/CCA observation, ITS-G5 RF behavior,
|
||
timing, reliability, or power measurements. The message layer below is transport
|
||
independent. Nothing here is an ETSI on-air format or a conformance claim for
|
||
the phone-to-micrOBU link.
|
||
|
||
Implementations: firmware `firmware/main/link_protocol.*`,
|
||
`ble_framing.*` and `ble_link.*` (C++); phone-side protocol module
|
||
`microbuapp/station-link/` and Android central
|
||
`microbuapp/app/src/main/java/com/hawhamburg/micr0bu/data/transport/BleStationLinkTransport.kt`
|
||
(Kotlin); bench reference `station-link/python/microbu_link/`
|
||
(Python, with serial and BLE adapters used by the phone emulator, TTCN SUT
|
||
bridge and unit tests). Golden
|
||
vectors and fragmentation are checked across the Python, C++ and Kotlin
|
||
implementations.
|
||
|
||
## Message layer (version 1)
|
||
|
||
A reassembled message is at most **512 octets**. All integers
|
||
are **little-endian** (the app's existing serial framing convention);
|
||
ETSI-encoded payloads inside (UPER VAMs, COER certificates, GeoNetworking
|
||
octets) are opaque and keep their own byte order.
|
||
|
||
```
|
||
octet 0 opcode
|
||
octet 1 flags bit 0: FRAGMENT_FIRST, bit 1: FRAGMENT_MORE (see Fragmentation); other bits 0
|
||
octets 2-3 sequence u16, see below
|
||
octets 4- body opcode specific
|
||
```
|
||
|
||
Sequence: every request the phone sends carries its own counter; the micrOBU
|
||
answers with a `RESULT` (or the typed reply named below) carrying the **same
|
||
sequence**. Messages the micrOBU originates (indications, events, status) carry the
|
||
micrOBU's own counter and are never answered, except `SF_IDCHANGE_EVENT` which the
|
||
phone answers with `SF_IDCHANGE_EVENT_RESPONSE` echoing the event's sequence.
|
||
|
||
### Phone → micrOBU
|
||
|
||
| Opcode | Name | Reply | Standard basis |
|
||
|---|---|---|---|
|
||
| 0x01 | STATION_CONFIGURE | RESULT + station info | project management (MIB values the micrOBU cannot know) |
|
||
| 0x02 | POTI_UPDATE | none (RESULT only on error) | EN 302 890-2 Table 2 (NF-SAP) and Table 3 (SF-SAP) minimum data sets, plus speed/heading for the GN position vector |
|
||
| 0x03 | BTP_DATA_REQUEST | RESULT | TS 102 723-11 clause 5 / TS 103 836-5-1 Annex A.2; VBS PCI TS 103 300-3 Table 4 |
|
||
| 0x04 | CREDENTIALS_PROVISION | RESULT + apply report (last segment) | DEC-VBS-004: one `VCR1` bundle (vanetza_idf/credentials.hpp) |
|
||
| 0x05 | CREDENTIALS_ERASE | RESULT | project |
|
||
| 0x06 | SF_IDCHANGE_SUBSCRIBE | RESULT + subscription handle | TS 102 723-9 Tables 10/11 |
|
||
| 0x07 | SF_IDCHANGE_UNSUBSCRIBE | RESULT | Tables 14/15 |
|
||
| 0x08 | SF_IDCHANGE_EVENT_RESPONSE | none | Table 13 |
|
||
| 0x09 | SF_IDCHANGE_TRIGGER | RESULT | Tables 16/17 |
|
||
| 0x0A | SF_ID_LOCK | RESULT + lock handle | Tables 18/19 |
|
||
| 0x0B | SF_ID_UNLOCK | RESULT | Tables 20/21 |
|
||
| 0x0C | STATUS_REQUEST | STATUS | project |
|
||
|
||
### micrOBU → phone
|
||
|
||
| Opcode | Name | Standard basis |
|
||
|---|---|---|
|
||
| 0x80 | RESULT | project (the library's `Result` plus link codes) |
|
||
| 0x81 | BTP_DATA_INDICATION | TS 103 836-5-1 Annex A.3 with the GN-DATA.indication parameters it preserves |
|
||
| 0x82 | SF_IDCHANGE_EVENT | TS 102 723-9 Table 12 |
|
||
| 0x83 | MF_SET_REQUEST | TS 103 175 Table 11 (DCC F-Params of Table 13) |
|
||
| 0x84 | STATUS | project |
|
||
|
||
### Bodies
|
||
|
||
**RESULT (0x80)**: `u8 result`, `u8 detail_length`, `detail[]`. `result` is
|
||
`vanetza_idf::Result` (0 accepted, 1 invalid_argument, 2 unsupported, 3
|
||
wrong_entry_point, 4 security_unavailable, 5 resource_limit, 6 rejected, 7
|
||
time_regression, 8 identity_change_pending) or a link code: 0x10
|
||
unknown_opcode, 0x11 malformed, 0x12 not_configured (no STATION_CONFIGURE
|
||
yet), 0x13 busy, 0x14 no_credentials. `detail` depends on the request:
|
||
|
||
| Request | detail |
|
||
|---|---|
|
||
| STATION_CONFIGURE | `u8[8] gn_address` (as on the wire, TS 103 836-4-1 clause 6.3), `u8[8] identifier` (HashedId8 of the current authorization ticket, zeros when unsecured), `u8 credentials_loaded`, `u8 tickets` |
|
||
| CREDENTIALS_PROVISION (last segment) | `u8 roots`, `u8 authorities`, `u8 tickets` accepted (ApplyReport) |
|
||
| SF_IDCHANGE_SUBSCRIBE | `u64 subscription` |
|
||
| SF_ID_LOCK | `u64 lock_handle` |
|
||
| others | empty |
|
||
|
||
**STATION_CONFIGURE (0x01)**: `u8 station_type` (TS 102 894-2 StationType, 2 =
|
||
cyclist), `u8 security` (0 unsecured, 1 secured: itsGnSecurity), `u8
|
||
address_configuration` (0 AUTO with the given MID, 1 ANONYMOUS: MID from the
|
||
ticket digest, TS 103 836-4-1 clause 10.2.1.4), `u8[6] mid` (AUTO only),
|
||
`u8 beaconing` (0/1, GN-MGMT beacons), `u16 channel_number` (ITS-G5 channel,
|
||
180 = 5 900 MHz), `u8 transmit_power_dbm`, `u8 radio` (0 off, 1 receive only, 2
|
||
transmit and receive — laboratory transmission stays an explicit choice), `u8
|
||
default_traffic_class` (raw TC octet used when a request carries none), `u8
|
||
default_lifetime` (raw GN Lifetime octet). Configuring (re)creates the stack on the micrOBU;
|
||
a request before it is answered `not_configured`.
|
||
|
||
**POTI_UPDATE (0x02)**: `u64 timestamp` (TimestampIts, TAI ms since
|
||
2004-01-01; also synchronises the micrOBU's ITS clock), `i32 latitude`, `i32
|
||
longitude` (1/10 µdeg, WGS84), `u16 semi_major`, `u16 semi_minor` (cm, 95 %
|
||
horizontal confidence ellipse), `u16 orientation` (0.1°, semi-major axis from
|
||
north), `u8 flags` (bit 0 altitude present, bit 1 speed present, bit 2 heading
|
||
present, bit 3 position accuracy indicator), `i32 altitude` (cm), `u16 speed`
|
||
(0.01 m/s), `u16 heading` (0.1°). The micrOBU answers only when it rejects the
|
||
update (a `RESULT` with `time_regression` or `invalid_argument`); accepted
|
||
updates are silent so a 10 Hz PoTi does not double the link traffic.
|
||
|
||
**BTP_DATA_REQUEST (0x03)** (TS 103 300-3 Table 4 order):
|
||
|
||
```
|
||
u8 btp_type 0 BTP-A, 1 BTP-B
|
||
u16 destination_port
|
||
u16 destination_port_info (BTP-B) or source_port (BTP-A)
|
||
u8 gn_packet_transport_type 1 SHB, 2 GBC (3 TSB, 4 GAC, 5 GUC are refused: unsupported)
|
||
u8 gn_communication_profile 0 unspecified, 1 ITS-G5, 2 LTE-V2X
|
||
u8 gn_security_profile 0 not given (station configuration), 1 unsecured, 2 secured
|
||
u8 gn_traffic_class raw TC octet (TS 103 836-4-1 clause 9.7.5); 0xFF = station default
|
||
u8 gn_maximum_packet_lifetime raw Lifetime octet (clause 9.6.4); 0xFF = station default
|
||
u8 gn_maximum_hop_limit 0 = station default
|
||
u16 gn_repetition_interval_ms 0 = no repetition
|
||
u16 gn_repetition_maximum_ms
|
||
u32 its_aid ITS-AID (TS 102 965), the SN-ENCAP input
|
||
u8 permissions_length SSP octets (TS 103 097 / TS 102 965) …
|
||
u8 permissions[]
|
||
u8 context_length SN-ENCAP context_information (vam_cluster = 01) …
|
||
u8 context[]
|
||
if GBC: u8 shape (0 circle, 1 rectangle, 2 ellipse), i32 latitude, i32 longitude,
|
||
u16 distance_a, u16 distance_b (m), u16 angle (°)
|
||
u16 length of the FL-SDU (the VAM)
|
||
u8 fl_sdu[length]
|
||
```
|
||
|
||
The micrOBU hands this to `NF_SAP::BTP_DATA_request_submit`. `RESULT.result` is
|
||
the library's acceptance (accepted = submitted for protocol processing, not
|
||
proof of transmission). A secured request whose signing the entity refuses
|
||
(no valid ticket, identifier change pending) is visible in `STATUS` counters.
|
||
|
||
**BTP_DATA_INDICATION (0x81)**: `u8 btp_type`, `u16 destination_port`, `u16
|
||
destination_port_info` (or source_port), `u8 gn_packet_transport_type`, `u8
|
||
gn_traffic_class`, `u8 gn_remaining_packet_lifetime` (raw, 0xFF absent), `u8
|
||
gn_remaining_hop_limit` (0xFF absent), `u8[8] source_gn_address`, `u32
|
||
source_timestamp` (TST ms), `i32 source_latitude`, `i32 source_longitude`,
|
||
`u8 security_report` (0 unsecured, 1 + `VerificationReport` ordinal: 1
|
||
Success, 2 False_Signature, …), `u32 its_aid` (0 when absent), `u8
|
||
permissions_length`, `permissions[]`, `u8 certificate_present`, `u8[8]
|
||
certificate_id`, `u8 area_present`, [`u8 shape`, `i32 lat`, `i32 lon`, `u16
|
||
a`, `u16 b`, `u16 angle`], `u16 length`, `received_fl_sdu[]`.
|
||
|
||
**CREDENTIALS_PROVISION (0x04)**: `u16 total_length`, `u16 offset`, `u8
|
||
segment_length`, `segment[]`. The segments of one bundle arrive in order;
|
||
`offset + segment_length == total_length` ends it. The micrOBU decodes the bundle,
|
||
applies it through the security entity's checks (`security::apply`), saves it
|
||
in the NVS credential store when every item was accepted, and rebuilds the
|
||
station with it. Keys travel in the clear: the transport must be a trusted
|
||
one (a paired, encrypted BLE link; a USB cable on the bench).
|
||
|
||
**SF_IDCHANGE_SUBSCRIBE (0x06)**: `u8 subscriber_data_length`,
|
||
`subscriber_data[]`. **SF_IDCHANGE_UNSUBSCRIBE (0x07)**: `u64 subscription`.
|
||
**SF_IDCHANGE_EVENT (0x82)**: `u64 subscription`, `u8 command` (0 PREPARE, 1
|
||
COMMIT, 2 ABORT, 3 DEREG), `u8[8] id` (HashedId8 of the next ticket), `u8
|
||
subscriber_data_length`, `subscriber_data[]`. **SF_IDCHANGE_EVENT_RESPONSE
|
||
(0x08)**: `u64 subscription`, `u8 return_code` — sent for PREPARE and COMMIT
|
||
with the event's sequence; the service's response timeout (500 ms) applies
|
||
across the link. **SF_IDCHANGE_TRIGGER (0x09)**: empty. **SF_ID_LOCK (0x0A)**:
|
||
`u8 duration_seconds`. **SF_ID_UNLOCK (0x0B)**: `u64 lock_handle`.
|
||
|
||
**MF_SET_REQUEST (0x83)**: `u32 fac_id`, `u8 command_ref`, `u8 count`, then
|
||
`count` × (`u8 f_param_no`, `u32 value`) with TS 103 175 Table 13 numbers (0
|
||
CHANNEL_NUMBER, 1 AVAILABLE_RESOURCE). The micrOBU sends it only for measurements
|
||
its access layer actually made; it never invents a channel load (ACT-021).
|
||
|
||
**STATUS (0x84)**: `u32 uptime_ms`, `u8 configured`, `u8[8] gn_address`,
|
||
`u8[8] identifier`, `u8 change_pending`, `u8 tickets`, `u32 signed_messages`,
|
||
`u32 refused_no_ticket`, `u32 refused_change_pending`, `u32
|
||
refused_permission`, `u32 sign_failed`, `u32 verified`, `u32 rejected`
|
||
(SN-DECAP rejections of every kind), `u32 requests_accepted`, `u32
|
||
requests_refused`, `u32 indications`, `u32 radio_submitted`, `u32
|
||
radio_failed`, `u32 radio_received`, `u32 radio_dropped`, `u32
|
||
link_rx_frames`, `u32 link_crc_errors`, `u32 link_malformed`, `u32
|
||
poti_updates`, `u64 its_time` (current micrOBU ITS clock, ms; 0 before the first
|
||
POTI_UPDATE). The micrOBU sends it every second unsolicited and on STATUS_REQUEST.
|
||
|
||
### Fragmentation
|
||
|
||
A message longer than the transport's unit (ATT_MTU − 3 for a notification; a
|
||
GATT client's long write handles 512 B values itself) is sent as fragments,
|
||
each with the full 4-octet header and the same sequence: the first with
|
||
FRAGMENT_FIRST, every one but the last with FRAGMENT_MORE. The receiver
|
||
concatenates the bodies. The serial transport carries whole messages and never
|
||
sets these bits.
|
||
|
||
## Transports
|
||
|
||
### Serial (bench and conformance tests)
|
||
|
||
Native USB Serial/JTAG of the micrOBU's ESP32-C5 (VID 303A, PID 1001), the app's Phase 03
|
||
framing (`obu-firmware/main/serial_link.h`, `SerialFrame.kt`):
|
||
|
||
```
|
||
[0xAA][0x55][type:1][length:2 LE][payload][crc16:2 LE]
|
||
```
|
||
|
||
CRC-16/CCITT-FALSE (poly 0x1021, init 0xFFFF) over type + length + payload.
|
||
Frame types of this firmware:
|
||
|
||
| Type | Payload |
|
||
|---|---|
|
||
| 0x10 | one link message (above), ≤ 512 octets |
|
||
| 0x11 | test channel request (PC → micrOBU) / reply (micrOBU → PC), ≤ 1 536 octets, only in a test build |
|
||
| 0x7F | UTF-8 log line from the firmware (ESP_LOG output; the console stays free of binary frames) |
|
||
|
||
The maximum frame payload is 1 536 octets (a mirrored GNPDU can exceed 512).
|
||
Frames the phone does not know are ignored; text outside frames (ROM boot
|
||
messages) is skipped by the sync/CRC search.
|
||
|
||
### BLE GATT (production transport)
|
||
|
||
The transport uses the Nordic UART Service UUID layout so standard BLE tools
|
||
can inspect it. It is a byte transport for the project message layer, not the
|
||
Nordic UART wire protocol:
|
||
|
||
| GATT object | UUID | Properties | Direction |
|
||
|---|---|---|---|
|
||
| Primary service | `6e400001-b5a3-f393-e0a9-e50e24dcca9e` | primary | — |
|
||
| RX/request | `6e400002-b5a3-f393-e0a9-e50e24dcca9e` | write, write without response | phone → micrOBU |
|
||
| TX/notification | `6e400003-b5a3-f393-e0a9-e50e24dcca9e` | notify | micrOBU → phone |
|
||
|
||
The peripheral advertises the service and the name `micrOBU-XXXX`, where the
|
||
suffix is derived from its Bluetooth MAC. GATT access requires an authenticated,
|
||
encrypted connection. The ESP32 uses LE Secure Connections, bonding and a
|
||
fresh six-digit passkey printed to the USB log (DisplayOnly); NimBLE persists
|
||
the bond in NVS. Android presents the normal system pairing UI and retains the
|
||
bond. This protects the credential bundle in transit; it is not application-
|
||
layer encryption.
|
||
|
||
Android requests an ATT MTU of 517; the ESP32 preference is 512. The Android
|
||
writer uses `ATT_MTU - 3`, capped at 512, after negotiation (509 octets with
|
||
the current ESP32 configuration) and falls back to 20 octets.
|
||
The ESP32 notification path deliberately emits values of at most 20 octets so
|
||
it also works at the mandatory default MTU. Every value uses the fragmentation
|
||
flags above and repeats the link header; a complete message is limited to 512
|
||
octets. Writes are acknowledged and serialized. Notifications are enabled
|
||
before the Android adapter reports `READY`. The USB test channel is never
|
||
exposed over BLE.
|
||
|
||
## Test channel (test firmware only, USB only)
|
||
|
||
`CONFIG_MICROBU_TEST_CHANNEL` compiles a software lower tester and the
|
||
diagnostic hooks the ETSI campaigns need, behind frame type 0x11. It is not
|
||
part of the phone interface and never exists on BLE. Requests: `u8 command`,
|
||
reply `u8 result`, `u8 record_count`, records `[kind u8][length u16 LE][bytes]`:
|
||
|
||
| Command | Body | Effect |
|
||
|---|---|---|
|
||
| 0x01 MIRROR | `u8 mode` (0 off, 1 mirror, 2 divert) | every AL_DATA.request the stack makes is copied (mirror) or kept from the radio (divert) and queued as kind-1 records |
|
||
| 0x02 INJECT | `u8[6] source`, `u8[6] destination`, `gnpdu[]` | AL_DATA.indication from the lower tester into the stack |
|
||
| 0x03 GN_REQUEST | `u8 traffic_class`, `payload[]` | GN-DATA.request SHB with next header "Any" (the Security ATS GN-MGMT/GENMSG cases; N-SAP, not a phone primitive) |
|
||
| 0x04 DRAIN | – | returns the queued records: kind 1 AL_DATA.request (the GNPDU as submitted), kind 2 BTP-DATA.indication (`[type][dest port][port info][SDU]`), kind 3 GN-DATA.indication payload |
|
||
| 0x05 RESET | – | drops the queue and the mirror mode |
|
||
|
||
The queue is bounded (32 records, 8 KiB); an overflow is reported as
|
||
`resource_limit` on the next DRAIN and the records lost are counted.
|
||
|
||
## Phone emulator, SUT bridge and campaigns (PC side)
|
||
|
||
`python/microbu_link/` is the phone side of the message layer (codec, serial
|
||
transport, request/reply client, the small VBS of `vbs.py`), shared by:
|
||
|
||
- `python/phone_emulator.py` — the phone half of the VRU ITS-S on a PC:
|
||
configures the micrOBU, provisions a `VCR1` bundle, feeds PoTi (static or a
|
||
bicycle on a circle), assembles VAMs with asn1tools from the ETSI ASN.1 of the
|
||
submodule and hands them over with the PCI of TS 103 300-3 Table 4 under the
|
||
generation rules of clause 6.4, subscribes to the identifier-change events
|
||
(stop on PREPARE, new StationId after COMMIT), prints the indications, status
|
||
and log lines; `--divert --pcap` records what the micrOBU signs for an
|
||
independent verifier. Its default synthetic trajectory is a 20 m-radius circle
|
||
at 2 m/s centred at 53.5652470 N, 10.0082390 E over the Außenalster's open
|
||
water; `--lat`, `--lon`, `--radius` and `--speed` override it. Radio emission
|
||
remains opt-in through `--radio txrx`. The center is not itself a transmitted
|
||
position: generated positions lie on the circle perimeter. The firmware's
|
||
standalone BOOT-button VAM is a separate fixed payload and does not use these
|
||
phone-emulator coordinates.
|
||
- `python/sut_bridge.py` — the SUT process of the vanetza-idf ETSI adapters
|
||
(hex-line protocol of `hil_sut.hpp`) with the micrOBU behind the phone
|
||
interfaces: BTP-DATA.request, PoTi and credentials over the link, the software
|
||
lower tester over the USB test channel, CAM/DENM stimuli assembled on the PC.
|
||
`--listen host:port` serves the protocol over TCP; `python/sut_tcp_relay.py`
|
||
inside WSL is the SUT executable that forwards it (WSL interop drops piped
|
||
stdin of Windows executables).
|
||
|
||
```powershell
|
||
# Windows: the micrOBU on COM11, the campaign pool packed as a bundle
|
||
python sut_bridge.py --port COM11 --bundle pool.vcr --listen 0.0.0.0:7788
|
||
```
|
||
|
||
`LinkClient` is transport-independent. `SerialTransport` and `BleTransport`
|
||
implement the same `TransportAdapter.write(frame_type, payload)` / `close()`
|
||
boundary and feed the same receive callback; configure, PoTi, credential,
|
||
BTP-DATA, status and event code is therefore identical. Select only the adapter
|
||
at the command line:
|
||
|
||
```powershell
|
||
cd implementation\station-link\python
|
||
python -m pip install -r requirements.txt
|
||
|
||
# Safe first BLE run: hand VAMs to an unsecured station, with ITS-G5 RF off.
|
||
python phone_emulator.py --ble --unsecured --radio off --period 1000 --duration 10 --verbose `
|
||
--report ble-vam-session.json
|
||
|
||
# Select a particular advertisement when several micrOBUs are present.
|
||
python phone_emulator.py --ble micrOBU-1234 --unsecured --radio off --duration 10
|
||
```
|
||
|
||
For the signed run, replace `--unsecured` with `--bundle <path-to-chain.vcr>`
|
||
(or use credentials already retained in the micrOBU NVS). Change `--radio off`
|
||
to `--radio txrx` only for the intended laboratory RF test. The USB test
|
||
channel has no BLE equivalent, so `--mirror`, `--divert` and `--pcap` are
|
||
rejected with `--ble`; all station-link/VAM behavior above that adapter is the
|
||
same.
|
||
|
||
Instead of an offline `--bundle`, `--pki-url` makes the emulator obtain an
|
||
enrolment credential and authorization ticket from `tools/local_pki.py`, build
|
||
the same `VCR1` bundle, and provision it before starting the VBS. Supply
|
||
`--pki-root`, `--pki-ea`, `--pki-aa`, `--pki-canonical-key`, `--pki-its-id`,
|
||
`--pki-issue-tool`, `--pki-client-tool`, and `--pki-bundle-tool` together; use
|
||
`--pki-hours` to override the 24-hour AT validity. The message flow and retained
|
||
host-side proof are documented in `docs/idf/evidence/enrolment-authorization-01`
|
||
and the EA/AA section of `docs/idf/test-campaigns.md` in vanetza-idf.
|
||
`local_pki.py` is a separate localhost HTTP process, normally bound to
|
||
`127.0.0.1`; it is not imported into or hosted by the emulator. Consequently,
|
||
the lab authority can later be replaced by a hosted PKI without changing the
|
||
phone flow: set `--pki-url` to the hosted access point and keep the same S3/S4
|
||
HTTP interface.
|
||
|
||
## End-to-end hardware workflow: Online PKI, Transmission & Reception
|
||
|
||
This workflow runs the full online TS 102 941 enrolment/authorization and 5.9 GHz ITS-G5 over-the-air transmission between two ESP32-C5 boards:
|
||
- **COM4 (TX)**: micrOBU running firmware microbu_firmware.bin
|
||
- **COM5 (RX)**: independent receiver running 2x2map-0.3.0 bridge firmware
|
||
|
||
### 1. Initialize the lab PKI hierarchy (run once)
|
||
|
||
`powershell
|
||
$dir = "C:\microbu-pki-wt\pki-lab"
|
||
New-Item -ItemType Directory -Force -Path "$dir\chain", "$dir\issued"
|
||
& C:\Strawberry\c\bin\openssl.exe ecparam -name prime256v1 -genkey -noout -out "$dir\root.pem"
|
||
& C:\Strawberry\c\bin\openssl.exe ecparam -name prime256v1 -genkey -noout -out "$dir\canonical.pem"
|
||
|
||
$issue = "C:\bachelorthesis_micrOBU\implementation\external\vanetza-idf\build-host-sec\vidf_issue.exe"
|
||
& $issue root --key "$dir\root.pem" --name "lab root" --id ROOT --out "$dir\chain"
|
||
& $issue authority --issuer "$dir\chain\ROOT.oer" --issuer-key "$dir\root.pem" --name "lab EA" --id EA --out "$dir\chain"
|
||
& $issue authority --issuer "$dir\chain\ROOT.oer" --issuer-key "$dir\root.pem" --name "lab AA" --id AA --out "$dir\chain"
|
||
`
|
||
|
||
### 2. Start the localhost PKI server (Terminal 1)
|
||
|
||
`powershell
|
||
python C:\microbu-pki-wt\implementation\external\vanetza-idf\ports\esp_idf\tools\local_pki.py `
|
||
--issue-tool C:\bachelorthesis_micrOBU\implementation\external\vanetza-idf\build-host-sec\vidf_issue.exe `
|
||
--ea C:\microbu-pki-wt\pki-lab\chain\EA.oer `
|
||
--ea-key C:\microbu-pki-wt\pki-lab\chain\EA.vkey `
|
||
--ea-enc-key C:\microbu-pki-wt\pki-lab\chain\EA.ekey `
|
||
--canonical-key C:\microbu-pki-wt\pki-lab\canonical.pem `
|
||
--aa C:\microbu-pki-wt\pki-lab\chain\AA.oer `
|
||
--aa-key C:\microbu-pki-wt\pki-lab\chain\AA.vkey `
|
||
--aa-enc-key C:\microbu-pki-wt\pki-lab\chain\AA.ekey `
|
||
--dir C:\microbu-pki-wt\pki-lab\issued `
|
||
--port 8092
|
||
`
|
||
|
||
### 3. Start the independent v2x2map receiver (Terminal 2)
|
||
|
||
`powershell
|
||
python C:\microbu-pki-wt\tools\vendor\v2x2map-0.3.0\bridge\its_g5_bridge.py `
|
||
--port COM5 `
|
||
--node-id V2X2MAP:001122334455 `
|
||
--no-mqtt `
|
||
--open-browser `
|
||
--pcap-out C:\microbu-pki-wt\rx_v2x2map.pcap
|
||
`
|
||
- Automatically opens the live dashboard at http://127.0.0.1:8080.
|
||
- --pcap-out <file.pcap> continuously records all received 802.11 frames to disk.
|
||
- Alternatively, click **Record** and **Stop** in the web dashboard: a direct **Download PCAP** button appears to download the file, which is also saved under ools/vendor/v2x2map-0.3.0/bridge/recordings/.
|
||
|
||
### 4. Transmit signed VAMs via Phone Emulator (Terminal 3)
|
||
|
||
`powershell
|
||
python C:\microbu-pki-wt\implementation\station-link\python\phone_emulator.py `
|
||
--port COM4 `
|
||
--radio txrx `
|
||
--mirror `
|
||
--pcap C:\microbu-pki-wt\tx_vams.pcap `
|
||
--pki-url http://127.0.0.1:8092/ `
|
||
--pki-root C:\microbu-pki-wt\pki-lab\chain\ROOT.oer `
|
||
--pki-ea C:\microbu-pki-wt\pki-lab\chain\EA.oer `
|
||
--pki-aa C:\microbu-pki-wt\pki-lab\chain\AA.oer `
|
||
--pki-canonical-key C:\microbu-pki-wt\pki-lab\canonical.pem `
|
||
--pki-its-id vidf-vru-station `
|
||
--pki-issue-tool C:\bachelorthesis_micrOBU\implementation\external\vanetza-idf\build-host-sec\vidf_issue.exe `
|
||
--pki-client-tool C:\microbu-pki-wt\implementation\external\vanetza-idf\ports\esp_idf\tools\pki_client.py `
|
||
--pki-bundle-tool C:\microbu-pki-wt\implementation\external\vanetza-idf\ports\esp_idf\tools\credential_bundle.py `
|
||
--duration 30
|
||
`
|
||
- --mirror --pcap tx_vams.pcap ensures the transmitter saves an exact PCAP capture of all generated and signed GNPDUs.
|
||
- Omit --duration 30 to transmit continuously until Ctrl+C.
|
||
|
||
### Where recordings are saved
|
||
|
||
| Recording | Destination | Description |
|
||
|---|---|---|
|
||
| **Transmitter (TX) PCAP** | --pcap <filename> | Local 802.11 PCAP captured via the USB test channel (--mirror copies all frames while the radio transmits). |
|
||
| **Receiver (RX) PCAP (CLI)** | --pcap-out <filename> | Continuous capture of all over-the-air frames received by the ESP32-C5 on COM5. |
|
||
| **Receiver (RX) PCAP (Web)** | ools/vendor/v2x2map-0.3.0/bridge/recordings/ | Saved automatically when clicking **Record** and **Stop** in the web dashboard, with a direct **Download PCAP** button in the browser. |
|
||
|
||
```sh
|
||
# WSL: the security campaigns with the compiled AtsSecurity (docs/idf/test-campaigns.md of the submodule)
|
||
python3 run_etsi.py --titan ... --binary /root/vidf-ats-security/AtsSecurity \
|
||
--config tests/etsi_security_gn.cfg --expected-cases tests/etsi_security_gn_cases.json \
|
||
--sut /usr/bin/python3 --sut-args "/root/sut_tcp_relay.py --connect <windows host>:7788" \
|
||
--pool /root/vidf-pool --out /root/vidf-results/security-gn-microbu
|
||
```
|
||
|
||
Retained runs of 2026-09-14: `experiments/raw/microbu-link-20260914/`
|
||
(manifests in `experiments/manifests/test-runs/microbu-link-20260914-*.yaml`).
|
||
|
||
## BLE verification (2026-09-16)
|
||
|
||
The retained host verification is described by
|
||
`experiments/manifests/test-runs/microbu-link-20260916-ble-host-01.yaml`:
|
||
|
||
- Python/C++ protocol and framing suite: 8 tests passed, including the common
|
||
serial/BLE adapter contract and a 512-octet
|
||
message split into 32 default-MTU values, reassembly, malformed interleaving
|
||
rejection and recovery.
|
||
- Kotlin/JVM station-link suite: 4 tests passed against the Python/C++ golden
|
||
bytes, including fragmentation, recovery and credential segmentation.
|
||
- Android `:app:compileDebugKotlin`: passed with the BLE central and manifest
|
||
permissions included.
|
||
- ESP-IDF 6.0.2 ESP32-C5 build: passed with NimBLE and the encrypted GATT
|
||
peripheral; the binary is `0x1d8780` bytes and leaves 38% of the app partition.
|
||
|
||
No ESP32 serial device or Pixel 9 was attached to this laptop during this run,
|
||
so pairing, notification exchange, the <= 2 s reconnection requirement and the
|
||
connection-loss-rate requirement were not measured. `TEST-AC-SYS-MICROBU-013` and
|
||
`TEST-AC-SYS-MICROBU-014` intentionally remain planned hardware tests.
|