Separate Masque OHTTP Client into its own library and update connection pool to support Private key and Public Cert via blobs

PiperOrigin-RevId: 827478186
diff --git a/build/source_list.bzl b/build/source_list.bzl
index 9fd77f0..b42f07a 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -1016,6 +1016,7 @@
     "quic/masque/masque_encapsulated_client.h",
     "quic/masque/masque_encapsulated_client_session.h",
     "quic/masque/masque_h2_connection.h",
+    "quic/masque/masque_ohttp_client.h",
     "quic/masque/masque_server.h",
     "quic/masque/masque_server_backend.h",
     "quic/masque/masque_server_session.h",
@@ -1030,6 +1031,7 @@
     "quic/masque/masque_encapsulated_client.cc",
     "quic/masque/masque_encapsulated_client_session.cc",
     "quic/masque/masque_h2_connection.cc",
+    "quic/masque/masque_ohttp_client.cc",
     "quic/masque/masque_server.cc",
     "quic/masque/masque_server_backend.cc",
     "quic/masque/masque_server_session.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index be8a3b4..7d6d1d0 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -1016,6 +1016,7 @@
     "src/quiche/quic/masque/masque_encapsulated_client.h",
     "src/quiche/quic/masque/masque_encapsulated_client_session.h",
     "src/quiche/quic/masque/masque_h2_connection.h",
+    "src/quiche/quic/masque/masque_ohttp_client.h",
     "src/quiche/quic/masque/masque_server.h",
     "src/quiche/quic/masque/masque_server_backend.h",
     "src/quiche/quic/masque/masque_server_session.h",
@@ -1030,6 +1031,7 @@
     "src/quiche/quic/masque/masque_encapsulated_client.cc",
     "src/quiche/quic/masque/masque_encapsulated_client_session.cc",
     "src/quiche/quic/masque/masque_h2_connection.cc",
+    "src/quiche/quic/masque/masque_ohttp_client.cc",
     "src/quiche/quic/masque/masque_server.cc",
     "src/quiche/quic/masque/masque_server_backend.cc",
     "src/quiche/quic/masque/masque_server_session.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 41abf16..5e340a3 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -1015,6 +1015,7 @@
     "quiche/quic/masque/masque_encapsulated_client.h",
     "quiche/quic/masque/masque_encapsulated_client_session.h",
     "quiche/quic/masque/masque_h2_connection.h",
+    "quiche/quic/masque/masque_ohttp_client.h",
     "quiche/quic/masque/masque_server.h",
     "quiche/quic/masque/masque_server_backend.h",
     "quiche/quic/masque/masque_server_session.h",
@@ -1029,6 +1030,7 @@
     "quiche/quic/masque/masque_encapsulated_client.cc",
     "quiche/quic/masque/masque_encapsulated_client_session.cc",
     "quiche/quic/masque/masque_h2_connection.cc",
+    "quiche/quic/masque/masque_ohttp_client.cc",
     "quiche/quic/masque/masque_server.cc",
     "quiche/quic/masque/masque_server_backend.cc",
     "quiche/quic/masque/masque_server_session.cc",
diff --git a/quiche/quic/masque/masque_connection_pool.cc b/quiche/quic/masque/masque_connection_pool.cc
index 8481f98..8ba6594 100644
--- a/quiche/quic/masque/masque_connection_pool.cc
+++ b/quiche/quic/masque/masque_connection_pool.cc
@@ -375,4 +375,36 @@
   return ctx;
 }
 
+// static
+absl::StatusOr<bssl::UniquePtr<SSL_CTX>>
+MasqueConnectionPool::CreateSslCtxFromData(
+    const std::string& client_cert_pem_data,
+    const std::string& client_cert_key_data) {
+  bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
+  // Load public cert.
+  BIO* cert_bio = BIO_new_mem_buf(client_cert_pem_data.c_str(), -1);
+  QUICHE_CHECK(cert_bio);
+  X509* cert = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr);
+  QUICHE_CHECK(cert);
+  BIO_free(cert_bio);
+  int rv = SSL_CTX_use_certificate(ctx.get(), cert);
+  QUICHE_CHECK_EQ(rv, 1);
+  X509_free(cert);
+
+  // Load private key.
+  BIO* key_bio = BIO_new_mem_buf(client_cert_key_data.c_str(), -1);
+  QUICHE_CHECK(key_bio);
+  EVP_PKEY* private_key =
+      PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr, nullptr);
+  QUICHE_CHECK(private_key);
+  BIO_free(key_bio);
+  rv = SSL_CTX_use_PrivateKey(ctx.get(), private_key);
+  QUICHE_CHECK_EQ(rv, 1);
+  EVP_PKEY_free(private_key);
+
+  SSL_CTX_set_min_proto_version(ctx.get(), TLS1_2_VERSION);
+  SSL_CTX_set_max_proto_version(ctx.get(), TLS1_3_VERSION);
+  return ctx;
+}
+
 }  // namespace quic
