// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <cstddef>
#include <cstdint>

#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "mojo/public/cpp/test_support/fake_message_dispatch_context.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "net/http/http_status_code.h"
#include "net/log/test_net_log.h"
#include "net/log/test_net_log_util.h"
#include "net/third_party/quiche/src/quiche/binary_http/binary_http_message.h"
#include "net/third_party/quiche/src/quiche/common/quiche_data_writer.h"
#include "net/third_party/quiche/src/quiche/oblivious_http/oblivious_http_gateway.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/network_context.h"
#include "services/network/network_service.h"
#include "services/network/oblivious_http_request_handler.h"
#include "services/network/public/mojom/fetch_api.mojom-shared.h"
#include "services/network/public/mojom/oblivious_http_request.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/test/fake_test_cert_verifier_params_factory.h"
#include "services/network/test/test_network_context.h"
#include "services/network/test/test_url_loader_factory.h"
#include "services/network/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/include/openssl/hpke.h"

namespace {

const char kRelayURL[] = "https://relay.test:13/";
const char kResourceURL[] = "https://resource.test:37/path";

// These keys were randomly generated as follows:
// EVP_HPKE_KEY keys;
// EVP_HPKE_KEY_generate(&keys, EVP_hpke_x25519_hkdf_sha256());
// and then EVP_HPKE_KEY_public_key and EVP_HPKE_KEY_private_key were used to
// extract the keys.
const uint8_t kTestPrivateKey[] = {
    0xff, 0x1f, 0x47, 0xb1, 0x68, 0xb6, 0xb9, 0xea, 0x65, 0xf7, 0x97,
    0x4f, 0xf2, 0x2e, 0xf2, 0x36, 0x94, 0xe2, 0xf6, 0xb6, 0x8d, 0x66,
    0xf3, 0xa7, 0x64, 0x14, 0x28, 0xd4, 0x45, 0x35, 0x01, 0x8f,
};

const uint8_t kTestPublicKey[] = {
    0xa1, 0x5f, 0x40, 0x65, 0x86, 0xfa, 0xc4, 0x7b, 0x99, 0x59, 0x70,
    0xf1, 0x85, 0xd9, 0xd8, 0x91, 0xc7, 0x4d, 0xcf, 0x1e, 0xb9, 0x1a,
    0x7d, 0x50, 0xa5, 0x8b, 0x01, 0x68, 0x3e, 0x60, 0x05, 0x2d,
};

testing::Matcher<net::HttpRequestHeaders::HeaderVector> UnorderedHeadersAre(
    const net::HttpRequestHeaders::HeaderVector& expected) {
  std::vector<testing::Matcher<net::HttpRequestHeaders::HeaderKeyValuePair>> ms;
  for (const auto& expected_pair : expected) {
    ms.emplace_back(testing::AllOf(
        testing::Field("key", &net::HttpRequestHeaders::HeaderKeyValuePair::key,
                       expected_pair.key),
        testing::Field("value",
                       &net::HttpRequestHeaders::HeaderKeyValuePair::value,
                       expected_pair.value)));
  }
  return testing::UnorderedElementsAreArray(ms);
}

class TestOhttpClient : public network::mojom::ObliviousHttpClient {
 public:
  enum class ResponseType { kSuccess, kNetError, kOuterResponseErrorCode };

  TestOhttpClient() : receiver_(this) {}

  void SetExpectedNetError(int expected_net_error) {
    expected_response_type_ = ResponseType::kNetError;
    expected_net_error_ = expected_net_error;
  }

  void SetExpectedOuterResponseErrorCode(
      int expected_outer_response_error_code) {
    expected_response_type_ = ResponseType::kOuterResponseErrorCode;
    expected_outer_response_error_code_ = expected_outer_response_error_code;
  }

  void SetExpectedInnerResponse(
      int expected_inner_response_code,
      std::string expected_body,
      std::multimap<std::string, std::string> expected_headers) {
    expected_response_type_ = ResponseType::kSuccess;
    expected_inner_response_code_ = expected_inner_response_code;
    expected_body_ = expected_body;
    expected_headers_ = std::move(expected_headers);
  }

  mojo::PendingRemote<network::mojom::ObliviousHttpClient>
  CreatePendingRemote() {
    return receiver_.BindNewPipeAndPassRemote();
  }

