Sign ITS messages on the ESP32-C5 with vanetza-idf, over USB or BLE

obu-firmware is now a port of the colleague's standalone VRU station
(microbu-esp32c5/firmware, kept beside this repository and gitignored): the
vanetza-idf C-ITS stack with the TS 103 097 security entity, credentials in
NVS, the station-link v1 protocol over the native USB port (frame type 0x10
in the existing 0xAA55 framing) and over a BLE GATT peripheral, and its
ITS-G5 radio adapter. The phone still builds CAM and VAM; the board adds
GeoNetworking/BTP and signs with the provisioned authorization ticket. The
private key never leaves the board. Builds with ESP-IDF 6.0.2 only, which
vanetza-idf pins for the radio's private driver ABI. The previous C firmware
stays on disk unbuilt; a full-flash backup of the bench board is kept in
firmware-backups/ (gitignored).

Changed against the colleague's firmware, marked MicrOBU: in the sources:
- Reception unchanged for the app. vanetza-idf drops what it cannot verify
  (unsigned traffic, every RSU), so each captured frame also goes through the
  previous gn_unwrap.c and reaches the phone as link opcode V2X_RX (0x85),
  whose body is the old SERIAL_MSG_V2X_RX payload.
- Unsigned transmission still possible, with the previous geonet.c header;
  the phone chooses per message.
- Console on UART0 (CH343 port); the native USB port carries only link frames.
- BLE advertising pauses while the USB link is in use: BLE and ITS-G5 share
  one RF front end.
- NVS 80 KB (app at 0x20000). At 24 KB, with Wi-Fi settings the previous
  firmware left behind, the BLE bond could not be stored and the phone had to
  pair on every connection.
- Bench fixes: the radio queue is drained before the first PoTi (no RX and
  ~177 queue drops before); the station loop waited pdMS_TO_TICKS(5) = 0
  ticks at 100 Hz and starved the idle task; the 2.4 KB RX capture buffer is
  off the Wi-Fi task stack; BLE notifications longer than the MTU are dropped
  instead of cut short, MTU 517; serial writes are skipped with no USB host.
- Manual country policy and TX-power read-back from the previous radio setup;
  logs for BLE encryption changes and the number of stored bonds.

Verified on the bench board (COM3) with the phone over USB and BLE: CAM and
VAM, signed and unsigned, go out; reception of the sim car and the RSU's
CAM/SPATEM/MAPEM continues; the board survives app restarts and reconnects.
See docs/06-signed-its-vam-ble.md.
This commit is contained in:
Ashin Walpola
2026-09-23 17:27:54 +02:00
parent 7285fa19b7
commit d2fd222a62
31 changed files with 4837 additions and 1022 deletions
+75
View File
@@ -0,0 +1,75 @@
#pragma once
// Serial transport of the station-internal link over the ESP32-C5 native USB Serial/JTAG port.
// Framing is the app's Phase 03 one: [AA][55][type][len LE][payload][crc16 LE] with
// CRC-16/CCITT-FALSE over type+len+payload (implementation/station-link/README.md, "Serial").
// One writer serialises complete frames. The byte stream never carries plain text: ESP_LOG stays on
// UART0 here, or travels as LOG frames with CONFIG_MICROBU_LOG_OVER_LINK.
#include <cstddef>
#include <cstdint>
#include <functional>
#include <vector>
namespace microbu::serial {
/// @brief Byte buffer type for frame payloads.
using Bytes = std::vector<std::uint8_t>;
/// @brief The type of a frame. Either Link (link protocol), Test (test channel), or Log (ESP_LOG output).
enum class FrameType : std::uint8_t { LINK = 0x10, TEST = 0x11, LOG = 0x7F };
constexpr std::size_t maximum_payload = 1536;
/// @brief A complete frame with a type and a payload.
struct Frame { FrameType type; Bytes payload; };
/// @brief Computes the CRC-16/CCITT-FALSE checksum over a byte range.
/// @param data Pointer to the first byte to checksum.
/// @param length Number of bytes to checksum.
/// @return Checksum value.
std::uint16_t crc16_ccitt_false(const std::uint8_t* data, std::size_t length);
/// @brief Frames a payload as [AA][55][type][len LE][payload][crc16 LE].
/// @param type Frame type tag.
/// @param payload Payload bytes; must not exceed maximum_payload.
/// @return Encoded frame bytes.
Bytes encode_frame(FrameType type, const Bytes& payload);
/// Byte-at-a-time decoder, same state machine as the app's SerialFrameDecoder.
class Decoder {
public:
using Handler = std::function<void(Frame)>;
/// @brief Feeds raw bytes through the frame state machine, invoking the handler per complete frame.
/// @param data Pointer to the first byte to feed.
/// @param length Number of bytes to feed.
/// @param handler Invoked once per complete, CRC-valid frame; may be called zero or more times.
void feed(const std::uint8_t* data, std::size_t length, const Handler& handler);
/// @return Number of frames rejected for a CRC mismatch so far.
std::uint32_t crc_errors() const { return crc_errors_; }
/// @return Number of frames decoded successfully so far.
std::uint32_t frames() const { return frames_; }
private:
enum class State { SYNC0, SYNC1, TYPE, LEN_LO, LEN_HI, PAYLOAD, CRC_LO, CRC_HI } state_ = State::SYNC0;
std::uint8_t type_ = 0;
std::uint16_t length_ = 0, crc_ = 0;
Bytes payload_;
std::uint32_t crc_errors_ = 0, frames_ = 0;
};
struct Counters { std::uint32_t frames = 0, crc_errors = 0, write_failures = 0, not_connected = 0; };
/// @brief Installs the USB Serial/JTAG driver and starts the reader task. ESP_LOG goes into LOG frames
/// only with CONFIG_MICROBU_LOG_OVER_LINK; on this board it stays on the UART0 console.
/// @param on_frame Invoked once per complete, CRC-valid frame.
/// @note The handler runs on the reader task; it must only enqueue, never block.
void start(const Decoder::Handler& on_frame);
/// @brief Writes one complete frame (blocking, mutex-protected). Safe from any task.
/// @param type Frame type tag.
/// @param payload Payload bytes; must not exceed maximum_payload.
/// @return false on a full payload, missing driver, lock timeout, or write failure.
bool write(FrameType type, const Bytes& payload);
/// @return Current frame/error counters.
Counters counters();
/// @return esp_timer time in ms (wrapping) at which the last CRC-valid frame arrived from the host,
/// 0 if none yet. MicrOBU: app_main pauses BLE advertising while this is recent.
std::uint32_t last_frame_ms();
} // namespace microbu::serial