QBONE Server Simulator: Add generic logic to handle control messages PiperOrigin-RevId: 930025790
diff --git a/build/source_list.bzl b/build/source_list.bzl index 9c579af..1a78372 100644 --- a/build/source_list.bzl +++ b/build/source_list.bzl
@@ -1759,6 +1759,7 @@ "quic/qbone/qbone_session_base.h", "quic/qbone/qbone_stream.h", "quic/qbone/test_tools/basic_quic_server.h", + "quic/qbone/test_tools/qbone_basic_quic_server_handler.h", ] qbone_srcs = [ "quic/qbone/bonnet/icmp_reachable.cc", @@ -1797,6 +1798,7 @@ "quic/qbone/qbone_stream.cc", "quic/qbone/qbone_stream_test.cc", "quic/qbone/test_tools/basic_quic_server.cc", + "quic/qbone/test_tools/qbone_basic_quic_server_handler.cc", ] blind_sign_auth_hdrs = [ "blind_sign_auth/blind_sign_auth.h",
diff --git a/build/source_list.gni b/build/source_list.gni index b6a23cf..65f0259 100644 --- a/build/source_list.gni +++ b/build/source_list.gni
@@ -1764,6 +1764,7 @@ "src/quiche/quic/qbone/qbone_session_base.h", "src/quiche/quic/qbone/qbone_stream.h", "src/quiche/quic/qbone/test_tools/basic_quic_server.h", + "src/quiche/quic/qbone/test_tools/qbone_basic_quic_server_handler.h", ] qbone_srcs = [ "src/quiche/quic/qbone/bonnet/icmp_reachable.cc", @@ -1802,6 +1803,7 @@ "src/quiche/quic/qbone/qbone_stream.cc", "src/quiche/quic/qbone/qbone_stream_test.cc", "src/quiche/quic/qbone/test_tools/basic_quic_server.cc", + "src/quiche/quic/qbone/test_tools/qbone_basic_quic_server_handler.cc", ] blind_sign_auth_hdrs = [ "src/quiche/blind_sign_auth/blind_sign_auth.h",
diff --git a/build/source_list.json b/build/source_list.json index d0589ee..ef8b9de 100644 --- a/build/source_list.json +++ b/build/source_list.json
@@ -1762,7 +1762,8 @@ "quiche/quic/qbone/qbone_server_session.h", "quiche/quic/qbone/qbone_session_base.h", "quiche/quic/qbone/qbone_stream.h", - "quiche/quic/qbone/test_tools/basic_quic_server.h" + "quiche/quic/qbone/test_tools/basic_quic_server.h", + "quiche/quic/qbone/test_tools/qbone_basic_quic_server_handler.h" ], "qbone_srcs": [ "quiche/quic/qbone/bonnet/icmp_reachable.cc", @@ -1800,7 +1801,8 @@ "quiche/quic/qbone/qbone_session_test.cc", "quiche/quic/qbone/qbone_stream.cc", "quiche/quic/qbone/qbone_stream_test.cc", - "quiche/quic/qbone/test_tools/basic_quic_server.cc" + "quiche/quic/qbone/test_tools/basic_quic_server.cc", + "quiche/quic/qbone/test_tools/qbone_basic_quic_server_handler.cc" ], "blind_sign_auth_hdrs": [ "quiche/blind_sign_auth/blind_sign_auth.h",
diff --git a/quiche/quic/qbone/test_tools/qbone_basic_quic_server_handler.cc b/quiche/quic/qbone/test_tools/qbone_basic_quic_server_handler.cc new file mode 100644 index 0000000..223c1bc --- /dev/null +++ b/quiche/quic/qbone/test_tools/qbone_basic_quic_server_handler.cc
@@ -0,0 +1,193 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/qbone/test_tools/qbone_basic_quic_server_handler.h" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <limits> +#include <vector> + +#include "absl/base/attributes.h" +#include "absl/base/nullability.h" +#include "absl/status/status.h" +#include "absl/types/span.h" +#include "quiche/quic/core/quic_connection_id.h" +#include "quiche/quic/core/quic_stream_sequencer.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/core/quic_versions.h" +#include "quiche/quic/qbone/qbone_constants.h" +#include "quiche/quic/qbone/test_tools/basic_quic_server.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/common/quiche_status_utils.h" + +namespace quic::test { + +class QboneBasicQuicServerHandler::SequencerReader { + public: + explicit SequencerReader(const QuicStreamSequencer* absl_nonnull sequencer + ABSL_ATTRIBUTE_LIFETIME_BOUND) + : sequencer_(sequencer), offset_(sequencer->NumBytesConsumed()) {} + + bool Read(void* buffer, int num_bytes) { + if (sequencer_->ReadableBytes() < + offset_ - sequencer_->NumBytesConsumed() + num_bytes) { + return false; + } + + QuicStreamOffset new_offset = offset_; + QuicStreamOffset end_offset = offset_ + num_bytes; + iovec iov; + while (new_offset < end_offset) { + QUICHE_CHECK(sequencer_->PeekRegion(new_offset, &iov)); + QUICHE_CHECK_LE(iov.iov_len, std::numeric_limits<int>::max()); + + int bytes_to_copy = std::min<int>(end_offset - new_offset, iov.iov_len); + memcpy(reinterpret_cast<std::byte*>(buffer) + (new_offset - offset_), + iov.iov_base, bytes_to_copy); + + new_offset += bytes_to_copy; + } + QUICHE_CHECK_EQ(new_offset, end_offset); + + offset_ = new_offset; + return true; + } + + const QuicStreamSequencer* absl_nonnull sequencer_; + QuicStreamOffset offset_; +}; + +QboneBasicQuicServerHandler::QboneBasicQuicServerHandler( + BasicQuicServer* absl_nonnull server) + : server_(server) {} + +// Lots of unnecessary copying here, but control messages are not performance- +// sensitive. +absl::Status QboneBasicQuicServerHandler::SendControlMessage( + QuicConnectionId server_connection_id, const QboneClientRequest& request) { + QUICHE_CHECK(connections_.contains(server_connection_id)) + << "No control stream for connection " << server_connection_id; + + QUICHE_CHECK_LE(request.ByteSizeLong(), std::numeric_limits<uint16_t>::max()); + uint16_t serialized_size = static_cast<uint16_t>(request.ByteSizeLong()); + + std::vector<std::byte> buffer(sizeof(serialized_size) + + request.ByteSizeLong()); + + // Size-prefix the serialized message. (Not network byte order.) + memcpy(buffer.data(), &serialized_size, sizeof(serialized_size)); + + if (!request.SerializeToArray(buffer.data() + sizeof(serialized_size), + serialized_size)) { + return absl::InternalError("Failed to serialize QboneClientRequest"); + } + + QUICHE_ASSIGN_OR_RETURN( + int bytes_written, + server_->SendStreamData( + server_connection_id, + QboneConstants::GetControlStreamId(QUIC_VERSION_IETF_RFC_V1), + buffer)); + QUICHE_CHECK_EQ(bytes_written, buffer.size()); + + return absl::OkStatus(); +} + +absl::Status QboneBasicQuicServerHandler::SendTunnelPacket( + QuicConnectionId server_connection_id, absl::Span<const std::byte> data) { + QUICHE_CHECK(connections_.contains(server_connection_id)) + << "No control stream for connection " << server_connection_id; + + return server_->SendDatagram(server_connection_id, data); +} + +void QboneBasicQuicServerHandler::OnSessionEnd( + QuicConnectionId server_connection_id) { + if (connections_.erase(server_connection_id) == 1) { + OnConnectionClosed(server_connection_id); + } +} + +// Assume only QUIC_VERSION_IETF_RFC_V1 is used and validate the appropriate +// control stream ID for that QUIC version. Support only the control stream as +// the only stream in the connection. +bool QboneBasicQuicServerHandler::OnNewStream( + QuicConnectionId server_connection_id, QuicStreamId stream_id) { + auto [it, inserted] = connections_.insert(server_connection_id); + QUICHE_CHECK(inserted) << "Unexpected new stream for connection " + << server_connection_id; + + QUICHE_CHECK_EQ(stream_id, + QboneConstants::GetControlStreamId(QUIC_VERSION_IETF_RFC_V1)) + << "Server received stream with unexpected ID " << stream_id; + + OnNewConnection(server_connection_id); + + return true; +} + +int QboneBasicQuicServerHandler::OnStreamDataAvailable( + QuicConnectionId server_connection_id, QuicStreamId stream_id, + const QuicStreamSequencer& data_sequencer) { + QUICHE_CHECK(connections_.contains(server_connection_id)); + QUICHE_CHECK_EQ(stream_id, + QboneConstants::GetControlStreamId(QUIC_VERSION_IETF_RFC_V1)); + + SequencerReader reader(&data_sequencer); + int total_consumed = 0; + + while (true) { + int consumed = + ParseAndHandleSingleControlRequest(server_connection_id, reader); + if (consumed == 0) { + break; + } + total_consumed += consumed; + } + + return total_consumed; +} + +void QboneBasicQuicServerHandler::OnDatagramReceived( + QuicConnectionId server_connection_id, absl::Span<const std::byte> data) { + QUICHE_CHECK(connections_.contains(server_connection_id)) + << "Unexpected datagram for connection " << server_connection_id + << " before creating control stream."; + + OnTunnelPacket(server_connection_id, data); +} + +// Lots of unnecessary copying here (and wastefully throw it away on incomplete +// messages), but control messages are not performance-sensitive. +int QboneBasicQuicServerHandler::ParseAndHandleSingleControlRequest( + QuicConnectionId server_connection_id, SequencerReader& reader) { + // Expect size-prepended (uint16_t) serialized QboneServerRequest proto + // messages. (Not network byte order.) + uint16_t request_size; + if (!reader.Read(&request_size, sizeof(request_size))) { + // Wait for more data. + return 0; + } + + std::vector<std::byte> raw_proto(request_size); + if (!reader.Read(raw_proto.data(), request_size)) { + // Wait for more data. + return 0; + } + + QboneServerRequest request; + if (request.ParseFromArray(raw_proto.data(), raw_proto.size())) { + OnControlMessageReceived(server_connection_id, request); + } else { + // Could not parse proto + OnControlMessageParseError(); + } + + return sizeof(request_size) + request_size; +} + +} // namespace quic::test
diff --git a/quiche/quic/qbone/test_tools/qbone_basic_quic_server_handler.h b/quiche/quic/qbone/test_tools/qbone_basic_quic_server_handler.h new file mode 100644 index 0000000..2dc8dad --- /dev/null +++ b/quiche/quic/qbone/test_tools/qbone_basic_quic_server_handler.h
@@ -0,0 +1,70 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_QBONE_TEST_TOOLS_QBONE_BASIC_QUIC_SERVER_HANDLER_H_ +#define QUICHE_QUIC_QBONE_TEST_TOOLS_QBONE_BASIC_QUIC_SERVER_HANDLER_H_ + +#include <cstddef> + +#include "absl/base/attributes.h" +#include "absl/base/nullability.h" +#include "absl/container/flat_hash_set.h" +#include "absl/status/status.h" +#include "absl/types/span.h" +#include "quiche/quic/core/quic_connection_id.h" +#include "quiche/quic/core/quic_stream_sequencer.h" +#include "quiche/quic/core/quic_types.h" +#include "quiche/quic/qbone/qbone_control.pb.h" +#include "quiche/quic/qbone/test_tools/basic_quic_server.h" +#include "quiche/common/platform/api/quiche_logging.h" + +namespace quic::test { + +class QboneBasicQuicServerHandler : public BasicQuicServer::Handler { + public: + QboneBasicQuicServerHandler( + BasicQuicServer* absl_nonnull server ABSL_ATTRIBUTE_LIFETIME_BOUND); + + ~QboneBasicQuicServerHandler() override = default; + + virtual void OnNewConnection(QuicConnectionId server_connection_id) {} + virtual void OnConnectionClosed(QuicConnectionId server_connection_id) {} + virtual void OnControlMessageReceived(QuicConnectionId server_connection_id, + const QboneServerRequest& request) = 0; + virtual void OnControlMessageParseError() { + QUICHE_CHECK(false) << "Invalid control message received."; + } + virtual void OnTunnelPacket(QuicConnectionId server_connection_id, + absl::Span<const std::byte> data) = 0; + + absl::Status SendControlMessage(QuicConnectionId server_connection_id, + const QboneClientRequest& request); + absl::Status SendTunnelPacket(QuicConnectionId server_connection_id, + absl::Span<const std::byte> data); + + // BasicQuicServer::Handler: + void OnSessionEnd(QuicConnectionId server_connection_id) override; + bool OnNewStream(QuicConnectionId server_connection_id, + QuicStreamId stream_id) override; + int OnStreamDataAvailable(QuicConnectionId server_connection_id, + QuicStreamId stream_id, + const QuicStreamSequencer& data_sequencer) override; + void OnDatagramReceived(QuicConnectionId server_connection_id, + absl::Span<const std::byte> data) override; + + private: + class SequencerReader; + + int ParseAndHandleSingleControlRequest(QuicConnectionId server_connection_id, + SequencerReader& reader); + + BasicQuicServer* absl_nonnull server_; + + // Connections registered on establishment of the control stream. + absl::flat_hash_set<QuicConnectionId> connections_; +}; + +} // namespace quic::test + +#endif // QUICHE_QUIC_QBONE_TEST_TOOLS_QBONE_BASIC_QUIC_SERVER_HANDLER_H_