#pragma once // The ESP32-C5 half of the VRU ITS-S: BTP-B, GeoNetworking, the SN-SAP // security entity with the station's provisioned credentials, and the ITS-G5 access adapter. // Everything here runs on one task (the station task). #include "c5_radio.hpp" #include "link_protocol.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace microbu { #if CONFIG_MICROBU_TEST_CHANNEL /// Records the software lower tester collects (test channel, test firmware only). struct TestRecord { std::uint8_t kind; link::Bytes bytes; }; #endif /// @note All public members except the on_*() callback setters must be called from the station task. class Station final : public vanetza_idf::Access, public vanetza::PositionProvider { public: using Indication = std::function; using IdEvent = std::function; using Disseminated = std::function; using Received = std::function; /// MicrOBU: body of one link V2X_RX message (see link_protocol.hpp). using RawIts = std::function; /// @brief Constructs an unconfigured station (call configure() before use). Station(); /// @brief Tears down the stack, security entity and radio in dependency order. ~Station() override; // ---- link primitives (station task) ---- /// @brief (Re)builds the stack and security entity for a new station configuration. /// @param config Requested configuration. /// @param detail Receives the assigned GN address, identifier, and ticket count. /// @return Result code. link::Code configure(const link::StationConfigure& config, link::Bytes& detail); /// @brief Applies a position/time fix from the phone and advances the ITS clock. /// @param fix Position/time fix to apply. /// @return Result code. link::Code poti(const link::PotiUpdate& fix); /// @brief Submits a BTP data request to the stack. /// @param request Request to submit. /// @return Result code. link::Code btp_request(const link::BtpDataRequest& request); /// @brief Decodes and stores a credential bundle, rebuilding the security entity if configured. /// @param bundle Raw credential bundle bytes. /// @param report Receives the applied root/authority/ticket counts. /// @return Result code. link::Code provision(const link::Bytes& bundle, link::ApplyReport& report); /// @brief Erases stored credentials and rebuilds if security is configured. /// @return Result code. link::Code erase_credentials(); /// @brief Subscribes to pseudonym-change events. /// @param subscriber_data Opaque data echoed back with events for this subscription. /// @param subscription Receives the assigned subscription handle. /// @return Result code. link::Code subscribe(const link::Bytes& subscriber_data, std::uint64_t& subscription); /// @brief Cancels a pseudonym-change subscription. /// @param subscription Handle returned by subscribe(). /// @return Result code. link::Code unsubscribe(std::uint64_t subscription); /// @brief Resolves a pending PREPARE/COMMIT identity-change event. /// @param subscription Handle the event was delivered for. /// @param return_code Caller's response (accept/reject) to the pending event. /// @return Result code. link::Code event_response(std::uint64_t subscription, bool return_code); /// @brief Triggers an immediate pseudonym change. /// @return Result code. link::Code trigger(); /// @brief Locks the current pseudonym for the given duration. /// @param seconds Lock duration in seconds. /// @param handle Receives the assigned lock handle. /// @return Result code. link::Code lock(std::uint8_t seconds, std::uint64_t& handle); /// @brief Releases a pseudonym lock. /// @param handle Handle returned by lock(). /// @return Result code. link::Code unlock(std::uint64_t handle); /// @return Current station status snapshot. link::Status status(); /// @brief Advances the ITS clock, runs timers, polls the radio; call every few milliseconds. void tick(); /// @brief Registers the callback invoked when a BTP data indication arrives. /// @param f Callback to invoke; replaces any previously registered callback. void on_indication(Indication f) { indication_ = std::move(f); } /// @brief Registers the callback invoked when a pseudonym-change event fires. /// @param f Callback to invoke; replaces any previously registered callback. void on_id_event(IdEvent f) { id_event_ = std::move(f); } /// @brief Registers the callback invoked after a frame is disseminated. /// @param f Callback to invoke; replaces any previously registered callback. void on_disseminated(Disseminated f) { disseminated_ = std::move(f); } /// @brief Registers the callback invoked after a frame is received. /// @param f Callback to invoke; replaces any previously registered callback. void on_received(Received f) { received_ = std::move(f); } /// @brief MicrOBU: registers the callback for every ITS message heard on air, unwrapped by /// gn_unwrap.c before (and independently of) the stack's security checks. The stack drops /// what it cannot verify (itsGnSnDecapResultHandling is STRICT in vanetza-idf): unsigned /// traffic, and anything signed under a root other than the provisioned demo root, i.e. every /// RSU. This path is how those still reach the phone, exactly as with the previous firmware. /// @param f Callback to invoke with a V2X_RX body; replaces any previously registered callback. void on_raw_its(RawIts f) { raw_its_ = std::move(f); } #if CONFIG_MICROBU_TEST_CHANNEL // ---- test channel (software lower tester; not part of the phone interface -- see test_channel.hpp) ---- enum class Mirror : std::uint8_t { off = 0, mirror = 1, divert = 2 }; /// @brief Sets whether requests/indications are mirrored to, or diverted through, the test channel. /// @param mode Off, mirror (copy) or divert (intercept) mode. void test_mirror(Mirror mode); /// @brief Injects a raw GN PDU as if received over the air. /// @param source Source MAC address to report for the injected frame. /// @param destination Destination MAC address to report for the injected frame. /// @param gnpdu Raw GeoNetworking PDU bytes. /// @return Result code. link::Code test_inject(const vanetza::MacAddress& source, const vanetza::MacAddress& destination, link::Bytes gnpdu); /// @brief Submits a raw GeoNetworking request bypassing BTP. /// @param traffic_class GeoNetworking traffic class to submit with. /// @param payload Raw payload bytes. /// @return Result code. link::Code test_gn_request(std::uint8_t traffic_class, link::Bytes payload); /// @brief Pops queued mirror/divert records fitting into budget octets (3 per record header); the rest stays queued. /// @param overflow Receives true if records were dropped because the queue budget was exceeded. /// @param budget Maximum number of octets of records to drain. /// @return Drained records. std::deque test_drain(bool& overflow, std::size_t budget); /// @brief Clears mirror mode and any queued records. void test_reset(); /// @brief Starts the radio if needed and sends a burst of raw test frames. /// @param channel ITS-G5 channel number to transmit on. /// @param power_dbm Transmit power in dBm. /// @param mcs 802.11p modulation and coding scheme index. /// @param count Number of frames to send. /// @param interval_ms Interval between frames in milliseconds. /// @param payload_len Length of each frame's payload in bytes. /// @return Result code. link::Code test_radio_burst(std::uint16_t channel, double power_dbm, unsigned mcs, unsigned count, unsigned interval_ms, std::size_t payload_len); /// @brief Starts the radio if needed and samples the CCA state for duration_ms. /// @param duration_ms Sampling window duration in milliseconds. /// @param detail Receives the sampling statistics. /// @return Result code. link::Code test_cca_sample(unsigned duration_ms, link::Bytes& detail); #endif // vanetza_idf::Access /// @brief Submits a frame to the radio (through DCC_ACC when enabled), mirroring/diverting for the test channel. /// @param request Frame and transmit parameters from the access layer. /// @return Result code. vanetza_idf::Result request(vanetza_idf::AlDataRequest request) override; // vanetza::PositionProvider /// @return The most recently applied position fix. const vanetza::PositionFix& position_fix() override { return fix_; } private: struct Security; struct Clock { bool synchronised = false; std::int64_t base_its_us = 0; // ITS time at base_esp_us std::int64_t base_esp_us = 0; std::int64_t now_us() const; }; /// @brief (Re)builds the stack and security entity from config_, stored credentials and the clock. /// @return Result code. link::Code build(); /// @brief Destroys the stack, security entity and runtime in dependency order. void teardown(); /// @brief Maps a station configuration onto the GeoNetworking MIB. /// @param mib MIB to populate. /// @param config Station configuration to map from. void apply_mib(vanetza_idf::StackConfig& mib, const link::StationConfigure& config) const; /// @brief Re-subscribes existing pseudonym-change handles to a freshly built security entity. void resubscribe_id_change(); /// @brief Pushes the current position fix into the stack, clamped to not precede the station clock. void apply_position(); /// @brief Samples the hardware CCA counters at the DCC cadence and feeds both DCC entities. void sample_dcc_channel_load(); #if CONFIG_MICROBU_TEST_CHANNEL /// @brief Queues bytes for the test channel, subject to the mirror-buffer budget. /// @param kind Record kind tag. /// @param bytes Record payload bytes. void record(std::uint8_t kind, link::Bytes bytes); #endif /// @brief MicrOBU: unwraps one raw received frame with gn_unwrap.c and hands it to raw_its_. /// @param frame Complete 802.11 frame as captured (FCS included; gn_unwrap reads declared lengths). /// @param rssi Received signal strength, dBm. void forward_raw(const vanetza::ByteBuffer& frame, int rssi); /// @brief MicrOBU: transmits an unsecured BTP-B/SHB request the way the previous firmware did, /// with geonet.c's GN header and the Source Position Vector of the last PoTi, through request(). /// @param request The phone's request (already checked: configured, clock and fix present). /// @return Result code. link::Code unsecured_request(const link::BtpDataRequest& request); /// @brief Translates and forwards a stack BTP indication to the link service and test channel. /// @param indication Indication received from the stack. void deliver(vanetza_idf::BtpIndication indication); link::Status counters_; std::optional config_; Clock clock_; vanetza::PositionFix fix_; bool have_fix_ = false; link::PotiUpdate last_poti_; // MicrOBU: the GN Source Position Vector of unsecured_request() std::uint32_t raw_forwarded_ = 0, raw_oversize_ = 0; std::unique_ptr runtime_; std::unique_ptr security_; std::unique_ptr stack_; std::unique_ptr radio_; std::unique_ptr access_stack_; // DCC_ACC gate in front of radio_ vanetza_idf::AlDataRequest radio_parameters_; std::optional dcc_cca_last_; // previous read_cca_counters() sample, for the 100 ms LCBR delta std::int64_t dcc_last_sample_its_us_ = 0; Indication indication_; IdEvent id_event_; Disseminated disseminated_; Received received_; RawIts raw_its_; std::map> pending_responders_; std::map link_subscriptions_; // subscription -> service handle (identity) bool rebuilding_ = false; #if CONFIG_MICROBU_TEST_CHANNEL Mirror mirror_ = Mirror::off; std::deque records_; std::size_t record_bytes_ = 0; bool overflow_ = false; #endif }; } // namespace microbu