diff --git a/quiche/quic/masque/masque_connection_pool.h b/quiche/quic/masque/masque_connection_pool.h
index b70c391..89ab5ca 100644
--- a/quiche/quic/masque/masque_connection_pool.h
+++ b/quiche/quic/masque/masque_connection_pool.h
@@ -66,6 +66,10 @@
       const std::string& client_cert_file,
       const std::string& client_cert_key_file);
 
+  static absl::StatusOr<bssl::UniquePtr<SSL_CTX>> CreateSslCtxFromData(
+      const std::string& client_cert_pem_data,
+      const std::string& client_cert_key_data);
+
  private:
   class ConnectionState : public QuicSocketEventListener {
    public:
diff --git a/quiche/quic/masque/masque_ohttp_client.cc b/quiche/quic/masque/masque_ohttp_client.cc
new file mode 100644
index 0000000..0e08b59
--- /dev/null
+++ b/quiche/quic/masque/masque_ohttp_client.cc
@@ -0,0 +1,284 @@
+// Copyright 2025 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/masque/masque_ohttp_client.h"
+
+#include <optional>
+#include <ostream>
+#include <string>
+#include <utility>
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/match.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "openssl/base.h"
+#include "quiche/quic/masque/masque_connection_pool.h"
+#include "quiche/quic/tools/quic_url.h"
+#include "quiche/binary_http/binary_http_message.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/quiche_status_utils.h"
+#include "quiche/oblivious_http/buffers/oblivious_http_request.h"
+#include "quiche/oblivious_http/buffers/oblivious_http_response.h"
+#include "quiche/oblivious_http/common/oblivious_http_header_key_config.h"
+#include "quiche/oblivious_http/oblivious_http_client.h"
+
+namespace quic {
+
+using ::quic::MasqueConnectionPool;
+using ::quic::QuicUrl;
+using ::quiche::BinaryHttpRequest;
+using ::quiche::BinaryHttpResponse;
+using ::quiche::ObliviousHttpClient;
+using ::quiche::ObliviousHttpHeaderKeyConfig;
+using ::quiche::ObliviousHttpKeyConfigs;
+using ::quiche::ObliviousHttpRequest;
+using ::quiche::ObliviousHttpResponse;
+using RequestId = ::quic::MasqueConnectionPool::RequestId;
+using Message = ::quic::MasqueConnectionPool::Message;
+
+absl::Status MasqueOhttpClient::Start() {
+  if (urls_.empty()) {
+    QUICHE_LOG(ERROR) << "No URLs to request";
+    Abort();
+    return absl::InvalidArgumentError("No URLs to request");
+  }
+  absl::Status status = StartKeyFetch(urls_[0]);
+  if (!status.ok()) {
+    Abort();
+    return status;
+  }
+  return absl::OkStatus();
+}
+bool MasqueOhttpClient::IsDone() {
+  if (aborted_) {
+    return true;
+  }
+  if (!ohttp_client_.has_value()) {
+    // Key fetch request is still pending.
+    return false;
+  }
+  return pending_ohttp_requests_.empty();
+}
+
+absl::StatusOr<QuicUrl> ParseUrl(const std::string& url_string) {
+  QuicUrl url(url_string, "https");
+  if (url.host().empty() && !absl::StrContains(url_string, "://")) {
+    url = QuicUrl(absl::StrCat("https://", url_string));
+  }
+  if (url.host().empty()) {
+    return absl::InvalidArgumentError(
+        absl::StrCat("Failed to parse key URL ", url_string));
+  }
+  return url;
+}
+
+absl::Status MasqueOhttpClient::StartKeyFetch(const std::string& url_string) {
+  QuicUrl url(url_string, "https");
+  if (url.host().empty() && !absl::StrContains(url_string, "://")) {
+    url = QuicUrl(absl::StrCat("https://", url_string));
+  }
+  if (url.host().empty()) {
+    QUICHE_LOG(ERROR) << "Failed to parse key URL \"" << url_string << "\"";
+    return absl::InvalidArgumentError(
+        absl::StrCat("Failed to parse key URL ", url_string));
+  }
+  Message request;
+  request.headers[":method"] = "GET";
+  request.headers[":scheme"] = url.scheme();
+  request.headers[":authority"] = url.HostPort();
+  request.headers[":path"] = url.path();
+  request.headers["accept"] = "application/ohttp-keys";
+  request.headers["content-type"] = "application/ohttp-keys";
+  absl::StatusOr<RequestId> request_id = connection_pool_.SendRequest(request);
+  if (!request_id.ok()) {
+    QUICHE_LOG(ERROR) << "Failed to send request: " << request_id.status();
+    return request_id.status();
+  }
+  key_fetch_request_id_ = *request_id;
+  return absl::OkStatus();
+}
+
+absl::Status MasqueOhttpClient::HandleKeyResponse(
+    const absl::StatusOr<Message>& response) {
+  key_fetch_request_id_ = std::nullopt;
+
+  if (!response.ok()) {
+    QUICHE_LOG(ERROR) << "Failed to fetch key: " << response.status();
+    return response.status();
+  }
+  QUICHE_LOG(INFO) << "Received key response: "
+                   << response->headers.DebugString();
+  absl::StatusOr<ObliviousHttpKeyConfigs> key_configs =
+      ObliviousHttpKeyConfigs::ParseConcatenatedKeys(response->body);
+  if (!key_configs.ok()) {
+    QUICHE_LOG(ERROR) << "Failed to parse OHTTP keys: " << key_configs.status();
+    Abort();
+    return key_configs.status();
+  }
+  QUICHE_LOG(INFO) << "Successfully got " << key_configs->NumKeys()
+                   << " OHTTP keys: " << std::endl
+                   << key_configs->DebugString();
+  if (urls_.size() <= 2) {
+    QUICHE_LOG(INFO) << "No OHTTP URLs to request, exiting.";
+    Abort();
+    return absl::InvalidArgumentError("No OHTTP URLs to request, exiting.");
+  }
+  relay_url_ = QuicUrl(urls_[1], "https");
+  if (relay_url_.host().empty() && !absl::StrContains(urls_[1], "://")) {
+    relay_url_ = QuicUrl(absl::StrCat("https://", urls_[1]));
+  }
+  QUICHE_LOG(INFO) << "Using relay URL: " << relay_url_.ToString();
+  ObliviousHttpHeaderKeyConfig key_config = key_configs->PreferredConfig();
+  absl::StatusOr<absl::string_view> public_key =
+      key_configs->GetPublicKeyForId(key_config.GetKeyId());
+  if (!public_key.ok()) {
+    QUICHE_LOG(ERROR) << "Failed to get public key for key ID "
+                      << static_cast<int>(key_config.GetKeyId()) << ": "
+                      << public_key.status();
+    Abort();
+    return public_key.status();
+  }
+  absl::StatusOr<ObliviousHttpClient> ohttp_client =
+      ObliviousHttpClient::Create(*public_key, key_config);
+  if (!ohttp_client.ok()) {
+    QUICHE_LOG(ERROR) << "Failed to create OHTTP client: "
+                      << ohttp_client.status();
+    Abort();
+    return ohttp_client.status();
+  }
+  ohttp_client_.emplace(std::move(*ohttp_client));
+  for (size_t i = 2; i < urls_.size(); ++i) {
+    QUICHE_RETURN_IF_ERROR(SendOhttpRequestForUrl(urls_[i]));
+  }
+  return absl::OkStatus();
+}
+
+absl::Status MasqueOhttpClient::SendOhttpRequestForUrl(
+    const std::string& url_string) {
+  QuicUrl url(url_string, "https");
+  if (url.host().empty() && !absl::StrContains(url_string, "://")) {
+    url = QuicUrl(absl::StrCat("https://", url_string));
+  }
+  if (url.host().empty()) {
+    return absl::InvalidArgumentError(
+        absl::StrCat("Failed to parse key URL ", url_string));
+  }
+  BinaryHttpRequest::ControlData control_data;
+  control_data.method = post_data_.empty() ? "GET" : "POST";
+  control_data.scheme = url.scheme();
+  control_data.authority = url.HostPort();
+  control_data.path = url.path();
+  BinaryHttpRequest binary_request(control_data);
+  binary_request.set_body(post_data_);
+  absl::StatusOr<std::string> encoded_request = binary_request.Serialize();
+  if (!encoded_request.ok()) {
+    return encoded_request.status();
+  }
+  if (!ohttp_client_.has_value()) {
+    QUICHE_LOG(FATAL) << "Cannot send OHTTP request without OHTTP client";
+    return absl::InternalError(
+        "Cannot send OHTTP request without OHTTP client");
+  }
+  absl::StatusOr<ObliviousHttpRequest> ohttp_request =
+      ohttp_client_->CreateObliviousHttpRequest(*encoded_request);
+  if (!ohttp_request.ok()) {
+    QUICHE_LOG(ERROR) << "Failed to create OHTTP request: "
+                      << ohttp_request.status();
+    return ohttp_request.status();
+  }
+  Message request;
+  request.headers[":method"] = "POST";
+  request.headers[":scheme"] = relay_url_.scheme();
+  request.headers[":authority"] = relay_url_.HostPort();
+  request.headers[":path"] = relay_url_.path();
+  request.headers["content-type"] = "message/ohttp-req";
+  request.body = ohttp_request->EncapsulateAndSerialize();
+  absl::StatusOr<RequestId> request_id = connection_pool_.SendRequest(request);
+  if (!request_id.ok()) {
+    QUICHE_LOG(ERROR) << "Failed to send request: " << request_id.status();
+    return request_id.status();
+  }
+  QUICHE_LOG(INFO) << "Sent OHTTP request for " << url_string;
+  auto context = std::move(*ohttp_request).ReleaseContext();
+  pending_ohttp_requests_.insert({*request_id, std::move(context)});
+  return absl::OkStatus();
+}
+
+absl::StatusOr<BinaryHttpResponse> MasqueOhttpClient::TryExtractBinaryResponse(
+    const RequestId request_id, quiche::ObliviousHttpRequest::Context& context,
+    const absl::StatusOr<Message>& response) {
+  if (response.ok()) {
+    if (!ohttp_client_.has_value()) {
+      QUICHE_LOG(FATAL) << "Received OHTTP response without OHTTP client";
+      return absl::InternalError(
+          "Received OHTTP response without OHTTP client");
+    }
+    absl::StatusOr<ObliviousHttpResponse> ohttp_response =
+        ohttp_client_->DecryptObliviousHttpResponse(response->body, context);
+    if (ohttp_response.ok()) {
+      QUICHE_LOG(INFO) << "Received OHTTP response for " << request_id;
+      absl::StatusOr<BinaryHttpResponse> binary_response =
+          BinaryHttpResponse::Create(ohttp_response->GetPlaintextData());
+      if (binary_response.ok()) {
+        QUICHE_LOG(INFO) << "Successfully decoded OHTTP response:";
+        QUICHE_LOG(INFO) << "Status: " << binary_response->status_code();
+        for (const quiche::BinaryHttpMessage::Field& field :
+             binary_response->GetHeaderFields()) {
+          QUICHE_LOG(INFO) << field.name << ": " << field.value;
+        }
+        QUICHE_LOG(INFO) << "Body:" << std::endl << binary_response->body();
+        return binary_response;
+      } else {
+        QUICHE_LOG(ERROR) << "Failed to parse binary response: "
+                          << binary_response.status();
+        return binary_response;
+      }
+    } else {
+      QUICHE_LOG(ERROR) << "Failed to decrypt OHTTP response: "
+                        << ohttp_response.status();
+      return ohttp_response.status();
+    }
+  } else {
+    QUICHE_LOG(ERROR) << "OHTTP request " << request_id
+                      << " failed: " << response.status();
+    return response.status();
+  }
+}
+
+absl::Status MasqueOhttpClient::HandleOhttpResponse(
+    RequestId request_id, const absl::StatusOr<Message>& response) {
+  auto it = pending_ohttp_requests_.find(request_id);
+  if (it == pending_ohttp_requests_.end()) {
+    QUICHE_LOG(ERROR) << "Received unexpected response for unknown request "
+                      << request_id;
+    Abort();
+    return absl::InternalError(
+        "Received unexpected response for unknown request");
+  }
+  absl::StatusOr<BinaryHttpResponse> binary_response =
+      TryExtractBinaryResponse(request_id, it->second, response);
+  absl::Status status = HandleBinaryResponse(binary_response);
+  pending_ohttp_requests_.erase(it);
+  return status;
+}
+
+void MasqueOhttpClient::OnResponse(MasqueConnectionPool* /*pool*/,
+                                   RequestId request_id,
+                                   const absl::StatusOr<Message>& response) {
+  if (key_fetch_request_id_.has_value() &&
+      *key_fetch_request_id_ == request_id) {
+    auto status = HandleKeyResponse(response);
+    if (!status.ok()) {
+      QUICHE_LOG(ERROR) << "Failed to handle key response: " << status;
+    }
+  } else {
+    auto status = HandleOhttpResponse(request_id, response);
+    if (!status.ok()) {
+      QUICHE_LOG(ERROR) << "Failed to handle OHTTP response: " << status;
+    }
+  }
+}
+}  // namespace quic
diff --git a/quiche/quic/masque/masque_ohttp_client.h b/quiche/quic/masque/masque_ohttp_client.h
new file mode 100644
index 0000000..f7a80cf
--- /dev/null
+++ b/quiche/quic/masque/masque_ohttp_client.h
@@ -0,0 +1,92 @@
+// Copyright 2025 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_MASQUE_MASQUE_OHTTP_CLIENT_H_
+#define QUICHE_QUIC_MASQUE_MASQUE_OHTTP_CLIENT_H_
+
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "openssl/base.h"
+#include "quiche/quic/core/io/quic_event_loop.h"
+#include "quiche/quic/masque/masque_connection_pool.h"
+#include "quiche/quic/tools/quic_url.h"
+#include "quiche/binary_http/binary_http_message.h"
+#include "quiche/common/platform/api/quiche_export.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/oblivious_http/buffers/oblivious_http_request.h"
+#include "quiche/oblivious_http/oblivious_http_client.h"
+
+namespace quic {
+
+// A client that sends OHTTP requests through a relay/gateway to target URLs.
+class QUICHE_EXPORT MasqueOhttpClient
+    : public quic::MasqueConnectionPool::Visitor {
+ public:
+  using RequestId = quic::MasqueConnectionPool::RequestId;
+  using Message = quic::MasqueConnectionPool::Message;
+
+  explicit MasqueOhttpClient(quic::QuicEventLoop* event_loop, SSL_CTX* ssl_ctx,
+                             std::vector<std::string> urls,
+                             bool disable_certificate_verification,
+                             int address_family_for_lookup,
+                             const std::string& post_data)
+      : urls_(urls),
+        post_data_(post_data),
+        connection_pool_(event_loop, ssl_ctx, disable_certificate_verification,
+                         address_family_for_lookup, this) {}
+
+  // Starts fetching for the key and sends the OHTTP request.
+  absl::Status Start();
+
+  // Returns true if the client has completed all requests.
+  bool IsDone();
+
+ protected:
+  // From quic::MasqueConnectionPool::Visitor.
+  void OnResponse(quic::MasqueConnectionPool* /*pool*/, RequestId request_id,
+                  const absl::StatusOr<Message>& response) override;
+
+  // Fetch key from the key URL.
+  absl::Status StartKeyFetch(const std::string& url_string);
+
+  // Handles the key response and starts the OHTTP request.
+  absl::Status HandleKeyResponse(const absl::StatusOr<Message>& response);
+
+  // Sends the OHTTP request for the given URL.
+  absl::Status SendOhttpRequestForUrl(const std::string& url_string);
+
+  // Signals the client to abort.
+  void Abort() {
+    QUICHE_LOG(INFO) << "Aborting";
+    aborted_ = true;
+  }
+  absl::StatusOr<quiche::BinaryHttpResponse> TryExtractBinaryResponse(
+      RequestId request_id, quiche::ObliviousHttpRequest::Context& context,
+      const absl::StatusOr<Message>& response);
+  virtual absl::Status HandleOhttpResponse(
+      RequestId request_id, const absl::StatusOr<Message>& response);
+  virtual absl::Status HandleBinaryResponse(
+      const absl::StatusOr<quiche::BinaryHttpResponse>& binary_response) {
+    return binary_response.status();
+  }
+
+ private:
+  std::vector<std::string> urls_;
+  std::string post_data_;
+  quic::MasqueConnectionPool connection_pool_;
+  std::optional<RequestId> key_fetch_request_id_;
+  bool aborted_ = false;
+  std::optional<quiche::ObliviousHttpClient> ohttp_client_;
+  quic::QuicUrl relay_url_;
+  absl::flat_hash_map<RequestId, quiche::ObliviousHttpRequest::Context>
+      pending_ohttp_requests_;
+};
+}  // namespace quic
+
+#endif  // QUICHE_QUIC_MASQUE_MASQUE_OHTTP_CLIENT_H_
diff --git a/quiche/quic/masque/masque_ohttp_client_bin.cc b/quiche/quic/masque/masque_ohttp_client_bin.cc
index eaa3822..baa439f 100644
--- a/quiche/quic/masque/masque_ohttp_client_bin.cc
+++ b/quiche/quic/masque/masque_ohttp_client_bin.cc
@@ -4,18 +4,11 @@
 
 #include <stdbool.h>
 
-#include <cstddef>
 #include <memory>
-#include <optional>
-#include <ostream>
 #include <string>
-#include <utility>
 #include <vector>
 
-#include "absl/container/flat_hash_map.h"
 #include "absl/status/statusor.h"
-#include "absl/strings/match.h"
-#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "openssl/base.h"
 #include "quiche/quic/core/io/quic_default_event_loop.h"
@@ -23,15 +16,10 @@
 #include "quiche/quic/core/quic_default_clock.h"
 #include "quiche/quic/core/quic_time.h"
 #include "quiche/quic/masque/masque_connection_pool.h"
-#include "quiche/quic/tools/quic_url.h"
-#include "quiche/binary_http/binary_http_message.h"
+#include "quiche/quic/masque/masque_ohttp_client.h"
 #include "quiche/common/platform/api/quiche_command_line_flags.h"
 #include "quiche/common/platform/api/quiche_logging.h"
 #include "quiche/common/platform/api/quiche_system_event_loop.h"
-#include "quiche/oblivious_http/buffers/oblivious_http_request.h"
-#include "quiche/oblivious_http/buffers/oblivious_http_response.h"
-#include "quiche/oblivious_http/common/oblivious_http_header_key_config.h"
-#include "quiche/oblivious_http/oblivious_http_client.h"
 
 DEFINE_QUICHE_COMMAND_LINE_FLAG(
     bool, disable_certificate_verification, false,
@@ -52,251 +40,8 @@
     std::string, post_data, "",
     "When set, the client will send a POST request with this data.");
 
-using quiche::BinaryHttpRequest;
-using quiche::BinaryHttpResponse;
-using quiche::ObliviousHttpClient;
-using quiche::ObliviousHttpHeaderKeyConfig;
-using quiche::ObliviousHttpKeyConfigs;
-using quiche::ObliviousHttpRequest;
-using quiche::ObliviousHttpResponse;
-
 namespace quic {
 namespace {
-
-class MasqueOhttpClient : public MasqueConnectionPool::Visitor {
- public:
-  using RequestId = MasqueConnectionPool::RequestId;
-  using Message = MasqueConnectionPool::Message;
-  explicit MasqueOhttpClient(QuicEventLoop* event_loop, SSL_CTX* ssl_ctx,
-                             std::vector<std::string> urls,
-                             bool disable_certificate_verification,
-                             int address_family_for_lookup,
-                             const std::string& post_data)
-      : urls_(urls),
-        post_data_(post_data),
-        connection_pool_(event_loop, ssl_ctx, disable_certificate_verification,
-                         address_family_for_lookup, this) {}
-
-  bool Start() {
-    if (urls_.empty()) {
-      QUICHE_LOG(ERROR) << "No URLs to request";
-      Abort();
-      return false;
-    }
-    if (!StartKeyFetch(urls_[0])) {
-      Abort();
-      return false;
-    }
-    return true;
-  }
-  bool IsDone() {
-    if (aborted_) {
-      return true;
-    }
-    if (!ohttp_client_.has_value()) {
-      // Key fetch request is still pending.
-      return false;
-    }
-    return pending_ohttp_requests_.empty();
-  }
-
-  // From MasqueConnectionPool::Visitor.
-  void OnResponse(MasqueConnectionPool* /*pool*/, RequestId request_id,
-                  const absl::StatusOr<Message>& response) override {
-    if (key_fetch_request_id_.has_value() &&
-        *key_fetch_request_id_ == request_id) {
-      key_fetch_request_id_ = std::nullopt;
-      HandleKeyResponse(response);
-    } else {
-      auto it = pending_ohttp_requests_.find(request_id);
-      if (it == pending_ohttp_requests_.end()) {
-        QUICHE_LOG(ERROR) << "Received unexpected response for unknown request "
-                          << request_id;
-        Abort();
-        return;
-      }
-      if (response.ok()) {
-        if (!ohttp_client_.has_value()) {
-          QUICHE_LOG(FATAL) << "Received OHTTP response without OHTTP client";
-          return;
-        }
-        absl::StatusOr<ObliviousHttpResponse> ohttp_response =
-            ohttp_client_->DecryptObliviousHttpResponse(response->body,
-                                                        it->second);
-        if (ohttp_response.ok()) {
-          QUICHE_LOG(INFO) << "Received OHTTP response for " << request_id;
-          absl::StatusOr<BinaryHttpResponse> binary_response =
-              BinaryHttpResponse::Create(ohttp_response->GetPlaintextData());
-          if (binary_response.ok()) {
-            QUICHE_LOG(INFO) << "Successfully decoded OHTTP response:";
-            QUICHE_LOG(INFO) << "Status: " << binary_response->status_code();
-            for (const quiche::BinaryHttpMessage::Field& field :
-                 binary_response->GetHeaderFields()) {
-              QUICHE_LOG(INFO) << field.name << ": " << field.value;
-            }
-            QUICHE_LOG(INFO) << "Body:" << std::endl << binary_response->body();
-          } else {
-            QUICHE_LOG(ERROR) << "Failed to parse binary response: "
-                              << binary_response.status();
-          }
-        } else {
-          QUICHE_LOG(ERROR) << "Failed to decrypt OHTTP response: "
-                            << ohttp_response.status();
-        }
-      } else {
-        QUICHE_LOG(ERROR) << "OHTTP request " << request_id
-                          << " failed: " << response.status();
-      }
-      pending_ohttp_requests_.erase(it);
-    }
-  }
-
- private:
-  bool StartKeyFetch(const std::string& url_string) {
-    QuicUrl url(url_string, "https");
-    if (url.host().empty() && !absl::StrContains(url_string, "://")) {
-      url = QuicUrl(absl::StrCat("https://", url_string));
-    }
-    if (url.host().empty()) {
-      QUICHE_LOG(ERROR) << "Failed to parse key URL \"" << url_string << "\"";
-      return false;
-    }
-    Message request;
-    request.headers[":method"] = "GET";
-    request.headers[":scheme"] = url.scheme();
-    request.headers[":authority"] = url.HostPort();
-    request.headers[":path"] = url.path();
-    request.headers["accept"] = "application/ohttp-keys";
-    request.headers["content-type"] = "application/ohttp-keys";
-    absl::StatusOr<RequestId> request_id =
-        connection_pool_.SendRequest(request);
-    if (!request_id.ok()) {
-      QUICHE_LOG(ERROR) << "Failed to send request: " << request_id.status();
-      return false;
-    }
-    key_fetch_request_id_ = *request_id;
-    return true;
-  }
-
-  void HandleKeyResponse(const absl::StatusOr<Message>& response) {
-    if (!response.ok()) {
-      QUICHE_LOG(ERROR) << "Failed to fetch key: " << response.status();
-      return;
-    }
-    QUICHE_LOG(INFO) << "Received key response: "
-                     << response->headers.DebugString();
-    absl::StatusOr<ObliviousHttpKeyConfigs> key_configs =
-        ObliviousHttpKeyConfigs::ParseConcatenatedKeys(response->body);
-    if (!key_configs.ok()) {
-      QUICHE_LOG(ERROR) << "Failed to parse OHTTP keys: "
-                        << key_configs.status();
-      Abort();
-      return;
-    }
-    QUICHE_LOG(INFO) << "Successfully got " << key_configs->NumKeys()
-                     << " OHTTP keys: " << std::endl
-                     << key_configs->DebugString();
-    if (urls_.size() <= 2) {
-      QUICHE_LOG(INFO) << "No OHTTP URLs to request, exiting.";
-      Abort();
-      return;
-    }
-    relay_url_ = QuicUrl(urls_[1], "https");
-    if (relay_url_.host().empty() && !absl::StrContains(urls_[1], "://")) {
-      relay_url_ = QuicUrl(absl::StrCat("https://", urls_[1]));
-    }
-    QUICHE_LOG(INFO) << "Using relay URL: " << relay_url_.ToString();
-    ObliviousHttpHeaderKeyConfig key_config = key_configs->PreferredConfig();
-    absl::StatusOr<absl::string_view> public_key =
-        key_configs->GetPublicKeyForId(key_config.GetKeyId());
-    if (!public_key.ok()) {
-      QUICHE_LOG(ERROR) << "Failed to get public key for key ID "
-                        << static_cast<int>(key_config.GetKeyId()) << ": "
-                        << public_key.status();
-      Abort();
-      return;
-    }
-    absl::StatusOr<ObliviousHttpClient> ohttp_client =
-        ObliviousHttpClient::Create(*public_key, key_config);
-    if (!ohttp_client.ok()) {
-      QUICHE_LOG(ERROR) << "Failed to create OHTTP client: "
-                        << ohttp_client.status();
-      Abort();
-      return;
-    }
-    ohttp_client_.emplace(std::move(*ohttp_client));
-    for (size_t i = 2; i < urls_.size(); ++i) {
-      SendOhttpRequestForUrl(urls_[i]);
-    }
-  }
-
-  void SendOhttpRequestForUrl(const std::string& url_string) {
-    QuicUrl url(url_string, "https");
-    if (url.host().empty() && !absl::StrContains(url_string, "://")) {
-      url = QuicUrl(absl::StrCat("https://", url_string));
-    }
-    if (url.host().empty()) {
-      QUICHE_LOG(ERROR) << "Failed to parse key URL \"" << url_string << "\"";
-      return;
-    }
-    BinaryHttpRequest::ControlData control_data;
-    control_data.method = post_data_.empty() ? "GET" : "POST";
-    control_data.scheme = url.scheme();
-    control_data.authority = url.HostPort();
-    control_data.path = url.path();
-    BinaryHttpRequest binary_request(control_data);
-    binary_request.set_body(post_data_);
-    absl::StatusOr<std::string> encoded_request = binary_request.Serialize();
-    if (!encoded_request.ok()) {
-      QUICHE_LOG(ERROR) << "Failed to encode request: "
-                        << encoded_request.status();
-      return;
-    }
-    if (!ohttp_client_.has_value()) {
-      QUICHE_LOG(FATAL) << "Cannot send OHTTP request without OHTTP client";
-      return;
-    }
-    absl::StatusOr<ObliviousHttpRequest> ohttp_request =
-        ohttp_client_->CreateObliviousHttpRequest(*encoded_request);
-    if (!ohttp_request.ok()) {
-      QUICHE_LOG(ERROR) << "Failed to create OHTTP request: "
-                        << ohttp_request.status();
-      return;
-    }
-    Message request;
-    request.headers[":method"] = "POST";
-    request.headers[":scheme"] = relay_url_.scheme();
-    request.headers[":authority"] = relay_url_.HostPort();
-    request.headers[":path"] = relay_url_.path();
-    request.headers["content-type"] = "message/ohttp-req";
-    request.body = ohttp_request->EncapsulateAndSerialize();
-    absl::StatusOr<RequestId> request_id =
-        connection_pool_.SendRequest(request);
-    if (!request_id.ok()) {
-      QUICHE_LOG(ERROR) << "Failed to send request: " << request_id.status();
-      return;
-    }
-    QUICHE_LOG(INFO) << "Sent OHTTP request for " << url_string;
-    auto context = std::move(*ohttp_request).ReleaseContext();
-    pending_ohttp_requests_.insert({*request_id, std::move(context)});
-  }
-
-  void Abort() {
-    QUICHE_LOG(INFO) << "Aborting";
-    aborted_ = true;
-  }
-
-  std::vector<std::string> urls_;
-  std::string post_data_;
-  MasqueConnectionPool connection_pool_;
-  std::optional<RequestId> key_fetch_request_id_;
-  bool aborted_ = false;
-  std::optional<ObliviousHttpClient> ohttp_client_;
-  QuicUrl relay_url_;
-  absl::flat_hash_map<RequestId, ObliviousHttpRequest::Context>
-      pending_ohttp_requests_;
-};
-
 int RunMasqueOhttpClient(int argc, char* argv[]) {
   const char* usage =
       "Usage: masque_ohttp_client <key-url> <relay-url> <url>...";
@@ -315,7 +60,6 @@
     QUICHE_LOG(ERROR) << "Failed to create SSL context: " << ssl_ctx.status();
     return 1;
   }
-
   const int address_family =
       quiche::GetQuicheCommandLineFlag(FLAGS_address_family);
   int address_family_for_lookup;
@@ -336,7 +80,7 @@
   MasqueOhttpClient masque_ohttp_client(event_loop.get(), ssl_ctx->get(), urls,
                                         disable_certificate_verification,
                                         address_family_for_lookup, post_data);
-  if (!masque_ohttp_client.Start()) {
+  if (!masque_ohttp_client.Start().ok()) {
     return 1;
   }
   while (!masque_ohttp_client.IsDone()) {