diff --git a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp index aa28683..3ae3c87 100644 --- a/include/bringauto/external_client/connection/communication/QuicCommunication.hpp +++ b/include/bringauto/external_client/connection/communication/QuicCommunication.hpp @@ -78,6 +78,14 @@ namespace bringauto::external_client::connection::communication { void closeConnection() override; private: + /// 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 + }; + /// Pointer to the MsQuic API function table const QUIC_API_TABLE *quic_{nullptr}; /// QUIC registration handle associated with the application @@ -98,6 +106,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}; @@ -233,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. @@ -318,21 +328,40 @@ 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 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, - std::string_view key + std::string_view key, + 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/include/bringauto/settings/Constants.hpp b/include/bringauto/settings/Constants.hpp index e9628b5..2316f8b 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 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..34fd4a0 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,23 @@ 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.value_or(0) + ); + + self->quic_->SetCallbackHandler(event->PEER_STREAM_STARTED.Stream, + reinterpret_cast(streamCallback), // NOSONAR - MsQuic C API requires passing function pointer as void* + 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: { @@ -487,16 +511,39 @@ 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 ) { - 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 setting '{}' not found, using default", key); + return defaultValue; + } + + const auto &raw = it->second; - if (nlohmann::json::accept(raw)) { - auto j = nlohmann::json::parse(raw); - if (j.is_string()) { - return j.get(); + try { + if (nlohmann::json::accept(raw)) { + auto j = nlohmann::json::parse(raw); + if (j.is_string()) { + return j.get(); + } } + return raw; + } catch (const nlohmann::json::exception &) { + settings::Logger::logWarning("[quic] Protocol setting '{}' contains invalid JSON, using default", key); + return defaultValue; } - return raw; + } + + QuicCommunication::StreamMode QuicCommunication::parseStreamMode( + const structures::ExternalConnectionSettings &settings + ) { + const std::string mode = getProtocolSettingsString(settings, settings::Constants::STREAM_MODE); + + if (mode == "unidirectional" || mode == "unidir") + return StreamMode::Unidirectional; + + return StreamMode::Bidirectional; } } diff --git a/source/bringauto/settings/QuicSettingsParser.cpp b/source/bringauto/settings/QuicSettingsParser.cpp index 4c3dab7..c7ba0f6 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"); value.has_value()) { + settings::Logger::logDebug("[quic] [settings] PeerUnidiStreamCount settings loaded"); + quic.PeerUnidiStreamCount = *value; + quic.IsSet.PeerUnidiStreamCount = TRUE; + } + return quic; }