// 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 "components/ip_protection/android/blind_sign_message_android_impl.h"
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include "base/containers/queue.h"
#include "base/metrics/histogram_functions.h"
#include "base/sequence_checker.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/types/expected.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 "net/third_party/quiche/src/quiche/blind_sign_auth/blind_sign_message_interface.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"
#include "third_party/abseil-cpp/absl/status/statusor.h"
namespace {
template <typename ResponseType>
void EmitRequestTimingHistogram(base::TimeDelta time_delta);
template <>
void EmitRequestTimingHistogram<privacy::ppn::GetInitialDataResponse>(
base::TimeDelta time_delta) {
base::UmaHistogramMediumTimes(
"NetworkService.IpProtection.AndroidAuthClient.GetInitialDataTime",
time_delta);
}
template <>
void EmitRequestTimingHistogram<privacy::ppn::AuthAndSignResponse>(
base::TimeDelta time_delta) {
base::UmaHistogramMediumTimes(
"NetworkService.IpProtection.AndroidAuthClient.AuthAndSignTime",
time_delta);
}
} // namespace
namespace ip_protection {
BlindSignMessageAndroidImpl::BlindSignMessageAndroidImpl()
: client_factory_(
base::BindRepeating(&ip_protection::android::IpProtectionAuthClient::
CreateConnectedInstance)) {}
BlindSignMessageAndroidImpl::~BlindSignMessageAndroidImpl() = default;
void BlindSignMessageAndroidImpl::DoRequest(
quiche::BlindSignMessageRequestType request_type,
std::optional<std::string_view> authorization_header,
const std::string& body,
quiche::BlindSignMessageCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (authorization_header) {
std::move(callback)(
absl::InternalError("Failed Request to Android IP Protection Service. "
"Authorization header must be empty."));
return;
}
if (ip_protection_auth_client_ != nullptr) {
CHECK(pending_requests_.empty());
SendRequest(request_type, body, std::move(callback));
return;
}
pending_requests_.emplace(request_type, body, std::move(callback));
// If `ip_protection_auth_client_` is not yet set, try to create a new
// connected instance.
if (pending_requests_.size() == 1u) {
CreateIpProtectionAuthClient();
}
}
void BlindSignMessageAndroidImpl::CreateIpProtectionAuthClient() {
TRACE_EVENT0("toplevel",
"BlindSignMessageAndroidImpl::CreateIpProtectionAuthClient");
client_factory_.Run(base::BindPostTaskToCurrentDefault(base::BindOnce(
&BlindSignMessageAndroidImpl::OnCreateIpProtectionAuthClientComplete,
weak_ptr_factory_.GetWeakPtr(), /*start_time=*/base::TimeTicks::Now())));
}
void BlindSignMessageAndroidImpl::OnCreateIpProtectionAuthClientComplete(
base::TimeTicks start_time,
base::expected<std::unique_ptr<IpProtectionAuthClientInterface>,
std::string> ip_protection_auth_client) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (ip_protection_auth_client.has_value()) {
CHECK(!ip_protection_auth_client_);
ip_protection_auth_client_ = std::move(ip_protection_auth_client.value());
base::UmaHistogramMediumTimes(
"NetworkService.IpProtection.AndroidAuthClient.CreationTime",
base::TimeTicks::Now() - start_time);
}
while (!pending_requests_.empty()) {
auto [request_type, body, callback] = std::move(pending_requests_.front());
if (ip_protection_auth_client_ != nullptr) {
SendRequest(request_type, body, std::move(callback));
} else {
std::move(callback)(absl::InternalError(
"Failed request to bind to the Android IP Protection service."));
}
pending_requests_.pop();
}
}
void BlindSignMessageAndroidImpl::SendRequest(
quiche::BlindSignMessageRequestType request_type,
const std::string& body,
quiche::BlindSignMessageCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TRACE_EVENT0("toplevel", "BlindSignMessageAndroidImpl::SendRequest");
switch (request_type) {
case quiche::BlindSignMessageRequestType::kGetInitialData: {
privacy::ppn::GetInitialDataRequest get_initial_data_request_proto;
get_initial_data_request_proto.ParseFromString(body);
ip_protection_auth_client_->GetInitialData(
get_initial_data_request_proto,
base::BindPostTaskToCurrentDefault(base::BindOnce(
&BlindSignMessageAndroidImpl::OnSendRequestComplete<
privacy::ppn::GetInitialDataResponse>,
weak_ptr_factory_.GetWeakPtr(),
ip_protection_auth_client_->GetWeakPtr(), std::move(callback),
/*start_time=*/base::TimeTicks::Now())));
break;
}
case quiche::BlindSignMessageRequestType::kAuthAndSign: {
privacy::ppn::AuthAndSignRequest auth_and_sign_request_proto;
auth_and_sign_request_proto.ParseFromString(body);
ip_protection_auth_client_->AuthAndSign(
auth_and_sign_request_proto,
base::BindPostTaskToCurrentDefault(base::BindOnce(
&BlindSignMessageAndroidImpl::OnSendRequestComplete<
privacy::ppn::AuthAndSignResponse>,
weak_ptr_factory_.GetWeakPtr(),
ip_protection_auth_client_->GetWeakPtr(), std::move(callback),
/*start_time=*/base::TimeTicks::Now())));
break;
}
case quiche::BlindSignMessageRequestType::kUnknown:
NOTREACHED();
}
}
template <typename ResponseType>
void BlindSignMessageAndroidImpl::OnSendRequestComplete(
base::WeakPtr<IpProtectionAuthClientInterface>
requesting_ip_protection_auth_client,
quiche::BlindSignMessageCallback callback,
base::TimeTicks start_time,
base::expected<ResponseType, ip_protection::android::AuthRequestError>
response) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (response.has_value()) {
EmitRequestTimingHistogram<ResponseType>(base::TimeTicks::Now() -
start_time);
quiche::BlindSignMessageResponse bsa_response(
absl::StatusCode::kOk, response->SerializeAsString());
std::move(callback)(std::move(bsa_response));
} else {
switch (response.error()) {
case ip_protection::android::AuthRequestError::kPersistent: {
std::move(callback)(absl::FailedPreconditionError(
"Persistent error when making request to the service implementing "
"IP Protection."));
break;
}
case ip_protection::android::AuthRequestError::kTransient: {
std::move(callback)(
absl::UnavailableError("Transient error when making request to the "
"service implementing IP Protection"));
break;
}
case ip_protection::android::AuthRequestError::kOther:
// `kOther` error may indicate that the service became disconnected
// during the request. Because binding succeeded previously, reset the
// `ip_protection_auth_client_` only if the current client is
// responsible for the request.
if (requesting_ip_protection_auth_client) {
CHECK(requesting_ip_protection_auth_client.get() ==
ip_protection_auth_client_.get());
CHECK(pending_requests_.empty());
ip_protection_auth_client_.reset();
}
std::move(callback)(absl::InternalError(
"An internal error where there is no longer a connection to the "
"Android IP Protection service during a request."));
break;
}
}
}
} // namespace ip_protection