  void OnCompleted(
      network::mojom::ObliviousHttpCompletionResultPtr status) override {
    is_on_completed_called_ = true;
    switch (expected_response_type_) {
      case ResponseType::kSuccess: {
        ASSERT_TRUE(status->is_inner_response());
        EXPECT_EQ(expected_inner_response_code_,
                  status->get_inner_response()->response_code);
        EXPECT_EQ(expected_body_, status->get_inner_response()->response_body);
        // Verify headers.
        size_t iter = 0;
        std::string name;
        std::string value;
        std::multimap<std::string, std::string> actual_headers;
        while (status->get_inner_response()->headers->EnumerateHeaderLines(
            &iter, &name, &value)) {
          actual_headers.insert(
              std::pair<std::string, std::string>(name, value));
        }
        EXPECT_EQ(expected_headers_, actual_headers);
        break;
      }
      case ResponseType::kNetError: {
        ASSERT_TRUE(status->is_net_error());
        EXPECT_EQ(expected_net_error_, status->get_net_error());
        break;
      }
      case ResponseType::kOuterResponseErrorCode: {
        ASSERT_TRUE(status->is_outer_response_error_code());
        EXPECT_EQ(expected_outer_response_error_code_,
                  status->get_outer_response_error_code());
        break;
      }
    }
    run_loop_.Quit();
  }

  void WaitForCall() { run_loop_.Run(); }

  bool IsOnCompletedCalled() { return is_on_completed_called_; }

 private:
  ResponseType expected_response_type_;
  int expected_inner_response_code_ = 0;
  std::string expected_body_;
  std::multimap<std::string, std::string> expected_headers_;
  int expected_outer_response_error_code_ = 0;
  int expected_net_error_ = 0;
  bool is_on_completed_called_ = false;
  mojo::Receiver<network::mojom::ObliviousHttpClient> receiver_;
  base::RunLoop run_loop_;
};

}  // namespace

class TestObliviousHttpRequestHandler : public testing::Test {
 public:
  TestObliviousHttpRequestHandler()
      : task_environment_(
            base::test::TaskEnvironment::MainThreadType::IO,
            base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME),
        network_service_(network::NetworkService::CreateForTesting()),
        loader_factory_receiver_(&loader_factory_) {
    network::mojom::NetworkContextParamsPtr context_params =
        network::CreateNetworkContextParamsForTesting();
    context_params->cert_verifier_params =
        network::FakeTestCertVerifierParamsFactory::GetCertVerifierParams();
    network_context_ = std::make_unique<network::NetworkContext>(
        network_service_.get(),
        network_context_remote_.BindNewPipeAndPassReceiver(),
        std::move(context_params),
        network::NetworkContext::OnConnectionCloseCallback());

    auto key_configs = quiche::ObliviousHttpKeyConfigs::ParseConcatenatedKeys(
        CreateTestKeyConfig());
    CHECK(key_configs.ok()) << key_configs.status();
    quiche::ObliviousHttpHeaderKeyConfig key_config =
        key_configs->PreferredConfig();
    auto ohttp_gateway = quiche::ObliviousHttpGateway::Create(
        std::string(reinterpret_cast<const char*>(&kTestPrivateKey[0]),
                    sizeof(kTestPrivateKey)),
        key_config);
    CHECK(ohttp_gateway.ok()) << ohttp_gateway.status();
    ohttp_gateway_ = std::move(ohttp_gateway).value();
  }

  network::NetworkContext* network_context() { return network_context_.get(); }
  network::TestURLLoaderFactory* loader_factory() { return &loader_factory_; }

  std::unique_ptr<network::ObliviousHttpRequestHandler> CreateHandler() {
    auto handler = std::make_unique<network::ObliviousHttpRequestHandler>(
        network_context());
    handler->SetURLLoaderFactoryForTesting(
        loader_factory_receiver_.BindNewPipeAndPassRemote());
    return handler;
  }

  std::string DecryptRequest(std::string cipher_text) {
    auto request = ohttp_gateway_->DecryptObliviousHttpRequest(cipher_text);
    EXPECT_TRUE(request.ok()) << request.status();
    return std::string(request->GetPlaintextData());
  }

  void RespondToPendingRequest(std::string body,
                               std::multimap<std::string, std::string> headers,
                               GURL relay_url = GURL(kRelayURL),
                               net::HttpStatusCode status = net::HTTP_OK) {
    const network::ResourceRequest* pending_request;
    ASSERT_TRUE(
        loader_factory()->IsPending(relay_url.spec(), &pending_request));

    ASSERT_TRUE(pending_request->request_body);
    ASSERT_EQ(1u, pending_request->request_body->elements()->size());

    std::string request_body =
        std::string(pending_request->request_body->elements()
                        ->at(0)
                        .As<network::DataElementBytes>()
                        .AsStringPiece());

    auto request = ohttp_gateway_->DecryptObliviousHttpRequest(request_body);
    ASSERT_TRUE(request.ok()) << request.status();
    auto ohttp_context = std::move(*request).ReleaseContext();

    quiche::BinaryHttpResponse bhttp_response(status);
    bhttp_response.set_body(std::move(body));
    for (auto kv : headers) {
      bhttp_response.AddHeaderField({kv.first, kv.second});
    }
    auto payload = bhttp_response.Serialize();
    ASSERT_TRUE(payload.ok()) << payload.status();

    auto response =
        ohttp_gateway_->CreateObliviousHttpResponse(*payload, ohttp_context);
    ASSERT_TRUE(response.ok()) << response.status();

    EXPECT_TRUE(loader_factory()->SimulateResponseForPendingRequest(
        /*url=*/relay_url,
        /*completion_status=*/network::URLLoaderCompletionStatus(),
        /*response_head=*/network::CreateURLResponseHead(net::HTTP_OK),
        /*content=*/response->EncapsulateAndSerialize()));
  }

