Files
MicrOBU/microbu-esp32c5/firmware/main/station.hpp
T

243 lines
11 KiB
C++
Raw Normal View History

#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 <vanetza_idf/stack.hpp>
#include <vanetza_idf/security.hpp>
#include <vanetza_idf/credentials.hpp>
#include <vanetza_idf/backend_mbedtls.hpp>
#include <vanetza/common/manual_runtime.hpp>
#include <vanetza/common/position_provider.hpp>
#include <deque>
#include <functional>
#include <map>
#include <memory>
#include <optional>
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<void(const link::BtpDataIndication&)>;
using IdEvent = std::function<void(const link::IdChangeEvent&)>;
using Disseminated = std::function<void()>;
using Received = std::function<void()>;
/// @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); }
#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<TestRecord> 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 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<link::StationConfigure> config_;
Clock clock_;
vanetza::PositionFix fix_;
bool have_fix_ = false;
std::unique_ptr<vanetza::ManualRuntime> runtime_;
std::unique_ptr<Security> security_;
std::unique_ptr<vanetza_idf::Stack> stack_;
std::unique_ptr<C5Radio> radio_;
std::unique_ptr<vanetza_idf::AccessStack> access_stack_; // DCC_ACC gate in front of radio_
vanetza_idf::AlDataRequest radio_parameters_;
std::optional<CcaCounters> 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_;
std::map<std::uint64_t, std::shared_ptr<vanetza_idf::security::IdChangeResponder>> pending_responders_;
std::map<std::uint64_t, std::uint64_t> link_subscriptions_; // subscription -> service handle (identity)
bool rebuilding_ = false;
#if CONFIG_MICROBU_TEST_CHANNEL
Mirror mirror_ = Mirror::off;
std::deque<TestRecord> records_;
std::size_t record_bytes_ = 0;
bool overflow_ = false;
#endif
};
} // namespace microbu