chromium/components/ip_protection/android/android_auth_client_lib/cpp/ip_protection_auth_test_natives.cc

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

#include <memory>
#include <ostream>

#include "base/android/jni_android.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/run_loop.h"
#include "base/task/bind_post_task.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/test/test_timeouts.h"
#include "components/ip_protection/android/android_auth_client_lib/cpp/ip_protection_auth_client.h"
#include "components/ip_protection/android/android_auth_client_lib/cpp/ip_protection_auth_client_interface.h"
#include "components/ip_protection/android/android_auth_client_lib/javatests/jni_headers/IpProtectionAuthTestNatives_jni.h"
#include "net/third_party/quiche/src/quiche/blind_sign_auth/proto/auth_and_sign.pb.h"
#include "net/third_party/quiche/src/quiche/blind_sign_auth/proto/get_initial_data.pb.h"

namespace {

const char kMockPackageName[] = "org.chromium.components.ip_protection_auth";

const char kMockClassNameForDefault[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "IpProtectionAuthServiceMock";
const char kMockClassNameForNonexistant[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "IntentionallyNonexistantClass";
const char kMockClassNameForNullBinding[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "NullBindingService";
const char kMockClassNameForDisabled[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "NullBindingService$DisabledService";
const char kMockClassNameForRestricted[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "NullBindingService$RestrictedService";
const char kMockClassNameForTransientError[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "ConstantResponseService$TransientError";
const char kMockClassNameForPersistentError[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "ConstantResponseService$PersistentError";
const char kMockClassNameForIllegalErrorCode[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "ConstantResponseService$IllegalErrorCode";
const char kMockClassNameForNullResponse[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "ConstantResponseService$Null";
const char kMockClassNameForUnparsableResponse[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "ConstantResponseService$Unparsable";
const char kMockClassNameForSynchronousError[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "ConstantResponseService$SynchronousError";
const char kMockClassNameForNeverResolve[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "ConstantResponseService$NeverResolve";
const char kMockClassNameForCrashOnRequestSyncWithoutResponse[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "CrashingService$CrashOnRequestSyncWithoutResponse";
const char kMockClassNameForCrashOnRequestAsyncWithoutResponse[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "CrashingService$CrashOnRequestAsyncWithoutResponse";
const char kMockClassNameForCrashOnRequestSyncWithResponse[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "CrashingService$CrashOnRequestSyncWithResponse";
const char kMockClassNameForCrashAfterTwoRequestsSyncWithoutResponses[] =
    "org.chromium.components.ip_protection_auth.mock_service."
    "CrashingService$CrashAfterTwoRequestsSyncWithoutResponses";

// Perform an IpProtectionAuthClient::CreateConnectedInstanceForTesting call in
// a RunLoop, quitting the RunLoop when either a client is ready or an error
// occurs. Returns the resulting base::expected value.
//
// A task environment must already be set up.
//
// Beware that this only wraps the _creation_ of an IpProtectionAuthClient in a
// RunLoop and not its destruction! If any tasks need to be run as part of
// client destruction, such as handling unresolved getInitialData/authAndSign
// callbacks, this is the responsibility of the caller (or client owner).
base::expected<
    std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>,
    std::string>
CreateClientBlocking(const std::string_view mockClassName) {
  base::expected<
      std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>,
      std::string>
      result;
  base::RunLoop run_loop;
  ip_protection::android::IpProtectionAuthClient::
      CreateConnectedInstanceForTesting(
          kMockPackageName, mockClassName,
          base::BindPostTask(
              base::SequencedTaskRunner::GetCurrentDefault(),
              base::BindLambdaForTesting(
                  [&run_loop, &result](
                      base::expected<
                          std::unique_ptr<ip_protection::android::
                                              IpProtectionAuthClientInterface>,
                          std::string> maybe_client) {
                    result = std::move(maybe_client);
                    run_loop.Quit();
                  })));
  run_loop.Run();
  return result;
}

// Same as CreateClientBlocking, but asserts that a client is acquired and
// returns the unwrapped client from the base::expected.
//
// A task environment must already be set up.
std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
CreateAndExpectClientBlocking(const std::string_view mockClassName) {
  base::expected<
      std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>,
      std::string>
      client = CreateClientBlocking(mockClassName);
  CHECK(client.has_value()) << client.error();
  return std::move(*client);
}

// Perform an getInitialData request in a RunLoop, quitting the RunLoop when a
// response is received and returning the base::expected result.
//
// A task environment must already be set up.
base::expected<privacy::ppn::GetInitialDataResponse,
               ip_protection::android::AuthRequestError>
GetInitialDataBlocking(
    ip_protection::android::IpProtectionAuthClientInterface& client,
    privacy::ppn::GetInitialDataRequest request) {
  base::expected<privacy::ppn::GetInitialDataResponse,
                 ip_protection::android::AuthRequestError>
      result;
  base::RunLoop run_loop;
  client.GetInitialData(
      std::move(request),
      base::BindPostTask(
          base::SequencedTaskRunner::GetCurrentDefault(),
          base::BindLambdaForTesting(
              [&run_loop,
               &result](base::expected<privacy::ppn::GetInitialDataResponse,
                                       ip_protection::android::AuthRequestError>
                            response) {
                result = std::move(response);
                run_loop.Quit();
              })));
  run_loop.Run();
  return result;
}

// Perform an authAndSign request in a RunLoop, quitting the RunLoop when a
// response is received and returning the base::expected result.
base::expected<privacy::ppn::AuthAndSignResponse,
               ip_protection::android::AuthRequestError>
AuthAndSignBlocking(
    ip_protection::android::IpProtectionAuthClientInterface& client,
    privacy::ppn::AuthAndSignRequest request) {
  base::expected<privacy::ppn::AuthAndSignResponse,
                 ip_protection::android::AuthRequestError>
      result;
  base::RunLoop run_loop;
  client.AuthAndSign(
      std::move(request),
      base::BindPostTask(
          base::SequencedTaskRunner::GetCurrentDefault(),
          base::BindLambdaForTesting(
              [&run_loop,
               &result](base::expected<privacy::ppn::AuthAndSignResponse,
                                       ip_protection::android::AuthRequestError>
                            response) {
                result = std::move(response);
                run_loop.Quit();
              })));
  run_loop.Run();
  return result;
}

}  // namespace

static void JNI_IpProtectionAuthTestNatives_Initialize(JNIEnv* env) {
  // TaskEnvironment requires TestTimeouts::Initialize() to be called in order
  // to run posted tasks. It must be run exactly once, so this function is
  // called in the static initializer of IpProtectionAuthTestNatives.java.
  TestTimeouts::Initialize();
}

// Tests a normal case where the service is available.
static void JNI_IpProtectionAuthTestNatives_CreateConnectedInstanceForTesting(
    JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;

  CreateAndExpectClientBlocking(kMockClassNameForDefault);
}

// Tests a normal case where the service isn't installed.
static void JNI_IpProtectionAuthTestNatives_TestNonexistantService(
    JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  base::expected<
      std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>,
      std::string>
      client = CreateClientBlocking(kMockClassNameForNonexistant);

  const std::string expected_error =
      "Unable to locate the IP Protection authentication provider package "
      "(android.net.http.IpProtectionAuthService action). This is expected if "
      "the host system is not set up to provide IP Protection services.";

  CHECK(!client.has_value());
  CHECK_EQ(client.error(), expected_error);
}

// Tests a normal case where the service is installed but rejects bindings, for
// example, due to a feature flag.
static void JNI_IpProtectionAuthTestNatives_TestNullBindingService(
    JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  base::expected<
      std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>,
      std::string>
      client = CreateClientBlocking(kMockClassNameForNullBinding);

  const std::string expected_error = "Service returned null from onBind()";

  CHECK(!client.has_value());
  CHECK_EQ(client.error(), expected_error);
}

// Tests an abnormal case where bindService would return false.
static void JNI_IpProtectionAuthTestNatives_TestDisabledService(JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  base::expected<
      std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>,
      std::string>
      client = CreateClientBlocking(kMockClassNameForDisabled);

  const std::string expected_error = "bindService() failed: returned false";

  CHECK(!client.has_value());
  CHECK_EQ(client.error(), expected_error);
}

// Tests an abnormal case where bindService fails with a SecurityException. Note
// that the only permission which would apply in production is the INTERNET
// permission. IpProtectionAuthClient code shouldn't be reached if the app
// doesn't have INTERNET permission. This test case instead tests for resiliance
// against an unusual service implementation with unexpected restrictions.
static void JNI_IpProtectionAuthTestNatives_TestRestrictedService(JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  base::expected<
      std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>,
      std::string>
      client = CreateClientBlocking(kMockClassNameForRestricted);

  const std::string expected_error_substr =
      "Failed to bind service: java.lang.SecurityException: ";

  CHECK(!client.has_value());
  CHECK(base::Contains(client.error(), expected_error_substr));
}

static void JNI_IpProtectionAuthTestNatives_TestGetInitialData(JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
      client = CreateAndExpectClientBlocking(kMockClassNameForDefault);
  // TODO(b/344853279): This request is missing required fields. It should be
  // updated.
  privacy::ppn::GetInitialDataRequest get_initial_data_request;
  get_initial_data_request.set_service_type("webviewipblinding");

  base::expected<privacy::ppn::GetInitialDataResponse,
                 ip_protection::android::AuthRequestError>
      get_initial_data_response =
          GetInitialDataBlocking(*client, get_initial_data_request);

  CHECK(get_initial_data_response.has_value());
  CHECK(get_initial_data_response->privacy_pass_data().token_key_id() == "test")
      << "expected \"test\" for token_key_id, got: "
      << get_initial_data_response->privacy_pass_data().token_key_id();
}

static void JNI_IpProtectionAuthTestNatives_TestAuthAndSign(JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
      client = CreateAndExpectClientBlocking(kMockClassNameForDefault);
  // TODO(b/344853279): This request has deprecated OAuth field and is missing
  // required fields. It should be updated.
  privacy::ppn::AuthAndSignRequest auth_and_sign_request;
  auth_and_sign_request.set_oauth_token("test");

  base::expected<privacy::ppn::AuthAndSignResponse,
                 ip_protection::android::AuthRequestError>
      auth_and_sign_response =
          AuthAndSignBlocking(*client, auth_and_sign_request);

  CHECK(auth_and_sign_response.has_value());
  CHECK(auth_and_sign_response->apn_type() == "test")
      << "expected \"test\" for apn_type, got: "
      << auth_and_sign_response->apn_type();
}

static void JNI_IpProtectionAuthTestNatives_TestTransientError(JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
      client = CreateAndExpectClientBlocking(kMockClassNameForTransientError);

  base::expected<privacy::ppn::GetInitialDataResponse,
                 ip_protection::android::AuthRequestError>
      get_initial_data_response = GetInitialDataBlocking(
          *client, privacy::ppn::GetInitialDataRequest());
  base::expected<privacy::ppn::AuthAndSignResponse,
                 ip_protection::android::AuthRequestError>
      auth_and_sign_response =
          AuthAndSignBlocking(*client, privacy::ppn::AuthAndSignRequest());

  CHECK(!get_initial_data_response.has_value());
  CHECK(get_initial_data_response.error() ==
        ip_protection::android::AuthRequestError::kTransient);
  CHECK(!auth_and_sign_response.has_value());
  CHECK(auth_and_sign_response.error() ==
        ip_protection::android::AuthRequestError::kTransient);
}

static void JNI_IpProtectionAuthTestNatives_TestPersistentError(JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
      client = CreateAndExpectClientBlocking(kMockClassNameForPersistentError);

  base::expected<privacy::ppn::GetInitialDataResponse,
                 ip_protection::android::AuthRequestError>
      get_initial_data_response = GetInitialDataBlocking(
          *client, privacy::ppn::GetInitialDataRequest());
  base::expected<privacy::ppn::AuthAndSignResponse,
                 ip_protection::android::AuthRequestError>
      auth_and_sign_response =
          AuthAndSignBlocking(*client, privacy::ppn::AuthAndSignRequest());

  CHECK(!get_initial_data_response.has_value());
  CHECK(get_initial_data_response.error() ==
        ip_protection::android::AuthRequestError::kPersistent);
  CHECK(!auth_and_sign_response.has_value());
  CHECK(auth_and_sign_response.error() ==
        ip_protection::android::AuthRequestError::kPersistent);
}

static void JNI_IpProtectionAuthTestNatives_TestIllegalErrorCode(JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
      client = CreateAndExpectClientBlocking(kMockClassNameForIllegalErrorCode);

  base::expected<privacy::ppn::GetInitialDataResponse,
                 ip_protection::android::AuthRequestError>
      get_initial_data_response = GetInitialDataBlocking(
          *client, privacy::ppn::GetInitialDataRequest());
  base::expected<privacy::ppn::AuthAndSignResponse,
                 ip_protection::android::AuthRequestError>
      auth_and_sign_response =
          AuthAndSignBlocking(*client, privacy::ppn::AuthAndSignRequest());

  CHECK(!get_initial_data_response.has_value());
  CHECK(get_initial_data_response.error() ==
        ip_protection::android::AuthRequestError::kOther);
  CHECK(!auth_and_sign_response.has_value());
  CHECK(auth_and_sign_response.error() ==
        ip_protection::android::AuthRequestError::kOther);
}

static void JNI_IpProtectionAuthTestNatives_TestNullResponse(JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
      client = CreateAndExpectClientBlocking(kMockClassNameForNullResponse);

  base::expected<privacy::ppn::GetInitialDataResponse,
                 ip_protection::android::AuthRequestError>
      get_initial_data_response = GetInitialDataBlocking(
          *client, privacy::ppn::GetInitialDataRequest());
  base::expected<privacy::ppn::AuthAndSignResponse,
                 ip_protection::android::AuthRequestError>
      auth_and_sign_response =
          AuthAndSignBlocking(*client, privacy::ppn::AuthAndSignRequest());

  CHECK(!get_initial_data_response.has_value());
  CHECK(get_initial_data_response.error() ==
        ip_protection::android::AuthRequestError::kOther);
  CHECK(!auth_and_sign_response.has_value());
  CHECK(auth_and_sign_response.error() ==
        ip_protection::android::AuthRequestError::kOther);
}

static void JNI_IpProtectionAuthTestNatives_TestUnparsableResponse(
    JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
      client =
          CreateAndExpectClientBlocking(kMockClassNameForUnparsableResponse);

  base::expected<privacy::ppn::GetInitialDataResponse,
                 ip_protection::android::AuthRequestError>
      get_initial_data_response = GetInitialDataBlocking(
          *client, privacy::ppn::GetInitialDataRequest());
  base::expected<privacy::ppn::AuthAndSignResponse,
                 ip_protection::android::AuthRequestError>
      auth_and_sign_response =
          AuthAndSignBlocking(*client, privacy::ppn::AuthAndSignRequest());

  CHECK(!get_initial_data_response.has_value());
  CHECK(get_initial_data_response.error() ==
        ip_protection::android::AuthRequestError::kOther);
  CHECK(!auth_and_sign_response.has_value());
  CHECK(auth_and_sign_response.error() ==
        ip_protection::android::AuthRequestError::kOther);
}

static void JNI_IpProtectionAuthTestNatives_TestSynchronousError(JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
      client = CreateAndExpectClientBlocking(kMockClassNameForSynchronousError);

  base::expected<privacy::ppn::GetInitialDataResponse,
                 ip_protection::android::AuthRequestError>
      get_initial_data_response = GetInitialDataBlocking(
          *client, privacy::ppn::GetInitialDataRequest());
  base::expected<privacy::ppn::AuthAndSignResponse,
                 ip_protection::android::AuthRequestError>
      auth_and_sign_response =
          AuthAndSignBlocking(*client, privacy::ppn::AuthAndSignRequest());

  CHECK(!get_initial_data_response.has_value());
  CHECK(get_initial_data_response.error() ==
        ip_protection::android::AuthRequestError::kOther);
  CHECK(!auth_and_sign_response.has_value());
  CHECK(auth_and_sign_response.error() ==
        ip_protection::android::AuthRequestError::kOther);
}

static void JNI_IpProtectionAuthTestNatives_TestUnresolvedWhenClosed(
    JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  base::expected<privacy::ppn::GetInitialDataResponse,
                 ip_protection::android::AuthRequestError>
      get_initial_data_response;
  base::expected<privacy::ppn::AuthAndSignResponse,
                 ip_protection::android::AuthRequestError>
      auth_and_sign_response;

  int remaining = 2;
  base::RunLoop run_loop;
  {
    std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
        client = CreateAndExpectClientBlocking(kMockClassNameForNeverResolve);
    client->GetInitialData(
        privacy::ppn::GetInitialDataRequest(),
        base::BindPostTask(
            base::SequencedTaskRunner::GetCurrentDefault(),
            base::BindLambdaForTesting(
                [&run_loop, &remaining, &get_initial_data_response](
                    base::expected<privacy::ppn::GetInitialDataResponse,
                                   ip_protection::android::AuthRequestError>
                        response) {
                  get_initial_data_response = std::move(response);
                  remaining--;
                  if (remaining == 0) {
                    run_loop.Quit();
                  }
                })));
    client->AuthAndSign(
        privacy::ppn::AuthAndSignRequest(),
        base::BindPostTask(
            base::SequencedTaskRunner::GetCurrentDefault(),
            base::BindLambdaForTesting(
                [&run_loop, &remaining, &auth_and_sign_response](
                    base::expected<privacy::ppn::AuthAndSignResponse,
                                   ip_protection::android::AuthRequestError>
                        response) {
                  auth_and_sign_response = std::move(response);
                  remaining--;
                  if (remaining == 0) {
                    run_loop.Quit();
                  }
                })));
    // client closes when out of scope
  }
  run_loop.Run();

  CHECK(!get_initial_data_response.has_value());
  CHECK(get_initial_data_response.error() ==
        ip_protection::android::AuthRequestError::kOther);
  CHECK(!auth_and_sign_response.has_value());
  CHECK(auth_and_sign_response.error() ==
        ip_protection::android::AuthRequestError::kOther);
}

// Service crashes whilst handling the binder call, without calling the
// callback. The client must synthetically report an error via the callback.
static void
JNI_IpProtectionAuthTestNatives_TestCrashOnRequestSyncWithoutResponse(
    JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  base::expected<privacy::ppn::GetInitialDataResponse,
                 ip_protection::android::AuthRequestError>
      get_initial_data_response;
  base::expected<privacy::ppn::AuthAndSignResponse,
                 ip_protection::android::AuthRequestError>
      auth_and_sign_response;

  {
    std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
        client = CreateAndExpectClientBlocking(
            kMockClassNameForCrashOnRequestSyncWithoutResponse);
    get_initial_data_response =
        GetInitialDataBlocking(*client, privacy::ppn::GetInitialDataRequest());
  }
  {
    std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
        client = CreateAndExpectClientBlocking(
            kMockClassNameForCrashOnRequestSyncWithoutResponse);
    auth_and_sign_response =
        AuthAndSignBlocking(*client, privacy::ppn::AuthAndSignRequest());
  }

  CHECK(!get_initial_data_response.has_value());
  CHECK(get_initial_data_response.error() ==
        ip_protection::android::AuthRequestError::kOther);
  CHECK(!auth_and_sign_response.has_value());
  CHECK(auth_and_sign_response.error() ==
        ip_protection::android::AuthRequestError::kOther);
}

// Service handles the binder call, without calling the callback, and then later
// crashes in the background. The client must detect this and synthetically
// report an error via the callback.
static void
JNI_IpProtectionAuthTestNatives_TestCrashOnRequestAsyncWithoutResponse(
    JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  base::expected<privacy::ppn::GetInitialDataResponse,
                 ip_protection::android::AuthRequestError>
      get_initial_data_response;
  base::expected<privacy::ppn::AuthAndSignResponse,
                 ip_protection::android::AuthRequestError>
      auth_and_sign_response;

  {
    std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
        client = CreateAndExpectClientBlocking(
            kMockClassNameForCrashOnRequestAsyncWithoutResponse);
    get_initial_data_response =
        GetInitialDataBlocking(*client, privacy::ppn::GetInitialDataRequest());
  }
  {
    std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
        client = CreateAndExpectClientBlocking(
            kMockClassNameForCrashOnRequestAsyncWithoutResponse);
    auth_and_sign_response =
        AuthAndSignBlocking(*client, privacy::ppn::AuthAndSignRequest());
  }

  CHECK(!get_initial_data_response.has_value());
  CHECK(get_initial_data_response.error() ==
        ip_protection::android::AuthRequestError::kOther);
  CHECK(!auth_and_sign_response.has_value());
  CHECK(auth_and_sign_response.error() ==
        ip_protection::android::AuthRequestError::kOther);
}

// Service resolves the callback and then crashes whilst still handling the
// binder call. The client must tollerate both error flows.
//
// It is not strictly deterministic as to which error flow runs first!
static void JNI_IpProtectionAuthTestNatives_TestCrashOnRequestSyncWithResponse(
    JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  base::expected<privacy::ppn::GetInitialDataResponse,
                 ip_protection::android::AuthRequestError>
      get_initial_data_response;
  base::expected<privacy::ppn::AuthAndSignResponse,
                 ip_protection::android::AuthRequestError>
      auth_and_sign_response;

  {
    std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
        client = CreateAndExpectClientBlocking(
            kMockClassNameForCrashOnRequestSyncWithResponse);
    get_initial_data_response =
        GetInitialDataBlocking(*client, privacy::ppn::GetInitialDataRequest());
  }
  {
    std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
        client = CreateAndExpectClientBlocking(
            kMockClassNameForCrashOnRequestSyncWithResponse);
    auth_and_sign_response =
        AuthAndSignBlocking(*client, privacy::ppn::AuthAndSignRequest());
  }

  // The synchronous error handler and the callback handler may theoretically
  // race, so don't assert between kTransient and kOther.
  //
  // The most important result is that the combination of two error paths don't
  // result in a crash.
  CHECK(!get_initial_data_response.has_value());
  CHECK(!auth_and_sign_response.has_value());
}

// Client tries to send three requests:
// - Service will handle the first call (without calling back).
// - Service will crash within the second call (without calling back).
// - Service is already dead by time of third request.
// This is done once for getInitialData and once for authAndSign.
//
// The client will need to reject all the callbacks itself.
static void
JNI_IpProtectionAuthTestNatives_TestUnresolvedCallbacksRejectedAfterCrash(
    JNIEnv* env) {
  base::test::SingleThreadTaskEnvironment task_environment;
  std::vector<base::expected<privacy::ppn::GetInitialDataResponse,
                             ip_protection::android::AuthRequestError>>
      get_initial_data_responses;
  std::vector<base::expected<privacy::ppn::AuthAndSignResponse,
                             ip_protection::android::AuthRequestError>>
      auth_and_sign_responses;
  const int numCalls = 3;

  {
    std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
        client = CreateAndExpectClientBlocking(
            kMockClassNameForCrashAfterTwoRequestsSyncWithoutResponses);
    int remaining = numCalls;
    base::RunLoop run_loop;
    for (int i = 0; i < numCalls; i++) {
      client->GetInitialData(
          privacy::ppn::GetInitialDataRequest(),
          base::BindPostTask(
              base::SequencedTaskRunner::GetCurrentDefault(),
              base::BindLambdaForTesting(
                  [&run_loop, &remaining, &get_initial_data_responses](
                      base::expected<privacy::ppn::GetInitialDataResponse,
                                     ip_protection::android::AuthRequestError>
                          response) {
                    get_initial_data_responses.emplace_back(
                        std::move(response));
                    remaining--;
                    if (remaining == 0) {
                      run_loop.Quit();
                    }
                  })));
    }
    run_loop.Run();
  }
  {
    std::unique_ptr<ip_protection::android::IpProtectionAuthClientInterface>
        client = CreateAndExpectClientBlocking(
            kMockClassNameForCrashAfterTwoRequestsSyncWithoutResponses);
    int remaining = numCalls;
    base::RunLoop run_loop;
    for (int i = 0; i < numCalls; i++) {
      client->AuthAndSign(
          privacy::ppn::AuthAndSignRequest(),
          base::BindPostTask(
              base::SequencedTaskRunner::GetCurrentDefault(),
              base::BindLambdaForTesting(
                  [&run_loop, &remaining, &auth_and_sign_responses](
                      base::expected<privacy::ppn::AuthAndSignResponse,
                                     ip_protection::android::AuthRequestError>
                          response) {
                    auth_and_sign_responses.emplace_back(std::move(response));
                    remaining--;
                    if (remaining == 0) {
                      run_loop.Quit();
                    }
                  })));
    }
    run_loop.Run();
  }

  CHECK(get_initial_data_responses.size() == 3);
  CHECK(auth_and_sign_responses.size() == 3);
  for (const auto& response : get_initial_data_responses) {
    CHECK(!response.has_value());
    CHECK(response.error() == ip_protection::android::AuthRequestError::kOther);
  }
  for (const auto& response : auth_and_sign_responses) {
    CHECK(!response.has_value());
    CHECK(response.error() == ip_protection::android::AuthRequestError::kOther);
  }
}