  std::string CreateTestKeyConfig() {
    const size_t kOHTTPKeySizeBytes =
        1 + 2 + sizeof(kTestPublicKey) + 2 + 2 + 2;
    std::string ohttp_key_config(kOHTTPKeySizeBytes, '\0');
    quiche::QuicheDataWriter writer(ohttp_key_config.size(),
                                    ohttp_key_config.data());
    EXPECT_TRUE(writer.WriteUInt8('K'));  // Key ID can be arbitrary.
    EXPECT_TRUE(writer.WriteUInt16(EVP_HPKE_DHKEM_X25519_HKDF_SHA256));
    EXPECT_TRUE(writer.WriteBytes(&kTestPublicKey[0], sizeof(kTestPublicKey)));
    EXPECT_TRUE(writer.WriteUInt16(4u));
    EXPECT_TRUE(writer.WriteUInt16(EVP_HPKE_HKDF_SHA256));
    EXPECT_TRUE(writer.WriteUInt16(EVP_HPKE_AES_256_GCM));
    EXPECT_EQ(ohttp_key_config.length(), writer.length());
    return ohttp_key_config;
  }

  network::mojom::ObliviousHttpRequestPtr CreateRequest() {
    network::mojom::ObliviousHttpRequestPtr request =
        CreateRequestWithoutBody();
    request->request_body = network::mojom::ObliviousHttpRequestBody::New(
        /*content=*/"test data", /*content_type=*/"application/testdata");
    return request;
  }

  network::mojom::ObliviousHttpRequestPtr CreateRequestWithoutBody() {
    network::mojom::ObliviousHttpRequestPtr request =
        network::mojom::ObliviousHttpRequest::New();
    request->relay_url = GURL(kRelayURL);
    request->key_config = CreateTestKeyConfig();
    request->resource_url = GURL(kResourceURL);
    request->method = net::HttpRequestHeaders::kGetMethod;
    request->traffic_annotation =
        net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
    return request;
  }

  void FastForward(base::TimeDelta delta) {
    task_environment_.FastForwardBy(delta);
    task_environment_.RunUntilIdle();
  }

  void VerifyNetLog(const net::RecordingNetLogObserver& net_log_observer,
                    bool expected_has_response_data_and_headers,
                    int expected_net_error,
                    absl::optional<int> expected_outer_response_error_code,
                    absl::optional<int> expected_inner_response_code) {
    auto entries = net_log_observer.GetEntries();
    size_t pos = net::ExpectLogContainsSomewhereAfter(
        entries, /*start_offset=*/0,
        net::NetLogEventType::OBLIVIOUS_HTTP_REQUEST,
        net::NetLogEventPhase::BEGIN);
    pos = net::ExpectLogContainsSomewhere(
        entries, /*start_offset=*/pos + 1,
        net::NetLogEventType::OBLIVIOUS_HTTP_REQUEST_DATA,
        net::NetLogEventPhase::NONE);
    EXPECT_TRUE(
        net::GetOptionalIntegerValueFromParams(entries[pos], "byte_count"));
    if (expected_has_response_data_and_headers) {
      pos = net::ExpectLogContainsSomewhereAfter(
          entries, /*start_offset=*/pos + 1,
          net::NetLogEventType::OBLIVIOUS_HTTP_RESPONSE_DATA,
          net::NetLogEventPhase::NONE);
      EXPECT_TRUE(
          net::GetOptionalIntegerValueFromParams(entries[pos], "byte_count"));
      pos = net::ExpectLogContainsSomewhereAfter(
          entries, /*start_offset=*/pos + 1,
          net::NetLogEventType::OBLIVIOUS_HTTP_RESPONSE_HEADERS,
          net::NetLogEventPhase::NONE);
      EXPECT_TRUE(entries[pos].params.FindList("headers"));
    }
    pos = net::ExpectLogContainsSomewhereAfter(
        entries, /*start_offset=*/pos + 1,
        net::NetLogEventType::OBLIVIOUS_HTTP_REQUEST,
        net::NetLogEventPhase::END);
    EXPECT_EQ(expected_net_error,
              net::GetOptionalNetErrorCodeFromParams(entries[pos]));
    EXPECT_EQ(expected_outer_response_error_code,
              net::GetOptionalIntegerValueFromParams(
                  entries[pos], "outer_response_error_code"));
    EXPECT_EQ(expected_inner_response_code,
              net::GetOptionalIntegerValueFromParams(entries[pos],
                                                     "inner_response_code"));
  }

