From cbc7a0df17d712a4b05114b2254f440099ea2159 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Thu, 18 Dec 2025 11:08:23 +0000 Subject: [PATCH 01/30] Introduce QuicCommunication: Add basic connection of QUIC protocol support to ExternalClient --- CMakeLists.txt | 2 + include/bringauto/common_utils/EnumUtils.hpp | 2 + .../communication/QuicCommunication.hpp | 94 ++++++++++ include/bringauto/settings/Constants.hpp | 2 + .../structures/ExternalConnectionSettings.hpp | 1 + source/bringauto/common_utils/EnumUtils.cpp | 2 + .../external_client/ExternalClient.cpp | 6 + .../communication/QuicCommunication.cpp | 170 ++++++++++++++++++ source/bringauto/settings/SettingsParser.cpp | 18 +- 9 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 include/bringauto/external_client/connection/communication/QuicCommunication.hpp create mode 100644 source/bringauto/external_client/connection/communication/QuicCommunication.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a84157..4ff6e19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,7 @@ FIND_PACKAGE(ZLIB 1.2.11 REQUIRED) FIND_PACKAGE(fleet-protocol-cxx-helpers-static 1.2.0 REQUIRED) FIND_PACKAGE(aeron 1.48.6 REQUIRED) FIND_PACKAGE(async-function-execution-shared 1.0.0 REQUIRED) +FIND_PACKAGE(msquic CONFIG REQUIRED) FILE(GLOB_RECURSE source_files "source/*") ADD_LIBRARY(module-gateway-lib STATIC "${source_files}") @@ -76,6 +77,7 @@ TARGET_LINK_LIBRARIES(module-gateway-lib PUBLIC ZLIB::ZLIB fleet-protocol-cxx-helpers-static::fleet-protocol-cxx-helpers-static async-function-execution-shared::async-function-execution-shared + msquic ${CMAKE_DL_LIBS} ) diff --git a/include/bringauto/common_utils/EnumUtils.hpp b/include/bringauto/common_utils/EnumUtils.hpp index cb6b0bc..6618c36 100644 --- a/include/bringauto/common_utils/EnumUtils.hpp +++ b/include/bringauto/common_utils/EnumUtils.hpp @@ -32,6 +32,8 @@ class EnumUtils { switch(toString) { case structures::ProtocolType::MQTT: return settings::Constants::MQTT; + case structures::ProtocolType::QUIC: + return settings::Constants::QUIC; case structures::ProtocolType::DUMMY: return settings::Constants::DUMMY; case structures::ProtocolType::INVALID: diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp new file mode 100644 index 0000000..7a49c34 --- /dev/null +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include + +#include + +namespace bringauto::external_client::connection::communication { + +class QuicCommunication: public ICommunicationChannel { +public: + explicit QuicCommunication(const structures::ExternalConnectionSettings &settings, const std::string &company, + const std::string &vehicleName); + + ~QuicCommunication() override; + + /// void setProperties(const std::string &company, const std::string &vehicleName); + + void initializeConnection() override; + + bool sendMessage(ExternalProtocol::ExternalClient *message) override; + + std::shared_ptr receiveMessage() override; + + void closeConnection() override; + +private: + const QUIC_API_TABLE* quic_ { nullptr }; + HQUIC registration_ { nullptr }; + HQUIC config_ { nullptr }; + HQUIC connection_ { nullptr }; + std::string alpn_; + QUIC_BUFFER alpnBuffer_ {}; + + QUIC_CERTIFICATE_FILE certificate_ {}; + QUIC_CREDENTIAL_CONFIG credential_ {}; + + + + std::string host_; + uint16_t port_ { 0 }; + std::string certFile_; + std::string keyFile_; + std::string caFile_; + + static QUIC_STATUS QUIC_API connectionCallback(HQUIC connection, void *context, QUIC_CONNECTION_EVENT *event); + + /// void connect(); + + // /** + // * @brief Create a client id from company name and vehicle name + // * + // * @param company name of the company + // * @param vehicleName name of the vehicle + // * @return std::string + // */ + // static std::string createClientId(const std::string &company, const std::string &vehicleName); + + // /** + // * @brief Create a publish topic from company name and vehicle name + // * + // * @param company name of the company + // * @param vehicleName name of the vehicle + // * @return std::string + // */ + // static std::string createPublishTopic(const std::string &company, const std::string &vehicleName); + + // /** + // * @brief Create a subscribe topic from company name and vehicle name + // * + // * @param company name of the company + // * @param vehicleName name of the vehicle + // * @return std::string + // */ + // static std::string createSubscribeTopic(const std::string &company, const std::string &vehicleName); + + // /// MQTT client handling the connection + // std::unique_ptr client_ { nullptr }; + // /// Unique ID of the client, changes with every connection + // std::string clientId_ {}; + // /// Topic to publish messages to, sender is external client, receiver is external server + // std::string publishTopic_ {}; + // /// Topic to subscribe to, sender is external server, receiver is external client + // std::string subscribeTopic_ {}; + // /// MQTT library connection options + // mqtt::connect_options connopts_ {}; + // /// Address of the MQTT server + // std::string serverAddress_ {}; + // /// MQTT QOS level. Level 1 assures that message is delivered at least once + // constexpr static int8_t qos { 1 }; + // /// Mutex to prevent deadlocks when receiving messages + // std::mutex receiveMessageMutex_ {}; +}; + +} diff --git a/include/bringauto/settings/Constants.hpp b/include/bringauto/settings/Constants.hpp index 3aa6238..2f9b148 100644 --- a/include/bringauto/settings/Constants.hpp +++ b/include/bringauto/settings/Constants.hpp @@ -164,8 +164,10 @@ class Constants { inline static constexpr std::string_view PROTOCOL_TYPE { "protocol-type" }; inline static constexpr std::string_view MQTT { "MQTT" }; + inline static constexpr std::string_view QUIC { "QUIC" }; inline static constexpr std::string_view DUMMY { "DUMMY" }; inline static constexpr std::string_view MQTT_SETTINGS { "mqtt-settings" }; + inline static constexpr std::string_view QUIC_SETTINGS { "quic-settings" }; inline static constexpr std::string_view SSL { "ssl" }; inline static constexpr std::string_view CA_FILE { "ca-file" }; inline static constexpr std::string_view CLIENT_CERT { "client-cert" }; diff --git a/include/bringauto/structures/ExternalConnectionSettings.hpp b/include/bringauto/structures/ExternalConnectionSettings.hpp index bdc462e..5aee40b 100644 --- a/include/bringauto/structures/ExternalConnectionSettings.hpp +++ b/include/bringauto/structures/ExternalConnectionSettings.hpp @@ -14,6 +14,7 @@ namespace bringauto::structures { enum class ProtocolType { INVALID = -1, MQTT, + QUIC, DUMMY }; diff --git a/source/bringauto/common_utils/EnumUtils.cpp b/source/bringauto/common_utils/EnumUtils.cpp index d7ab700..ae8976d 100644 --- a/source/bringauto/common_utils/EnumUtils.cpp +++ b/source/bringauto/common_utils/EnumUtils.cpp @@ -10,6 +10,8 @@ structures::ProtocolType EnumUtils::stringToProtocolType(std::string toEnum) { std::transform(toEnum.begin(), toEnum.end(), toEnum.begin(), ::toupper); if(toEnum == settings::Constants::MQTT) { return structures::ProtocolType::MQTT; + } else if(toEnum == settings::Constants::QUIC) { + return structures::ProtocolType::QUIC; } else if(toEnum == settings::Constants::DUMMY) { return structures::ProtocolType::DUMMY; } diff --git a/source/bringauto/external_client/ExternalClient.cpp b/source/bringauto/external_client/ExternalClient.cpp index 65c964f..5835491 100644 --- a/source/bringauto/external_client/ExternalClient.cpp +++ b/source/bringauto/external_client/ExternalClient.cpp @@ -10,6 +10,7 @@ #include +#include "bringauto/external_client/connection/communication/QuicCommunication.hpp" namespace bringauto::external_client { @@ -99,6 +100,11 @@ void ExternalClient::initConnections() { connectionSettings, context_->settings->company, context_->settings->vehicleName ); break; + case structures::ProtocolType::QUIC: + communicationChannel = std::make_shared( + connectionSettings, context_->settings->company, context_->settings->vehicleName + ); + break; case structures::ProtocolType::DUMMY: communicationChannel = std::make_shared( connectionSettings diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp new file mode 100644 index 0000000..ea0445e --- /dev/null +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -0,0 +1,170 @@ +#include +#include + +#include + +#include + + +namespace bringauto::external_client::connection::communication { + + QuicCommunication::QuicCommunication(const structures::ExternalConnectionSettings &settings, const std::string &company, + const std::string &vehicleName) : ICommunicationChannel(settings), + certFile_(settings.protocolSettings.at(std::string(settings::Constants::CLIENT_CERT))), + keyFile_(settings.protocolSettings.at(std::string(settings::Constants::CLIENT_KEY))), + caFile_(settings.protocolSettings.at(std::string(settings::Constants::CA_FILE))) + { + std::cout << "QuicCommunication for " << vehicleName << "/" << company << std::endl; + + // TODO: Implement + + alpn_ = "sample"; + alpnBuffer_.Buffer = reinterpret_cast(alpn_.data()); + alpnBuffer_.Length = static_cast(alpn_.size()); + + QUIC_STATUS status = MsQuicOpen2(&quic_); + if (QUIC_FAILED(status)) { + throw std::runtime_error("Failed to open QUIC"); + } + + QUIC_REGISTRATION_CONFIG config {}; + config.AppName = const_cast("module-gateway-quic-client"); + config.ExecutionProfile = QUIC_EXECUTION_PROFILE_LOW_LATENCY; + + status = quic_->RegistrationOpen(&config, ®istration_); + if (QUIC_FAILED(status)) { + throw std::runtime_error("Failed to open QUIC registration"); + } + + status = quic_->ConfigurationOpen( + registration_, + &alpnBuffer_, + 1, + nullptr, + 0, + nullptr, + &config_ + ); + + if (QUIC_FAILED(status)) { + throw std::runtime_error("Failed to open QUIC configuration"); + } + + certificate_.CertificateFile = certFile_.c_str(); + certificate_.PrivateKeyFile = keyFile_.c_str(); + + credential_.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE; + credential_.Flags = QUIC_CREDENTIAL_FLAG_CLIENT | QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_FILE; + credential_.CertificateFile = &certificate_; + credential_.CaCertificateFile = caFile_.c_str(); + + status = quic_->ConfigurationLoadCredential(config_, &credential_); + if (QUIC_FAILED(status)) { + throw std::runtime_error("Failed to load QUIC credential"); + } + + status = quic_->ConnectionOpen(registration_, connectionCallback, this, &connection_); + if (QUIC_FAILED(status)) { + throw std::runtime_error("Failed to open QUIC connection"); + } + + status = quic_->ConnectionStart( + connection_, + config_, + QUIC_ADDRESS_FAMILY_INET, + settings.serverIp.c_str(), + settings.port + ); + + if (QUIC_FAILED(status)) { + throw std::runtime_error("Failed to start QUIC connection"); + } + + // loadMsQuic(); + // initRegistration("quic_client"); + // initConfiguration(); + } + + QuicCommunication::~QuicCommunication() { + // TODO: Implement + + // stop() + } + + void QuicCommunication::initializeConnection() { + // TODO: Implement + + // initConnection(); + // isRunning_ = true; + + } + + bool QuicCommunication::sendMessage(ExternalProtocol::ExternalClient *message) { + (void) message; + return true; + + // TODO: Implement + + // send() + } + + std::shared_ptr QuicCommunication::receiveMessage() { + return nullptr; + + // TODO: Implement + + // Budeme potřebovat? + } + + void QuicCommunication::closeConnection() { + // TODO: Implement + + // stop() + } + + QUIC_STATUS QUIC_API QuicCommunication::connectionCallback(HQUIC connection, + void* context, + QUIC_CONNECTION_EVENT* event) { + auto* self = static_cast(context); + + switch (event->Type) { + case QUIC_CONNECTION_EVENT_CONNECTED: { + + std::cout << "[quic] Connected\n"; + + // self->connectionState_ = ConnectionState::Connected; + // self->tryFlushQueue(); + break; + } + + case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE: { + std::cout << "[quic] Connection shutdown complete\n"; + + self->quic_->ConnectionClose(connection); + self->connection_ = nullptr; + // self->connectionState_ = ConnectionState::Disconnected; + break; + } + + // case QUIC_CONNECTION_EVENT_RESUMPTION_TICKET_RECEIVED: { + // if (self->zeroRttMode_ == ZeroRttMode::Enabled) { + // self->resumptionTicket_.assign( + // event->RESUMPTION_TICKET_RECEIVED.ResumptionTicket, + // event->RESUMPTION_TICKET_RECEIVED.ResumptionTicket + + // event->RESUMPTION_TICKET_RECEIVED.ResumptionTicketLength + // ); + // + // std::cout << "[quic] Resumption ticket received!\n"; + // } + // break; + // } + + default: { + std::cout << "[quic] Connection event type: 0x" << std::hex << event->Type << std::dec << "\n"; + break; + } + } + + return QUIC_STATUS_SUCCESS; + } +} diff --git a/source/bringauto/settings/SettingsParser.cpp b/source/bringauto/settings/SettingsParser.cpp index ae2e332..d149b11 100644 --- a/source/bringauto/settings/SettingsParser.cpp +++ b/source/bringauto/settings/SettingsParser.cpp @@ -186,6 +186,9 @@ void SettingsParser::fillExternalConnectionSettings(const nlohmann::json &file) case structures::ProtocolType::MQTT: settingsName = std::string(Constants::MQTT_SETTINGS); break; + case structures::ProtocolType::QUIC: + settingsName = std::string(Constants::QUIC_SETTINGS); + break; case structures::ProtocolType::DUMMY: break; case structures::ProtocolType::INVALID: @@ -200,7 +203,13 @@ void SettingsParser::fillExternalConnectionSettings(const nlohmann::json &file) if(!settingsName.empty() && endpoint.find(settingsName) != endpoint.end()) { for(auto &[key, val]: endpoint[settingsName].items()) { + if (val.is_string()) { + externalConnectionSettings.protocolSettings[key] = val.get(); + std::cout << externalConnectionSettings.protocolSettings[key] << std::endl; + continue; + } externalConnectionSettings.protocolSettings[key] = to_string(val); + std::cout << externalConnectionSettings.protocolSettings[key] << std::endl; } } @@ -241,6 +250,9 @@ std::string SettingsParser::serializeToJson() const { case structures::ProtocolType::MQTT: settingsName = std::string(Constants::MQTT_SETTINGS); break; + case structures::ProtocolType::QUIC: + settingsName = std::string(Constants::QUIC_SETTINGS); + break; case structures::ProtocolType::DUMMY: settingsName = std::string(Constants::DUMMY); break; @@ -249,7 +261,11 @@ std::string SettingsParser::serializeToJson() const { break; } for(const auto &[key, val]: endpoint.protocolSettings) { - endpointAsJson[settingsName][key] = nlohmann::json::parse(val); + if (nlohmann::json::accept(val)) { + endpointAsJson[settingsName][key] = nlohmann::json::parse(val); + continue; + } + endpointAsJson[settingsName][key] = val; } endpoints.push_back(endpointAsJson); } From 1bd7893ef1e6051b6bd618b45bd5673197e077f4 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Thu, 18 Dec 2025 13:27:09 +0000 Subject: [PATCH 02/30] Refactor QuicCommunication: Modularize QUIC initialization, improve logging, and implement connection management. --- .../communication/QuicCommunication.hpp | 15 +- .../communication/QuicCommunication.cpp | 189 ++++++++++-------- 2 files changed, 115 insertions(+), 89 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index 7a49c34..45080f5 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -31,17 +31,18 @@ class QuicCommunication: public ICommunicationChannel { std::string alpn_; QUIC_BUFFER alpnBuffer_ {}; - QUIC_CERTIFICATE_FILE certificate_ {}; - QUIC_CREDENTIAL_CONFIG credential_ {}; - - - - std::string host_; - uint16_t port_ { 0 }; std::string certFile_; std::string keyFile_; std::string caFile_; + /// ---------- Connection ---------- + void loadMsQuic(); + void initRegistration(const char *appName); + void initConfiguration(); + void configurationOpen(const QUIC_SETTINGS *settings); + void configurationLoadCredential(const QUIC_CREDENTIAL_CONFIG *credential) const; + void stop(); + static QUIC_STATUS QUIC_API connectionCallback(HQUIC connection, void *context, QUIC_CONNECTION_EVENT *event); /// void connect(); diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index ea0445e..510c811 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -5,6 +5,8 @@ #include +#include "bringauto/settings/LoggerId.hpp" + namespace bringauto::external_client::connection::communication { @@ -14,89 +16,38 @@ namespace bringauto::external_client::connection::communication { keyFile_(settings.protocolSettings.at(std::string(settings::Constants::CLIENT_KEY))), caFile_(settings.protocolSettings.at(std::string(settings::Constants::CA_FILE))) { - std::cout << "QuicCommunication for " << vehicleName << "/" << company << std::endl; - - // TODO: Implement - alpn_ = "sample"; alpnBuffer_.Buffer = reinterpret_cast(alpn_.data()); alpnBuffer_.Length = static_cast(alpn_.size()); - QUIC_STATUS status = MsQuicOpen2(&quic_); - if (QUIC_FAILED(status)) { - throw std::runtime_error("Failed to open QUIC"); - } - - QUIC_REGISTRATION_CONFIG config {}; - config.AppName = const_cast("module-gateway-quic-client"); - config.ExecutionProfile = QUIC_EXECUTION_PROFILE_LOW_LATENCY; + settings::Logger::logInfo("Initialize QUIC connection to {}:{} for {}/{}", settings.serverIp, settings.port, company, vehicleName); - status = quic_->RegistrationOpen(&config, ®istration_); - if (QUIC_FAILED(status)) { - throw std::runtime_error("Failed to open QUIC registration"); - } - - status = quic_->ConfigurationOpen( - registration_, - &alpnBuffer_, - 1, - nullptr, - 0, - nullptr, - &config_ - ); - - if (QUIC_FAILED(status)) { - throw std::runtime_error("Failed to open QUIC configuration"); - } - - certificate_.CertificateFile = certFile_.c_str(); - certificate_.PrivateKeyFile = keyFile_.c_str(); - - credential_.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE; - credential_.Flags = QUIC_CREDENTIAL_FLAG_CLIENT | QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_FILE; - credential_.CertificateFile = &certificate_; - credential_.CaCertificateFile = caFile_.c_str(); + loadMsQuic(); + initRegistration("module-gateway-quic-client"); + initConfiguration(); + } - status = quic_->ConfigurationLoadCredential(config_, &credential_); - if (QUIC_FAILED(status)) { - throw std::runtime_error("Failed to load QUIC credential"); - } + QuicCommunication::~QuicCommunication() { + stop(); + } - status = quic_->ConnectionOpen(registration_, connectionCallback, this, &connection_); + void QuicCommunication::initializeConnection() { + QUIC_STATUS status = quic_->ConnectionOpen(registration_, connectionCallback, this, &connection_); if (QUIC_FAILED(status)) { - throw std::runtime_error("Failed to open QUIC connection"); + settings::Logger::logError("[quic] Failed to open QUIC connection; QUIC_STATUS => {:x}", status); } status = quic_->ConnectionStart( connection_, config_, QUIC_ADDRESS_FAMILY_INET, - settings.serverIp.c_str(), - settings.port + settings_.serverIp.c_str(), + settings_.port ); if (QUIC_FAILED(status)) { - throw std::runtime_error("Failed to start QUIC connection"); + settings::Logger::logError("[quic] Failed to start QUIC connection; QUIC_STATUS => {:x}", status); } - - // loadMsQuic(); - // initRegistration("quic_client"); - // initConfiguration(); - } - - QuicCommunication::~QuicCommunication() { - // TODO: Implement - - // stop() - } - - void QuicCommunication::initializeConnection() { - // TODO: Implement - - // initConnection(); - // isRunning_ = true; - } bool QuicCommunication::sendMessage(ExternalProtocol::ExternalClient *message) { @@ -122,6 +73,94 @@ namespace bringauto::external_client::connection::communication { // stop() } + + /// ---------- Connection ---------- + void QuicCommunication::loadMsQuic() { + QUIC_STATUS status = MsQuicOpen2(&quic_); + if (QUIC_FAILED(status)) { + settings::Logger::logError("[quic] Failed to open QUIC; QUIC_STATUS => {:x}", status); + } + } + + void QuicCommunication::initRegistration(const char* appName) { + QUIC_REGISTRATION_CONFIG config {}; + config.AppName = const_cast(appName); + config.ExecutionProfile = QUIC_EXECUTION_PROFILE_LOW_LATENCY; + + QUIC_STATUS status = quic_->RegistrationOpen(&config, ®istration_); + if (QUIC_FAILED(status)) { + settings::Logger::logError("[quic] Failed to open QUIC registration; QUIC_STATUS => {:x}", status); + } + } + + void QuicCommunication::initConfiguration() { + configurationOpen(nullptr); + + QUIC_CERTIFICATE_FILE certificate {}; + certificate.CertificateFile = certFile_.c_str(); + certificate.PrivateKeyFile = keyFile_.c_str(); + + QUIC_CREDENTIAL_CONFIG credential {}; + credential.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE; + credential.Flags = QUIC_CREDENTIAL_FLAG_CLIENT | QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_FILE; + credential.CertificateFile = &certificate; + credential.CaCertificateFile = caFile_.c_str(); + + configurationLoadCredential(&credential); + } + + void QuicCommunication::configurationOpen(const QUIC_SETTINGS* settings) { + const uint32_t settingsSize = settings ? sizeof(*settings) : 0; + + QUIC_STATUS status = quic_->ConfigurationOpen( + registration_, + &alpnBuffer_, + 1, + settings, + settingsSize, + nullptr, + &config_ + ); + + if (QUIC_FAILED(status)) { + settings::Logger::logError("[quic] Failed to open QUIC configuration; QUIC_STATUS => {:x}", status); + } + } + + void QuicCommunication::configurationLoadCredential(const QUIC_CREDENTIAL_CONFIG* credential) const { + const QUIC_STATUS status = quic_->ConfigurationLoadCredential(config_, credential); + if (QUIC_FAILED(status)) { + settings::Logger::logError("[quic] Failed to load QUIC credential; QUIC_STATUS => {:x}", status); + } + } + + void QuicCommunication::stop() { + // if (!isRunning_.exchange(false)) { + // return; + // } + + if (connection_) { + quic_->ConnectionShutdown(connection_, QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0); + quic_->ConnectionClose(connection_); + } + if (config_) { + quic_->ConfigurationClose(config_); + } + if (registration_) { + quic_->RegistrationClose(registration_); + } + if (quic_) { + MsQuicClose(quic_); + } + + settings::Logger::logInfo("[quic] Connection stopped"); + + connection_ = nullptr; + config_ = nullptr; + registration_ = nullptr; + quic_ = nullptr; + } + QUIC_STATUS QUIC_API QuicCommunication::connectionCallback(HQUIC connection, void* context, QUIC_CONNECTION_EVENT* event) { @@ -129,8 +168,7 @@ namespace bringauto::external_client::connection::communication { switch (event->Type) { case QUIC_CONNECTION_EVENT_CONNECTED: { - - std::cout << "[quic] Connected\n"; + settings::Logger::logInfo("[quic] Connected to server"); // self->connectionState_ = ConnectionState::Connected; // self->tryFlushQueue(); @@ -138,7 +176,7 @@ namespace bringauto::external_client::connection::communication { } case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE: { - std::cout << "[quic] Connection shutdown complete\n"; + settings::Logger::logInfo("[quic] Connection shutdown complete"); self->quic_->ConnectionClose(connection); self->connection_ = nullptr; @@ -146,21 +184,8 @@ namespace bringauto::external_client::connection::communication { break; } - // case QUIC_CONNECTION_EVENT_RESUMPTION_TICKET_RECEIVED: { - // if (self->zeroRttMode_ == ZeroRttMode::Enabled) { - // self->resumptionTicket_.assign( - // event->RESUMPTION_TICKET_RECEIVED.ResumptionTicket, - // event->RESUMPTION_TICKET_RECEIVED.ResumptionTicket + - // event->RESUMPTION_TICKET_RECEIVED.ResumptionTicketLength - // ); - // - // std::cout << "[quic] Resumption ticket received!\n"; - // } - // break; - // } - default: { - std::cout << "[quic] Connection event type: 0x" << std::hex << event->Type << std::dec << "\n"; + settings::Logger::logInfo("[quic] Unexpected connection event 0x{:x}", static_cast(event->Type)); break; } } From f5d1ef520b6685e397dd727d577aa77936010f0f Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Mon, 22 Dec 2025 11:41:57 +0000 Subject: [PATCH 03/30] Enhance QuicCommunication: Add stream callback + message sending, implement connection state management, improve logging, and refine cleanup processes. --- .../communication/QuicCommunication.hpp | 11 + .../communication/QuicCommunication.cpp | 192 +++++++++++++++--- 2 files changed, 174 insertions(+), 29 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index 45080f5..55e756c 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -24,6 +24,8 @@ class QuicCommunication: public ICommunicationChannel { void closeConnection() override; private: + enum class ConnectionState : uint8_t { DISCONNECTED, CONNECTING, CONNECTED, CLOSING }; + const QUIC_API_TABLE* quic_ { nullptr }; HQUIC registration_ { nullptr }; HQUIC config_ { nullptr }; @@ -35,6 +37,8 @@ class QuicCommunication: public ICommunicationChannel { std::string keyFile_; std::string caFile_; + std::atomic connectionState_ { ConnectionState::DISCONNECTED }; + /// ---------- Connection ---------- void loadMsQuic(); void initRegistration(const char *appName); @@ -43,7 +47,14 @@ class QuicCommunication: public ICommunicationChannel { void configurationLoadCredential(const QUIC_CREDENTIAL_CONFIG *credential) const; void stop(); + /// ---------- Closing client ---------- + void closeMsQuic(); + void closeConfiguration(); + void closeRegistration(); + + /// ---------- Callbacks ---------- static QUIC_STATUS QUIC_API connectionCallback(HQUIC connection, void *context, QUIC_CONNECTION_EVENT *event); + static unsigned int streamCallback(HQUIC stream, void *context, QUIC_STREAM_EVENT *event); /// void connect(); diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 510c811..9d2b50d 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -20,7 +20,7 @@ namespace bringauto::external_client::connection::communication { alpnBuffer_.Buffer = reinterpret_cast(alpn_.data()); alpnBuffer_.Length = static_cast(alpn_.size()); - settings::Logger::logInfo("Initialize QUIC connection to {}:{} for {}/{}", settings.serverIp, settings.port, company, vehicleName); + settings::Logger::logInfo("[quic] Initialize QUIC connection to {}:{} for {}/{}", settings.serverIp, settings.port, company, vehicleName); loadMsQuic(); initRegistration("module-gateway-quic-client"); @@ -32,6 +32,14 @@ namespace bringauto::external_client::connection::communication { } void QuicCommunication::initializeConnection() { + settings::Logger::logInfo("[quic] Connecting to server when {}", toString(connectionState_)); + + + ConnectionState expected = ConnectionState::DISCONNECTED; + if (! connectionState_.compare_exchange_strong(expected, ConnectionState::CONNECTING, std::memory_order_acq_rel)) { + return; + } + QUIC_STATUS status = quic_->ConnectionOpen(registration_, connectionCallback, this, &connection_); if (QUIC_FAILED(status)) { settings::Logger::logError("[quic] Failed to open QUIC connection; QUIC_STATUS => {:x}", status); @@ -48,15 +56,48 @@ namespace bringauto::external_client::connection::communication { if (QUIC_FAILED(status)) { settings::Logger::logError("[quic] Failed to start QUIC connection; QUIC_STATUS => {:x}", status); } + + connectionState_ = ConnectionState::CONNECTING; } bool QuicCommunication::sendMessage(ExternalProtocol::ExternalClient *message) { - (void) message; - return true; + HQUIC stream { nullptr }; + if (QUIC_FAILED(quic_->StreamOpen(connection_, QUIC_STREAM_OPEN_FLAG_NONE, streamCallback, this, &stream))) { + settings::Logger::logError("[quic] StreamOpen failed"); + return false; + } - // TODO: Implement + const auto size = message->ByteSizeLong(); + const auto buffer = std::make_unique(size); + + if (!message->SerializeToArray(buffer.get(), static_cast(size))) { + settings::Logger::logError("[quic] Message serialization failed"); + return false; + } + + auto* buf = new QUIC_BUFFER{}; + buf->Length = static_cast(size); + buf->Buffer = new uint8_t[buf->Length]; + + std::memcpy(buf->Buffer, buffer.get(), buf->Length); + + const QUIC_STATUS status = quic_->StreamSend( + stream, + buf, + 1, + QUIC_SEND_FLAG_START | QUIC_SEND_FLAG_FIN, + buf + ); - // send() + if (QUIC_FAILED(status)) { + delete[] buf->Buffer; + delete buf; + + settings::Logger::logError("[quic] Failed to send QUIC stream; QUIC_STATUS => {:x}", status); + return false; + } + + return true; } std::shared_ptr QuicCommunication::receiveMessage() { @@ -67,14 +108,8 @@ namespace bringauto::external_client::connection::communication { // Budeme potřebovat? } - void QuicCommunication::closeConnection() { - // TODO: Implement - - // stop() - } - - /// ---------- Connection ---------- + void QuicCommunication::loadMsQuic() { QUIC_STATUS status = MsQuicOpen2(&quic_); if (QUIC_FAILED(status)) { @@ -134,43 +169,61 @@ namespace bringauto::external_client::connection::communication { } } - void QuicCommunication::stop() { - // if (!isRunning_.exchange(false)) { - // return; - // } + /// ---------- Closing client ---------- - if (connection_) { - quic_->ConnectionShutdown(connection_, QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0); - quic_->ConnectionClose(connection_); + void QuicCommunication::closeConnection() { + if (! connection_) { + return; } + + quic_->ConnectionShutdown(connection_, QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0); + + // Waiting for QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE then continue in connectionCallback + } + + void QuicCommunication::closeConfiguration() { if (config_) { quic_->ConfigurationClose(config_); } + + config_ = nullptr; + } + + void QuicCommunication::closeRegistration() { if (registration_) { quic_->RegistrationClose(registration_); } + + registration_ = nullptr; + } + + void QuicCommunication::closeMsQuic() { if (quic_) { MsQuicClose(quic_); } - settings::Logger::logInfo("[quic] Connection stopped"); - - connection_ = nullptr; - config_ = nullptr; - registration_ = nullptr; quic_ = nullptr; } - QUIC_STATUS QUIC_API QuicCommunication::connectionCallback(HQUIC connection, - void* context, - QUIC_CONNECTION_EVENT* event) { + void QuicCommunication::stop() { + closeConnection(); + closeConfiguration(); + closeRegistration(); + closeMsQuic(); + + settings::Logger::logInfo("[quic] Connection stopped"); + } + + /// ---------- Callbacks ---------- + + QUIC_STATUS QUIC_API QuicCommunication::connectionCallback(HQUIC connection, void* context, QUIC_CONNECTION_EVENT* event) { auto* self = static_cast(context); switch (event->Type) { case QUIC_CONNECTION_EVENT_CONNECTED: { settings::Logger::logInfo("[quic] Connected to server"); - // self->connectionState_ = ConnectionState::Connected; + self->connectionState_ = ConnectionState::CONNECTED; // self->tryFlushQueue(); break; } @@ -179,11 +232,17 @@ namespace bringauto::external_client::connection::communication { settings::Logger::logInfo("[quic] Connection shutdown complete"); self->quic_->ConnectionClose(connection); + self->connection_ = nullptr; - // self->connectionState_ = ConnectionState::Disconnected; + self->connectionState_ = ConnectionState::DISCONNECTED; break; } + case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER: + settings::Logger::logWarning("[quic] Connection shutdown initiated"); + self->connectionState_ = ConnectionState::CLOSING; + break; + default: { settings::Logger::logInfo("[quic] Unexpected connection event 0x{:x}", static_cast(event->Type)); break; @@ -192,4 +251,79 @@ namespace bringauto::external_client::connection::communication { return QUIC_STATUS_SUCCESS; } + + QUIC_STATUS QUIC_API QuicCommunication::streamCallback(HQUIC stream, void* context, QUIC_STREAM_EVENT* event) { + auto* self = static_cast(context); + + switch (event->Type) { + case QUIC_STREAM_EVENT_RECEIVE: { + settings::Logger::logInfo("[quic] Received {:d} bytes", event->RECEIVE.TotalBufferLength); + + //quicHandle(event->RECEIVE.Buffers, event->RECEIVE.BufferCount); + self->quic_->StreamReceiveComplete(stream, event->RECEIVE.TotalBufferLength); + break; + } + + case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN: { + settings::Logger::logInfo("[quic] Peer stream send shutdown"); + self->quic_->StreamShutdown(stream, QUIC_STREAM_SHUTDOWN_FLAG_GRACEFUL, 0); + break; + } + + case QUIC_STREAM_EVENT_START_COMPLETE: { + settings::Logger::logInfo("[quic] Stream start completed"); + break; + } + + case QUIC_STREAM_EVENT_SEND_COMPLETE: { + /** + * This event is raised when MsQuic has finished processing + * a single StreamSend request. + * + * Meaning: + * - MsQuic no longer needs the application-provided buffer: + * - the data has been acknowledged (ACKed) by the peer + * at the QUIC transport level and will not be retransmitted + * - OR the send was canceled (Canceled == TRUE), e.g. due to + * stream or connection shutdown + * + * Reliability semantics: + * - the ACK is strictly a QUIC transport-level acknowledgment + * - it does NOT mean the peer application has read or processed + * the data + * + * Practical consequence: + * - this is the only correct place to safely free the memory + * passed to StreamSend (via ClientContext) + */ + if (event->SEND_COMPLETE.Canceled) { + settings::Logger::logError("[quic] Stream send canceled"); + } else { + settings::Logger::logInfo("[quic] Stream send completed"); + } + + const auto* buf = + static_cast(event->SEND_COMPLETE.ClientContext); + + if (buf) { + delete[] buf->Buffer; + delete buf; + } + break; + } + + case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: { + settings::Logger::logInfo("[quic] Stream shutdown complete"); + self->quic_->StreamClose(stream); + break; + } + + default: { + settings::Logger::logInfo("[quic] Unexpected stream event 0x{:x}", static_cast(event->Type)); + break; + } + } + + return QUIC_STATUS_SUCCESS; + } } From ccf380e6fa0f52ad48ad06933ef6be35898ced8c Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Mon, 22 Dec 2025 11:56:32 +0000 Subject: [PATCH 04/30] Add `toString` method for `ConnectionState` in QuicCommunication for improved state debugging --- .../connection/communication/QuicCommunication.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index 55e756c..0354e43 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -101,6 +101,16 @@ class QuicCommunication: public ICommunicationChannel { // constexpr static int8_t qos { 1 }; // /// Mutex to prevent deadlocks when receiving messages // std::mutex receiveMessageMutex_ {}; + + static const char* toString(ConnectionState state) { + switch (state) { + case ConnectionState::DISCONNECTED: return "Disconnected"; + case ConnectionState::CONNECTING: return "Connecting"; + case ConnectionState::CONNECTED: return "Connected"; + case ConnectionState::CLOSING: return "Closing"; + default: return "Unknown"; + } + } }; } From a052f92e4ff4dc81338a7a3ab2330af586b5eea7 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Sat, 3 Jan 2026 12:13:06 +0000 Subject: [PATCH 05/30] Implement message queuing and threading in QuicCommunication for bidirectional communication --- .../communication/QuicCommunication.hpp | 23 +- .../communication/QuicCommunication.cpp | 249 ++++++++++++++---- 2 files changed, 217 insertions(+), 55 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index 0354e43..72b9c1f 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -1,8 +1,11 @@ #pragma once +#include #include #include +#include +#include namespace bringauto::external_client::connection::communication { @@ -13,8 +16,6 @@ class QuicCommunication: public ICommunicationChannel { ~QuicCommunication() override; - /// void setProperties(const std::string &company, const std::string &vehicleName); - void initializeConnection() override; bool sendMessage(ExternalProtocol::ExternalClient *message) override; @@ -39,6 +40,18 @@ class QuicCommunication: public ICommunicationChannel { std::atomic connectionState_ { ConnectionState::DISCONNECTED }; + // inbound (server → client) + std::queue> inboundQueue_; + std::mutex inboundMutex_; + std::condition_variable inboundCv_; + + // outbound (client → server) + std::queue> outboundQueue_; + std::mutex outboundMutex_; + std::condition_variable outboundCv_; + + std::jthread senderThread_; + /// ---------- Connection ---------- void loadMsQuic(); void initRegistration(const char *appName); @@ -47,6 +60,10 @@ class QuicCommunication: public ICommunicationChannel { void configurationLoadCredential(const QUIC_CREDENTIAL_CONFIG *credential) const; void stop(); + void onMessageDecoded(std::shared_ptr msg); + + bool sendViaQuicStream(const std::shared_ptr &message); + /// ---------- Closing client ---------- void closeMsQuic(); void closeConfiguration(); @@ -56,6 +73,8 @@ class QuicCommunication: public ICommunicationChannel { static QUIC_STATUS QUIC_API connectionCallback(HQUIC connection, void *context, QUIC_CONNECTION_EVENT *event); static unsigned int streamCallback(HQUIC stream, void *context, QUIC_STREAM_EVENT *event); + void senderLoop(); + /// void connect(); // /** diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 9d2b50d..20af789 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "bringauto/settings/LoggerId.hpp" @@ -60,52 +61,37 @@ namespace bringauto::external_client::connection::communication { connectionState_ = ConnectionState::CONNECTING; } - bool QuicCommunication::sendMessage(ExternalProtocol::ExternalClient *message) { - HQUIC stream { nullptr }; - if (QUIC_FAILED(quic_->StreamOpen(connection_, QUIC_STREAM_OPEN_FLAG_NONE, streamCallback, this, &stream))) { - settings::Logger::logError("[quic] StreamOpen failed"); - return false; - } + bool QuicCommunication::sendMessage(ExternalProtocol::ExternalClient* message) { + settings::Logger::logInfo("[quic] Enqueueing message"); + auto copy = std::make_shared(*message); - const auto size = message->ByteSizeLong(); - const auto buffer = std::make_unique(size); - - if (!message->SerializeToArray(buffer.get(), static_cast(size))) { - settings::Logger::logError("[quic] Message serialization failed"); - return false; + { + std::lock_guard lock(outboundMutex_); + outboundQueue_.push(std::move(copy)); } - - auto* buf = new QUIC_BUFFER{}; - buf->Length = static_cast(size); - buf->Buffer = new uint8_t[buf->Length]; - - std::memcpy(buf->Buffer, buffer.get(), buf->Length); - - const QUIC_STATUS status = quic_->StreamSend( - stream, - buf, - 1, - QUIC_SEND_FLAG_START | QUIC_SEND_FLAG_FIN, - buf - ); - - if (QUIC_FAILED(status)) { - delete[] buf->Buffer; - delete buf; - - settings::Logger::logError("[quic] Failed to send QUIC stream; QUIC_STATUS => {:x}", status); - return false; - } - + settings::Logger::logInfo("[quic] Notifying sender thread"); + outboundCv_.notify_one(); return true; } std::shared_ptr QuicCommunication::receiveMessage() { - return nullptr; + std::unique_lock lock(inboundMutex_); + + if (!inboundCv_.wait_for( + lock, + settings::receive_message_timeout, + [this] { return !inboundQueue_.empty() || connectionState_.load() != ConnectionState::CONNECTED; } + )) { + return nullptr; + } - // TODO: Implement + if (connectionState_.load() != ConnectionState::CONNECTED || inboundQueue_.empty()) { + return nullptr; + } - // Budeme potřebovat? + auto msg = inboundQueue_.front(); + inboundQueue_.pop(); + return msg; } /// ---------- Connection ---------- @@ -211,9 +197,72 @@ namespace bringauto::external_client::connection::communication { closeRegistration(); closeMsQuic(); + inboundCv_.notify_all(); + outboundCv_.notify_all(); + settings::Logger::logInfo("[quic] Connection stopped"); } + + /// ---------- Outgoings ---------- + void QuicCommunication::onMessageDecoded( + std::shared_ptr msg + ) { + { + std::lock_guard lock(inboundMutex_); + settings::Logger::logInfo("[quic] Moving message to inboundQueue"); + inboundQueue_.push(std::move(msg)); + } + settings::Logger::logInfo("[quic] Notifying receiver thread"); + inboundCv_.notify_one(); + } + + bool QuicCommunication::sendViaQuicStream(const std::shared_ptr& message) { + settings::Logger::logInfo("[quic] Sending message"); + + HQUIC stream { nullptr }; + if (QUIC_FAILED(quic_->StreamOpen(connection_, QUIC_STREAM_OPEN_FLAG_NONE, streamCallback, this, &stream))) { + settings::Logger::logError("[quic] StreamOpen failed"); + return false; + } + + const auto size = message->ByteSizeLong(); + const auto buffer = std::make_unique(size); + + if (!message->SerializeToArray(buffer.get(), static_cast(size))) { + settings::Logger::logError("[quic] Message serialization failed"); + return false; + } + + auto* buf = new QUIC_BUFFER{}; + buf->Length = static_cast(size); + buf->Buffer = new uint8_t[buf->Length]; + + std::memcpy(buf->Buffer, buffer.get(), buf->Length); + + const QUIC_STATUS status = quic_->StreamSend( + stream, + buf, + 1, + /** + * START => Simulates quic_->StreamStart before send + * FIN => Simulates quic_->StreamShutdown after send + */ + QUIC_SEND_FLAG_START | QUIC_SEND_FLAG_FIN, + buf + ); + + if (QUIC_FAILED(status)) { + delete[] buf->Buffer; + delete buf; + + settings::Logger::logError("[quic] Failed to send QUIC stream; QUIC_STATUS => {:x}", status); + return false; + } + + return true; + } + /// ---------- Callbacks ---------- QUIC_STATUS QUIC_API QuicCommunication::connectionCallback(HQUIC connection, void* context, QUIC_CONNECTION_EVENT* event) { @@ -223,25 +272,34 @@ namespace bringauto::external_client::connection::communication { case QUIC_CONNECTION_EVENT_CONNECTED: { settings::Logger::logInfo("[quic] Connected to server"); - self->connectionState_ = ConnectionState::CONNECTED; - // self->tryFlushQueue(); + auto expected = ConnectionState::CONNECTING; + if (self->connectionState_.compare_exchange_strong(expected, ConnectionState::CONNECTED)) { + self->senderThread_ = std::jthread(&QuicCommunication::senderLoop, self); + self->outboundCv_.notify_all(); + } break; } case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE: { settings::Logger::logInfo("[quic] Connection shutdown complete"); - self->quic_->ConnectionClose(connection); + self->connectionState_ = ConnectionState::DISCONNECTED; + self->outboundCv_.notify_all(); + if (self->senderThread_.joinable()) { + self->senderThread_.request_stop(); + } + + self->quic_->ConnectionClose(connection); self->connection_ = nullptr; - self->connectionState_ = ConnectionState::DISCONNECTED; break; } - case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER: + case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER: { settings::Logger::logWarning("[quic] Connection shutdown initiated"); self->connectionState_ = ConnectionState::CLOSING; break; + } default: { settings::Logger::logInfo("[quic] Unexpected connection event 0x{:x}", static_cast(event->Type)); @@ -256,20 +314,80 @@ namespace bringauto::external_client::connection::communication { auto* self = static_cast(context); switch (event->Type) { - case QUIC_STREAM_EVENT_RECEIVE: { - settings::Logger::logInfo("[quic] Received {:d} bytes", event->RECEIVE.TotalBufferLength); - - //quicHandle(event->RECEIVE.Buffers, event->RECEIVE.BufferCount); - self->quic_->StreamReceiveComplete(stream, event->RECEIVE.TotalBufferLength); - break; - } - - case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN: { + case QUIC_STREAM_EVENT_RECEIVE: { + settings::Logger::logInfo( + "[quic] Received {:d} bytes in {:d} buffers", + event->RECEIVE.TotalBufferLength, + event->RECEIVE.BufferCount + ); + + uint64_t streamId = 0; + uint32_t streamIdLen = sizeof(streamId); + + self->quic_->GetParam( + stream, + QUIC_PARAM_STREAM_ID, + &streamIdLen, + &streamId + ); + + settings::Logger::logInfo( + "[quic] [stream {}] Event RECEIVE", + streamId + ); + + std::vector data; + data.reserve(event->RECEIVE.TotalBufferLength); + + for (uint32_t i = 0; i < event->RECEIVE.BufferCount; ++i) { + const auto& b = event->RECEIVE.Buffers[i]; + data.insert(data.end(), b.Buffer, b.Buffer + b.Length); + } + + auto msg = std::make_shared(); + if (!msg->ParseFromArray(data.data(), static_cast(data.size()))) { + settings::Logger::logError("[quic] Failed to parse ExternalServer message"); + } else { + self->onMessageDecoded(std::move(msg)); + } + + self->quic_->StreamReceiveComplete(stream, event->RECEIVE.TotalBufferLength); + + if (event->RECEIVE.Flags & QUIC_RECEIVE_FLAG_FIN) { + settings::Logger::logInfo( + "[quic] [stream {}] Peer FIN received, shutting down receive", + streamId + ); + + QUIC_STATUS status = self->quic_->StreamShutdown( + stream, + QUIC_STREAM_SHUTDOWN_FLAG_ABORT_RECEIVE, + 0 + ); + + settings::Logger::logInfo( + "[quic] [stream {}] StreamShutdown(RECEIVE) -> 0x{:x}", + streamId, + status + ); + } + + break; + } + + /// Server send FIN + case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN: { settings::Logger::logInfo("[quic] Peer stream send shutdown"); - self->quic_->StreamShutdown(stream, QUIC_STREAM_SHUTDOWN_FLAG_GRACEFUL, 0); + //self->quic_->StreamShutdown(stream, QUIC_STREAM_SHUTDOWN_FLAG_GRACEFUL, 0); break; } + /// My FIN was sended to server + case QUIC_STREAM_EVENT_SEND_SHUTDOWN_COMPLETE: { + settings::Logger::logInfo("[quic] Stream send shutdown complete"); + break; + } + case QUIC_STREAM_EVENT_START_COMPLETE: { settings::Logger::logInfo("[quic] Stream start completed"); break; @@ -312,6 +430,7 @@ namespace bringauto::external_client::connection::communication { break; } + /// Stream is closed from both sides case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: { settings::Logger::logInfo("[quic] Stream shutdown complete"); self->quic_->StreamClose(stream); @@ -326,4 +445,28 @@ namespace bringauto::external_client::connection::communication { return QUIC_STATUS_SUCCESS; } + + void QuicCommunication::senderLoop() { + settings::Logger::logInfo("[quic] Sender thread loop started"); + while (connectionState_.load() == ConnectionState::CONNECTED) { + std::unique_lock lock(outboundMutex_); + + settings::Logger::logInfo("[quic] Sender thread loop waiting for outbound queue"); + outboundCv_.wait(lock, [this] { + return !outboundQueue_.empty() || + connectionState_.load() != ConnectionState::CONNECTED; + }); + + if (connectionState_.load() != ConnectionState::CONNECTED) { + break; + } + + settings::Logger::logInfo("[quic] Sender thread loop sending outbound queue"); + auto msg = outboundQueue_.front(); + outboundQueue_.pop(); + lock.unlock(); + + sendViaQuicStream(msg); + } + } } From e55e2443316864d4b8c3115be60ac5db607591e7 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Mon, 5 Jan 2026 06:45:51 +0000 Subject: [PATCH 06/30] Refactor `QuicCommunication`: Improve comments and documentation, remove outdated code sections, and organize code structure for clarity. --- .../communication/QuicCommunication.hpp | 276 ++++++++++++++---- .../communication/QuicCommunication.cpp | 10 +- 2 files changed, 224 insertions(+), 62 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index 72b9c1f..1bb0fc9 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -16,111 +16,277 @@ class QuicCommunication: public ICommunicationChannel { ~QuicCommunication() override; + /** + * @brief Initializes a QUIC connection to the server. + * + * Attempts to establish a new QUIC connection. + * It first atomically verifies that the current connection state is + * DISCONNECTED and transitions it to CONNECTING in order to prevent + * concurrent connection attempts. + * + * After the state transition, it opens a QUIC connection handle and + * starts the connection using the configured server address, port, + * and QUIC configuration. + * + * Any failures during the connection open or start process are logged. + */ void initializeConnection() override; + /** + * @brief Enqueues an outgoing message to be sent over the QUIC connection. + * + * Creates a shared copy of the provided ExternalClient message + * and pushes it into the outbound message queue in a thread-safe manner. + * After enqueuing, it notifies the sender thread via a condition variable + * that a new message is available for sending. + * + * @param message Pointer to the message that should be sent. + * @return true Always returns true to indicate the message was successfully enqueued. + */ bool sendMessage(ExternalProtocol::ExternalClient *message) override; + /** + * @brief Receives an incoming message from the QUIC connection. + * + * Waits for an incoming message to appear in the inbound + * queue or for the connection state to change from CONNECTED. + * The wait is bounded by a configurable timeout. + * + * If the wait times out, the connection is no longer in the CONNECTED + * state, or no message is available, the function returns nullptr. + * Otherwise, it retrieves and removes the next message from the inbound + * queue and returns it. + * + * @return A shared pointer to the received ExternalServer message, + * or nullptr if no message is available or the connection is not active. + */ std::shared_ptr receiveMessage() override; + /** + * @brief Initiates a graceful shutdown of the QUIC connection. + * + * Requests an orderly shutdown of the active QUIC connection. + * If no connection is currently established, the function returns immediately. + * + * The shutdown is performed asynchronously. Completion is signaled via the + * QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE event, which is handled in + * the connectionCallback. + */ void closeConnection() override; private: + /// Represents the current state of the QUIC connection lifecycle enum class ConnectionState : uint8_t { DISCONNECTED, CONNECTING, CONNECTED, CLOSING }; + /// Pointer to the MsQuic API function table const QUIC_API_TABLE* quic_ { nullptr }; + /// QUIC registration handle associated with the application HQUIC registration_ { nullptr }; + /// QUIC configuration handle (ALPN, credentials, transport settings) HQUIC config_ { nullptr }; + /// Active QUIC connection handle HQUIC connection_ { nullptr }; + /// Application-Layer Protocol Negotiation (ALPN) string std::string alpn_; + /// QUIC buffer wrapping the ALPN string QUIC_BUFFER alpnBuffer_ {}; + /// Path to the client certificate file std::string certFile_; + /// Path to the client private key file std::string keyFile_; + /// Path to the CA certificate file std::string caFile_; + /// Atomic state of the connection used for synchronization across threads std::atomic connectionState_ { ConnectionState::DISCONNECTED }; - // inbound (server → client) + /// @name Inbound (peer → this) + /// @{ + /// Queue of incoming messages received from the peer std::queue> inboundQueue_; + /// Mutex protecting access to the inbound message queue std::mutex inboundMutex_; + /// Condition variable for signaling inbound message availability std::condition_variable inboundCv_; + /// @} - // outbound (client → server) + /// @name Outbound (this → peer) + /// @{ + /// Queue of outgoing messages to be sent to the peer std::queue> outboundQueue_; + /// Mutex protecting access to the outbound message queue std::mutex outboundMutex_; + /// Condition variable for signaling outbound message availability std::condition_variable outboundCv_; - + /// Dedicated sender thread responsible for transmitting outbound messages std::jthread senderThread_; + /// @} - /// ---------- Connection ---------- + /** + * @brief Loads and initializes the MsQuic API. + * + * Initializes the MsQuic library and retrieves the + * QUIC API function table. The resulting table is stored for later + * use when creating registrations, configurations, and connections. + * + * If the initialization fails, an error is logged. + */ void loadMsQuic(); + + /** + * @brief Initializes a QUIC registration. + * + * Creates a QUIC registration with the specified application name and + * a low-latency execution profile. The registration is required for + * creating QUIC configurations and connections. + * + * If registration creation fails, an error is logged. + * + * @param appName Application name used to identify the QUIC registration. + */ void initRegistration(const char *appName); + + /** + * @brief Initializes the QUIC configuration and loads client credentials. + * + * Opens a QUIC configuration using the configured ALPN and default QUIC + * transport settings. Client TLS credentials are then set up using a + * certificate file, private key file, and CA certificate file. + * + * If configuration creation or credential loading fails, an error is logged. + */ void initConfiguration(); + + /** + * @brief Opens a QUIC configuration. + * + * Creates a QUIC configuration associated with the current registration, + * configured ALPN, and optional transport settings. + * + * If settings are not provided, default QUIC settings are used. + * On failure, an error is logged. + * + * @param settings Optional QUIC transport settings. + */ void configurationOpen(const QUIC_SETTINGS *settings); + + /** + * @brief Loads TLS credentials into the QUIC configuration. + * + * Loads client-side TLS credentials into the active QUIC configuration. + * The credentials define the certificate, private key, and CA certificate + * used for secure communication. + * + * If credential loading fails, an error is logged. + * + * @param credential Pointer to the QUIC credential configuration. + */ void configurationLoadCredential(const QUIC_CREDENTIAL_CONFIG *credential) const; - void stop(); + /** + * @brief Handles a successfully decoded incoming message. + * + * Pushes the decoded ExternalServer message into the inbound queue + * in a thread-safe manner and notifies the receiver thread that a + * new message is available. + * + * @param msg Decoded message received from the peer. + */ void onMessageDecoded(std::shared_ptr msg); + /** + * @brief Sends a message to the peer using a QUIC stream. + * + * Opens a new QUIC stream on the active connection and serializes the + * provided ExternalClient message into a byte buffer. + * The message is sent using a single StreamSend call with START and FIN + * flags, effectively opening, sending, and closing the stream. + * + * The allocated send buffer is released asynchronously in the + * QUIC_STREAM_EVENT_SEND_COMPLETE callback. + * + * @param message Message to be sent to the peer. + * @return true if the send operation was successfully initiated, false otherwise. + */ bool sendViaQuicStream(const std::shared_ptr &message); - /// ---------- Closing client ---------- - void closeMsQuic(); + /** + * @brief Closes the active QUIC configuration. + */ void closeConfiguration(); + + /** + * @brief Closes the QUIC registration. + */ void closeRegistration(); - /// ---------- Callbacks ---------- + /** + * @brief Closes the MsQuic API and releases associated resources. + */ + void closeMsQuic(); + + /** + * @brief Stops the QUIC communication and releases all resources. + * + * Initiates connection shutdown and closes the QUIC configuration, + * registration, and MsQuic API in the correct order. + * All waiting sender and receiver threads are unblocked by notifying + * the associated condition variables. + */ + void stop(); + + /** + * @brief Handles QUIC connection-level events. + * + * Processes connection lifecycle events reported by MsQuic, including + * successful connection establishment, peer-initiated shutdown, and + * shutdown completion. + * + * All QUIC_CONNECTION_EVENT cases are documented at + * https://microsoft.github.io/msquic/msquicdocs/docs/api/QUIC_CONNECTION_EVENT.html + * + * @param connection QUIC connection handle. + * @param context User-defined context pointer (QuicCommunication instance). + * @param event Connection event information provided by MsQuic. + * @return QUIC_STATUS_SUCCESS to indicate successful event handling. + */ static QUIC_STATUS QUIC_API connectionCallback(HQUIC connection, void *context, QUIC_CONNECTION_EVENT *event); - static unsigned int streamCallback(HQUIC stream, void *context, QUIC_STREAM_EVENT *event); - void senderLoop(); + /** + * @brief Handles QUIC stream-level events. + * + * Processes stream events reported by MsQuic, including data reception, + * send completion, stream startup, and shutdown notifications. + * + * All QUIC_STREAM_EVENT cases are documented at + * https://microsoft.github.io/msquic/msquicdocs/docs/api/QUIC_STREAM_EVENT.html + * + * @param stream QUIC stream handle associated with the event. + * @param context User-defined context pointer (QuicCommunication instance). + * @param event Stream event information provided by MsQuic. + * @return QUIC_STATUS_SUCCESS to indicate successful event handling. + */ + static QUIC_STATUS QUIC_API streamCallback(HQUIC stream, void *context, QUIC_STREAM_EVENT *event); - /// void connect(); - - // /** - // * @brief Create a client id from company name and vehicle name - // * - // * @param company name of the company - // * @param vehicleName name of the vehicle - // * @return std::string - // */ - // static std::string createClientId(const std::string &company, const std::string &vehicleName); - - // /** - // * @brief Create a publish topic from company name and vehicle name - // * - // * @param company name of the company - // * @param vehicleName name of the vehicle - // * @return std::string - // */ - // static std::string createPublishTopic(const std::string &company, const std::string &vehicleName); - - // /** - // * @brief Create a subscribe topic from company name and vehicle name - // * - // * @param company name of the company - // * @param vehicleName name of the vehicle - // * @return std::string - // */ - // static std::string createSubscribeTopic(const std::string &company, const std::string &vehicleName); - - // /// MQTT client handling the connection - // std::unique_ptr client_ { nullptr }; - // /// Unique ID of the client, changes with every connection - // std::string clientId_ {}; - // /// Topic to publish messages to, sender is external client, receiver is external server - // std::string publishTopic_ {}; - // /// Topic to subscribe to, sender is external server, receiver is external client - // std::string subscribeTopic_ {}; - // /// MQTT library connection options - // mqtt::connect_options connopts_ {}; - // /// Address of the MQTT server - // std::string serverAddress_ {}; - // /// MQTT QOS level. Level 1 assures that message is delivered at least once - // constexpr static int8_t qos { 1 }; - // /// Mutex to prevent deadlocks when receiving messages - // std::mutex receiveMessageMutex_ {}; + /** + * @brief Sender thread main loop for outbound messages. + * + * Waits for messages to appear in the outbound queue while the + * connection remains in the CONNECTED state. + * When a message becomes available, it is dequeued and sent + * over a newly created QUIC stream. + * + * The loop exits when the connection state changes from CONNECTED. + */ + void senderLoop(); + /** + * @brief Converts a ConnectionState value to a human-readable string. + * + * @param state Connection state to convert. + * @return Null-terminated string describing the connection state. + */ static const char* toString(ConnectionState state) { switch (state) { case ConnectionState::DISCONNECTED: return "Disconnected"; diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 20af789..ce79818 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -94,8 +94,6 @@ namespace bringauto::external_client::connection::communication { return msg; } - /// ---------- Connection ---------- - void QuicCommunication::loadMsQuic() { QUIC_STATUS status = MsQuicOpen2(&quic_); if (QUIC_FAILED(status)) { @@ -155,8 +153,6 @@ namespace bringauto::external_client::connection::communication { } } - /// ---------- Closing client ---------- - void QuicCommunication::closeConnection() { if (! connection_) { return; @@ -164,7 +160,7 @@ namespace bringauto::external_client::connection::communication { quic_->ConnectionShutdown(connection_, QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0); - // Waiting for QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE then continue in connectionCallback + /// Asynchronously waiting for QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE event, then continue in connectionCallback } void QuicCommunication::closeConfiguration() { @@ -203,8 +199,6 @@ namespace bringauto::external_client::connection::communication { settings::Logger::logInfo("[quic] Connection stopped"); } - - /// ---------- Outgoings ---------- void QuicCommunication::onMessageDecoded( std::shared_ptr msg ) { @@ -321,6 +315,7 @@ namespace bringauto::external_client::connection::communication { event->RECEIVE.BufferCount ); + /// -------- START - Just for debugging -------- uint64_t streamId = 0; uint32_t streamIdLen = sizeof(streamId); @@ -335,6 +330,7 @@ namespace bringauto::external_client::connection::communication { "[quic] [stream {}] Event RECEIVE", streamId ); + /// -------- END - Just for debugging -------- std::vector data; data.reserve(event->RECEIVE.TotalBufferLength); From d6f401efbee4e209881b6f23d50f8fbbb563a4a6 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Mon, 5 Jan 2026 09:54:36 +0000 Subject: [PATCH 07/30] Refactor `QuicCommunication`: Extract `getStreamId` method, improve logging for stream events, and clean up debug code. --- .../communication/QuicCommunication.hpp | 15 +++++++++++++++ .../communication/QuicCommunication.cpp | 17 +++++------------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index 1bb0fc9..f63c9b5 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -296,6 +296,21 @@ class QuicCommunication: public ICommunicationChannel { default: return "Unknown"; } } + + std::optional getStreamId(HQUIC stream) const { + uint64_t streamId = 0; + uint32_t streamIdLen = sizeof(streamId); + + if (QUIC_FAILED(quic_->GetParam( + stream, + QUIC_PARAM_STREAM_ID, + &streamIdLen, + &streamId))) { + return std::nullopt; + } + + return streamId; + } }; } diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index ce79818..ce0703f 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -316,19 +316,10 @@ namespace bringauto::external_client::connection::communication { ); /// -------- START - Just for debugging -------- - uint64_t streamId = 0; - uint32_t streamIdLen = sizeof(streamId); - - self->quic_->GetParam( - stream, - QUIC_PARAM_STREAM_ID, - &streamIdLen, - &streamId - ); - + auto streamId = self->getStreamId(stream); settings::Logger::logInfo( "[quic] [stream {}] Event RECEIVE", - streamId + streamId ? *streamId : 0 ); /// -------- END - Just for debugging -------- @@ -347,6 +338,7 @@ namespace bringauto::external_client::connection::communication { self->onMessageDecoded(std::move(msg)); } + settings::Logger::logInfo("[quic] Approving stream receive completed"); self->quic_->StreamReceiveComplete(stream, event->RECEIVE.TotalBufferLength); if (event->RECEIVE.Flags & QUIC_RECEIVE_FLAG_FIN) { @@ -380,7 +372,8 @@ namespace bringauto::external_client::connection::communication { /// My FIN was sended to server case QUIC_STREAM_EVENT_SEND_SHUTDOWN_COMPLETE: { - settings::Logger::logInfo("[quic] Stream send shutdown complete"); + auto streamId = self->getStreamId(stream); + settings::Logger::logInfo("[quic] [stream {}] Stream send shutdown complete", streamId ? *streamId : 0); break; } From 724050bda4e84f9c9f93fcf33371967fc86c7128 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Mon, 5 Jan 2026 12:38:58 +0000 Subject: [PATCH 08/30] Refactor `QuicCommunication`: Simplify `StreamSend` logic, improve error handling with `StreamShutdown`, and clean up unused callbacks and logging. --- .../communication/QuicCommunication.cpp | 32 ++----------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index ce0703f..7ca6b4e 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -234,10 +234,7 @@ namespace bringauto::external_client::connection::communication { std::memcpy(buf->Buffer, buffer.get(), buf->Length); - const QUIC_STATUS status = quic_->StreamSend( - stream, - buf, - 1, + const QUIC_STATUS status = quic_->StreamSend(stream, buf, 1, /** * START => Simulates quic_->StreamStart before send * FIN => Simulates quic_->StreamShutdown after send @@ -251,14 +248,13 @@ namespace bringauto::external_client::connection::communication { delete buf; settings::Logger::logError("[quic] Failed to send QUIC stream; QUIC_STATUS => {:x}", status); + quic_->StreamShutdown(stream, QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0); return false; } return true; } - /// ---------- Callbacks ---------- - QUIC_STATUS QUIC_API QuicCommunication::connectionCallback(HQUIC connection, void* context, QUIC_CONNECTION_EVENT* event) { auto* self = static_cast(context); @@ -338,35 +334,12 @@ namespace bringauto::external_client::connection::communication { self->onMessageDecoded(std::move(msg)); } - settings::Logger::logInfo("[quic] Approving stream receive completed"); - self->quic_->StreamReceiveComplete(stream, event->RECEIVE.TotalBufferLength); - - if (event->RECEIVE.Flags & QUIC_RECEIVE_FLAG_FIN) { - settings::Logger::logInfo( - "[quic] [stream {}] Peer FIN received, shutting down receive", - streamId - ); - - QUIC_STATUS status = self->quic_->StreamShutdown( - stream, - QUIC_STREAM_SHUTDOWN_FLAG_ABORT_RECEIVE, - 0 - ); - - settings::Logger::logInfo( - "[quic] [stream {}] StreamShutdown(RECEIVE) -> 0x{:x}", - streamId, - status - ); - } - break; } /// Server send FIN case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN: { settings::Logger::logInfo("[quic] Peer stream send shutdown"); - //self->quic_->StreamShutdown(stream, QUIC_STREAM_SHUTDOWN_FLAG_GRACEFUL, 0); break; } @@ -416,6 +389,7 @@ namespace bringauto::external_client::connection::communication { delete[] buf->Buffer; delete buf; } + break; } From 6628c78452339e2b67dc6e1c5526bdda5a6e1b00 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Mon, 5 Jan 2026 12:56:20 +0000 Subject: [PATCH 09/30] Refactor `QuicCommunication`: Add detailed event handling comments, improve stream and connection logging, and clean up unnecessary debug code. --- .../communication/QuicCommunication.hpp | 11 ++++++- .../communication/QuicCommunication.cpp | 30 +++++++++++++------ 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index f63c9b5..059c956 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -297,6 +297,15 @@ class QuicCommunication: public ICommunicationChannel { } } + /** + * @brief Retrieves the QUIC stream identifier for the given stream handle. + * + * Queries MsQuic for the stream ID associated with the provided HQUIC stream. + * If the parameter query fails, an empty optional is returned. + * + * @param stream Valid QUIC stream handle. + * @return Stream identifier on success, or std::nullopt if the query fails. + */ std::optional getStreamId(HQUIC stream) const { uint64_t streamId = 0; uint32_t streamIdLen = sizeof(streamId); @@ -307,7 +316,7 @@ class QuicCommunication: public ICommunicationChannel { &streamIdLen, &streamId))) { return std::nullopt; - } + } return streamId; } diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 7ca6b4e..84e5817 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -259,17 +259,22 @@ namespace bringauto::external_client::connection::communication { auto* self = static_cast(context); switch (event->Type) { + /// Fired when the QUIC handshake is complete and the connection is ready + /// for stream creation and data transfer. case QUIC_CONNECTION_EVENT_CONNECTED: { settings::Logger::logInfo("[quic] Connected to server"); auto expected = ConnectionState::CONNECTING; if (self->connectionState_.compare_exchange_strong(expected, ConnectionState::CONNECTED)) { + /// Start sender thread only after connection is fully established self->senderThread_ = std::jthread(&QuicCommunication::senderLoop, self); self->outboundCv_.notify_all(); } break; } + /// Final notification that the connection has been fully shut down. + /// This is the last event delivered for the connection handle. case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE: { settings::Logger::logInfo("[quic] Connection shutdown complete"); @@ -285,6 +290,8 @@ namespace bringauto::external_client::connection::communication { break; } + /// Peer or transport initiated connection shutdown (error or graceful close). + /// Further sends may fail after this event. case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER: { settings::Logger::logWarning("[quic] Connection shutdown initiated"); self->connectionState_ = ConnectionState::CLOSING; @@ -292,7 +299,7 @@ namespace bringauto::external_client::connection::communication { } default: { - settings::Logger::logInfo("[quic] Unexpected connection event 0x{:x}", static_cast(event->Type)); + settings::Logger::logInfo("[quic] Unhandled connection event 0x{:x}", static_cast(event->Type)); break; } } @@ -302,8 +309,10 @@ namespace bringauto::external_client::connection::communication { QUIC_STATUS QUIC_API QuicCommunication::streamCallback(HQUIC stream, void* context, QUIC_STREAM_EVENT* event) { auto* self = static_cast(context); + auto streamId = self->getStreamId(stream); switch (event->Type) { + /// Raised when the peer sends stream data and MsQuic delivers received bytes to the application. case QUIC_STREAM_EVENT_RECEIVE: { settings::Logger::logInfo( "[quic] Received {:d} bytes in {:d} buffers", @@ -311,13 +320,10 @@ namespace bringauto::external_client::connection::communication { event->RECEIVE.BufferCount ); - /// -------- START - Just for debugging -------- - auto streamId = self->getStreamId(stream); settings::Logger::logInfo( "[quic] [stream {}] Event RECEIVE", streamId ? *streamId : 0 ); - /// -------- END - Just for debugging -------- std::vector data; data.reserve(event->RECEIVE.TotalBufferLength); @@ -337,24 +343,29 @@ namespace bringauto::external_client::connection::communication { break; } - /// Server send FIN + /// Raised when the peer has finished sending on this stream + /// (peer's FIN has been fully received and processed). case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN: { settings::Logger::logInfo("[quic] Peer stream send shutdown"); break; } - /// My FIN was sended to server + /// Raised when the local send direction is fully shut down + /// and the peer has acknowledged the FIN. case QUIC_STREAM_EVENT_SEND_SHUTDOWN_COMPLETE: { - auto streamId = self->getStreamId(stream); settings::Logger::logInfo("[quic] [stream {}] Stream send shutdown complete", streamId ? *streamId : 0); break; } + /// Raised after StreamStart completes successfully + /// and the stream becomes active with a valid stream ID. case QUIC_STREAM_EVENT_START_COMPLETE: { settings::Logger::logInfo("[quic] Stream start completed"); break; } + /// Raised when a single StreamSend operation completes + /// (data was accepted, acknowledged, or the send was canceled). case QUIC_STREAM_EVENT_SEND_COMPLETE: { /** * This event is raised when MsQuic has finished processing @@ -393,7 +404,8 @@ namespace bringauto::external_client::connection::communication { break; } - /// Stream is closed from both sides + /// Raised when both send and receive directions are closed + /// and the stream lifecycle is fully complete. case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: { settings::Logger::logInfo("[quic] Stream shutdown complete"); self->quic_->StreamClose(stream); @@ -401,7 +413,7 @@ namespace bringauto::external_client::connection::communication { } default: { - settings::Logger::logInfo("[quic] Unexpected stream event 0x{:x}", static_cast(event->Type)); + settings::Logger::logInfo("[quic] Unhandled stream event 0x{:x}", static_cast(event->Type)); break; } } From c32cab05c4728e6fb0a2cf1a227ec635f9f06113 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Tue, 6 Jan 2026 08:03:08 +0000 Subject: [PATCH 10/30] Refactor `QuicCommunication`: Extract `getProtocolSettingsString` method, improve handling of JSON-encoded protocol settings, and refine `SettingsParser` logic for cleaner value parsing. --- .../communication/QuicCommunication.hpp | 61 +++++++++++-------- .../communication/QuicCommunication.cpp | 51 ++++++++++++---- source/bringauto/settings/SettingsParser.cpp | 12 +--- 3 files changed, 77 insertions(+), 47 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index 059c956..9de810c 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -1,12 +1,16 @@ #pragma once -#include #include #include +#include + +#include #include #include + + namespace bringauto::external_client::connection::communication { class QuicCommunication: public ICommunicationChannel { @@ -281,6 +285,37 @@ class QuicCommunication: public ICommunicationChannel { */ void senderLoop(); + /** + * @brief Retrieves the QUIC stream identifier for the given stream handle. + * + * Queries MsQuic for the stream ID associated with the provided HQUIC stream. + * If the parameter query fails, an empty optional is returned. + * + * @param stream Valid QUIC stream handle. + * @return Stream identifier on success, or std::nullopt if the query fails. + */ + std::optional getStreamId(HQUIC stream); + + /** + * @brief Retrieves a protocol setting value as a plain string. + * + * Extracts a value from ExternalConnectionSettings::protocolSettings and + * transparently handles values stored as JSON-encoded strings. + * + * Allows uniform access to protocol settings regardless of whether + * they were stored as plain strings or JSON-serialized values. + * + * @param settings External connection settings containing protocolSettings. + * @param key Key identifying the protocol setting. + * @return Plain string value suitable for direct use (e.g. file paths). + * + * @throws std::out_of_range if the key is not present in protocolSettings. + */ + static std::string getProtocolSettingsString( + const structures::ExternalConnectionSettings& settings, + std::string_view key + ); + /** * @brief Converts a ConnectionState value to a human-readable string. * @@ -296,30 +331,6 @@ class QuicCommunication: public ICommunicationChannel { default: return "Unknown"; } } - - /** - * @brief Retrieves the QUIC stream identifier for the given stream handle. - * - * Queries MsQuic for the stream ID associated with the provided HQUIC stream. - * If the parameter query fails, an empty optional is returned. - * - * @param stream Valid QUIC stream handle. - * @return Stream identifier on success, or std::nullopt if the query fails. - */ - std::optional getStreamId(HQUIC stream) const { - uint64_t streamId = 0; - uint32_t streamIdLen = sizeof(streamId); - - if (QUIC_FAILED(quic_->GetParam( - stream, - QUIC_PARAM_STREAM_ID, - &streamIdLen, - &streamId))) { - return std::nullopt; - } - - return streamId; - } }; } diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 84e5817..16d0242 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -1,21 +1,20 @@ #include #include +#include #include #include -#include -#include "bringauto/settings/LoggerId.hpp" namespace bringauto::external_client::connection::communication { QuicCommunication::QuicCommunication(const structures::ExternalConnectionSettings &settings, const std::string &company, const std::string &vehicleName) : ICommunicationChannel(settings), - certFile_(settings.protocolSettings.at(std::string(settings::Constants::CLIENT_CERT))), - keyFile_(settings.protocolSettings.at(std::string(settings::Constants::CLIENT_KEY))), - caFile_(settings.protocolSettings.at(std::string(settings::Constants::CA_FILE))) + certFile_(getProtocolSettingsString(settings, settings::Constants::CLIENT_CERT)), + keyFile_(getProtocolSettingsString(settings, settings::Constants::CLIENT_KEY)), + caFile_(getProtocolSettingsString(settings, settings::Constants::CA_FILE)) { alpn_ = "sample"; alpnBuffer_.Buffer = reinterpret_cast(alpn_.data()); @@ -133,12 +132,12 @@ namespace bringauto::external_client::connection::communication { QUIC_STATUS status = quic_->ConfigurationOpen( registration_, - &alpnBuffer_, - 1, - settings, - settingsSize, - nullptr, - &config_ + &alpnBuffer_, + 1, + settings, + settingsSize, + nullptr, + &config_ ); if (QUIC_FAILED(status)) { @@ -444,4 +443,34 @@ namespace bringauto::external_client::connection::communication { sendViaQuicStream(msg); } } + + std::optional QuicCommunication::getStreamId(HQUIC stream) { + uint64_t streamId = 0; + uint32_t streamIdLen = sizeof(streamId); + + if (QUIC_FAILED(quic_->GetParam( + stream, + QUIC_PARAM_STREAM_ID, + &streamIdLen, + &streamId))) { + return std::nullopt; + } + + return streamId; + } + + std::string QuicCommunication::getProtocolSettingsString( + const structures::ExternalConnectionSettings& settings, + std::string_view key + ) { + const auto& raw = settings.protocolSettings.at(std::string(key)); + + if (nlohmann::json::accept(raw)) { + auto j = nlohmann::json::parse(raw); + if (j.is_string()) { + return j.get(); + } + } + return raw; + } } diff --git a/source/bringauto/settings/SettingsParser.cpp b/source/bringauto/settings/SettingsParser.cpp index d149b11..ce60789 100644 --- a/source/bringauto/settings/SettingsParser.cpp +++ b/source/bringauto/settings/SettingsParser.cpp @@ -203,13 +203,7 @@ void SettingsParser::fillExternalConnectionSettings(const nlohmann::json &file) if(!settingsName.empty() && endpoint.find(settingsName) != endpoint.end()) { for(auto &[key, val]: endpoint[settingsName].items()) { - if (val.is_string()) { - externalConnectionSettings.protocolSettings[key] = val.get(); - std::cout << externalConnectionSettings.protocolSettings[key] << std::endl; - continue; - } externalConnectionSettings.protocolSettings[key] = to_string(val); - std::cout << externalConnectionSettings.protocolSettings[key] << std::endl; } } @@ -261,11 +255,7 @@ std::string SettingsParser::serializeToJson() const { break; } for(const auto &[key, val]: endpoint.protocolSettings) { - if (nlohmann::json::accept(val)) { - endpointAsJson[settingsName][key] = nlohmann::json::parse(val); - continue; - } - endpointAsJson[settingsName][key] = val; + endpointAsJson[settingsName][key] = nlohmann::json::parse(val); } endpoints.push_back(endpointAsJson); } From 6245c10ac59cd9de9873ee72aa1c3a62a44a4a06 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Tue, 6 Jan 2026 08:22:00 +0000 Subject: [PATCH 11/30] Refactor `QuicCommunication`: Replace `logInfo` with `logDebug`, refine logging messages for clarity, and remove unused includes. --- .../communication/QuicCommunication.cpp | 51 ++++++++----------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 16d0242..861328e 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -2,10 +2,6 @@ #include #include -#include - -#include - namespace bringauto::external_client::connection::communication { @@ -32,7 +28,7 @@ namespace bringauto::external_client::connection::communication { } void QuicCommunication::initializeConnection() { - settings::Logger::logInfo("[quic] Connecting to server when {}", toString(connectionState_)); + settings::Logger::logDebug("[quic] Connecting to server when {}", toString(connectionState_)); ConnectionState expected = ConnectionState::DISCONNECTED; @@ -61,14 +57,13 @@ namespace bringauto::external_client::connection::communication { } bool QuicCommunication::sendMessage(ExternalProtocol::ExternalClient* message) { - settings::Logger::logInfo("[quic] Enqueueing message"); auto copy = std::make_shared(*message); { std::lock_guard lock(outboundMutex_); outboundQueue_.push(std::move(copy)); } - settings::Logger::logInfo("[quic] Notifying sender thread"); + settings::Logger::logDebug("[quic] Notifying sender thread about enqueued message"); outboundCv_.notify_one(); return true; } @@ -203,16 +198,13 @@ namespace bringauto::external_client::connection::communication { ) { { std::lock_guard lock(inboundMutex_); - settings::Logger::logInfo("[quic] Moving message to inboundQueue"); inboundQueue_.push(std::move(msg)); } - settings::Logger::logInfo("[quic] Notifying receiver thread"); + settings::Logger::logDebug("[quic] Notifying receiver thread about dequeued message"); inboundCv_.notify_one(); } bool QuicCommunication::sendViaQuicStream(const std::shared_ptr& message) { - settings::Logger::logInfo("[quic] Sending message"); - HQUIC stream { nullptr }; if (QUIC_FAILED(quic_->StreamOpen(connection_, QUIC_STREAM_OPEN_FLAG_NONE, streamCallback, this, &stream))) { settings::Logger::logError("[quic] StreamOpen failed"); @@ -242,6 +234,9 @@ namespace bringauto::external_client::connection::communication { buf ); + auto streamId = getStreamId(stream); + settings::Logger::logDebug("[quic] [stream {}] Message sent", streamId ? *streamId : 0); + if (QUIC_FAILED(status)) { delete[] buf->Buffer; delete buf; @@ -292,13 +287,13 @@ namespace bringauto::external_client::connection::communication { /// Peer or transport initiated connection shutdown (error or graceful close). /// Further sends may fail after this event. case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER: { - settings::Logger::logWarning("[quic] Connection shutdown initiated"); + settings::Logger::logWarning("[quic] Connection shutdown initiated by peer"); self->connectionState_ = ConnectionState::CLOSING; break; } default: { - settings::Logger::logInfo("[quic] Unhandled connection event 0x{:x}", static_cast(event->Type)); + settings::Logger::logDebug("[quic] Unhandled connection event 0x{:x}", static_cast(event->Type)); break; } } @@ -313,17 +308,13 @@ namespace bringauto::external_client::connection::communication { switch (event->Type) { /// Raised when the peer sends stream data and MsQuic delivers received bytes to the application. case QUIC_STREAM_EVENT_RECEIVE: { - settings::Logger::logInfo( - "[quic] Received {:d} bytes in {:d} buffers", + settings::Logger::logDebug( + "[quic] [stream {}] Received {:d} bytes in {:d} buffers", + streamId ? *streamId : 0, event->RECEIVE.TotalBufferLength, event->RECEIVE.BufferCount ); - settings::Logger::logInfo( - "[quic] [stream {}] Event RECEIVE", - streamId ? *streamId : 0 - ); - std::vector data; data.reserve(event->RECEIVE.TotalBufferLength); @@ -345,21 +336,21 @@ namespace bringauto::external_client::connection::communication { /// Raised when the peer has finished sending on this stream /// (peer's FIN has been fully received and processed). case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN: { - settings::Logger::logInfo("[quic] Peer stream send shutdown"); + settings::Logger::logDebug("[quic] Peer stream send shutdown"); break; } /// Raised when the local send direction is fully shut down /// and the peer has acknowledged the FIN. case QUIC_STREAM_EVENT_SEND_SHUTDOWN_COMPLETE: { - settings::Logger::logInfo("[quic] [stream {}] Stream send shutdown complete", streamId ? *streamId : 0); + settings::Logger::logDebug("[quic] [stream {}] Stream send shutdown complete", streamId ? *streamId : 0); break; } /// Raised after StreamStart completes successfully /// and the stream becomes active with a valid stream ID. case QUIC_STREAM_EVENT_START_COMPLETE: { - settings::Logger::logInfo("[quic] Stream start completed"); + settings::Logger::logDebug("[quic] Stream start completed"); break; } @@ -387,9 +378,9 @@ namespace bringauto::external_client::connection::communication { * passed to StreamSend (via ClientContext) */ if (event->SEND_COMPLETE.Canceled) { - settings::Logger::logError("[quic] Stream send canceled"); + settings::Logger::logWarning("[quic] Stream send canceled"); } else { - settings::Logger::logInfo("[quic] Stream send completed"); + settings::Logger::logDebug("[quic] Stream send completed"); } const auto* buf = @@ -406,13 +397,13 @@ namespace bringauto::external_client::connection::communication { /// Raised when both send and receive directions are closed /// and the stream lifecycle is fully complete. case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: { - settings::Logger::logInfo("[quic] Stream shutdown complete"); + settings::Logger::logDebug("[quic] Stream shutdown complete"); self->quic_->StreamClose(stream); break; } default: { - settings::Logger::logInfo("[quic] Unhandled stream event 0x{:x}", static_cast(event->Type)); + settings::Logger::logDebug("[quic] Unhandled stream event 0x{:x}", static_cast(event->Type)); break; } } @@ -421,11 +412,11 @@ namespace bringauto::external_client::connection::communication { } void QuicCommunication::senderLoop() { - settings::Logger::logInfo("[quic] Sender thread loop started"); + settings::Logger::logDebug("[quic] Sender thread loop started"); while (connectionState_.load() == ConnectionState::CONNECTED) { std::unique_lock lock(outboundMutex_); - settings::Logger::logInfo("[quic] Sender thread loop waiting for outbound queue"); + settings::Logger::logDebug("[quic] Sender thread loop waiting for outbound queue"); outboundCv_.wait(lock, [this] { return !outboundQueue_.empty() || connectionState_.load() != ConnectionState::CONNECTED; @@ -435,7 +426,7 @@ namespace bringauto::external_client::connection::communication { break; } - settings::Logger::logInfo("[quic] Sender thread loop sending outbound queue"); + settings::Logger::logDebug("[quic] Sender thread loop sending outbound queue"); auto msg = outboundQueue_.front(); outboundQueue_.pop(); lock.unlock(); From 67530a5265d53a89d3992fc5fd304d2044bdac84 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Tue, 6 Jan 2026 09:53:00 +0000 Subject: [PATCH 12/30] Refactor `QuicCommunication`: Replace error logs with exceptions for better error handling, improve sender loop logic, and refine logging for message and connection events. --- .../communication/QuicCommunication.hpp | 12 ++-- .../communication/QuicCommunication.cpp | 58 +++++++++++-------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index 9de810c..c0f961a 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -211,9 +211,8 @@ class QuicCommunication: public ICommunicationChannel { * QUIC_STREAM_EVENT_SEND_COMPLETE callback. * * @param message Message to be sent to the peer. - * @return true if the send operation was successfully initiated, false otherwise. */ - bool sendViaQuicStream(const std::shared_ptr &message); + void sendViaQuicStream(const std::shared_ptr &message); /** * @brief Closes the active QUIC configuration. @@ -276,12 +275,11 @@ class QuicCommunication: public ICommunicationChannel { /** * @brief Sender thread main loop for outbound messages. * - * Waits for messages to appear in the outbound queue while the - * connection remains in the CONNECTED state. - * When a message becomes available, it is dequeued and sent - * over a newly created QUIC stream. + * Waits for outbound messages while the connection is in the CONNECTED state. + * Messages are dequeued and sent over individual QUIC streams. * - * The loop exits when the connection state changes from CONNECTED. + * If sending fails, the message is re-enqueued for a later retry. + * The loop terminates when the connection leaves the CONNECTED state. */ void senderLoop(); diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 861328e..4e2f817 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -16,11 +16,15 @@ namespace bringauto::external_client::connection::communication { alpnBuffer_.Buffer = reinterpret_cast(alpn_.data()); alpnBuffer_.Length = static_cast(alpn_.size()); - settings::Logger::logInfo("[quic] Initialize QUIC connection to {}:{} for {}/{}", settings.serverIp, settings.port, company, vehicleName); - - loadMsQuic(); - initRegistration("module-gateway-quic-client"); - initConfiguration(); + settings::Logger::logInfo("[quic] Initialize QUIC communication to {}:{} for {}/{}", settings.serverIp, settings.port, company, vehicleName); + + try { + loadMsQuic(); + initRegistration("module-gateway-quic-client"); + initConfiguration(); + } catch (const std::exception& e) { + settings::Logger::logError("[quic] Failed to initialize QUIC communication; {}", e.what()); + } } QuicCommunication::~QuicCommunication() { @@ -33,12 +37,12 @@ namespace bringauto::external_client::connection::communication { ConnectionState expected = ConnectionState::DISCONNECTED; if (! connectionState_.compare_exchange_strong(expected, ConnectionState::CONNECTING, std::memory_order_acq_rel)) { - return; + throw std::logic_error("Connection already in progress or established"); } QUIC_STATUS status = quic_->ConnectionOpen(registration_, connectionCallback, this, &connection_); if (QUIC_FAILED(status)) { - settings::Logger::logError("[quic] Failed to open QUIC connection; QUIC_STATUS => {:x}", status); + throw std::runtime_error(std::format("ConnectionOpen failed (status=0x{:x})", status)); } status = quic_->ConnectionStart( @@ -50,13 +54,15 @@ namespace bringauto::external_client::connection::communication { ); if (QUIC_FAILED(status)) { - settings::Logger::logError("[quic] Failed to start QUIC connection; QUIC_STATUS => {:x}", status); + throw std::runtime_error(std::format("ConnectionOpen failed (status=0x{:x})", status)); } connectionState_ = ConnectionState::CONNECTING; } bool QuicCommunication::sendMessage(ExternalProtocol::ExternalClient* message) { + settings::Logger::logDebug("[quic] Sending message when {}", toString(connectionState_)); + auto copy = std::make_shared(*message); { @@ -91,7 +97,7 @@ namespace bringauto::external_client::connection::communication { void QuicCommunication::loadMsQuic() { QUIC_STATUS status = MsQuicOpen2(&quic_); if (QUIC_FAILED(status)) { - settings::Logger::logError("[quic] Failed to open QUIC; QUIC_STATUS => {:x}", status); + throw std::runtime_error(std::format("[quic] Failed to open QUIC; QUIC_STATUS => {:x}", status)); } } @@ -102,7 +108,7 @@ namespace bringauto::external_client::connection::communication { QUIC_STATUS status = quic_->RegistrationOpen(&config, ®istration_); if (QUIC_FAILED(status)) { - settings::Logger::logError("[quic] Failed to open QUIC registration; QUIC_STATUS => {:x}", status); + throw std::runtime_error(std::format("[quic] Failed to open QUIC registration; QUIC_STATUS => {:x}", status)); } } @@ -136,14 +142,14 @@ namespace bringauto::external_client::connection::communication { ); if (QUIC_FAILED(status)) { - settings::Logger::logError("[quic] Failed to open QUIC configuration; QUIC_STATUS => {:x}", status); + throw std::runtime_error(std::format("[quic] Failed to open QUIC configuration; QUIC_STATUS => {:x}", status)); } } void QuicCommunication::configurationLoadCredential(const QUIC_CREDENTIAL_CONFIG* credential) const { const QUIC_STATUS status = quic_->ConfigurationLoadCredential(config_, credential); if (QUIC_FAILED(status)) { - settings::Logger::logError("[quic] Failed to load QUIC credential; QUIC_STATUS => {:x}", status); + throw std::runtime_error(std::format("[quic] Failed to load QUIC credential; QUIC_STATUS => {:x}", status)); } } @@ -204,19 +210,17 @@ namespace bringauto::external_client::connection::communication { inboundCv_.notify_one(); } - bool QuicCommunication::sendViaQuicStream(const std::shared_ptr& message) { + void QuicCommunication::sendViaQuicStream(const std::shared_ptr& message) { HQUIC stream { nullptr }; if (QUIC_FAILED(quic_->StreamOpen(connection_, QUIC_STREAM_OPEN_FLAG_NONE, streamCallback, this, &stream))) { - settings::Logger::logError("[quic] StreamOpen failed"); - return false; + throw std::runtime_error("[quic] StreamOpen failed"); } const auto size = message->ByteSizeLong(); const auto buffer = std::make_unique(size); if (!message->SerializeToArray(buffer.get(), static_cast(size))) { - settings::Logger::logError("[quic] Message serialization failed"); - return false; + throw std::runtime_error("[quic] Message serialization failed"); } auto* buf = new QUIC_BUFFER{}; @@ -234,19 +238,16 @@ namespace bringauto::external_client::connection::communication { buf ); - auto streamId = getStreamId(stream); - settings::Logger::logDebug("[quic] [stream {}] Message sent", streamId ? *streamId : 0); - if (QUIC_FAILED(status)) { delete[] buf->Buffer; delete buf; - settings::Logger::logError("[quic] Failed to send QUIC stream; QUIC_STATUS => {:x}", status); quic_->StreamShutdown(stream, QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0); - return false; + throw std::runtime_error(std::format("[quic] Failed to send QUIC stream; QUIC_STATUS => {:x}", status)); } - return true; + auto streamId = getStreamId(stream); + settings::Logger::logDebug("[quic] [stream {}] Message sent", streamId ? *streamId : 0); } QUIC_STATUS QUIC_API QuicCommunication::connectionCallback(HQUIC connection, void* context, QUIC_CONNECTION_EVENT* event) { @@ -413,7 +414,10 @@ namespace bringauto::external_client::connection::communication { void QuicCommunication::senderLoop() { settings::Logger::logDebug("[quic] Sender thread loop started"); + while (connectionState_.load() == ConnectionState::CONNECTED) { + std::shared_ptr msg; + std::unique_lock lock(outboundMutex_); settings::Logger::logDebug("[quic] Sender thread loop waiting for outbound queue"); @@ -427,11 +431,15 @@ namespace bringauto::external_client::connection::communication { } settings::Logger::logDebug("[quic] Sender thread loop sending outbound queue"); - auto msg = outboundQueue_.front(); + msg = outboundQueue_.front(); outboundQueue_.pop(); lock.unlock(); - sendViaQuicStream(msg); + try { + sendViaQuicStream(msg); + } catch (const std::exception& e) { + settings::Logger::logError(std::format("[quic] Message send failed; {}", e.what())); + } } } From e069bc5f713514cf92883208ca24c91e7eba4254 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Tue, 6 Jan 2026 11:25:03 +0000 Subject: [PATCH 13/30] Refactor `QuicCommunication`: Add connection state check before sending messages. --- .../connection/communication/QuicCommunication.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 4e2f817..850255c 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -61,12 +61,14 @@ namespace bringauto::external_client::connection::communication { } bool QuicCommunication::sendMessage(ExternalProtocol::ExternalClient* message) { - settings::Logger::logDebug("[quic] Sending message when {}", toString(connectionState_)); - - auto copy = std::make_shared(*message); + if (connectionState_.load() == ConnectionState::DISCONNECTED) { + settings::Logger::logWarning("[quic] Connection not established, cannot send message"); + return false; + } - { - std::lock_guard lock(outboundMutex_); + { + auto copy = std::make_shared(*message); + std::lock_guard lock(outboundMutex_); outboundQueue_.push(std::move(copy)); } settings::Logger::logDebug("[quic] Notifying sender thread about enqueued message"); From f7bf6ec1b6339d51828622ed1d2a7b4dfebfa919 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Wed, 7 Jan 2026 08:17:11 +0000 Subject: [PATCH 14/30] Refactor `QuicCommunication`: Replace QuicCommunication::ConnectionState with external_client::connection::ConnectionState --- include/bringauto/common_utils/EnumUtils.hpp | 18 + .../connection/ConnectionState.hpp | 6 +- .../communication/QuicCommunication.hpp | 618 +++++++++--------- .../connection/ExternalConnection.cpp | 3 + .../communication/QuicCommunication.cpp | 311 ++++----- 5 files changed, 487 insertions(+), 469 deletions(-) diff --git a/include/bringauto/common_utils/EnumUtils.hpp b/include/bringauto/common_utils/EnumUtils.hpp index 6618c36..991ba50 100644 --- a/include/bringauto/common_utils/EnumUtils.hpp +++ b/include/bringauto/common_utils/EnumUtils.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -73,6 +74,23 @@ class EnumUtils { } }; + /** + * @brief Converts connection state to string + * + * @param toString structures::ProtocolType + * @return std::string_view + */ + static constexpr std::string_view connectionStateToString(external_client::connection::ConnectionState toString) { + switch(toString) { + case external_client::connection::ConnectionState::NOT_INITIALIZED: return "Not Initialized"; + case external_client::connection::ConnectionState::NOT_CONNECTED: return "Not Connected"; + case external_client::connection::ConnectionState::CONNECTING: return "Connecting"; + case external_client::connection::ConnectionState::CONNECTED: return "Connected"; + case external_client::connection::ConnectionState::CLOSING: return "Closing"; + default: return "Unknown"; + } + }; + }; } diff --git a/include/bringauto/external_client/connection/ConnectionState.hpp b/include/bringauto/external_client/connection/ConnectionState.hpp index 91ec13f..375ca30 100644 --- a/include/bringauto/external_client/connection/ConnectionState.hpp +++ b/include/bringauto/external_client/connection/ConnectionState.hpp @@ -18,6 +18,10 @@ enum class ConnectionState { /** * CONNECTED - Client is connected to the External server */ - CONNECTED + CONNECTED, + /** + * CLOSING - Client closing connection to the External server + */ + CLOSING }; } diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index c0f961a..e1a986d 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -12,323 +13,302 @@ namespace bringauto::external_client::connection::communication { - -class QuicCommunication: public ICommunicationChannel { -public: - explicit QuicCommunication(const structures::ExternalConnectionSettings &settings, const std::string &company, - const std::string &vehicleName); - - ~QuicCommunication() override; - - /** - * @brief Initializes a QUIC connection to the server. - * - * Attempts to establish a new QUIC connection. - * It first atomically verifies that the current connection state is - * DISCONNECTED and transitions it to CONNECTING in order to prevent - * concurrent connection attempts. - * - * After the state transition, it opens a QUIC connection handle and - * starts the connection using the configured server address, port, - * and QUIC configuration. - * - * Any failures during the connection open or start process are logged. - */ - void initializeConnection() override; - - /** - * @brief Enqueues an outgoing message to be sent over the QUIC connection. - * - * Creates a shared copy of the provided ExternalClient message - * and pushes it into the outbound message queue in a thread-safe manner. - * After enqueuing, it notifies the sender thread via a condition variable - * that a new message is available for sending. - * - * @param message Pointer to the message that should be sent. - * @return true Always returns true to indicate the message was successfully enqueued. - */ - bool sendMessage(ExternalProtocol::ExternalClient *message) override; - - /** - * @brief Receives an incoming message from the QUIC connection. - * - * Waits for an incoming message to appear in the inbound - * queue or for the connection state to change from CONNECTED. - * The wait is bounded by a configurable timeout. - * - * If the wait times out, the connection is no longer in the CONNECTED - * state, or no message is available, the function returns nullptr. - * Otherwise, it retrieves and removes the next message from the inbound - * queue and returns it. - * - * @return A shared pointer to the received ExternalServer message, - * or nullptr if no message is available or the connection is not active. - */ - std::shared_ptr receiveMessage() override; - - /** - * @brief Initiates a graceful shutdown of the QUIC connection. - * - * Requests an orderly shutdown of the active QUIC connection. - * If no connection is currently established, the function returns immediately. - * - * The shutdown is performed asynchronously. Completion is signaled via the - * QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE event, which is handled in - * the connectionCallback. - */ - void closeConnection() override; - -private: - /// Represents the current state of the QUIC connection lifecycle - enum class ConnectionState : uint8_t { DISCONNECTED, CONNECTING, CONNECTED, CLOSING }; - - /// Pointer to the MsQuic API function table - const QUIC_API_TABLE* quic_ { nullptr }; - /// QUIC registration handle associated with the application - HQUIC registration_ { nullptr }; - /// QUIC configuration handle (ALPN, credentials, transport settings) - HQUIC config_ { nullptr }; - /// Active QUIC connection handle - HQUIC connection_ { nullptr }; - /// Application-Layer Protocol Negotiation (ALPN) string - std::string alpn_; - /// QUIC buffer wrapping the ALPN string - QUIC_BUFFER alpnBuffer_ {}; - - /// Path to the client certificate file - std::string certFile_; - /// Path to the client private key file - std::string keyFile_; - /// Path to the CA certificate file - std::string caFile_; - - /// Atomic state of the connection used for synchronization across threads - std::atomic connectionState_ { ConnectionState::DISCONNECTED }; - - /// @name Inbound (peer → this) - /// @{ - /// Queue of incoming messages received from the peer - std::queue> inboundQueue_; - /// Mutex protecting access to the inbound message queue - std::mutex inboundMutex_; - /// Condition variable for signaling inbound message availability - std::condition_variable inboundCv_; - /// @} - - /// @name Outbound (this → peer) - /// @{ - /// Queue of outgoing messages to be sent to the peer - std::queue> outboundQueue_; - /// Mutex protecting access to the outbound message queue - std::mutex outboundMutex_; - /// Condition variable for signaling outbound message availability - std::condition_variable outboundCv_; - /// Dedicated sender thread responsible for transmitting outbound messages - std::jthread senderThread_; - /// @} - - /** - * @brief Loads and initializes the MsQuic API. - * - * Initializes the MsQuic library and retrieves the - * QUIC API function table. The resulting table is stored for later - * use when creating registrations, configurations, and connections. - * - * If the initialization fails, an error is logged. - */ - void loadMsQuic(); - - /** - * @brief Initializes a QUIC registration. - * - * Creates a QUIC registration with the specified application name and - * a low-latency execution profile. The registration is required for - * creating QUIC configurations and connections. - * - * If registration creation fails, an error is logged. - * - * @param appName Application name used to identify the QUIC registration. - */ - void initRegistration(const char *appName); - - /** - * @brief Initializes the QUIC configuration and loads client credentials. - * - * Opens a QUIC configuration using the configured ALPN and default QUIC - * transport settings. Client TLS credentials are then set up using a - * certificate file, private key file, and CA certificate file. - * - * If configuration creation or credential loading fails, an error is logged. - */ - void initConfiguration(); - - /** - * @brief Opens a QUIC configuration. - * - * Creates a QUIC configuration associated with the current registration, - * configured ALPN, and optional transport settings. - * - * If settings are not provided, default QUIC settings are used. - * On failure, an error is logged. - * - * @param settings Optional QUIC transport settings. - */ - void configurationOpen(const QUIC_SETTINGS *settings); - - /** - * @brief Loads TLS credentials into the QUIC configuration. - * - * Loads client-side TLS credentials into the active QUIC configuration. - * The credentials define the certificate, private key, and CA certificate - * used for secure communication. - * - * If credential loading fails, an error is logged. - * - * @param credential Pointer to the QUIC credential configuration. - */ - void configurationLoadCredential(const QUIC_CREDENTIAL_CONFIG *credential) const; - - /** - * @brief Handles a successfully decoded incoming message. - * - * Pushes the decoded ExternalServer message into the inbound queue - * in a thread-safe manner and notifies the receiver thread that a - * new message is available. - * - * @param msg Decoded message received from the peer. - */ - void onMessageDecoded(std::shared_ptr msg); - - /** - * @brief Sends a message to the peer using a QUIC stream. - * - * Opens a new QUIC stream on the active connection and serializes the - * provided ExternalClient message into a byte buffer. - * The message is sent using a single StreamSend call with START and FIN - * flags, effectively opening, sending, and closing the stream. - * - * The allocated send buffer is released asynchronously in the - * QUIC_STREAM_EVENT_SEND_COMPLETE callback. - * - * @param message Message to be sent to the peer. - */ - void sendViaQuicStream(const std::shared_ptr &message); - - /** - * @brief Closes the active QUIC configuration. - */ - void closeConfiguration(); - - /** - * @brief Closes the QUIC registration. - */ - void closeRegistration(); - - /** - * @brief Closes the MsQuic API and releases associated resources. - */ - void closeMsQuic(); - - /** - * @brief Stops the QUIC communication and releases all resources. - * - * Initiates connection shutdown and closes the QUIC configuration, - * registration, and MsQuic API in the correct order. - * All waiting sender and receiver threads are unblocked by notifying - * the associated condition variables. - */ - void stop(); - - /** - * @brief Handles QUIC connection-level events. - * - * Processes connection lifecycle events reported by MsQuic, including - * successful connection establishment, peer-initiated shutdown, and - * shutdown completion. - * - * All QUIC_CONNECTION_EVENT cases are documented at - * https://microsoft.github.io/msquic/msquicdocs/docs/api/QUIC_CONNECTION_EVENT.html - * - * @param connection QUIC connection handle. - * @param context User-defined context pointer (QuicCommunication instance). - * @param event Connection event information provided by MsQuic. - * @return QUIC_STATUS_SUCCESS to indicate successful event handling. - */ - static QUIC_STATUS QUIC_API connectionCallback(HQUIC connection, void *context, QUIC_CONNECTION_EVENT *event); - - /** - * @brief Handles QUIC stream-level events. - * - * Processes stream events reported by MsQuic, including data reception, - * send completion, stream startup, and shutdown notifications. - * - * All QUIC_STREAM_EVENT cases are documented at - * https://microsoft.github.io/msquic/msquicdocs/docs/api/QUIC_STREAM_EVENT.html - * - * @param stream QUIC stream handle associated with the event. - * @param context User-defined context pointer (QuicCommunication instance). - * @param event Stream event information provided by MsQuic. - * @return QUIC_STATUS_SUCCESS to indicate successful event handling. - */ - static QUIC_STATUS QUIC_API streamCallback(HQUIC stream, void *context, QUIC_STREAM_EVENT *event); - - /** - * @brief Sender thread main loop for outbound messages. - * - * Waits for outbound messages while the connection is in the CONNECTED state. - * Messages are dequeued and sent over individual QUIC streams. - * - * If sending fails, the message is re-enqueued for a later retry. - * The loop terminates when the connection leaves the CONNECTED state. - */ - void senderLoop(); - - /** - * @brief Retrieves the QUIC stream identifier for the given stream handle. - * - * Queries MsQuic for the stream ID associated with the provided HQUIC stream. - * If the parameter query fails, an empty optional is returned. - * - * @param stream Valid QUIC stream handle. - * @return Stream identifier on success, or std::nullopt if the query fails. - */ - std::optional getStreamId(HQUIC stream); - - /** - * @brief Retrieves a protocol setting value as a plain string. - * - * Extracts a value from ExternalConnectionSettings::protocolSettings and - * transparently handles values stored as JSON-encoded strings. - * - * Allows uniform access to protocol settings regardless of whether - * they were stored as plain strings or JSON-serialized values. - * - * @param settings External connection settings containing protocolSettings. - * @param key Key identifying the protocol setting. - * @return Plain string value suitable for direct use (e.g. file paths). - * - * @throws std::out_of_range if the key is not present in protocolSettings. - */ - static std::string getProtocolSettingsString( - const structures::ExternalConnectionSettings& settings, - std::string_view key - ); - - /** - * @brief Converts a ConnectionState value to a human-readable string. - * - * @param state Connection state to convert. - * @return Null-terminated string describing the connection state. - */ - static const char* toString(ConnectionState state) { - switch (state) { - case ConnectionState::DISCONNECTED: return "Disconnected"; - case ConnectionState::CONNECTING: return "Connecting"; - case ConnectionState::CONNECTED: return "Connected"; - case ConnectionState::CLOSING: return "Closing"; - default: return "Unknown"; - } - } -}; - + class QuicCommunication : public ICommunicationChannel { + public: + explicit QuicCommunication(const structures::ExternalConnectionSettings &settings, const std::string &company, + const std::string &vehicleName); + + ~QuicCommunication() override; + + /** + * @brief Initializes a QUIC connection to the server. + * + * Attempts to establish a new QUIC connection. + * It first atomically verifies that the current connection state is + * NOT_CONNECTED and transitions it to CONNECTING in order to prevent + * concurrent connection attempts. + * + * After the state transition, it opens a QUIC connection handle and + * starts the connection using the configured server address, port, + * and QUIC configuration. + * + * Any failures during the connection open or start process are logged. + */ + void initializeConnection() override; + + /** + * @brief Enqueues an outgoing message to be sent over the QUIC connection. + * + * Creates a shared copy of the provided ExternalClient message + * and pushes it into the outbound message queue in a thread-safe manner. + * After enqueuing, it notifies the sender thread via a condition variable + * that a new message is available for sending. + * + * @param message Pointer to the message that should be sent. + * @return true Always returns true to indicate the message was successfully enqueued. + */ + bool sendMessage(ExternalProtocol::ExternalClient *message) override; + + /** + * @brief Receives an incoming message from the QUIC connection. + * + * Waits for an incoming message to appear in the inbound + * queue or for the connection state to change from CONNECTED. + * The wait is bounded by a configurable timeout. + * + * If the wait times out, the connection is no longer in the CONNECTED + * state, or no message is available, the function returns nullptr. + * Otherwise, it retrieves and removes the next message from the inbound + * queue and returns it. + * + * @return A shared pointer to the received ExternalServer message, + * or nullptr if no message is available or the connection is not active. + */ + std::shared_ptr receiveMessage() override; + + /** + * @brief Initiates a graceful shutdown of the QUIC connection. + * + * Requests an orderly shutdown of the active QUIC connection. + * If no connection is currently established, the function returns immediately. + * + * The shutdown is performed asynchronously. Completion is signaled via the + * QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE event, which is handled in + * the connectionCallback. + */ + void closeConnection() override; + + private: + /// Pointer to the MsQuic API function table + const QUIC_API_TABLE *quic_{nullptr}; + /// QUIC registration handle associated with the application + HQUIC registration_{nullptr}; + /// QUIC configuration handle (ALPN, credentials, transport settings) + HQUIC config_{nullptr}; + /// Active QUIC connection handle + HQUIC connection_{nullptr}; + /// Application-Layer Protocol Negotiation (ALPN) string + std::string alpn_; + /// QUIC buffer wrapping the ALPN string + QUIC_BUFFER alpnBuffer_{}; + + /// Path to the client certificate file + std::string certFile_; + /// Path to the client private key file + std::string keyFile_; + /// Path to the CA certificate file + std::string caFile_; + + /// Atomic state of the connection used for synchronization across threads + std::atomic connectionState_{ConnectionState::NOT_CONNECTED}; + + /// @name Inbound (peer → this) + /// @{ + /// Queue of incoming messages received from the peer + std::queue > inboundQueue_; + /// Mutex protecting access to the inbound message queue + std::mutex inboundMutex_; + /// Condition variable for signaling inbound message availability + std::condition_variable inboundCv_; + /// @} + + /// @name Outbound (this → peer) + /// @{ + /// Queue of outgoing messages to be sent to the peer + std::queue > outboundQueue_; + /// Mutex protecting access to the outbound message queue + std::mutex outboundMutex_; + /// Condition variable for signaling outbound message availability + std::condition_variable outboundCv_; + /// Dedicated sender thread responsible for transmitting outbound messages + std::jthread senderThread_; + /// @} + + /** + * @brief Loads and initializes the MsQuic API. + * + * Initializes the MsQuic library and retrieves the + * QUIC API function table. The resulting table is stored for later + * use when creating registrations, configurations, and connections. + * + * If the initialization fails, an error is logged. + */ + void loadMsQuic(); + + /** + * @brief Initializes a QUIC registration. + * + * Creates a QUIC registration with the specified application name and + * a low-latency execution profile. The registration is required for + * creating QUIC configurations and connections. + * + * If registration creation fails, an error is logged. + * + * @param appName Application name used to identify the QUIC registration. + */ + void initRegistration(const char *appName); + + /** + * @brief Initializes the QUIC configuration and loads client credentials. + * + * Opens a QUIC configuration using the configured ALPN and default QUIC + * transport settings. Client TLS credentials are then set up using a + * certificate file, private key file, and CA certificate file. + * + * If configuration creation or credential loading fails, an error is logged. + */ + void initConfiguration(); + + /** + * @brief Opens a QUIC configuration. + * + * Creates a QUIC configuration associated with the current registration, + * configured ALPN, and optional transport settings. + * + * If settings are not provided, default QUIC settings are used. + * On failure, an error is logged. + * + * @param settings Optional QUIC transport settings. + */ + void configurationOpen(const QUIC_SETTINGS *settings); + + /** + * @brief Loads TLS credentials into the QUIC configuration. + * + * Loads client-side TLS credentials into the active QUIC configuration. + * The credentials define the certificate, private key, and CA certificate + * used for secure communication. + * + * If credential loading fails, an error is logged. + * + * @param credential Pointer to the QUIC credential configuration. + */ + void configurationLoadCredential(const QUIC_CREDENTIAL_CONFIG *credential) const; + + /** + * @brief Handles a successfully decoded incoming message. + * + * Pushes the decoded ExternalServer message into the inbound queue + * in a thread-safe manner and notifies the receiver thread that a + * new message is available. + * + * @param msg Decoded message received from the peer. + */ + void onMessageDecoded(std::shared_ptr msg); + + /** + * @brief Sends a message to the peer using a QUIC stream. + * + * Opens a new QUIC stream on the active connection and serializes the + * provided ExternalClient message into a byte buffer. + * The message is sent using a single StreamSend call with START and FIN + * flags, effectively opening, sending, and closing the stream. + * + * The allocated send buffer is released asynchronously in the + * QUIC_STREAM_EVENT_SEND_COMPLETE callback. + * + * @param message Message to be sent to the peer. + */ + void sendViaQuicStream(const std::shared_ptr &message); + + /** + * @brief Closes the active QUIC configuration. + */ + void closeConfiguration(); + + /** + * @brief Closes the QUIC registration. + */ + void closeRegistration(); + + /** + * @brief Closes the MsQuic API and releases associated resources. + */ + void closeMsQuic(); + + /** + * @brief Stops the QUIC communication and releases all resources. + * + * Initiates connection shutdown and closes the QUIC configuration, + * registration, and MsQuic API in the correct order. + * All waiting sender and receiver threads are unblocked by notifying + * the associated condition variables. + */ + void stop(); + + /** + * @brief Handles QUIC connection-level events. + * + * Processes connection lifecycle events reported by MsQuic, including + * successful connection establishment, peer-initiated shutdown, and + * shutdown completion. + * + * All QUIC_CONNECTION_EVENT cases are documented at + * https://microsoft.github.io/msquic/msquicdocs/docs/api/QUIC_CONNECTION_EVENT.html + * + * @param connection QUIC connection handle. + * @param context User-defined context pointer (QuicCommunication instance). + * @param event Connection event information provided by MsQuic. + * @return QUIC_STATUS_SUCCESS to indicate successful event handling. + */ + static QUIC_STATUS QUIC_API connectionCallback(HQUIC connection, void *context, QUIC_CONNECTION_EVENT *event); + + /** + * @brief Handles QUIC stream-level events. + * + * Processes stream events reported by MsQuic, including data reception, + * send completion, stream startup, and shutdown notifications. + * + * All QUIC_STREAM_EVENT cases are documented at + * https://microsoft.github.io/msquic/msquicdocs/docs/api/QUIC_STREAM_EVENT.html + * + * @param stream QUIC stream handle associated with the event. + * @param context User-defined context pointer (QuicCommunication instance). + * @param event Stream event information provided by MsQuic. + * @return QUIC_STATUS_SUCCESS to indicate successful event handling. + */ + static QUIC_STATUS QUIC_API streamCallback(HQUIC stream, void *context, QUIC_STREAM_EVENT *event); + + /** + * @brief Sender thread main loop for outbound messages. + * + * Waits for outbound messages while the connection is in the CONNECTED state. + * Messages are dequeued and sent over individual QUIC streams. + * + * If sending fails, the message is re-enqueued for a later retry. + * The loop terminates when the connection leaves the CONNECTED state. + */ + void senderLoop(); + + /** + * @brief Retrieves the QUIC stream identifier for the given stream handle. + * + * Queries MsQuic for the stream ID associated with the provided HQUIC stream. + * If the parameter query fails, an empty optional is returned. + * + * @param stream Valid QUIC stream handle. + * @return Stream identifier on success, or std::nullopt if the query fails. + */ + std::optional getStreamId(HQUIC stream); + + /** + * @brief Retrieves a protocol setting value as a plain string. + * + * Extracts a value from ExternalConnectionSettings::protocolSettings and + * transparently handles values stored as JSON-encoded strings. + * + * Allows uniform access to protocol settings regardless of whether + * they were stored as plain strings or JSON-serialized values. + * + * @param settings External connection settings containing protocolSettings. + * @param key Key identifying the protocol setting. + * @return Plain string value suitable for direct use (e.g. file paths). + * + * @throws std::out_of_range if the key is not present in protocolSettings. + */ + static std::string getProtocolSettingsString( + const structures::ExternalConnectionSettings &settings, + std::string_view key + ); + }; } diff --git a/source/bringauto/external_client/connection/ExternalConnection.cpp b/source/bringauto/external_client/connection/ExternalConnection.cpp index 4051818..802e275 100644 --- a/source/bringauto/external_client/connection/ExternalConnection.cpp +++ b/source/bringauto/external_client/connection/ExternalConnection.cpp @@ -155,6 +155,7 @@ int ExternalConnection::connectMessageHandle(const std::vectorreceiveMessage(); + return OK; if(connectResponseMsg == nullptr) { log::logError("Communication client couldn't receive any message"); return NO_MESSAGE_AVAILABLE; @@ -202,6 +203,7 @@ int ExternalConnection::statusMessageHandle(const std::vectorreceiveMessage(); + return OK; if(statusResponseMsg == nullptr) { log::logError("Communication client couldn't receive any message"); return NO_MESSAGE_AVAILABLE; @@ -227,6 +229,7 @@ int ExternalConnection::commandMessageHandle(const std::vectorreceiveMessage(); + return OK; if(commandMsg == nullptr) { log::logError("Communication client couldn't receive any message"); return NO_MESSAGE_AVAILABLE; diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 850255c..d75e3bd 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -2,27 +2,34 @@ #include #include +#include namespace bringauto::external_client::connection::communication { - - QuicCommunication::QuicCommunication(const structures::ExternalConnectionSettings &settings, const std::string &company, - const std::string &vehicleName) : ICommunicationChannel(settings), - certFile_(getProtocolSettingsString(settings, settings::Constants::CLIENT_CERT)), - keyFile_(getProtocolSettingsString(settings, settings::Constants::CLIENT_KEY)), - caFile_(getProtocolSettingsString(settings, settings::Constants::CA_FILE)) - { + QuicCommunication::QuicCommunication(const structures::ExternalConnectionSettings &settings, + const std::string &company, + const std::string &vehicleName) : ICommunicationChannel(settings), + certFile_(getProtocolSettingsString( + settings, + settings::Constants::CLIENT_CERT)), + keyFile_(getProtocolSettingsString( + settings, + settings::Constants::CLIENT_KEY)), + caFile_(getProtocolSettingsString( + settings, + settings::Constants::CA_FILE)) { alpn_ = "sample"; - alpnBuffer_.Buffer = reinterpret_cast(alpn_.data()); + alpnBuffer_.Buffer = reinterpret_cast(alpn_.data()); alpnBuffer_.Length = static_cast(alpn_.size()); - settings::Logger::logInfo("[quic] Initialize QUIC communication to {}:{} for {}/{}", settings.serverIp, settings.port, company, vehicleName); + settings::Logger::logInfo("[quic] Initialize QUIC communication to {}:{} for {}/{}", settings.serverIp, + settings.port, company, vehicleName); try { loadMsQuic(); initRegistration("module-gateway-quic-client"); initConfiguration(); - } catch (const std::exception& e) { + } catch (const std::exception &e) { settings::Logger::logError("[quic] Failed to initialize QUIC communication; {}", e.what()); } } @@ -32,11 +39,12 @@ namespace bringauto::external_client::connection::communication { } void QuicCommunication::initializeConnection() { - settings::Logger::logDebug("[quic] Connecting to server when {}", toString(connectionState_)); - + settings::Logger::logDebug("[quic] Connecting to server when {}", + common_utils::EnumUtils::connectionStateToString(connectionState_)); - ConnectionState expected = ConnectionState::DISCONNECTED; - if (! connectionState_.compare_exchange_strong(expected, ConnectionState::CONNECTING, std::memory_order_acq_rel)) { + ConnectionState expected = ConnectionState::NOT_CONNECTED; + if (!connectionState_. + compare_exchange_strong(expected, ConnectionState::CONNECTING, std::memory_order_acq_rel)) { throw std::logic_error("Connection already in progress or established"); } @@ -60,15 +68,15 @@ namespace bringauto::external_client::connection::communication { connectionState_ = ConnectionState::CONNECTING; } - bool QuicCommunication::sendMessage(ExternalProtocol::ExternalClient* message) { - if (connectionState_.load() == ConnectionState::DISCONNECTED) { - settings::Logger::logWarning("[quic] Connection not established, cannot send message"); - return false; - } + bool QuicCommunication::sendMessage(ExternalProtocol::ExternalClient *message) { + if (connectionState_.load() == ConnectionState::NOT_CONNECTED) { + settings::Logger::logWarning("[quic] Connection not established, cannot send message"); + return false; + } - { - auto copy = std::make_shared(*message); - std::lock_guard lock(outboundMutex_); + { + auto copy = std::make_shared(*message); + std::lock_guard lock(outboundMutex_); outboundQueue_.push(std::move(copy)); } settings::Logger::logDebug("[quic] Notifying sender thread about enqueued message"); @@ -80,12 +88,12 @@ namespace bringauto::external_client::connection::communication { std::unique_lock lock(inboundMutex_); if (!inboundCv_.wait_for( - lock, - settings::receive_message_timeout, - [this] { return !inboundQueue_.empty() || connectionState_.load() != ConnectionState::CONNECTED; } - )) { - return nullptr; - } + lock, + settings::receive_message_timeout, + [this] { return !inboundQueue_.empty() || connectionState_.load() != ConnectionState::CONNECTED; } + )) { + return nullptr; + } if (connectionState_.load() != ConnectionState::CONNECTED || inboundQueue_.empty()) { return nullptr; @@ -103,25 +111,26 @@ namespace bringauto::external_client::connection::communication { } } - void QuicCommunication::initRegistration(const char* appName) { - QUIC_REGISTRATION_CONFIG config {}; - config.AppName = const_cast(appName); + void QuicCommunication::initRegistration(const char *appName) { + QUIC_REGISTRATION_CONFIG config{}; + config.AppName = const_cast(appName); config.ExecutionProfile = QUIC_EXECUTION_PROFILE_LOW_LATENCY; QUIC_STATUS status = quic_->RegistrationOpen(&config, ®istration_); if (QUIC_FAILED(status)) { - throw std::runtime_error(std::format("[quic] Failed to open QUIC registration; QUIC_STATUS => {:x}", status)); + throw std::runtime_error( + std::format("[quic] Failed to open QUIC registration; QUIC_STATUS => {:x}", status)); } } void QuicCommunication::initConfiguration() { configurationOpen(nullptr); - QUIC_CERTIFICATE_FILE certificate {}; + QUIC_CERTIFICATE_FILE certificate{}; certificate.CertificateFile = certFile_.c_str(); certificate.PrivateKeyFile = keyFile_.c_str(); - QUIC_CREDENTIAL_CONFIG credential {}; + QUIC_CREDENTIAL_CONFIG credential{}; credential.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE; credential.Flags = QUIC_CREDENTIAL_FLAG_CLIENT | QUIC_CREDENTIAL_FLAG_SET_CA_CERTIFICATE_FILE; credential.CertificateFile = &certificate; @@ -130,7 +139,7 @@ namespace bringauto::external_client::connection::communication { configurationLoadCredential(&credential); } - void QuicCommunication::configurationOpen(const QUIC_SETTINGS* settings) { + void QuicCommunication::configurationOpen(const QUIC_SETTINGS *settings) { const uint32_t settingsSize = settings ? sizeof(*settings) : 0; QUIC_STATUS status = quic_->ConfigurationOpen( @@ -144,11 +153,12 @@ namespace bringauto::external_client::connection::communication { ); if (QUIC_FAILED(status)) { - throw std::runtime_error(std::format("[quic] Failed to open QUIC configuration; QUIC_STATUS => {:x}", status)); + throw std::runtime_error(std::format("[quic] Failed to open QUIC configuration; QUIC_STATUS => {:x}", + status)); } } - void QuicCommunication::configurationLoadCredential(const QUIC_CREDENTIAL_CONFIG* credential) const { + void QuicCommunication::configurationLoadCredential(const QUIC_CREDENTIAL_CONFIG *credential) const { const QUIC_STATUS status = quic_->ConfigurationLoadCredential(config_, credential); if (QUIC_FAILED(status)) { throw std::runtime_error(std::format("[quic] Failed to load QUIC credential; QUIC_STATUS => {:x}", status)); @@ -156,7 +166,7 @@ namespace bringauto::external_client::connection::communication { } void QuicCommunication::closeConnection() { - if (! connection_) { + if (!connection_) { return; } @@ -212,8 +222,8 @@ namespace bringauto::external_client::connection::communication { inboundCv_.notify_one(); } - void QuicCommunication::sendViaQuicStream(const std::shared_ptr& message) { - HQUIC stream { nullptr }; + void QuicCommunication::sendViaQuicStream(const std::shared_ptr &message) { + HQUIC stream{nullptr}; if (QUIC_FAILED(quic_->StreamOpen(connection_, QUIC_STREAM_OPEN_FLAG_NONE, streamCallback, this, &stream))) { throw std::runtime_error("[quic] StreamOpen failed"); } @@ -225,19 +235,19 @@ namespace bringauto::external_client::connection::communication { throw std::runtime_error("[quic] Message serialization failed"); } - auto* buf = new QUIC_BUFFER{}; + auto *buf = new QUIC_BUFFER{}; buf->Length = static_cast(size); buf->Buffer = new uint8_t[buf->Length]; std::memcpy(buf->Buffer, buffer.get(), buf->Length); const QUIC_STATUS status = quic_->StreamSend(stream, buf, 1, - /** - * START => Simulates quic_->StreamStart before send - * FIN => Simulates quic_->StreamShutdown after send - */ - QUIC_SEND_FLAG_START | QUIC_SEND_FLAG_FIN, - buf + /** + * START => Simulates quic_->StreamStart before send + * FIN => Simulates quic_->StreamShutdown after send + */ + QUIC_SEND_FLAG_START | QUIC_SEND_FLAG_FIN, + buf ); if (QUIC_FAILED(status)) { @@ -252,8 +262,9 @@ namespace bringauto::external_client::connection::communication { settings::Logger::logDebug("[quic] [stream {}] Message sent", streamId ? *streamId : 0); } - QUIC_STATUS QUIC_API QuicCommunication::connectionCallback(HQUIC connection, void* context, QUIC_CONNECTION_EVENT* event) { - auto* self = static_cast(context); + QUIC_STATUS QUIC_API QuicCommunication::connectionCallback(HQUIC connection, void *context, + QUIC_CONNECTION_EVENT *event) { + auto *self = static_cast(context); switch (event->Type) { /// Fired when the QUIC handshake is complete and the connection is ready @@ -275,7 +286,7 @@ namespace bringauto::external_client::connection::communication { case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE: { settings::Logger::logInfo("[quic] Connection shutdown complete"); - self->connectionState_ = ConnectionState::DISCONNECTED; + self->connectionState_ = ConnectionState::NOT_CONNECTED; self->outboundCv_.notify_all(); if (self->senderThread_.joinable()) { @@ -296,7 +307,8 @@ namespace bringauto::external_client::connection::communication { } default: { - settings::Logger::logDebug("[quic] Unhandled connection event 0x{:x}", static_cast(event->Type)); + settings::Logger::logDebug("[quic] Unhandled connection event 0x{:x}", + static_cast(event->Type)); break; } } @@ -304,114 +316,115 @@ namespace bringauto::external_client::connection::communication { return QUIC_STATUS_SUCCESS; } - QUIC_STATUS QUIC_API QuicCommunication::streamCallback(HQUIC stream, void* context, QUIC_STREAM_EVENT* event) { - auto* self = static_cast(context); + QUIC_STATUS QUIC_API QuicCommunication::streamCallback(HQUIC stream, void *context, QUIC_STREAM_EVENT *event) { + auto *self = static_cast(context); auto streamId = self->getStreamId(stream); - switch (event->Type) { - /// Raised when the peer sends stream data and MsQuic delivers received bytes to the application. - case QUIC_STREAM_EVENT_RECEIVE: { - settings::Logger::logDebug( + switch (event->Type) { + /// Raised when the peer sends stream data and MsQuic delivers received bytes to the application. + case QUIC_STREAM_EVENT_RECEIVE: { + settings::Logger::logDebug( "[quic] [stream {}] Received {:d} bytes in {:d} buffers", streamId ? *streamId : 0, event->RECEIVE.TotalBufferLength, event->RECEIVE.BufferCount ); - std::vector data; - data.reserve(event->RECEIVE.TotalBufferLength); + std::vector data; + data.reserve(event->RECEIVE.TotalBufferLength); - for (uint32_t i = 0; i < event->RECEIVE.BufferCount; ++i) { - const auto& b = event->RECEIVE.Buffers[i]; - data.insert(data.end(), b.Buffer, b.Buffer + b.Length); - } + for (uint32_t i = 0; i < event->RECEIVE.BufferCount; ++i) { + const auto &b = event->RECEIVE.Buffers[i]; + data.insert(data.end(), b.Buffer, b.Buffer + b.Length); + } - auto msg = std::make_shared(); - if (!msg->ParseFromArray(data.data(), static_cast(data.size()))) { - settings::Logger::logError("[quic] Failed to parse ExternalServer message"); + auto msg = std::make_shared(); + if (!msg->ParseFromArray(data.data(), static_cast(data.size()))) { + settings::Logger::logError("[quic] Failed to parse ExternalServer message"); } else { self->onMessageDecoded(std::move(msg)); } - break; - } - - /// Raised when the peer has finished sending on this stream - /// (peer's FIN has been fully received and processed). - case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN: { - settings::Logger::logDebug("[quic] Peer stream send shutdown"); - break; - } - - /// Raised when the local send direction is fully shut down - /// and the peer has acknowledged the FIN. - case QUIC_STREAM_EVENT_SEND_SHUTDOWN_COMPLETE: { - settings::Logger::logDebug("[quic] [stream {}] Stream send shutdown complete", streamId ? *streamId : 0); - break; - } - - /// Raised after StreamStart completes successfully - /// and the stream becomes active with a valid stream ID. - case QUIC_STREAM_EVENT_START_COMPLETE: { - settings::Logger::logDebug("[quic] Stream start completed"); - break; - } - - /// Raised when a single StreamSend operation completes - /// (data was accepted, acknowledged, or the send was canceled). - case QUIC_STREAM_EVENT_SEND_COMPLETE: { - /** - * This event is raised when MsQuic has finished processing - * a single StreamSend request. - * - * Meaning: - * - MsQuic no longer needs the application-provided buffer: - * - the data has been acknowledged (ACKed) by the peer - * at the QUIC transport level and will not be retransmitted - * - OR the send was canceled (Canceled == TRUE), e.g. due to - * stream or connection shutdown - * - * Reliability semantics: - * - the ACK is strictly a QUIC transport-level acknowledgment - * - it does NOT mean the peer application has read or processed - * the data - * - * Practical consequence: - * - this is the only correct place to safely free the memory - * passed to StreamSend (via ClientContext) - */ - if (event->SEND_COMPLETE.Canceled) { - settings::Logger::logWarning("[quic] Stream send canceled"); - } else { - settings::Logger::logDebug("[quic] Stream send completed"); - } - - const auto* buf = - static_cast(event->SEND_COMPLETE.ClientContext); - - if (buf) { - delete[] buf->Buffer; - delete buf; - } - - break; - } - - /// Raised when both send and receive directions are closed - /// and the stream lifecycle is fully complete. - case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: { - settings::Logger::logDebug("[quic] Stream shutdown complete"); - self->quic_->StreamClose(stream); - break; - } - - default: { - settings::Logger::logDebug("[quic] Unhandled stream event 0x{:x}", static_cast(event->Type)); - break; - } - } - - return QUIC_STATUS_SUCCESS; + break; + } + + /// Raised when the peer has finished sending on this stream + /// (peer's FIN has been fully received and processed). + case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN: { + settings::Logger::logDebug("[quic] Peer stream send shutdown"); + break; + } + + /// Raised when the local send direction is fully shut down + /// and the peer has acknowledged the FIN. + case QUIC_STREAM_EVENT_SEND_SHUTDOWN_COMPLETE: { + settings::Logger::logDebug("[quic] [stream {}] Stream send shutdown complete", + streamId ? *streamId : 0); + break; + } + + /// Raised after StreamStart completes successfully + /// and the stream becomes active with a valid stream ID. + case QUIC_STREAM_EVENT_START_COMPLETE: { + settings::Logger::logDebug("[quic] Stream start completed"); + break; + } + + /// Raised when a single StreamSend operation completes + /// (data was accepted, acknowledged, or the send was canceled). + case QUIC_STREAM_EVENT_SEND_COMPLETE: { + /** + * This event is raised when MsQuic has finished processing + * a single StreamSend request. + * + * Meaning: + * - MsQuic no longer needs the application-provided buffer: + * - the data has been acknowledged (ACKed) by the peer + * at the QUIC transport level and will not be retransmitted + * - OR the send was canceled (Canceled == TRUE), e.g. due to + * stream or connection shutdown + * + * Reliability semantics: + * - the ACK is strictly a QUIC transport-level acknowledgment + * - it does NOT mean the peer application has read or processed + * the data + * + * Practical consequence: + * - this is the only correct place to safely free the memory + * passed to StreamSend (via ClientContext) + */ + if (event->SEND_COMPLETE.Canceled) { + settings::Logger::logWarning("[quic] Stream send canceled"); + } else { + settings::Logger::logDebug("[quic] Stream send completed"); + } + + const auto *buf = + static_cast(event->SEND_COMPLETE.ClientContext); + + if (buf) { + delete[] buf->Buffer; + delete buf; + } + + break; + } + + /// Raised when both send and receive directions are closed + /// and the stream lifecycle is fully complete. + case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: { + settings::Logger::logDebug("[quic] Stream shutdown complete"); + self->quic_->StreamClose(stream); + break; + } + + default: { + settings::Logger::logDebug("[quic] Unhandled stream event 0x{:x}", static_cast(event->Type)); + break; + } + } + + return QUIC_STATUS_SUCCESS; } void QuicCommunication::senderLoop() { @@ -425,7 +438,7 @@ namespace bringauto::external_client::connection::communication { settings::Logger::logDebug("[quic] Sender thread loop waiting for outbound queue"); outboundCv_.wait(lock, [this] { return !outboundQueue_.empty() || - connectionState_.load() != ConnectionState::CONNECTED; + connectionState_.load() != ConnectionState::CONNECTED; }); if (connectionState_.load() != ConnectionState::CONNECTED) { @@ -439,7 +452,7 @@ namespace bringauto::external_client::connection::communication { try { sendViaQuicStream(msg); - } catch (const std::exception& e) { + } catch (const std::exception &e) { settings::Logger::logError(std::format("[quic] Message send failed; {}", e.what())); } } @@ -461,10 +474,10 @@ namespace bringauto::external_client::connection::communication { } std::string QuicCommunication::getProtocolSettingsString( - const structures::ExternalConnectionSettings& settings, + const structures::ExternalConnectionSettings &settings, std::string_view key ) { - const auto& raw = settings.protocolSettings.at(std::string(key)); + const auto &raw = settings.protocolSettings.at(std::string(key)); if (nlohmann::json::accept(raw)) { auto j = nlohmann::json::parse(raw); From c30c723e6a2cc63bd978d8e2041f27d031c9759c Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Wed, 7 Jan 2026 08:24:11 +0000 Subject: [PATCH 15/30] Refactor `QuicCommunication`: Replace hardcoded ALPN value with `settings::Constants::ALPN` for improved configurability. --- include/bringauto/settings/Constants.hpp | 1 + .../connection/communication/QuicCommunication.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/include/bringauto/settings/Constants.hpp b/include/bringauto/settings/Constants.hpp index 2f9b148..fdd358e 100644 --- a/include/bringauto/settings/Constants.hpp +++ b/include/bringauto/settings/Constants.hpp @@ -172,6 +172,7 @@ class Constants { inline static constexpr std::string_view CA_FILE { "ca-file" }; inline static constexpr std::string_view CLIENT_CERT { "client-cert" }; inline static constexpr std::string_view CLIENT_KEY { "client-key" }; + inline static constexpr std::string_view ALPN { "alpn" }; inline static constexpr std::string_view MODULES { "modules" }; inline static constexpr std::string_view AERON_CONNECTION { "aeron:ipc"}; diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index d75e3bd..749b628 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -9,6 +9,9 @@ namespace bringauto::external_client::connection::communication { QuicCommunication::QuicCommunication(const structures::ExternalConnectionSettings &settings, const std::string &company, const std::string &vehicleName) : ICommunicationChannel(settings), + alpn_(getProtocolSettingsString( + settings, + settings::Constants::ALPN)), certFile_(getProtocolSettingsString( settings, settings::Constants::CLIENT_CERT)), @@ -18,7 +21,6 @@ namespace bringauto::external_client::connection::communication { caFile_(getProtocolSettingsString( settings, settings::Constants::CA_FILE)) { - alpn_ = "sample"; alpnBuffer_.Buffer = reinterpret_cast(alpn_.data()); alpnBuffer_.Length = static_cast(alpn_.size()); From 35b56f4840309d8a348609084447503e215396e2 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Wed, 7 Jan 2026 09:02:40 +0000 Subject: [PATCH 16/30] Add `quic_example.json` configuration file for QUIC module settings --- resources/config/quic_example.json | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 resources/config/quic_example.json diff --git a/resources/config/quic_example.json b/resources/config/quic_example.json new file mode 100644 index 0000000..0edbbc1 --- /dev/null +++ b/resources/config/quic_example.json @@ -0,0 +1,35 @@ +{ + "logging": { + "console": { "level": "DEBUG", "use": true }, + "file": { "level": "DEBUG", "use": false, "path": "./log" } + }, + + "internal-server-settings": { "port": 1636 }, + + "module-paths": { + "1": "/home/bringauto/modules/mission_module/lib/libmission-module-gateway-shared.so", + "2": "/home/bringauto/modules/io_module/lib/libio-module-gateway-shared.so", + "3": "/home/bringauto/modules/transparent_module/lib/libtransparent-module-gateway-shared.so" + }, + + "module-binary-path": "", + + "external-connection" : { + "company" : "bringauto", + "vehicle-name" : "virtual_vehicle", + "endpoints" : [ + { + "protocol-type" : "QUIC", + "server-ip": "127.0.0.1", + "port": 6121, + "quic-settings": { + "ca-file" : "build/certs/ca.pem", + "client-cert" : "build/certs/client.pem", + "client-key" : "build/certs/client-key.pem", + "alpn" : "sample" + }, + "modules": [1,2,3] + } + ] + } +} From 23f05e61aa22bfc3022e2d08098242f9fc3e7f29 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Wed, 7 Jan 2026 09:03:47 +0000 Subject: [PATCH 17/30] Refactor `ExternalConnection`: Remove redundant `return OK` statements after `receiveMessage` calls. --- .../external_client/connection/ExternalConnection.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/source/bringauto/external_client/connection/ExternalConnection.cpp b/source/bringauto/external_client/connection/ExternalConnection.cpp index 802e275..4051818 100644 --- a/source/bringauto/external_client/connection/ExternalConnection.cpp +++ b/source/bringauto/external_client/connection/ExternalConnection.cpp @@ -155,7 +155,6 @@ int ExternalConnection::connectMessageHandle(const std::vectorreceiveMessage(); - return OK; if(connectResponseMsg == nullptr) { log::logError("Communication client couldn't receive any message"); return NO_MESSAGE_AVAILABLE; @@ -203,7 +202,6 @@ int ExternalConnection::statusMessageHandle(const std::vectorreceiveMessage(); - return OK; if(statusResponseMsg == nullptr) { log::logError("Communication client couldn't receive any message"); return NO_MESSAGE_AVAILABLE; @@ -229,7 +227,6 @@ int ExternalConnection::commandMessageHandle(const std::vectorreceiveMessage(); - return OK; if(commandMsg == nullptr) { log::logError("Communication client couldn't receive any message"); return NO_MESSAGE_AVAILABLE; From 704e33f14db02e0cee9906357364b6e2c678f77f Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Wed, 7 Jan 2026 09:05:31 +0000 Subject: [PATCH 18/30] Clean up includes in `ExternalClient` and `QuicCommunication` for consistent include order and readability. --- source/bringauto/external_client/ExternalClient.cpp | 2 +- .../connection/communication/QuicCommunication.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/source/bringauto/external_client/ExternalClient.cpp b/source/bringauto/external_client/ExternalClient.cpp index 5835491..c21b145 100644 --- a/source/bringauto/external_client/ExternalClient.cpp +++ b/source/bringauto/external_client/ExternalClient.cpp @@ -4,13 +4,13 @@ #include #include #include +#include #include #include #include -#include "bringauto/external_client/connection/communication/QuicCommunication.hpp" namespace bringauto::external_client { diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 749b628..a9196c1 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -1,9 +1,8 @@ +#include #include #include #include -#include - namespace bringauto::external_client::connection::communication { QuicCommunication::QuicCommunication(const structures::ExternalConnectionSettings &settings, From d9d308801e4878c6d16b38558cf65cf56b37524b Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Wed, 7 Jan 2026 10:42:46 +0000 Subject: [PATCH 19/30] Refactor `QuicCommunication`: Add `SendBuffer` abstraction for improved memory handling, update QUIC stream send logic, refine logging with stream IDs, and switch `initRegistration` to use `std::string`. --- .../communication/QuicCommunication.hpp | 10 ++- .../communication/QuicCommunication.cpp | 74 +++++++++++-------- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index e1a986d..8d7aca4 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -11,7 +11,6 @@ #include - namespace bringauto::external_client::connection::communication { class QuicCommunication : public ICommunicationChannel { public: @@ -124,6 +123,13 @@ namespace bringauto::external_client::connection::communication { std::jthread senderThread_; /// @} + struct SendBuffer { + QUIC_BUFFER buffer{}; + std::unique_ptr storage; + + explicit SendBuffer(size_t size); + }; + /** * @brief Loads and initializes the MsQuic API. * @@ -146,7 +152,7 @@ namespace bringauto::external_client::connection::communication { * * @param appName Application name used to identify the QUIC registration. */ - void initRegistration(const char *appName); + void initRegistration(std::string appName); /** * @brief Initializes the QUIC configuration and loads client credentials. diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index a9196c1..9d21391 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -112,9 +112,9 @@ namespace bringauto::external_client::connection::communication { } } - void QuicCommunication::initRegistration(const char *appName) { + void QuicCommunication::initRegistration(std::string appName) { QUIC_REGISTRATION_CONFIG config{}; - config.AppName = const_cast(appName); + config.AppName = appName.c_str(); config.ExecutionProfile = QUIC_EXECUTION_PROFILE_LOW_LATENCY; QUIC_STATUS status = quic_->RegistrationOpen(&config, ®istration_); @@ -223,37 +223,50 @@ namespace bringauto::external_client::connection::communication { inboundCv_.notify_one(); } + QuicCommunication::SendBuffer::SendBuffer(size_t size) : storage(std::make_unique(size)) { + buffer.Length = static_cast(size); + buffer.Buffer = storage.get(); + } + void QuicCommunication::sendViaQuicStream(const std::shared_ptr &message) { HQUIC stream{nullptr}; if (QUIC_FAILED(quic_->StreamOpen(connection_, QUIC_STREAM_OPEN_FLAG_NONE, streamCallback, this, &stream))) { throw std::runtime_error("[quic] StreamOpen failed"); } - const auto size = message->ByteSizeLong(); - const auto buffer = std::make_unique(size); + const size_t size = message->ByteSizeLong(); + auto sendBuffer = std::make_unique(size); - if (!message->SerializeToArray(buffer.get(), static_cast(size))) { + if (!message->SerializeToArray(sendBuffer->storage.get(), static_cast(size))) { throw std::runtime_error("[quic] Message serialization failed"); } - auto *buf = new QUIC_BUFFER{}; - buf->Length = static_cast(size); - buf->Buffer = new uint8_t[buf->Length]; + settings::Logger::logDebug( + "[quic][debug] SendBuffer ptr={}, QUIC_BUFFER ptr={}, data ptr={}, size={}", + static_cast(sendBuffer.get()), + static_cast(&sendBuffer->buffer), + static_cast(sendBuffer->buffer.Buffer), + sendBuffer->buffer.Length + ); - std::memcpy(buf->Buffer, buffer.get(), buf->Length); + const SendBuffer *raw = sendBuffer.get(); + const QUIC_BUFFER *quicBuf = &raw->buffer; + SendBuffer *quicBufContext = sendBuffer.release(); - const QUIC_STATUS status = quic_->StreamSend(stream, buf, 1, - /** - * START => Simulates quic_->StreamStart before send - * FIN => Simulates quic_->StreamShutdown after send - */ - QUIC_SEND_FLAG_START | QUIC_SEND_FLAG_FIN, - buf + const QUIC_STATUS status = quic_->StreamSend( + stream, + quicBuf, + 1, + /** + * START => Simulates quic_->StreamStart before send + * FIN => Simulates quic_->StreamShutdown after send + */ + QUIC_SEND_FLAG_START | QUIC_SEND_FLAG_FIN, + quicBufContext ); if (QUIC_FAILED(status)) { - delete[] buf->Buffer; - delete buf; + std::unique_ptr reclaim{quicBufContext}; quic_->StreamShutdown(stream, QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0); throw std::runtime_error(std::format("[quic] Failed to send QUIC stream; QUIC_STATUS => {:x}", status)); @@ -352,7 +365,7 @@ namespace bringauto::external_client::connection::communication { /// Raised when the peer has finished sending on this stream /// (peer's FIN has been fully received and processed). case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN: { - settings::Logger::logDebug("[quic] Peer stream send shutdown"); + settings::Logger::logDebug("[quic] [stream {}] Peer stream send shutdown", streamId ? *streamId : 0); break; } @@ -367,7 +380,7 @@ namespace bringauto::external_client::connection::communication { /// Raised after StreamStart completes successfully /// and the stream becomes active with a valid stream ID. case QUIC_STREAM_EVENT_START_COMPLETE: { - settings::Logger::logDebug("[quic] Stream start completed"); + settings::Logger::logDebug("[quic] [stream {}] Stream start completed", streamId ? *streamId : 0); break; } @@ -395,18 +408,16 @@ namespace bringauto::external_client::connection::communication { * passed to StreamSend (via ClientContext) */ if (event->SEND_COMPLETE.Canceled) { - settings::Logger::logWarning("[quic] Stream send canceled"); + settings::Logger::logDebug("[quic] [stream {}] Stream send canceled", + streamId ? *streamId : 0); } else { - settings::Logger::logDebug("[quic] Stream send completed"); + settings::Logger::logDebug("[quic] [stream {}] Stream send completed", + streamId ? *streamId : 0); } - const auto *buf = - static_cast(event->SEND_COMPLETE.ClientContext); - - if (buf) { - delete[] buf->Buffer; - delete buf; - } + std::unique_ptr sendBuf{ + static_cast(event->SEND_COMPLETE.ClientContext) + }; break; } @@ -414,13 +425,14 @@ namespace bringauto::external_client::connection::communication { /// Raised when both send and receive directions are closed /// and the stream lifecycle is fully complete. case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: { - settings::Logger::logDebug("[quic] Stream shutdown complete"); + settings::Logger::logDebug("[quic] [stream {}] Stream shutdown complete", streamId ? *streamId : 0); self->quic_->StreamClose(stream); break; } default: { - settings::Logger::logDebug("[quic] Unhandled stream event 0x{:x}", static_cast(event->Type)); + settings::Logger::logDebug("[quic] [stream {}] Unhandled stream event 0x{:x}", streamId ? *streamId : 0, + static_cast(event->Type)); break; } } From 0bc2a532b9c027b1435ebe54d07d49d4c5aa4dfc Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Wed, 7 Jan 2026 11:33:36 +0000 Subject: [PATCH 20/30] Refactor `QuicCommunication`: Replace exceptions with `settings::Logger::logError` for error handling and ensure graceful returns in failure scenarios. --- .../communication/QuicCommunication.cpp | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 9d21391..d1aad7b 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -46,12 +46,14 @@ namespace bringauto::external_client::connection::communication { ConnectionState expected = ConnectionState::NOT_CONNECTED; if (!connectionState_. compare_exchange_strong(expected, ConnectionState::CONNECTING, std::memory_order_acq_rel)) { - throw std::logic_error("Connection already in progress or established"); + settings::Logger::logError("Connection already in progress or established"); + return; } QUIC_STATUS status = quic_->ConnectionOpen(registration_, connectionCallback, this, &connection_); if (QUIC_FAILED(status)) { - throw std::runtime_error(std::format("ConnectionOpen failed (status=0x{:x})", status)); + settings::Logger::logError("ConnectionOpen failed (status=0x{:x})", status); + return; } status = quic_->ConnectionStart( @@ -63,7 +65,8 @@ namespace bringauto::external_client::connection::communication { ); if (QUIC_FAILED(status)) { - throw std::runtime_error(std::format("ConnectionOpen failed (status=0x{:x})", status)); + settings::Logger::logError("ConnectionOpen failed (status=0x{:x})", status); + return; } connectionState_ = ConnectionState::CONNECTING; @@ -108,7 +111,8 @@ namespace bringauto::external_client::connection::communication { void QuicCommunication::loadMsQuic() { QUIC_STATUS status = MsQuicOpen2(&quic_); if (QUIC_FAILED(status)) { - throw std::runtime_error(std::format("[quic] Failed to open QUIC; QUIC_STATUS => {:x}", status)); + settings::Logger::logError("[quic] Failed to open QUIC; QUIC_STATUS => {:x}", status); + return; } } @@ -119,8 +123,8 @@ namespace bringauto::external_client::connection::communication { QUIC_STATUS status = quic_->RegistrationOpen(&config, ®istration_); if (QUIC_FAILED(status)) { - throw std::runtime_error( - std::format("[quic] Failed to open QUIC registration; QUIC_STATUS => {:x}", status)); + settings::Logger::logError("[quic] Failed to open QUIC registration; QUIC_STATUS => {:x}", status); + return; } } @@ -154,15 +158,16 @@ namespace bringauto::external_client::connection::communication { ); if (QUIC_FAILED(status)) { - throw std::runtime_error(std::format("[quic] Failed to open QUIC configuration; QUIC_STATUS => {:x}", - status)); + settings::Logger::logError("[quic] Failed to open QUIC configuration; QUIC_STATUS => {:x}", status); + return; } } void QuicCommunication::configurationLoadCredential(const QUIC_CREDENTIAL_CONFIG *credential) const { const QUIC_STATUS status = quic_->ConfigurationLoadCredential(config_, credential); if (QUIC_FAILED(status)) { - throw std::runtime_error(std::format("[quic] Failed to load QUIC credential; QUIC_STATUS => {:x}", status)); + settings::Logger::logError("[quic] Failed to load QUIC credential; QUIC_STATUS => {:x}", status); + return; } } @@ -231,14 +236,16 @@ namespace bringauto::external_client::connection::communication { void QuicCommunication::sendViaQuicStream(const std::shared_ptr &message) { HQUIC stream{nullptr}; if (QUIC_FAILED(quic_->StreamOpen(connection_, QUIC_STREAM_OPEN_FLAG_NONE, streamCallback, this, &stream))) { - throw std::runtime_error("[quic] StreamOpen failed"); + settings::Logger::logError("[quic] StreamOpen failed"); + return; } const size_t size = message->ByteSizeLong(); auto sendBuffer = std::make_unique(size); if (!message->SerializeToArray(sendBuffer->storage.get(), static_cast(size))) { - throw std::runtime_error("[quic] Message serialization failed"); + settings::Logger::logError("[quic] Message serialization failed"); + return; } settings::Logger::logDebug( @@ -269,7 +276,8 @@ namespace bringauto::external_client::connection::communication { std::unique_ptr reclaim{quicBufContext}; quic_->StreamShutdown(stream, QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0); - throw std::runtime_error(std::format("[quic] Failed to send QUIC stream; QUIC_STATUS => {:x}", status)); + settings::Logger::logError("[quic] Failed to send QUIC stream; QUIC_STATUS => {:x}", status); + return; } auto streamId = getStreamId(stream); @@ -463,11 +471,7 @@ namespace bringauto::external_client::connection::communication { outboundQueue_.pop(); lock.unlock(); - try { - sendViaQuicStream(msg); - } catch (const std::exception &e) { - settings::Logger::logError(std::format("[quic] Message send failed; {}", e.what())); - } + sendViaQuicStream(msg); } } From ecee8a3885159fb2945f2c300c73e0a2d9845140 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Wed, 7 Jan 2026 11:40:39 +0000 Subject: [PATCH 21/30] Refactor `QuicCommunication`: Simplify `SendBuffer` by replacing `std::unique_ptr` with `std::string` for memory management. --- .../connection/communication/QuicCommunication.hpp | 8 ++++++-- .../connection/communication/QuicCommunication.cpp | 7 +------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index 8d7aca4..812ac4e 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -125,9 +125,13 @@ namespace bringauto::external_client::connection::communication { struct SendBuffer { QUIC_BUFFER buffer{}; - std::unique_ptr storage; + std::string storage; - explicit SendBuffer(size_t size); + explicit SendBuffer(size_t size) + : storage(size, '\0') { + buffer.Length = static_cast(storage.size()); + buffer.Buffer = reinterpret_cast(storage.data()); + } }; /** diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index d1aad7b..0933c59 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -228,11 +228,6 @@ namespace bringauto::external_client::connection::communication { inboundCv_.notify_one(); } - QuicCommunication::SendBuffer::SendBuffer(size_t size) : storage(std::make_unique(size)) { - buffer.Length = static_cast(size); - buffer.Buffer = storage.get(); - } - void QuicCommunication::sendViaQuicStream(const std::shared_ptr &message) { HQUIC stream{nullptr}; if (QUIC_FAILED(quic_->StreamOpen(connection_, QUIC_STREAM_OPEN_FLAG_NONE, streamCallback, this, &stream))) { @@ -243,7 +238,7 @@ namespace bringauto::external_client::connection::communication { const size_t size = message->ByteSizeLong(); auto sendBuffer = std::make_unique(size); - if (!message->SerializeToArray(sendBuffer->storage.get(), static_cast(size))) { + if (!message->SerializeToArray(sendBuffer->storage.data(), static_cast(size))) { settings::Logger::logError("[quic] Message serialization failed"); return; } From b82f77e2e9f5dd210f53ea5149f55a33767954e1 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Wed, 7 Jan 2026 11:42:31 +0000 Subject: [PATCH 22/30] Refactor `QuicCommunication`: Reorder initialization steps to ensure proper setup before logging. --- .../connection/communication/QuicCommunication.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 0933c59..77679e3 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -23,16 +23,12 @@ namespace bringauto::external_client::connection::communication { alpnBuffer_.Buffer = reinterpret_cast(alpn_.data()); alpnBuffer_.Length = static_cast(alpn_.size()); + loadMsQuic(); + initRegistration("module-gateway-quic-client"); + initConfiguration(); + settings::Logger::logInfo("[quic] Initialize QUIC communication to {}:{} for {}/{}", settings.serverIp, settings.port, company, vehicleName); - - try { - loadMsQuic(); - initRegistration("module-gateway-quic-client"); - initConfiguration(); - } catch (const std::exception &e) { - settings::Logger::logError("[quic] Failed to initialize QUIC communication; {}", e.what()); - } } QuicCommunication::~QuicCommunication() { From d1ade22b899eacf102fa57fa4e129cd84c594cb5 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Wed, 7 Jan 2026 12:05:39 +0000 Subject: [PATCH 23/30] Refactor `QuicCommunication`: Replace `std::lock_guard` with `std::scoped_lock` and simplify enum string conversion using `using enum`. --- include/bringauto/common_utils/EnumUtils.hpp | 24 ++++++++++--------- .../communication/QuicCommunication.cpp | 8 +++---- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/include/bringauto/common_utils/EnumUtils.hpp b/include/bringauto/common_utils/EnumUtils.hpp index 991ba50..b3b2773 100644 --- a/include/bringauto/common_utils/EnumUtils.hpp +++ b/include/bringauto/common_utils/EnumUtils.hpp @@ -77,20 +77,22 @@ class EnumUtils { /** * @brief Converts connection state to string * - * @param toString structures::ProtocolType + * @param state external_client::connection::ConnectionState * @return std::string_view */ - static constexpr std::string_view connectionStateToString(external_client::connection::ConnectionState toString) { - switch(toString) { - case external_client::connection::ConnectionState::NOT_INITIALIZED: return "Not Initialized"; - case external_client::connection::ConnectionState::NOT_CONNECTED: return "Not Connected"; - case external_client::connection::ConnectionState::CONNECTING: return "Connecting"; - case external_client::connection::ConnectionState::CONNECTED: return "Connected"; - case external_client::connection::ConnectionState::CLOSING: return "Closing"; + static constexpr std::string_view connectionStateToString( + external_client::connection::ConnectionState state + ) { + using enum external_client::connection::ConnectionState; + + switch (state) { + case NOT_INITIALIZED: return "Not Initialized"; + case NOT_CONNECTED: return "Not Connected"; + case CONNECTING: return "Connecting"; + case CONNECTED: return "Connected"; + case CLOSING: return "Closing"; default: return "Unknown"; } + } }; - -}; - } diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 77679e3..f41d8a7 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -76,7 +76,7 @@ namespace bringauto::external_client::connection::communication { { auto copy = std::make_shared(*message); - std::lock_guard lock(outboundMutex_); + std::scoped_lock lock(outboundMutex_); outboundQueue_.push(std::move(copy)); } settings::Logger::logDebug("[quic] Notifying sender thread about enqueued message"); @@ -85,7 +85,7 @@ namespace bringauto::external_client::connection::communication { } std::shared_ptr QuicCommunication::receiveMessage() { - std::unique_lock lock(inboundMutex_); + std::unique_lock lock(inboundMutex_); if (!inboundCv_.wait_for( lock, @@ -217,7 +217,7 @@ namespace bringauto::external_client::connection::communication { std::shared_ptr msg ) { { - std::lock_guard lock(inboundMutex_); + std::scoped_lock lock(inboundMutex_); inboundQueue_.push(std::move(msg)); } settings::Logger::logDebug("[quic] Notifying receiver thread about dequeued message"); @@ -445,7 +445,7 @@ namespace bringauto::external_client::connection::communication { while (connectionState_.load() == ConnectionState::CONNECTED) { std::shared_ptr msg; - std::unique_lock lock(outboundMutex_); + std::unique_lock lock(outboundMutex_); settings::Logger::logDebug("[quic] Sender thread loop waiting for outbound queue"); outboundCv_.wait(lock, [this] { From d2303a782ed235279f6d5dfb24676a42cb1c6b9f Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Thu, 15 Jan 2026 09:26:47 +0000 Subject: [PATCH 24/30] Refactor `QuicCommunication`: Add early exit for empty stream receive event. --- .../communication/QuicCommunication.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index f41d8a7..de57364 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -239,14 +239,6 @@ namespace bringauto::external_client::connection::communication { return; } - settings::Logger::logDebug( - "[quic][debug] SendBuffer ptr={}, QUIC_BUFFER ptr={}, data ptr={}, size={}", - static_cast(sendBuffer.get()), - static_cast(&sendBuffer->buffer), - static_cast(sendBuffer->buffer.Buffer), - sendBuffer->buffer.Length - ); - const SendBuffer *raw = sendBuffer.get(); const QUIC_BUFFER *quicBuf = &raw->buffer; SendBuffer *quicBufContext = sendBuffer.release(); @@ -336,6 +328,14 @@ namespace bringauto::external_client::connection::communication { switch (event->Type) { /// Raised when the peer sends stream data and MsQuic delivers received bytes to the application. case QUIC_STREAM_EVENT_RECEIVE: { + if (event->RECEIVE.BufferCount == 0) { + settings::Logger::logDebug( + "[quic] [stream {}] End of stream received", + streamId ? *streamId : 0 + ); + break; + } + settings::Logger::logDebug( "[quic] [stream {}] Received {:d} bytes in {:d} buffers", streamId ? *streamId : 0, From 5853c703cb6fc2ae9a4e64a8a9b5085231bd18d0 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Mon, 19 Jan 2026 06:03:26 +0000 Subject: [PATCH 25/30] Refactor `QuicCommunication`: Replace dynamic app name parameter in `initRegistration` with a constant for simplification and improved consistency. --- .../connection/communication/QuicCommunication.hpp | 4 +--- .../connection/communication/QuicCommunication.cpp | 8 +++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index 812ac4e..9824bfd 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -153,10 +153,8 @@ namespace bringauto::external_client::connection::communication { * creating QUIC configurations and connections. * * If registration creation fails, an error is logged. - * - * @param appName Application name used to identify the QUIC registration. */ - void initRegistration(std::string appName); + void initRegistration(); /** * @brief Initializes the QUIC configuration and loads client credentials. diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index de57364..4858df8 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -24,7 +24,7 @@ namespace bringauto::external_client::connection::communication { alpnBuffer_.Length = static_cast(alpn_.size()); loadMsQuic(); - initRegistration("module-gateway-quic-client"); + initRegistration(); initConfiguration(); settings::Logger::logInfo("[quic] Initialize QUIC communication to {}:{} for {}/{}", settings.serverIp, @@ -112,9 +112,11 @@ namespace bringauto::external_client::connection::communication { } } - void QuicCommunication::initRegistration(std::string appName) { + constexpr auto quicRegistrationAppName = "module-gateway-quic-client"; + + void QuicCommunication::initRegistration() { QUIC_REGISTRATION_CONFIG config{}; - config.AppName = appName.c_str(); + config.AppName = quicRegistrationAppName; config.ExecutionProfile = QUIC_EXECUTION_PROFILE_LOW_LATENCY; QUIC_STATUS status = quic_->RegistrationOpen(&config, ®istration_); From 30123d9877ffe375326523d4b8d571d22a2d84a7 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Mon, 19 Jan 2026 06:27:49 +0000 Subject: [PATCH 26/30] Refactor `QuicCommunication`: Replace `std::shared_ptr` with `std::unique_ptr` in outbound queue, update `sendViaQuicStream` to use references for improved memory management and clarity. --- .../communication/QuicCommunication.hpp | 4 ++-- .../communication/QuicCommunication.cpp | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index 9824bfd..a7ef704 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -114,7 +114,7 @@ namespace bringauto::external_client::connection::communication { /// @name Outbound (this → peer) /// @{ /// Queue of outgoing messages to be sent to the peer - std::queue > outboundQueue_; + std::queue > outboundQueue_; /// Mutex protecting access to the outbound message queue std::mutex outboundMutex_; /// Condition variable for signaling outbound message availability @@ -217,7 +217,7 @@ namespace bringauto::external_client::connection::communication { * * @param message Message to be sent to the peer. */ - void sendViaQuicStream(const std::shared_ptr &message); + void sendViaQuicStream(const ExternalProtocol::ExternalClient& message); /** * @brief Closes the active QUIC configuration. diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 4858df8..caec057 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -75,8 +75,8 @@ namespace bringauto::external_client::connection::communication { } { - auto copy = std::make_shared(*message); - std::scoped_lock lock(outboundMutex_); + auto copy = std::make_unique(*message); + std::lock_guard lock(outboundMutex_); outboundQueue_.push(std::move(copy)); } settings::Logger::logDebug("[quic] Notifying sender thread about enqueued message"); @@ -226,17 +226,17 @@ namespace bringauto::external_client::connection::communication { inboundCv_.notify_one(); } - void QuicCommunication::sendViaQuicStream(const std::shared_ptr &message) { + void QuicCommunication::sendViaQuicStream(const ExternalProtocol::ExternalClient& message) { HQUIC stream{nullptr}; if (QUIC_FAILED(quic_->StreamOpen(connection_, QUIC_STREAM_OPEN_FLAG_NONE, streamCallback, this, &stream))) { settings::Logger::logError("[quic] StreamOpen failed"); return; } - const size_t size = message->ByteSizeLong(); + const size_t size = message.ByteSizeLong(); auto sendBuffer = std::make_unique(size); - if (!message->SerializeToArray(sendBuffer->storage.data(), static_cast(size))) { + if (!message.SerializeToArray(sendBuffer->storage.data(), static_cast(size))) { settings::Logger::logError("[quic] Message serialization failed"); return; } @@ -445,7 +445,7 @@ namespace bringauto::external_client::connection::communication { settings::Logger::logDebug("[quic] Sender thread loop started"); while (connectionState_.load() == ConnectionState::CONNECTED) { - std::shared_ptr msg; + std::unique_ptr msg; std::unique_lock lock(outboundMutex_); @@ -460,11 +460,11 @@ namespace bringauto::external_client::connection::communication { } settings::Logger::logDebug("[quic] Sender thread loop sending outbound queue"); - msg = outboundQueue_.front(); + msg = std::move(outboundQueue_.front()); outboundQueue_.pop(); lock.unlock(); - sendViaQuicStream(msg); + sendViaQuicStream(*msg); } } From c1b3a8c34334fa4d9ab5ba58305434df06baa7cb Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Mon, 19 Jan 2026 06:36:24 +0000 Subject: [PATCH 27/30] Update `README.md`: Add QUIC configuration details alongside MQTT, expand protocol support, and provide examples for both. --- resources/config/README.md | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/resources/config/README.md b/resources/config/README.md index 2889f14..a67581a 100644 --- a/resources/config/README.md +++ b/resources/config/README.md @@ -28,18 +28,27 @@ Note: at least one logging sink needs to be used * company : company name used as identification in external connection (string) * vehicle-name : vehicle name used as identification in external connection (string) * endpoints : array of objects listing possible ways to connect to external server - - protocol-type : string (only mqtt is supported) + - protocol-type : string (only mqtt and quic are supported) - server-ip : ip of the external connection (string) - port : port of the external connection (int) - modules : array of integers that represent module numbers to be used on this connection - - mqtt-settings : **only for mqtt** - - ssl : if connection requires ssl, bool - - ca-file : public trusted certificate file name (string) - - client-cert : public certificate chain file name (string) - - client-key : private key file name (string) -## Example +#### mqtt-settings (only for MQTT) +* ssl : if connection requires ssl, bool +* ca-file : public trusted certificate file name (string) +* client-cert : public certificate chain file name (string) +* client-key : private key file name (string) -[Example](./example.json) +#### quic-settings (only for QUIC) +* ca-file : path to the trusted CA certificate file (string) +* client-cert : path to the client certificate file (string) +* client-key : path to the client private key file (string) +* alpn : Application-Layer Protocol Negotiation identifier (string), must match the ALPN configured on the server + +Note: QUIC uses TLS 1.3 internally. All certificate files must be provided in a format supported by MsQuic/OpenSSL. +## Examples + +[MQTT Example](./example.json) +[QUIC Example](./quic_example.json) From e74bc2aa346928f58807ccb6d2cebe5c25a03513 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Mon, 19 Jan 2026 06:44:41 +0000 Subject: [PATCH 28/30] Refactor `QuicCommunication`: Add comments and documentation for `SendBuffer` to clarify purpose, usage, and construction. --- .../communication/QuicCommunication.hpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index a7ef704..1cc10fe 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -123,10 +123,28 @@ namespace bringauto::external_client::connection::communication { std::jthread senderThread_; /// @} + /** + * @brief Owns memory for a single MsQuic StreamSend operation. + * + * SendBuffer wraps a QUIC_BUFFER together with its backing storage. + * The memory must remain valid until MsQuic signals + * QUIC_STREAM_EVENT_SEND_COMPLETE, at which point it can be safely freed. + * + * Instances of this struct are typically allocated on the heap and passed + * to MsQuic via the StreamSend ClientContext pointer. + */ struct SendBuffer { QUIC_BUFFER buffer{}; std::string storage; + /** + * @brief Constructs a SendBuffer with zero-initialized storage. + * + * Allocates storage of the given size, fills it with zero bytes, + * and initializes the QUIC_BUFFER to point to this storage. + * + * @param size Number of bytes to allocate for the send buffer. + */ explicit SendBuffer(size_t size) : storage(size, '\0') { buffer.Length = static_cast(storage.size()); From 3e5df149cd5328140d7827c8508fb8c81f25f241 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Mon, 19 Jan 2026 07:07:08 +0000 Subject: [PATCH 29/30] Refactor `EnumUtils`: Replace hardcoded connection state strings with `settings::Constants` for improved maintainability. --- include/bringauto/common_utils/EnumUtils.hpp | 18 ++++++++++++------ include/bringauto/settings/Constants.hpp | 7 +++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/include/bringauto/common_utils/EnumUtils.hpp b/include/bringauto/common_utils/EnumUtils.hpp index b3b2773..56994d6 100644 --- a/include/bringauto/common_utils/EnumUtils.hpp +++ b/include/bringauto/common_utils/EnumUtils.hpp @@ -86,12 +86,18 @@ class EnumUtils { using enum external_client::connection::ConnectionState; switch (state) { - case NOT_INITIALIZED: return "Not Initialized"; - case NOT_CONNECTED: return "Not Connected"; - case CONNECTING: return "Connecting"; - case CONNECTED: return "Connected"; - case CLOSING: return "Closing"; - default: return "Unknown"; + case NOT_INITIALIZED: + return settings::Constants::LOG_CONNECTION_STATE_NOT_INITIALIZED; + case NOT_CONNECTED: + return settings::Constants::LOG_CONNECTION_STATE_NOT_CONNECTED; + case CONNECTING: + return settings::Constants::LOG_CONNECTION_STATE_CONNECTING; + case CONNECTED: + return settings::Constants::LOG_CONNECTION_STATE_CONNECTED; + case CLOSING: + return settings::Constants::LOG_CONNECTION_STATE_CLOSING; + default: + return settings::Constants::LOG_UNKNOWN; } } }; diff --git a/include/bringauto/settings/Constants.hpp b/include/bringauto/settings/Constants.hpp index fdd358e..e9628b5 100644 --- a/include/bringauto/settings/Constants.hpp +++ b/include/bringauto/settings/Constants.hpp @@ -140,6 +140,7 @@ class Constants { inline static constexpr std::string_view LOG_LEVEL { "level" }; inline static constexpr std::string_view LOG_USE { "use" }; inline static constexpr std::string_view LOG_PATH { "path" }; + inline static constexpr std::string_view LOG_UNKNOWN { "unknown" }; inline static constexpr std::string_view LOG_LEVEL_DEBUG { "DEBUG" }; inline static constexpr std::string_view LOG_LEVEL_INFO { "INFO" }; @@ -148,6 +149,12 @@ class Constants { inline static constexpr std::string_view LOG_LEVEL_CRITICAL { "CRITICAL" }; inline static constexpr std::string_view LOG_LEVEL_INVALID { "INVALID" }; + inline static constexpr std::string_view LOG_CONNECTION_STATE_NOT_INITIALIZED { "not initialized" }; + inline static constexpr std::string_view LOG_CONNECTION_STATE_NOT_CONNECTED { "not connected" }; + inline static constexpr std::string_view LOG_CONNECTION_STATE_CONNECTING { "connecting" }; + inline static constexpr std::string_view LOG_CONNECTION_STATE_CONNECTED { "connected" }; + inline static constexpr std::string_view LOG_CONNECTION_STATE_CLOSING { "closing" }; + inline static constexpr std::string_view HELP { "help" }; inline static constexpr std::string_view PORT { "port" }; From be119429d663946df7d6bfb6f7aaabd92038f9d2 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Mon, 19 Jan 2026 08:25:22 +0000 Subject: [PATCH 30/30] Add `QuicSettingsParser` to parse QUIC settings and integrate with `QuicCommunication`. --- .../communication/QuicCommunication.hpp | 4 +- .../bringauto/settings/QuicSettingsParser.hpp | 21 ++++++ resources/config/quic_example.json | 3 +- .../communication/QuicCommunication.cpp | 10 +-- .../bringauto/settings/QuicSettingsParser.cpp | 64 +++++++++++++++++++ 5 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 include/bringauto/settings/QuicSettingsParser.hpp create mode 100644 source/bringauto/settings/QuicSettingsParser.cpp diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index 1cc10fe..aa28683 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -193,10 +193,8 @@ namespace bringauto::external_client::connection::communication { * * If settings are not provided, default QUIC settings are used. * On failure, an error is logged. - * - * @param settings Optional QUIC transport settings. */ - void configurationOpen(const QUIC_SETTINGS *settings); + void configurationOpen(); /** * @brief Loads TLS credentials into the QUIC configuration. diff --git a/include/bringauto/settings/QuicSettingsParser.hpp b/include/bringauto/settings/QuicSettingsParser.hpp new file mode 100644 index 0000000..b1df800 --- /dev/null +++ b/include/bringauto/settings/QuicSettingsParser.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include + +#include + + +namespace bringauto::settings { + class QuicSettingsParser { + public: + static QUIC_SETTINGS parse(const structures::ExternalConnectionSettings &settings); + + private: + static std::optional getOptionalUint( + const structures::ExternalConnectionSettings &settings, + std::string_view key + ); + }; +} diff --git a/resources/config/quic_example.json b/resources/config/quic_example.json index 0edbbc1..640b364 100644 --- a/resources/config/quic_example.json +++ b/resources/config/quic_example.json @@ -26,7 +26,8 @@ "ca-file" : "build/certs/ca.pem", "client-cert" : "build/certs/client.pem", "client-key" : "build/certs/client-key.pem", - "alpn" : "sample" + "alpn" : "sample", + "DisconnectTimeoutMs" : 800 }, "modules": [1,2,3] } diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index caec057..f356681 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace bringauto::external_client::connection::communication { @@ -127,7 +128,7 @@ namespace bringauto::external_client::connection::communication { } void QuicCommunication::initConfiguration() { - configurationOpen(nullptr); + configurationOpen(); QUIC_CERTIFICATE_FILE certificate{}; certificate.CertificateFile = certFile_.c_str(); @@ -142,14 +143,15 @@ namespace bringauto::external_client::connection::communication { configurationLoadCredential(&credential); } - void QuicCommunication::configurationOpen(const QUIC_SETTINGS *settings) { - const uint32_t settingsSize = settings ? sizeof(*settings) : 0; + void QuicCommunication::configurationOpen() { + const QUIC_SETTINGS settings = settings::QuicSettingsParser::parse(settings_); + const uint32_t settingsSize = sizeof(QUIC_SETTINGS); QUIC_STATUS status = quic_->ConfigurationOpen( registration_, &alpnBuffer_, 1, - settings, + &settings, settingsSize, nullptr, &config_ diff --git a/source/bringauto/settings/QuicSettingsParser.cpp b/source/bringauto/settings/QuicSettingsParser.cpp new file mode 100644 index 0000000..4c3dab7 --- /dev/null +++ b/source/bringauto/settings/QuicSettingsParser.cpp @@ -0,0 +1,64 @@ +#include +#include +#include + +#include + + +namespace bringauto::settings { + QUIC_SETTINGS QuicSettingsParser::parse( + const structures::ExternalConnectionSettings &settings + ) { + QUIC_SETTINGS quic{}; + + if (auto value = getOptionalUint(settings, "IdleTimeoutMs")) { + settings::Logger::logDebug("[quic] [settings] IdleTimeoutMs settings loaded"); + quic.IdleTimeoutMs = *value; + quic.IsSet.IdleTimeoutMs = TRUE; + } + + if (auto value = getOptionalUint(settings, "HandshakeIdleTimeoutMs")) { + settings::Logger::logDebug("[quic] [settings] HandshakeIdleTimeoutMs settings loaded"); + quic.HandshakeIdleTimeoutMs = *value; + quic.IsSet.HandshakeIdleTimeoutMs = TRUE; + } + + if (auto value = getOptionalUint(settings, "DisconnectTimeoutMs")) { + settings::Logger::logDebug("[quic] [settings] DisconnectTimeoutMs settings loaded"); + quic.DisconnectTimeoutMs = *value; + quic.IsSet.DisconnectTimeoutMs = TRUE; + } + + return quic; + } + + std::optional QuicSettingsParser::getOptionalUint( + const structures::ExternalConnectionSettings &settings, + std::string_view key + ) { + const auto it = settings.protocolSettings.find(std::string(key)); + if (it == settings.protocolSettings.end()) { + return std::nullopt; + } + + const auto &raw = it->second; + + if (!nlohmann::json::accept(raw)) { + settings::Logger::logWarning( + "[quic] QUIC setting '{}' is not valid JSON", key + ); + return std::nullopt; + } + + auto j = nlohmann::json::parse(raw); + + if (!j.is_number_unsigned()) { + settings::Logger::logWarning( + "[quic] QUIC setting '{}' must be an unsigned integer", key + ); + return std::nullopt; + } + + return j.get(); + } +}