# 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 ` (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 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 | Local 802.11 PCAP captured via the USB test channel (--mirror copies all frames while the radio transmits). | | **Receiver (RX) PCAP (CLI)** | --pcap-out | 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 :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.