 private:
  absl::optional<quiche::ObliviousHttpGateway> ohttp_gateway_;
  base::test::TaskEnvironment task_environment_;
  std::unique_ptr<network::NetworkService> network_service_;
  mojo::Remote<network::mojom::NetworkContext> network_context_remote_;
  std::unique_ptr<network::NetworkContext> network_context_;
  network::TestURLLoaderFactory loader_factory_;
  mojo::Receiver<network::mojom::URLLoaderFactory> loader_factory_receiver_;
};

TEST_F(TestObliviousHttpRequestHandler, TestDisconnect) {
  std::unique_ptr<network::ObliviousHttpRequestHandler> handler =
      CreateHandler();
  {
    TestOhttpClient client;
    client.SetExpectedInnerResponse(
        /*expected_inner_response_code=*/net::HTTP_OK,
        /*expected_body=*/"",
        /*expected_headers=*/{});
    handler->StartRequest(CreateRequest(), client.CreatePendingRemote());
  }

  {
    TestOhttpClient client;
    client.SetExpectedInnerResponse(
        /*expected_inner_response_code=*/net::HTTP_OK,
        /*expected_body=*/"",
        /*expected_headers=*/{});
    handler->StartRequest(CreateRequest(), client.CreatePendingRemote());
    RespondToPendingRequest("", {});
    client.WaitForCall();
  }
  // Empty body
  {
    TestOhttpClient client;
    client.SetExpectedInnerResponse(
        /*expected_inner_response_code=*/net::HTTP_OK,
        /*expected_body=*/"",
        /*expected_headers=*/{});
    handler->StartRequest(CreateRequestWithoutBody(),
                          client.CreatePendingRemote());
    RespondToPendingRequest("", {});
    client.WaitForCall();
  }
}

TEST_F(TestObliviousHttpRequestHandler, TestInvalidArguments) {
  std::unique_ptr<network::ObliviousHttpRequestHandler> handler =
      CreateHandler();
  {
    mojo::FakeMessageDispatchContext context;
    mojo::test::BadMessageObserver obs;
    TestOhttpClient client;
    client.SetExpectedNetError(net::ERR_INVALID_URL);
    network::mojom::ObliviousHttpRequestPtr request =
        network::mojom::ObliviousHttpRequest::New();

    handler->StartRequest(std::move(request), client.CreatePendingRemote());
    EXPECT_EQ("Invalid OHTTP Relay URL", obs.WaitForBadMessage());
  }
  {
    mojo::FakeMessageDispatchContext context;
    mojo::test::BadMessageObserver obs;
    TestOhttpClient client;
    client.SetExpectedNetError(net::ERR_INVALID_URL);
    network::mojom::ObliviousHttpRequestPtr request = CreateRequest();
    request->relay_url = GURL();

    handler->StartRequest(std::move(request), client.CreatePendingRemote());
    EXPECT_EQ("Invalid OHTTP Relay URL", obs.WaitForBadMessage());
  }
  {
    mojo::FakeMessageDispatchContext context;
    mojo::test::BadMessageObserver obs;
    TestOhttpClient client;
    client.SetExpectedNetError(net::ERR_INVALID_URL);
    network::mojom::ObliviousHttpRequestPtr request = CreateRequest();
    request->resource_url = GURL();

    handler->StartRequest(std::move(request), client.CreatePendingRemote());
    EXPECT_EQ("Invalid OHTTP Resource URL", obs.WaitForBadMessage());
  }
  {
    mojo::FakeMessageDispatchContext context;
    mojo::test::BadMessageObserver obs;
    TestOhttpClient client;
    client.SetExpectedNetError(net::ERR_INVALID_ARGUMENT);
    network::mojom::ObliviousHttpRequestPtr request = CreateRequest();
    request->traffic_annotation = net::MutableNetworkTrafficAnnotationTag();

    handler->StartRequest(std::move(request), client.CreatePendingRemote());
    EXPECT_EQ("Invalid OHTTP Traffic Annotation", obs.WaitForBadMessage());
  }
  {
    mojo::FakeMessageDispatchContext context;
    mojo::test::BadMessageObserver obs;
    TestOhttpClient client;
    client.SetExpectedNetError(net::ERR_INVALID_ARGUMENT);
    network::mojom::ObliviousHttpRequestPtr request = CreateRequest();
    request->method = std::string(17, 'A');

    handler->StartRequest(std::move(request), client.CreatePendingRemote());
    EXPECT_EQ("Invalid OHTTP Method", obs.WaitForBadMessage());
  }
  {
    mojo::FakeMessageDispatchContext context;
    mojo::test::BadMessageObserver obs;
    TestOhttpClient client;
    client.SetExpectedNetError(net::ERR_INVALID_ARGUMENT);
    network::mojom::ObliviousHttpRequestPtr request = CreateRequest();
    request->request_body->content = std::string(5 * 1024 * 1024 + 1, ' ');

    handler->StartRequest(std::move(request), client.CreatePendingRemote());
    EXPECT_EQ("Request body too large", obs.WaitForBadMessage());
  }
  {
    mojo::FakeMessageDispatchContext context;
    mojo::test::BadMessageObserver obs;
    TestOhttpClient client;
    client.SetExpectedNetError(net::ERR_INVALID_ARGUMENT);
    network::mojom::ObliviousHttpRequestPtr request = CreateRequest();
    request->request_body->content_type = std::string(257, ' ');

    handler->StartRequest(std::move(request), client.CreatePendingRemote());
    EXPECT_EQ("Content-Type too large", obs.WaitForBadMessage());
  }
}

