From b234a8132535d7834847477d9ee95cc77e10eaa4 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Thu, 29 Jan 2026 09:54:01 +0000 Subject: [PATCH 1/7] Enhance `QuicCommunication`: Add support for unidirectional and bidirectional streams with configurable `stream-mode`, improve error handling for missing or invalid settings, and update `QuicSettingsParser` to include `PeerUnidiStreamCount`. --- .../communication/QuicCommunication.hpp | 10 +++ include/bringauto/settings/Constants.hpp | 1 + resources/config/quic_example.json | 4 +- .../communication/QuicCommunication.cpp | 66 ++++++++++++++++--- .../bringauto/settings/QuicSettingsParser.cpp | 6 ++ 5 files changed, 77 insertions(+), 10 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index aa28683..c4aa5a7 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -78,6 +78,12 @@ namespace bringauto::external_client::connection::communication { void closeConnection() override; private: + enum class StreamMode + { + Unidirectional, + Bidirectional + }; + /// Pointer to the MsQuic API function table const QUIC_API_TABLE *quic_{nullptr}; /// QUIC registration handle associated with the application @@ -98,6 +104,8 @@ namespace bringauto::external_client::connection::communication { /// Path to the CA certificate file std::string caFile_; + StreamMode streamMode_{StreamMode::Bidirectional}; + /// Atomic state of the connection used for synchronization across threads std::atomic connectionState_{ConnectionState::NOT_CONNECTED}; @@ -334,5 +342,7 @@ namespace bringauto::external_client::connection::communication { const structures::ExternalConnectionSettings &settings, std::string_view key ); + + static StreamMode parseStreamMode(const structures::ExternalConnectionSettings &settings); }; } diff --git a/include/bringauto/settings/Constants.hpp b/include/bringauto/settings/Constants.hpp index e9628b5..14111d0 100644 --- a/include/bringauto/settings/Constants.hpp +++ b/include/bringauto/settings/Constants.hpp @@ -180,6 +180,7 @@ class Constants { 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 QUIC_STREAM_MODE { "stream-mode" }; inline static constexpr std::string_view MODULES { "modules" }; inline static constexpr std::string_view AERON_CONNECTION { "aeron:ipc"}; diff --git a/resources/config/quic_example.json b/resources/config/quic_example.json index 640b364..a4cd075 100644 --- a/resources/config/quic_example.json +++ b/resources/config/quic_example.json @@ -27,7 +27,9 @@ "client-cert" : "build/certs/client.pem", "client-key" : "build/certs/client-key.pem", "alpn" : "sample", - "DisconnectTimeoutMs" : 800 + "stream-mode": "unidirectional", + "DisconnectTimeoutMs" : 800, + "PeerUnidiStreamCount" : 100 }, "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 f356681..be9b4a2 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -20,7 +20,8 @@ namespace bringauto::external_client::connection::communication { settings::Constants::CLIENT_KEY)), caFile_(getProtocolSettingsString( settings, - settings::Constants::CA_FILE)) { + settings::Constants::CA_FILE)), + streamMode_(parseStreamMode(settings)) { alpnBuffer_.Buffer = reinterpret_cast(alpn_.data()); alpnBuffer_.Length = static_cast(alpn_.size()); @@ -228,9 +229,15 @@ namespace bringauto::external_client::connection::communication { inboundCv_.notify_one(); } - void QuicCommunication::sendViaQuicStream(const ExternalProtocol::ExternalClient& 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))) { + + QUIC_STREAM_OPEN_FLAGS flags = streamMode_ == StreamMode::Unidirectional + ? QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL + : QUIC_STREAM_OPEN_FLAG_NONE; + + if (QUIC_FAILED( + quic_->StreamOpen(connection_, flags, streamCallback, this, &stream))) { settings::Logger::logError("[quic] StreamOpen failed"); return; } @@ -290,6 +297,21 @@ namespace bringauto::external_client::connection::communication { break; } + case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED: { + auto streamId = self->getStreamId(event->PEER_STREAM_STARTED.Stream); + + settings::Logger::logDebug( + "[quic] [stream {}] Peer stream started", + streamId ? *streamId : 0 + ); + + self->quic_->SetCallbackHandler(event->PEER_STREAM_STARTED.Stream, + reinterpret_cast(streamCallback), context); + + self->quic_->StreamReceiveSetEnabled(event->PEER_STREAM_STARTED.Stream, TRUE); + 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: { @@ -489,14 +511,40 @@ namespace bringauto::external_client::connection::communication { const structures::ExternalConnectionSettings &settings, std::string_view key ) { - const auto &raw = settings.protocolSettings.at(std::string(key)); + try { + 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(); + if (nlohmann::json::accept(raw)) { + auto j = nlohmann::json::parse(raw); + if (j.is_string()) { + return j.get(); + } } + + return raw; + } catch (const std::out_of_range &) { + throw std::runtime_error( + "Missing required QUIC protocol setting: '" + std::string(key) + "'" + ); + } catch (const nlohmann::json::exception &e) { + throw std::runtime_error( + "Invalid JSON value for QUIC protocol setting '" + + std::string(key) + "': " + e.what() + ); } - return raw; + } + + QuicCommunication::StreamMode QuicCommunication::parseStreamMode( + const structures::ExternalConnectionSettings &settings + ) { + const std::string mode = getProtocolSettingsString(settings, settings::Constants::QUIC_STREAM_MODE); + + if (mode == "unidirectional" || mode == "unidir") + return StreamMode::Unidirectional; + + if (mode == "bidirectional" || mode == "bidir") + return StreamMode::Bidirectional; + + throw std::runtime_error("Invalid stream-mode: " + mode); } } diff --git a/source/bringauto/settings/QuicSettingsParser.cpp b/source/bringauto/settings/QuicSettingsParser.cpp index 4c3dab7..61c0f89 100644 --- a/source/bringauto/settings/QuicSettingsParser.cpp +++ b/source/bringauto/settings/QuicSettingsParser.cpp @@ -29,6 +29,12 @@ namespace bringauto::settings { quic.IsSet.DisconnectTimeoutMs = TRUE; } + if (auto value = getOptionalUint(settings, "PeerUnidiStreamCount")) { + settings::Logger::logDebug("[quic] [settings] PeerUnidiStreamCount settings loaded"); + quic.PeerUnidiStreamCount = *value; + quic.IsSet.PeerUnidiStreamCount = TRUE; + } + return quic; } From 38b8d306cd01534d803b5bf6df24ea07fbe37e91 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Thu, 29 Jan 2026 09:59:01 +0000 Subject: [PATCH 2/7] Refactor `Constants`: Rename `QUIC_STREAM_MODE` to `STREAM_MODE` for consistency across settings. --- include/bringauto/settings/Constants.hpp | 2 +- .../connection/communication/QuicCommunication.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/bringauto/settings/Constants.hpp b/include/bringauto/settings/Constants.hpp index 14111d0..2316f8b 100644 --- a/include/bringauto/settings/Constants.hpp +++ b/include/bringauto/settings/Constants.hpp @@ -180,7 +180,7 @@ class Constants { 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 QUIC_STREAM_MODE { "stream-mode" }; + inline static constexpr std::string_view STREAM_MODE { "stream-mode" }; 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 be9b4a2..9a2ad91 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -537,7 +537,7 @@ namespace bringauto::external_client::connection::communication { QuicCommunication::StreamMode QuicCommunication::parseStreamMode( const structures::ExternalConnectionSettings &settings ) { - const std::string mode = getProtocolSettingsString(settings, settings::Constants::QUIC_STREAM_MODE); + const std::string mode = getProtocolSettingsString(settings, settings::Constants::STREAM_MODE); if (mode == "unidirectional" || mode == "unidir") return StreamMode::Unidirectional; From 165a9049999be4537b73fe4218c5d47fc3307641 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Thu, 29 Jan 2026 10:20:12 +0000 Subject: [PATCH 3/7] Refactor `QuicCommunication` and `QuicSettingsParser`: Adjust formatting, add NOLINT for reinterpret_cast, and enhance `PeerUnidiStreamCount` parsing logic. --- .../connection/communication/QuicCommunication.cpp | 7 ++++--- source/bringauto/settings/QuicSettingsParser.cpp | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 9a2ad91..08fe493 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -233,8 +233,8 @@ namespace bringauto::external_client::connection::communication { HQUIC stream{nullptr}; QUIC_STREAM_OPEN_FLAGS flags = streamMode_ == StreamMode::Unidirectional - ? QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL - : QUIC_STREAM_OPEN_FLAG_NONE; + ? QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL + : QUIC_STREAM_OPEN_FLAG_NONE; if (QUIC_FAILED( quic_->StreamOpen(connection_, flags, streamCallback, this, &stream))) { @@ -306,7 +306,8 @@ namespace bringauto::external_client::connection::communication { ); self->quic_->SetCallbackHandler(event->PEER_STREAM_STARTED.Stream, - reinterpret_cast(streamCallback), context); + reinterpret_cast(streamCallback), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + context); self->quic_->StreamReceiveSetEnabled(event->PEER_STREAM_STARTED.Stream, TRUE); break; diff --git a/source/bringauto/settings/QuicSettingsParser.cpp b/source/bringauto/settings/QuicSettingsParser.cpp index 61c0f89..c7ba0f6 100644 --- a/source/bringauto/settings/QuicSettingsParser.cpp +++ b/source/bringauto/settings/QuicSettingsParser.cpp @@ -29,7 +29,7 @@ namespace bringauto::settings { quic.IsSet.DisconnectTimeoutMs = TRUE; } - if (auto value = getOptionalUint(settings, "PeerUnidiStreamCount")) { + if (auto value = getOptionalUint(settings, "PeerUnidiStreamCount"); value.has_value()) { settings::Logger::logDebug("[quic] [settings] PeerUnidiStreamCount settings loaded"); quic.PeerUnidiStreamCount = *value; quic.IsSet.PeerUnidiStreamCount = TRUE; From 8cd46452797e808271b4217ab92fc854b5129296 Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Thu, 29 Jan 2026 10:22:46 +0000 Subject: [PATCH 4/7] Remove redundant `NOLINT` comment for `reinterpret_cast` in `QuicCommunication`. --- .../connection/communication/QuicCommunication.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 08fe493..df422d1 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -306,7 +306,7 @@ namespace bringauto::external_client::connection::communication { ); self->quic_->SetCallbackHandler(event->PEER_STREAM_STARTED.Stream, - reinterpret_cast(streamCallback), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) + reinterpret_cast(streamCallback), context); self->quic_->StreamReceiveSetEnabled(event->PEER_STREAM_STARTED.Stream, TRUE); From 1ba4557642c25ab554843f9988276e5daa50e05d Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Thu, 29 Jan 2026 10:39:39 +0000 Subject: [PATCH 5/7] Refactor `QuicCommunication`: Simplify `getProtocolSettingsString` with default value support, improve error handling, and update `parseStreamMode` to default to bidirectional. --- .../communication/QuicCommunication.hpp | 4 ++- .../communication/QuicCommunication.cpp | 32 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index c4aa5a7..ab1e952 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -334,13 +334,15 @@ namespace bringauto::external_client::connection::communication { * * @param settings External connection settings containing protocolSettings. * @param key Key identifying the protocol setting. + * @param defaultValue Default value if not exists * @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 + std::string_view key, + std::string defaultValue = {} ); static StreamMode parseStreamMode(const structures::ExternalConnectionSettings &settings); diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index df422d1..7710dab 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -510,28 +510,29 @@ namespace bringauto::external_client::connection::communication { std::string QuicCommunication::getProtocolSettingsString( const structures::ExternalConnectionSettings &settings, - std::string_view key + std::string_view key, + std::string defaultValue ) { - try { - const auto &raw = settings.protocolSettings.at(std::string(key)); + const auto it = settings.protocolSettings.find(std::string(key)); + if (it == settings.protocolSettings.end()) { + settings::Logger::logWarning("[quic] Protocol settings key '{}' not found", key); + return defaultValue; + } + const auto& raw = it->second; + + try { if (nlohmann::json::accept(raw)) { auto j = nlohmann::json::parse(raw); if (j.is_string()) { return j.get(); } } - return raw; - } catch (const std::out_of_range &) { - throw std::runtime_error( - "Missing required QUIC protocol setting: '" + std::string(key) + "'" - ); - } catch (const nlohmann::json::exception &e) { - throw std::runtime_error( - "Invalid JSON value for QUIC protocol setting '" + - std::string(key) + "': " + e.what() - ); + } + catch (const nlohmann::json::exception&) { + settings::Logger::logWarning("[quic] Protocol settings key '{}' not found", key); + return defaultValue; } } @@ -543,9 +544,6 @@ namespace bringauto::external_client::connection::communication { if (mode == "unidirectional" || mode == "unidir") return StreamMode::Unidirectional; - if (mode == "bidirectional" || mode == "bidir") - return StreamMode::Bidirectional; - - throw std::runtime_error("Invalid stream-mode: " + mode); + return StreamMode::Bidirectional; } } From 4400641b70342ad1cb70438db1d30bc3c994d79e Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Thu, 29 Jan 2026 10:43:43 +0000 Subject: [PATCH 6/7] Improve `getProtocolSettingsString`: Enhance warnings for missing or invalid JSON keys, update documentation, and add `parseStreamMode` method with default behavior to handle stream modes. --- .../communication/QuicCommunication.hpp | 29 ++++++++++++++----- .../communication/QuicCommunication.cpp | 4 +-- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index ab1e952..700042b 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -326,18 +326,20 @@ namespace bringauto::external_client::connection::communication { /** * @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. + * Looks up a value in ExternalConnectionSettings::protocolSettings and returns + * it as a plain string. If the stored value is a JSON-encoded string, it is + * transparently parsed and unwrapped. * - * Allows uniform access to protocol settings regardless of whether - * they were stored as plain strings or JSON-serialized values. + * If the key does not exist or the value cannot be parsed as valid JSON, + * the provided default value is returned and a warning is logged. + * + * This allows uniform access to protocol settings regardless of whether + * they are stored as plain strings or JSON-serialized strings. * * @param settings External connection settings containing protocolSettings. * @param key Key identifying the protocol setting. - * @param defaultValue Default value if not exists + * @param defaultValue Value returned if the key is missing or invalid. * @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, @@ -345,6 +347,19 @@ namespace bringauto::external_client::connection::communication { std::string defaultValue = {} ); + /** + * @brief Parses QUIC stream mode from protocol settings. + * + * Reads the stream mode from the external connection settings and determines + * whether QUIC streams should be unidirectional or bidirectional. + * + * Supported values: + * - "unidirectional", "unidir" → Unidirectional streams + * - any other value or missing setting → Bidirectional streams (default) + * + * @param settings External connection settings containing QUIC protocol options + * @return StreamMode Parsed stream mode (defaults to Bidirectional) + */ static StreamMode parseStreamMode(const structures::ExternalConnectionSettings &settings); }; } diff --git a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp index 7710dab..f1a85c3 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -515,7 +515,7 @@ namespace bringauto::external_client::connection::communication { ) { const auto it = settings.protocolSettings.find(std::string(key)); if (it == settings.protocolSettings.end()) { - settings::Logger::logWarning("[quic] Protocol settings key '{}' not found", key); + settings::Logger::logWarning("[quic] Protocol setting '{}' not found, using default", key); return defaultValue; } @@ -531,7 +531,7 @@ namespace bringauto::external_client::connection::communication { return raw; } catch (const nlohmann::json::exception&) { - settings::Logger::logWarning("[quic] Protocol settings key '{}' not found", key); + settings::Logger::logWarning("[quic] Protocol setting '{}' contains invalid JSON, using default", key); return defaultValue; } } From 5b12113538562952ae1daf20ae3c2011aaa91eea Mon Sep 17 00:00:00 2001 From: Daniel Prudky Date: Thu, 29 Jan 2026 21:48:31 +0000 Subject: [PATCH 7/7] Add NOSONAR and documentation for MsQuic callback handler cast --- .../connection/communication/QuicCommunication.hpp | 8 +++++--- .../connection/communication/QuicCommunication.cpp | 10 +++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index 700042b..3ae3c87 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -78,9 +78,11 @@ namespace bringauto::external_client::connection::communication { void closeConnection() override; private: - enum class StreamMode - { + /// Directionality of QUIC streams created or accepted by this connection + enum class StreamMode { + /// Stream can only send data in one direction Unidirectional, + /// Stream supports bidirectional send and receive Bidirectional }; @@ -241,7 +243,7 @@ namespace bringauto::external_client::connection::communication { * * @param message Message to be sent to the peer. */ - void sendViaQuicStream(const ExternalProtocol::ExternalClient& 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 f1a85c3..34fd4a0 100644 --- a/source/bringauto/external_client/connection/communication/QuicCommunication.cpp +++ b/source/bringauto/external_client/connection/communication/QuicCommunication.cpp @@ -297,16 +297,17 @@ namespace bringauto::external_client::connection::communication { break; } + /// Fired when peer open new stream on connection, stream handler need to be def case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED: { auto streamId = self->getStreamId(event->PEER_STREAM_STARTED.Stream); settings::Logger::logDebug( "[quic] [stream {}] Peer stream started", - streamId ? *streamId : 0 + streamId.value_or(0) ); self->quic_->SetCallbackHandler(event->PEER_STREAM_STARTED.Stream, - reinterpret_cast(streamCallback), + reinterpret_cast(streamCallback), // NOSONAR - MsQuic C API requires passing function pointer as void* context); self->quic_->StreamReceiveSetEnabled(event->PEER_STREAM_STARTED.Stream, TRUE); @@ -519,7 +520,7 @@ namespace bringauto::external_client::connection::communication { return defaultValue; } - const auto& raw = it->second; + const auto &raw = it->second; try { if (nlohmann::json::accept(raw)) { @@ -529,8 +530,7 @@ namespace bringauto::external_client::connection::communication { } } return raw; - } - catch (const nlohmann::json::exception&) { + } catch (const nlohmann::json::exception &) { settings::Logger::logWarning("[quic] Protocol setting '{}' contains invalid JSON, using default", key); return defaultValue; }