Implement the Devious Baton protocol in QUICHE
Also add a web_transport_test_server binary that exposes WebTransport test visitors in addition to the devious-baton endpoint.
PiperOrigin-RevId: 550376350
diff --git a/build/source_list.bzl b/build/source_list.bzl
index f4149a8..c9a704d 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -404,6 +404,7 @@
"spdy/core/spdy_protocol.h",
"spdy/core/spdy_simple_arena.h",
"spdy/core/zero_copy_output_buffer.h",
+ "web_transport/complete_buffer_visitor.h",
"web_transport/web_transport.h",
]
quiche_core_srcs = [
@@ -701,6 +702,7 @@
"spdy/core/spdy_prefixed_buffer_reader.cc",
"spdy/core/spdy_protocol.cc",
"spdy/core/spdy_simple_arena.cc",
+ "web_transport/complete_buffer_visitor.cc",
]
quiche_tool_support_hdrs = [
"common/platform/api/quiche_command_line_flags.h",
@@ -711,6 +713,7 @@
"quic/tools/connect_server_backend.h",
"quic/tools/connect_tunnel.h",
"quic/tools/connect_udp_tunnel.h",
+ "quic/tools/devious_baton.h",
"quic/tools/fake_proof_verifier.h",
"quic/tools/quic_backend_response.h",
"quic/tools/quic_client_base.h",
@@ -735,6 +738,7 @@
"quic/tools/connect_server_backend.cc",
"quic/tools/connect_tunnel.cc",
"quic/tools/connect_udp_tunnel.cc",
+ "quic/tools/devious_baton.cc",
"quic/tools/quic_backend_response.cc",
"quic/tools/quic_client_base.cc",
"quic/tools/quic_memory_cache_backend.cc",
@@ -1351,6 +1355,7 @@
"quic/tools/quic_server_factory.cc",
"quic/tools/quic_toy_client.cc",
"quic/tools/quic_toy_server.cc",
+ "quic/tools/web_transport_test_server.cc",
]
nghttp2_hdrs = [
"http2/adapter/callback_visitor.h",
diff --git a/build/source_list.gni b/build/source_list.gni
index d5ee1d3..d701cd5 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -404,6 +404,7 @@
"src/quiche/spdy/core/spdy_protocol.h",
"src/quiche/spdy/core/spdy_simple_arena.h",
"src/quiche/spdy/core/zero_copy_output_buffer.h",
+ "src/quiche/web_transport/complete_buffer_visitor.h",
"src/quiche/web_transport/web_transport.h",
]
quiche_core_srcs = [
@@ -701,6 +702,7 @@
"src/quiche/spdy/core/spdy_prefixed_buffer_reader.cc",
"src/quiche/spdy/core/spdy_protocol.cc",
"src/quiche/spdy/core/spdy_simple_arena.cc",
+ "src/quiche/web_transport/complete_buffer_visitor.cc",
]
quiche_tool_support_hdrs = [
"src/quiche/common/platform/api/quiche_command_line_flags.h",
@@ -711,6 +713,7 @@
"src/quiche/quic/tools/connect_server_backend.h",
"src/quiche/quic/tools/connect_tunnel.h",
"src/quiche/quic/tools/connect_udp_tunnel.h",
+ "src/quiche/quic/tools/devious_baton.h",
"src/quiche/quic/tools/fake_proof_verifier.h",
"src/quiche/quic/tools/quic_backend_response.h",
"src/quiche/quic/tools/quic_client_base.h",
@@ -735,6 +738,7 @@
"src/quiche/quic/tools/connect_server_backend.cc",
"src/quiche/quic/tools/connect_tunnel.cc",
"src/quiche/quic/tools/connect_udp_tunnel.cc",
+ "src/quiche/quic/tools/devious_baton.cc",
"src/quiche/quic/tools/quic_backend_response.cc",
"src/quiche/quic/tools/quic_client_base.cc",
"src/quiche/quic/tools/quic_memory_cache_backend.cc",
@@ -1354,6 +1358,7 @@
"src/quiche/quic/tools/quic_server_factory.cc",
"src/quiche/quic/tools/quic_toy_client.cc",
"src/quiche/quic/tools/quic_toy_server.cc",
+ "src/quiche/quic/tools/web_transport_test_server.cc",
]
nghttp2_hdrs = [
"src/quiche/http2/adapter/callback_visitor.h",
diff --git a/build/source_list.json b/build/source_list.json
index 2a33437..118a360 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -403,6 +403,7 @@
"quiche/spdy/core/spdy_protocol.h",
"quiche/spdy/core/spdy_simple_arena.h",
"quiche/spdy/core/zero_copy_output_buffer.h",
+ "quiche/web_transport/complete_buffer_visitor.h",
"quiche/web_transport/web_transport.h"
],
"quiche_core_srcs": [
@@ -699,7 +700,8 @@
"quiche/spdy/core/spdy_pinnable_buffer_piece.cc",
"quiche/spdy/core/spdy_prefixed_buffer_reader.cc",
"quiche/spdy/core/spdy_protocol.cc",
- "quiche/spdy/core/spdy_simple_arena.cc"
+ "quiche/spdy/core/spdy_simple_arena.cc",
+ "quiche/web_transport/complete_buffer_visitor.cc"
],
"quiche_tool_support_hdrs": [
"quiche/common/platform/api/quiche_command_line_flags.h",
@@ -710,6 +712,7 @@
"quiche/quic/tools/connect_server_backend.h",
"quiche/quic/tools/connect_tunnel.h",
"quiche/quic/tools/connect_udp_tunnel.h",
+ "quiche/quic/tools/devious_baton.h",
"quiche/quic/tools/fake_proof_verifier.h",
"quiche/quic/tools/quic_backend_response.h",
"quiche/quic/tools/quic_client_base.h",
@@ -734,6 +737,7 @@
"quiche/quic/tools/connect_server_backend.cc",
"quiche/quic/tools/connect_tunnel.cc",
"quiche/quic/tools/connect_udp_tunnel.cc",
+ "quiche/quic/tools/devious_baton.cc",
"quiche/quic/tools/quic_backend_response.cc",
"quiche/quic/tools/quic_client_base.cc",
"quiche/quic/tools/quic_memory_cache_backend.cc",
@@ -1352,7 +1356,8 @@
"quiche/quic/tools/quic_server_bin.cc",
"quiche/quic/tools/quic_server_factory.cc",
"quiche/quic/tools/quic_toy_client.cc",
- "quiche/quic/tools/quic_toy_server.cc"
+ "quiche/quic/tools/quic_toy_server.cc",
+ "quiche/quic/tools/web_transport_test_server.cc"
],
"nghttp2_hdrs": [
"quiche/http2/adapter/callback_visitor.h",
diff --git a/quiche/BUILD.bazel b/quiche/BUILD.bazel
index 1b46de3..67bf84c 100644
--- a/quiche/BUILD.bazel
+++ b/quiche/BUILD.bazel
@@ -237,6 +237,7 @@
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
+ "@com_google_absl//absl/functional:bind_front",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
@@ -531,6 +532,24 @@
],
)
+cc_binary(
+ name = "web_transport_test_server",
+ srcs = ["quic/tools/web_transport_test_server.cc"],
+ deps = [
+ ":io_tool_support",
+ ":quic_server_factory",
+ ":quic_toy_server",
+ ":quiche_core",
+ ":quiche_platform_default",
+ ":quiche_platform_default_tools",
+ ":quiche_tool_support",
+ "@com_google_absl//absl/status",
+ "@com_google_absl//absl/status:statusor",
+ "@com_google_absl//absl/strings",
+ "@com_google_googleurl//url",
+ ],
+)
+
# Indicate that QUICHE APIs are explicitly unstable by providing only
# appropriately named aliases as publicly visible targets.
alias(
diff --git a/quiche/common/wire_serialization.h b/quiche/common/wire_serialization.h
index 7cc2596..4cb482b 100644
--- a/quiche/common/wire_serialization.h
+++ b/quiche/common/wire_serialization.h
@@ -398,6 +398,25 @@
return buffer;
}
+// SerializeIntoBuffer() that returns std::string instead of QuicheBuffer.
+template <typename... Ts>
+absl::StatusOr<std::string> SerializeIntoString(Ts... data) {
+ size_t buffer_size = ComputeLengthOnWire(data...);
+ if (buffer_size == 0) {
+ return std::string();
+ }
+
+ std::string buffer;
+ buffer.resize(buffer_size);
+ QuicheDataWriter writer(buffer.size(), buffer.data());
+ QUICHE_RETURN_IF_ERROR(SerializeIntoWriter(writer, data...));
+ if (writer.remaining() != 0) {
+ return absl::InternalError(absl::StrCat(
+ "Excess ", writer.remaining(), " bytes allocated while serializing"));
+ }
+ return buffer;
+}
+
} // namespace quiche
#endif // QUICHE_COMMON_WIRE_SERIALIZATION_H_
diff --git a/quiche/quic/tools/devious_baton.cc b/quiche/quic/tools/devious_baton.cc
new file mode 100644
index 0000000..a06ce86
--- /dev/null
+++ b/quiche/quic/tools/devious_baton.cc
@@ -0,0 +1,192 @@
+// Copyright 2023 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/tools/devious_baton.h"
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+
+#include "absl/functional/bind_front.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/crypto/quic_random.h"
+#include "quiche/quic/core/quic_types.h"
+#include "quiche/common/quiche_data_reader.h"
+#include "quiche/common/wire_serialization.h"
+#include "quiche/web_transport/complete_buffer_visitor.h"
+#include "quiche/web_transport/web_transport.h"
+
+namespace quic {
+
+namespace {
+
+constexpr QuicByteCount kMaxPaddingSize = 64;
+constexpr char kPaddingData[kMaxPaddingSize] = {0};
+
+absl::StatusOr<DeviousBatonValue> Parse(absl::string_view message) {
+ quiche::QuicheDataReader reader(message);
+ uint64_t padding_size;
+ if (!reader.ReadVarInt62(&padding_size)) {
+ return absl::InvalidArgumentError("Failed to read the padding size");
+ }
+ if (!reader.Seek(padding_size)) {
+ return absl::InvalidArgumentError("Failed to skip padding");
+ }
+ DeviousBatonValue value;
+ if (!reader.ReadUInt8(&value)) {
+ return absl::InvalidArgumentError("Failed to read the baton");
+ }
+ if (!reader.IsDoneReading()) {
+ return absl::InvalidArgumentError("Trailing data after the baton");
+ }
+ return value;
+}
+
+std::string Serialize(DeviousBatonValue value) {
+ // Randomize padding size for extra deviousness.
+ QuicByteCount padding_size =
+ QuicRandom::GetInstance()->InsecureRandUint64() % kMaxPaddingSize;
+ absl::string_view padding(kPaddingData, padding_size);
+
+ absl::StatusOr<std::string> result = quiche::SerializeIntoString(
+ quiche::WireStringWithLengthPrefix<quiche::WireVarInt62>(padding),
+ quiche::WireUint8(value));
+ QUICHE_DCHECK(result.ok());
+ return *std::move(result);
+}
+
+class IncomingBidiBatonVisitor : public webtransport::CompleteBufferVisitor {
+ public:
+ IncomingBidiBatonVisitor(webtransport::Session& session,
+ webtransport::Stream& stream)
+ : CompleteBufferVisitor(
+ &stream, absl::bind_front(
+ &IncomingBidiBatonVisitor::OnAllDataReceived, this)),
+ session_(&session) {}
+
+ private:
+ void OnAllDataReceived(std::string data) {
+ absl::StatusOr<DeviousBatonValue> value = Parse(data);
+ if (!value.ok()) {
+ session_->CloseSession(kDeviousBatonErrorBruh,
+ absl::StrCat("Failed to parse incoming baton: ",
+ value.status().message()));
+ return;
+ }
+ DeviousBatonValue next_value = 1 + *value;
+ if (next_value != 0) {
+ SetOutgoingData(Serialize(*value + 1));
+ }
+ }
+
+ webtransport::Session* session_;
+};
+
+} // namespace
+
+void DeviousBatonSessionVisitor::OnSessionReady() {
+ if (!is_server_) {
+ return;
+ }
+ for (int i = 0; i < count_; ++i) {
+ webtransport::Stream* stream = session_->OpenOutgoingUnidirectionalStream();
+ if (stream == nullptr) {
+ session_->CloseSession(
+ kDeviousBatonErrorDaYamn,
+ "Insufficient flow control when opening initial baton streams");
+ return;
+ }
+ stream->SetVisitor(std::make_unique<webtransport::CompleteBufferVisitor>(
+ stream, Serialize(initial_value_)));
+ stream->visitor()->OnCanWrite();
+ }
+}
+
+void DeviousBatonSessionVisitor::OnSessionClosed(
+ webtransport::SessionErrorCode error_code,
+ const std::string& error_message) {
+ QUICHE_LOG(INFO) << "Devious Baton session closed with error " << error_code
+ << " (message: " << error_message << ")";
+}
+
+void DeviousBatonSessionVisitor::OnIncomingBidirectionalStreamAvailable() {
+ while (true) {
+ webtransport::Stream* stream =
+ session_->AcceptIncomingBidirectionalStream();
+ if (stream == nullptr) {
+ return;
+ }
+ stream->SetVisitor(
+ std::make_unique<IncomingBidiBatonVisitor>(*session_, *stream));
+ stream->visitor()->OnCanRead();
+ }
+}
+
+void DeviousBatonSessionVisitor::OnIncomingUnidirectionalStreamAvailable() {
+ while (true) {
+ webtransport::Stream* stream =
+ session_->AcceptIncomingUnidirectionalStream();
+ if (stream == nullptr) {
+ return;
+ }
+ stream->SetVisitor(std::make_unique<webtransport::CompleteBufferVisitor>(
+ stream, CreateResponseCallback(
+ &DeviousBatonSessionVisitor::SendBidirectionalBaton)));
+ stream->visitor()->OnCanRead();
+ }
+}
+
+void DeviousBatonSessionVisitor::OnDatagramReceived(
+ absl::string_view datagram) {
+ // TODO(vasilvv): implement datagram behavior.
+}
+
+void DeviousBatonSessionVisitor::OnCanCreateNewOutgoingBidirectionalStream() {
+ while (!outgoing_bidi_batons_.empty()) {
+ webtransport::Stream* stream = session_->OpenOutgoingBidirectionalStream();
+ if (stream == nullptr) {
+ return;
+ }
+ stream->SetVisitor(std::make_unique<webtransport::CompleteBufferVisitor>(
+ stream, Serialize(outgoing_bidi_batons_.front()),
+ CreateResponseCallback(
+ &DeviousBatonSessionVisitor::SendUnidirectionalBaton)));
+ outgoing_bidi_batons_.pop_front();
+ stream->visitor()->OnCanWrite();
+ }
+}
+
+void DeviousBatonSessionVisitor::OnCanCreateNewOutgoingUnidirectionalStream() {
+ while (!outgoing_unidi_batons_.empty()) {
+ webtransport::Stream* stream = session_->OpenOutgoingUnidirectionalStream();
+ if (stream == nullptr) {
+ return;
+ }
+ stream->SetVisitor(std::make_unique<webtransport::CompleteBufferVisitor>(
+ stream, Serialize(outgoing_unidi_batons_.front())));
+ outgoing_unidi_batons_.pop_front();
+ stream->visitor()->OnCanWrite();
+ }
+}
+
+quiche::SingleUseCallback<void(std::string)>
+DeviousBatonSessionVisitor::CreateResponseCallback(SendFunction send_function) {
+ return [this, send_function](std::string data) {
+ absl::StatusOr<DeviousBatonValue> value = Parse(data);
+ if (!value.ok()) {
+ session_->CloseSession(kDeviousBatonErrorBruh,
+ absl::StrCat("Failed to parse incoming baton: ",
+ value.status().message()));
+ return;
+ }
+ DeviousBatonValue new_value = 1 + *value;
+ if (new_value != 0) {
+ std::invoke(send_function, this, *value);
+ }
+ };
+}
+
+} // namespace quic
diff --git a/quiche/quic/tools/devious_baton.h b/quiche/quic/tools/devious_baton.h
new file mode 100644
index 0000000..50337d2
--- /dev/null
+++ b/quiche/quic/tools/devious_baton.h
@@ -0,0 +1,73 @@
+// Copyright 2023 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_TOOLS_DEVIOUS_BATON_H_
+#define QUICHE_QUIC_TOOLS_DEVIOUS_BATON_H_
+
+#include "quiche/common/quiche_callbacks.h"
+#include "quiche/common/quiche_circular_deque.h"
+#include "quiche/web_transport/web_transport.h"
+
+namespace quic {
+
+// https://www.ietf.org/id/draft-frindell-webtrans-devious-baton-00.html#name-session-error-codes
+inline constexpr webtransport::SessionErrorCode kDeviousBatonErrorDaYamn =
+ 0x01; // Insufficient flow control credit
+inline constexpr webtransport::SessionErrorCode kDeviousBatonErrorBruh =
+ 0x02; // Parse error
+inline constexpr webtransport::SessionErrorCode kDeviousBatonErrorSus =
+ 0x03; // Unexpected message
+inline constexpr webtransport::SessionErrorCode kDeviousBatonErrorBored =
+ 0x04; // Timeout
+
+using DeviousBatonValue = uint8_t;
+
+// Implementation of the Devious Baton protocol as described in
+// https://www.ietf.org/id/draft-frindell-webtrans-devious-baton-00.html
+class DeviousBatonSessionVisitor : public webtransport::SessionVisitor {
+ public:
+ DeviousBatonSessionVisitor(webtransport::Session* session, bool is_server,
+ int initial_value, int count)
+ : session_(session),
+ is_server_(is_server),
+ initial_value_(initial_value),
+ count_(count) {}
+
+ void OnSessionReady() override;
+ void OnSessionClosed(webtransport::SessionErrorCode error_code,
+ const std::string& error_message) override;
+ void OnIncomingBidirectionalStreamAvailable() override;
+ void OnIncomingUnidirectionalStreamAvailable() override;
+ void OnDatagramReceived(absl::string_view datagram) override;
+ void OnCanCreateNewOutgoingBidirectionalStream() override;
+ void OnCanCreateNewOutgoingUnidirectionalStream() override;
+
+ private:
+ using SendFunction = void (DeviousBatonSessionVisitor::*)(DeviousBatonValue);
+ void SendUnidirectionalBaton(DeviousBatonValue value) {
+ outgoing_unidi_batons_.push_back(value);
+ OnCanCreateNewOutgoingUnidirectionalStream();
+ }
+ void SendBidirectionalBaton(DeviousBatonValue value) {
+ outgoing_bidi_batons_.push_back(value);
+ OnCanCreateNewOutgoingBidirectionalStream();
+ }
+
+ // Creates a callback that parses an incoming baton, parses it (while
+ // potentially handling parse errors), and then passes it into the
+ // `send_function`.
+ quiche::SingleUseCallback<void(std::string)> CreateResponseCallback(
+ SendFunction send_function);
+
+ webtransport::Session* session_;
+ bool is_server_;
+ DeviousBatonValue initial_value_;
+ int count_;
+ quiche::QuicheCircularDeque<DeviousBatonValue> outgoing_unidi_batons_;
+ quiche::QuicheCircularDeque<DeviousBatonValue> outgoing_bidi_batons_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_TOOLS_DEVIOUS_BATON_H_
diff --git a/quiche/quic/tools/web_transport_test_server.cc b/quiche/quic/tools/web_transport_test_server.cc
new file mode 100644
index 0000000..3e974e6
--- /dev/null
+++ b/quiche/quic/tools/web_transport_test_server.cc
@@ -0,0 +1,140 @@
+// Copyright 2023 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/string_view.h"
+#include "url/third_party/mozilla/url_parse.h"
+#include "quiche/quic/core/quic_error_codes.h"
+#include "quiche/quic/core/web_transport_interface.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/quic/tools/devious_baton.h"
+#include "quiche/quic/tools/quic_server.h"
+#include "quiche/quic/tools/quic_simple_server_backend.h"
+#include "quiche/quic/tools/web_transport_test_visitors.h"
+#include "quiche/common/platform/api/quiche_command_line_flags.h"
+#include "quiche/common/platform/api/quiche_default_proof_providers.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/platform/api/quiche_system_event_loop.h"
+#include "quiche/common/quiche_random.h"
+#include "quiche/web_transport/web_transport.h"
+
+DEFINE_QUICHE_COMMAND_LINE_FLAG(
+ int32_t, port, 6121, "The port the WebTransport server will listen on.");
+
+namespace quic {
+namespace {
+
+absl::StatusOr<std::unique_ptr<webtransport::SessionVisitor>> ProcessRequest(
+ const GURL& url, WebTransportSession* session) {
+ if (url.path() == "/webtransport/echo") {
+ return std::make_unique<EchoWebTransportSessionVisitor>(session);
+ }
+ if (url.path() == "/webtransport/devious-baton") {
+ int count = 1;
+ DeviousBatonValue initial_value =
+ quiche::QuicheRandom::GetInstance()->RandUint64() % 256;
+ std::string query = url.query();
+ url::Component query_component, key_component, value_component;
+ query_component.begin = 0;
+ query_component.len = query.size();
+ while (url::ExtractQueryKeyValue(query.data(), &query_component,
+ &key_component, &value_component)) {
+ absl::string_view key(query.data() + key_component.begin,
+ key_component.len);
+ absl::string_view value(query.data() + value_component.begin,
+ value_component.len);
+ int parsed_value;
+ if (!absl::SimpleAtoi(value, &parsed_value) || parsed_value < 0 ||
+ parsed_value > 255) {
+ if (key == "count" || key == "baton") {
+ return absl::InvalidArgumentError(
+ absl::StrCat("Failed to parse query param ", key));
+ }
+ continue;
+ }
+ if (key == "count") {
+ count = parsed_value;
+ }
+ if (key == "baton") {
+ initial_value = parsed_value;
+ }
+ }
+ return std::make_unique<DeviousBatonSessionVisitor>(
+ session, /*is_server=*/true, initial_value, count);
+ }
+ return absl::NotFoundError("Path not found");
+}
+
+class WebTransportTestBackend : public QuicSimpleServerBackend {
+ public:
+ bool InitializeBackend(const std::string&) override { return true; }
+ bool IsBackendInitialized() const override { return true; }
+ void FetchResponseFromBackend(const spdy::Http2HeaderBlock&,
+ const std::string&,
+ RequestHandler* request_handler) override {
+ request_handler->TerminateStreamWithError(
+ QuicResetStreamError::FromInternal(QUIC_STREAM_INTERNAL_ERROR));
+ }
+ void CloseBackendResponseStream(RequestHandler*) override {}
+ bool SupportsWebTransport() override { return true; }
+ WebTransportResponse ProcessWebTransportRequest(
+ const spdy::Http2HeaderBlock& request_headers,
+ WebTransportSession* session) override {
+ WebTransportResponse response;
+ response.response_headers[":status"] = "400";
+
+ auto path = request_headers.find(":path");
+ if (path == request_headers.end()) {
+ return response;
+ }
+ GURL url(absl::StrCat("https://localhost", path->second));
+ if (!url.is_valid()) {
+ return response;
+ }
+ absl::StatusOr<std::unique_ptr<webtransport::SessionVisitor>> processed =
+ ProcessRequest(url, session);
+ switch (processed.status().code()) {
+ case absl::StatusCode::kOk:
+ response.response_headers[":status"] = "200";
+ response.visitor = *std::move(processed);
+ return response;
+ case absl::StatusCode::kNotFound:
+ response.response_headers[":status"] = "404";
+ return response;
+ case absl::StatusCode::kInvalidArgument:
+ response.response_headers[":status"] = "400";
+ return response;
+ default:
+ response.response_headers[":status"] = "500";
+ return response;
+ }
+ }
+};
+
+int Main(int argc, char** argv) {
+ quiche::QuicheSystemEventLoop event_loop("web_transport_test_server");
+ const char* usage = "Usage: web_transport_test_server [options]";
+ std::vector<std::string> non_option_args =
+ quiche::QuicheParseCommandLineFlags(usage, argc, argv);
+
+ WebTransportTestBackend backend;
+ QuicServer server(quiche::CreateDefaultProofSource(), &backend);
+ quic::QuicSocketAddress addr(quic::QuicIpAddress::Any6(),
+ quiche::GetQuicheCommandLineFlag(FLAGS_port));
+ if (!server.CreateUDPSocketAndListen(addr)) {
+ QUICHE_LOG(ERROR) << "Failed to bind the port address";
+ }
+ QUICHE_LOG(INFO) << "Bound the server on " << addr;
+ server.HandleEventsForever();
+ return 0;
+}
+
+} // namespace
+} // namespace quic
+
+int main(int argc, char** argv) { return quic::Main(argc, argv); }
diff --git a/quiche/quic/tools/web_transport_test_visitors.h b/quiche/quic/tools/web_transport_test_visitors.h
index 8823562..685a892 100644
--- a/quiche/quic/tools/web_transport_test_visitors.h
+++ b/quiche/quic/tools/web_transport_test_visitors.h
@@ -11,13 +11,11 @@
#include "quiche/quic/core/web_transport_interface.h"
#include "quiche/quic/platform/api/quic_logging.h"
#include "quiche/common/platform/api/quiche_logging.h"
-#include "quiche/common/platform/api/quiche_mem_slice.h"
-#include "quiche/common/quiche_callbacks.h"
#include "quiche/common/quiche_circular_deque.h"
#include "quiche/common/quiche_stream.h"
#include "quiche/common/simple_buffer_allocator.h"
+#include "quiche/web_transport/complete_buffer_visitor.h"
#include "quiche/web_transport/web_transport.h"
-#include "quiche/spdy/core/http2_header_block.h"
namespace quic {
@@ -149,75 +147,10 @@
bool stop_sending_received_ = false;
};
-// Buffers all of the data and calls |callback| with the entirety of the stream
-// data.
-class WebTransportUnidirectionalEchoReadVisitor
- : public WebTransportStreamVisitor {
- public:
- using Callback = quiche::MultiUseCallback<void(const std::string&)>;
-
- WebTransportUnidirectionalEchoReadVisitor(WebTransportStream* stream,
- Callback callback)
- : stream_(stream), callback_(std::move(callback)) {}
-
- void OnCanRead() override {
- WebTransportStream::ReadResult result = stream_->Read(&buffer_);
- QUIC_DVLOG(1) << "Attempted reading on WebTransport unidirectional stream "
- << stream_->GetStreamId()
- << ", bytes read: " << result.bytes_read;
- if (result.fin) {
- QUIC_DVLOG(1) << "Finished receiving data on a WebTransport stream "
- << stream_->GetStreamId() << ", queueing up the echo";
- callback_(buffer_);
- }
- }
-
- void OnCanWrite() override { QUICHE_NOTREACHED(); }
-
- void OnResetStreamReceived(WebTransportStreamError /*error*/) override {}
- void OnStopSendingReceived(WebTransportStreamError /*error*/) override {}
- void OnWriteSideInDataRecvdState() override {}
-
- private:
- WebTransportStream* stream_;
- std::string buffer_;
- Callback callback_;
-};
-
-// Sends supplied data.
-class WebTransportUnidirectionalEchoWriteVisitor
- : public WebTransportStreamVisitor {
- public:
- WebTransportUnidirectionalEchoWriteVisitor(WebTransportStream* stream,
- const std::string& data)
- : stream_(stream), data_(data) {}
-
- void OnCanRead() override { QUICHE_NOTREACHED(); }
- void OnCanWrite() override {
- if (data_.empty()) {
- return;
- }
- absl::Status write_status = quiche::WriteIntoStream(*stream_, data_);
- if (!write_status.ok()) {
- QUICHE_DLOG_IF(WARNING, !absl::IsUnavailable(write_status))
- << "Failed to write into stream: " << write_status;
- return;
- }
- data_ = "";
- absl::Status fin_status = quiche::SendFinOnStream(*stream_);
- QUICHE_DVLOG(1)
- << "WebTransportUnidirectionalEchoWriteVisitor finished sending data.";
- QUICHE_DCHECK(fin_status.ok());
- }
-
- void OnResetStreamReceived(WebTransportStreamError /*error*/) override {}
- void OnStopSendingReceived(WebTransportStreamError /*error*/) override {}
- void OnWriteSideInDataRecvdState() override {}
-
- private:
- WebTransportStream* stream_;
- std::string data_;
-};
+using WebTransportUnidirectionalEchoReadVisitor =
+ ::webtransport::CompleteBufferVisitor;
+using WebTransportUnidirectionalEchoWriteVisitor =
+ ::webtransport::CompleteBufferVisitor;
// A session visitor which sets unidirectional or bidirectional stream visitors
// to echo.
diff --git a/quiche/web_transport/complete_buffer_visitor.cc b/quiche/web_transport/complete_buffer_visitor.cc
new file mode 100644
index 0000000..d0ef471
--- /dev/null
+++ b/quiche/web_transport/complete_buffer_visitor.cc
@@ -0,0 +1,51 @@
+// Copyright 2023 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/web_transport/complete_buffer_visitor.h"
+
+#include <utility>
+
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/quiche_stream.h"
+
+namespace webtransport {
+
+void CompleteBufferVisitor::OnCanRead() {
+ if (!incoming_data_callback_.has_value()) {
+ return;
+ }
+ Stream::ReadResult result = stream_->Read(&incoming_data_buffer_);
+ if (result.fin) {
+ (*std::move(incoming_data_callback_))(std::move(incoming_data_buffer_));
+ incoming_data_callback_.reset();
+ }
+}
+
+void CompleteBufferVisitor::OnCanWrite() {
+ if (!outgoing_data_.has_value()) {
+ return;
+ }
+ if (!stream_->CanWrite()) {
+ return;
+ }
+ quiche::StreamWriteOptions options;
+ options.set_send_fin(true);
+ absl::Status status =
+ quiche::WriteIntoStream(*stream_, *outgoing_data_, options);
+ if (!status.ok()) {
+ QUICHE_DLOG(WARNING) << "Write from OnCanWrite() failed: " << status;
+ return;
+ }
+ outgoing_data_.reset();
+}
+
+void CompleteBufferVisitor::SetOutgoingData(std::string data) {
+ QUICHE_DCHECK(!outgoing_data_.has_value());
+ outgoing_data_ = std::move(data);
+ if (stream_->CanWrite()) {
+ OnCanWrite();
+ }
+}
+
+} // namespace webtransport
diff --git a/quiche/web_transport/complete_buffer_visitor.h b/quiche/web_transport/complete_buffer_visitor.h
new file mode 100644
index 0000000..498f148
--- /dev/null
+++ b/quiche/web_transport/complete_buffer_visitor.h
@@ -0,0 +1,55 @@
+// Copyright 2023 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_WEB_TRANSPORT_COMPLETE_BUFFER_VISITOR_H_
+#define QUICHE_WEB_TRANSPORT_COMPLETE_BUFFER_VISITOR_H_
+
+#include "absl/types/optional.h"
+#include "absl/utility/utility.h"
+#include "quiche/common/quiche_callbacks.h"
+#include "quiche/web_transport/web_transport.h"
+
+namespace webtransport {
+
+// A visitor that will buffer the entirety of the incoming stream into a string,
+// and will send a pre-specified string all at once.
+class QUICHE_EXPORT CompleteBufferVisitor : public StreamVisitor {
+ public:
+ using AllDataReadCallback = quiche::SingleUseCallback<void(std::string)>;
+
+ CompleteBufferVisitor(webtransport::Stream* stream, std::string outgoing_data)
+ : stream_(stream),
+ outgoing_data_(absl::in_place, std::move(outgoing_data)) {}
+ CompleteBufferVisitor(webtransport::Stream* stream,
+ AllDataReadCallback incoming_data_callback)
+ : stream_(stream),
+ incoming_data_callback_(absl::in_place,
+ std::move(incoming_data_callback)) {}
+ CompleteBufferVisitor(webtransport::Stream* stream, std::string outgoing_data,
+ AllDataReadCallback incoming_data_callback)
+ : stream_(stream),
+ outgoing_data_(absl::in_place, std::move(outgoing_data)),
+ incoming_data_callback_(absl::in_place,
+ std::move(incoming_data_callback)) {}
+
+ void OnCanRead() override;
+ void OnCanWrite() override;
+
+ void OnResetStreamReceived(StreamErrorCode) override {}
+ void OnStopSendingReceived(StreamErrorCode) override {}
+ void OnWriteSideInDataRecvdState() override {}
+
+ protected:
+ void SetOutgoingData(std::string data);
+
+ private:
+ webtransport::Stream* stream_;
+ absl::optional<std::string> outgoing_data_;
+ absl::optional<AllDataReadCallback> incoming_data_callback_;
+ std::string incoming_data_buffer_;
+};
+
+} // namespace webtransport
+
+#endif // QUICHE_WEB_TRANSPORT_COMPLETE_BUFFER_VISITOR_H_