TEST_F(TestObliviousHttpRequestHandler, TestRequestFormat) {
  std::unique_ptr<network::ObliviousHttpRequestHandler> handler =
      CreateHandler();
  {
    net::RecordingNetLogObserver net_log_observer;
    TestOhttpClient client;
    client.SetExpectedInnerResponse(
        /*expected_inner_response_code=*/net::HTTP_OK,
        /*expected_body=*/"response body",
        /*expected_headers=*/
        {{"cache-control", "s-maxage=3600"}, {"content-type", "text/html"}});

    handler->StartRequest(CreateRequest(), client.CreatePendingRemote());
    const network::ResourceRequest* pending_request;
    ASSERT_TRUE(loader_factory()->IsPending(kRelayURL, &pending_request));
    EXPECT_EQ(net::HttpRequestHeaders::kPostMethod, pending_request->method);
    EXPECT_EQ(network::mojom::RedirectMode::kError,
              pending_request->redirect_mode);
    EXPECT_EQ(network::mojom::CredentialsMode::kOmit,
              pending_request->credentials_mode);
    EXPECT_TRUE(pending_request->site_for_cookies.IsNull());
    EXPECT_FALSE(pending_request->trust_token_params);
    EXPECT_THAT(
        pending_request->headers.GetHeaderVector(),
        UnorderedHeadersAre({
            {net::HttpRequestHeaders::kContentType, "message/ohttp-req"},
        }));
    ASSERT_TRUE(pending_request->request_body);
    ASSERT_EQ(1u, pending_request->request_body->elements()->size());

    std::string body = std::string(pending_request->request_body->elements()
                                       ->at(0)
                                       .As<network::DataElementBytes>()
                                       .AsStringPiece());
    std::string plain_text_body = DecryptRequest(body);

    auto maybe_request = quiche::BinaryHttpRequest::Create(plain_text_body);
    ASSERT_TRUE(maybe_request.ok()) << maybe_request.status();
    quiche::BinaryHttpRequest request = std::move(maybe_request).value();
    EXPECT_EQ(request.control_data().method, "GET");
    EXPECT_EQ(request.control_data().scheme, "https");
    EXPECT_EQ(request.control_data().authority, "");  // Stored in headers.
    EXPECT_EQ(request.control_data().path, "/path");
    EXPECT_THAT(request.GetHeaderFields(),
                testing::IsSupersetOf<quiche::BinaryHttpRequest::Field>({
                    {"host", "resource.test:37"},
                    {"content-length", "9"},
                    {"content-type", "application/testdata"},
                }));
    EXPECT_EQ(request.body(), "test data");
    RespondToPendingRequest(
        "response body",
        {{"cache-control", "s-maxage=3600"}, {"content-type", "text/html"}});
    client.WaitForCall();

    VerifyNetLog(net_log_observer,
                 /*expected_has_response_data_and_headers=*/true,
                 /*expected_net_error=*/net::OK,
                 /*expected_outer_response_error_code=*/absl::nullopt,
                 /*expected_inner_response_code=*/net::HTTP_OK);
  }
}

TEST_F(TestObliviousHttpRequestHandler, TestTimeout) {
  std::unique_ptr<network::ObliviousHttpRequestHandler> handler =
      CreateHandler();
  // Default timeout.
  {
    net::RecordingNetLogObserver net_log_observer;
    TestOhttpClient client;
    client.SetExpectedNetError(net::ERR_TIMED_OUT);

    handler->StartRequest(CreateRequest(), client.CreatePendingRemote());

    FastForward(base::Seconds(59));
    EXPECT_FALSE(client.IsOnCompletedCalled());
    FastForward(base::Seconds(1));
    EXPECT_TRUE(client.IsOnCompletedCalled());

    VerifyNetLog(net_log_observer,
                 /*expected_has_response_data_and_headers=*/false,
                 /*expected_net_error=*/net::ERR_TIMED_OUT,
                 /*expected_outer_response_error_code=*/absl::nullopt,
                 /*expected_inner_response_code=*/absl::nullopt);
  }
  // Configured timeout.
  {
    TestOhttpClient client;
    client.SetExpectedNetError(net::ERR_TIMED_OUT);

    network::mojom::ObliviousHttpRequestPtr request = CreateRequest();
    request->timeout_duration = base::Seconds(3);

    handler->StartRequest(std::move(request), client.CreatePendingRemote());

    FastForward(base::Seconds(2));
    EXPECT_FALSE(client.IsOnCompletedCalled());
    FastForward(base::Seconds(1));
    EXPECT_TRUE(client.IsOnCompletedCalled());
  }
}

TEST_F(TestObliviousHttpRequestHandler, HandlesOuterHttpError) {
  std::unique_ptr<network::ObliviousHttpRequestHandler> handler =
      CreateHandler();
  {
    net::RecordingNetLogObserver net_log_observer;
    loader_factory()->AddResponse(kRelayURL, "", net::HTTP_NOT_FOUND);
    TestOhttpClient client;
    client.SetExpectedOuterResponseErrorCode(net::HTTP_NOT_FOUND);

    handler->StartRequest(CreateRequest(), client.CreatePendingRemote());
    client.WaitForCall();

    VerifyNetLog(net_log_observer,
                 /*expected_has_response_data_and_headers=*/false,
                 /*expected_net_error=*/net::ERR_HTTP_RESPONSE_CODE_FAILURE,
                 /*expected_outer_response_error_code=*/net::HTTP_NOT_FOUND,
                 /*expected_inner_response_code=*/absl::nullopt);
  }
  {
    net::RecordingNetLogObserver net_log_observer;
    loader_factory()->AddResponse(
        GURL(kRelayURL), network::CreateURLResponseHead(net::HTTP_OK), "",
        network::URLLoaderCompletionStatus(net::ERR_CONNECTION_RESET),
        network::TestURLLoaderFactory::Redirects(),
        network::TestURLLoaderFactory::ResponseProduceFlags::
            kSendHeadersOnNetworkError);
    TestOhttpClient client;
    // The outer HTTP error code should be set only when the net error is
    // ERR_HTTP_RESPONSE_CODE_FAILURE. Otherwise, log the net error instead.
    client.SetExpectedNetError(net::ERR_CONNECTION_RESET);

    handler->StartRequest(CreateRequest(), client.CreatePendingRemote());
    client.WaitForCall();

    VerifyNetLog(net_log_observer,
                 /*expected_has_response_data_and_headers=*/false,
                 /*expected_net_error=*/net::ERR_CONNECTION_RESET,
                 /*expected_outer_response_error_code=*/absl::nullopt,
                 /*expected_inner_response_code=*/absl::nullopt);
  }
}

TEST_F(TestObliviousHttpRequestHandler, HandlesInnerHttpError) {
  std::unique_ptr<network::ObliviousHttpRequestHandler> handler =
      CreateHandler();
  {
    net::RecordingNetLogObserver net_log_observer;
    TestOhttpClient client;
    client.SetExpectedInnerResponse(
        /*expected_inner_response_code=*/net::HTTP_NOT_FOUND,
        /*expected_body=*/"",
        /*expected_headers=*/
        {{"cache-control", "s-maxage=60"}});

    handler->StartRequest(CreateRequest(), client.CreatePendingRemote());
    RespondToPendingRequest("", {{"cache-control", "s-maxage=60"}},
                            GURL(kRelayURL), net::HTTP_NOT_FOUND);
    client.WaitForCall();

    VerifyNetLog(net_log_observer,
                 /*expected_has_response_data_and_headers=*/true,
                 /*expected_net_error=*/net::OK,
                 /*expected_outer_response_error_code=*/absl::nullopt,
                 /*expected_inner_response_code=*/net::HTTP_NOT_FOUND);
  }
  {
    net::RecordingNetLogObserver net_log_observer;
    TestOhttpClient client;
    client.SetExpectedNetError(net::ERR_INVALID_RESPONSE);

    handler->StartRequest(CreateRequest(), client.CreatePendingRemote());
    ASSERT_TRUE(loader_factory()->IsPending(kRelayURL));
    EXPECT_TRUE(loader_factory()->SimulateResponseForPendingRequest(
        /*url=*/GURL(kRelayURL),
        /*completion_status=*/network::URLLoaderCompletionStatus(),
        /*response_head=*/network::CreateURLResponseHead(net::HTTP_OK),
        /*content=*/"malformed inner response"));
    client.WaitForCall();

    VerifyNetLog(net_log_observer,
                 /*expected_has_response_data_and_headers=*/false,
                 /*expected_net_error=*/net::ERR_INVALID_RESPONSE,
                 /*expected_outer_response_error_code=*/absl::nullopt,
                 /*expected_inner_response_code=*/absl::nullopt);
  }
}

TEST_F(TestObliviousHttpRequestHandler, HandlesMultipleRequests) {
  std::unique_ptr<network::ObliviousHttpRequestHandler> handler =
      CreateHandler();
  {
    TestOhttpClient client_a;
    client_a.SetExpectedInnerResponse(
        /*expected_inner_response_code=*/net::HTTP_OK,
        /*expected_body=*/"Response a",
        /*expected_headers=*/{{"cache-control", "s-maxage=60"}});
    network::mojom::ObliviousHttpRequestPtr request_a = CreateRequest();
    TestOhttpClient client_b;
    client_b.SetExpectedInnerResponse(
        /*expected_inner_response_code=*/net::HTTP_OK,
        /*expected_body=*/"Response b",
        /*expected_headers=*/{{"cache-control", "s-maxage=600"}});
    network::mojom::ObliviousHttpRequestPtr request_b = CreateRequest();
    request_b->relay_url = GURL("https://another.relay.test");

    handler->StartRequest(std::move(request_a), client_a.CreatePendingRemote());
    handler->StartRequest(std::move(request_b), client_b.CreatePendingRemote());

    RespondToPendingRequest("Response b", {{"cache-control", "s-maxage=600"}},
                            GURL("https://another.relay.test"));
    client_b.WaitForCall();

    RespondToPendingRequest("Response a", {{"cache-control", "s-maxage=60"}});
    client_a.WaitForCall();
  }
}

TEST_F(TestObliviousHttpRequestHandler, PadsUpToNextPowerOfTwo) {
  std::unique_ptr<network::ObliviousHttpRequestHandler> handler =
      CreateHandler();
  {
    TestOhttpClient client;
    client.SetExpectedInnerResponse(
        /*expected_inner_response_code=*/net::HTTP_OK,
        /*expected_body=*/"response body",
        /*expected_headers=*/{});
    network::mojom::ObliviousHttpRequestPtr request = CreateRequest();
    request->padding_params =
        network::mojom::ObliviousHttpPaddingParameters::New(
            /*add_exponential_pad=*/false, /*exponential_mean=*/0,
            /*pad_to_next_power_of_two=*/true);

    handler->StartRequest(std::move(request), client.CreatePendingRemote());
    const network::ResourceRequest* pending_request;
    ASSERT_TRUE(loader_factory()->IsPending(kRelayURL, &pending_request));
    ASSERT_TRUE(pending_request->request_body);
    ASSERT_EQ(1u, pending_request->request_body->elements()->size());

    std::string body = std::string(pending_request->request_body->elements()
                                       ->at(0)
                                       .As<network::DataElementBytes>()
                                       .AsStringPiece());
    std::string plain_text_body = DecryptRequest(body);

    EXPECT_EQ(256u, plain_text_body.size());
    EXPECT_EQ(std::string(56, '\0'), plain_text_body.substr(200));
  }
}

TEST_F(TestObliviousHttpRequestHandler, DoesntPadsIfAlreadyPowerOfTwo) {
  std::unique_ptr<network::ObliviousHttpRequestHandler> handler =
      CreateHandler();
  {
    TestOhttpClient client;
    client.SetExpectedInnerResponse(
        /*expected_inner_response_code=*/net::HTTP_OK,
        /*expected_body=*/"response body",
        /*expected_headers=*/{});
    network::mojom::ObliviousHttpRequestPtr request = CreateRequest();
    request->padding_params =
        network::mojom::ObliviousHttpPaddingParameters::New(
            /*add_exponential_pad=*/false, /*exponential_mean=*/0,
            /*pad_to_next_power_of_two=*/false);
    request->request_body = network::mojom::ObliviousHttpRequestBody::New(
        /*content=*/std::string(380, ' '),
        /*content_type=*/"application/testdata");

    handler->StartRequest(std::move(request), client.CreatePendingRemote());
    const network::ResourceRequest* pending_request;
    ASSERT_TRUE(loader_factory()->IsPending(kRelayURL, &pending_request));
    ASSERT_TRUE(pending_request->request_body);
    ASSERT_EQ(1u, pending_request->request_body->elements()->size());

    std::string body = std::string(pending_request->request_body->elements()
                                       ->at(0)
                                       .As<network::DataElementBytes>()
                                       .AsStringPiece());
    std::string plain_text_body = DecryptRequest(body);

    EXPECT_EQ(512u, plain_text_body.size());
    EXPECT_EQ(std::string(380, ' '), plain_text_body.substr(512 - 380));
  }
}

TEST_F(TestObliviousHttpRequestHandler, PadsExponentiallyRandomly) {
  std::unique_ptr<network::ObliviousHttpRequestHandler> handler =
      CreateHandler();
  const size_t kNumRuns = 100;
  double accum_size = 0;
  double accum_size_squared = 0;
  for (size_t i = 0; i < kNumRuns; i++) {
    {
      TestOhttpClient client;
      client.SetExpectedInnerResponse(
          /*expected_inner_response_code=*/net::HTTP_OK,
          /*expected_body=*/"response body",
          /*expected_headers=*/{});
      network::mojom::ObliviousHttpRequestPtr request = CreateRequest();
      request->padding_params =
          network::mojom::ObliviousHttpPaddingParameters::New(
              /*add_exponential_pad=*/true, /*exponential_mean=*/10,
              /*pad_to_next_power_of_two=*/false);

      handler->StartRequest(std::move(request), client.CreatePendingRemote());
      const network::ResourceRequest* pending_request;
      ASSERT_TRUE(loader_factory()->IsPending(kRelayURL, &pending_request));
      ASSERT_TRUE(pending_request->request_body);
      ASSERT_EQ(1u, pending_request->request_body->elements()->size());

      std::string body = std::string(pending_request->request_body->elements()
                                         ->at(0)
                                         .As<network::DataElementBytes>()
                                         .AsStringPiece());
      accum_size += body.size();
      accum_size_squared += body.size() * body.size();
    }
  }

  double mean = accum_size / kNumRuns;
  double variance = accum_size_squared / kNumRuns - mean * mean;
  // True variance should be 100, but we're not running enough iterations for
  // the estimate to converge. This at least excludes the case where the padding
  // is constant.
  EXPECT_LT(200l, mean);
  EXPECT_GT(210l, mean);
  EXPECT_LT(16l, variance);
  EXPECT_GT(256l, variance);
}

TEST_F(TestObliviousHttpRequestHandler,
       PadsBothExponentiallyRandomlyAndPowerOfTwo) {
  std::unique_ptr<network::ObliviousHttpRequestHandler> handler =
      CreateHandler();
  base::flat_set<size_t> sizes_seen;
  while (sizes_seen.size() < 2) {
    TestOhttpClient client;
    client.SetExpectedInnerResponse(
        /*expected_inner_response_code=*/net::HTTP_OK,
        /*expected_body=*/"response body",
        /*expected_headers=*/{});
    network::mojom::ObliviousHttpRequestPtr request = CreateRequest();
    request->padding_params =
        network::mojom::ObliviousHttpPaddingParameters::New(
            /*add_exponential_pad=*/true, /*exponential_mean=*/10,
            /*pad_to_next_power_of_two=*/true);
    // Set message size to 246 bytes plus an average of 10 bytes padding.
    request->request_body = network::mojom::ObliviousHttpRequestBody::New(
        /*content=*/std::string(114, ' '),
        /*content_type=*/"application/testdata");

    handler->StartRequest(std::move(request), client.CreatePendingRemote());
    const network::ResourceRequest* pending_request;
    ASSERT_TRUE(loader_factory()->IsPending(kRelayURL, &pending_request));
    ASSERT_TRUE(pending_request->request_body);
    ASSERT_EQ(1u, pending_request->request_body->elements()->size());

    std::string body = std::string(pending_request->request_body->elements()
                                       ->at(0)
                                       .As<network::DataElementBytes>()
                                       .AsStringPiece());

    std::string plain_text_body = DecryptRequest(body);
    size_t body_size = plain_text_body.size();
    sizes_seen.insert(body_size);

    // body_size must be a power of 2.
    // That means all of the bits except the high bit are 0.
    size_t cur = body_size;
    bool has_bad_bits = false;
    while (cur > 1) {
      has_bad_bits |= (cur & 0x1) != 0u;
      cur >>= 1;
    }
    EXPECT_FALSE(has_bad_bits) << "Got non-power of 2 body size " << body_size;
  }
}