blob: be0087cb919725c5d5fea482d29cdddc0c229d5f [file] [log] [blame]
// 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 "chrome/browser/k_anonymity_service/k_anonymity_service_client.h"
#include "base/base64.h"
#include "base/base64url.h"
#include "base/feature_list.h"
#include "base/functional/callback.h"
#include "base/json/json_writer.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/types/expected.h"
#include "chrome/browser/k_anonymity_service/k_anonymity_service_metrics.h"
#include "chrome/browser/k_anonymity_service/k_anonymity_service_urls.h"
#include "chrome/browser/k_anonymity_service/remote_trust_token_query_answerer.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/common/chrome_features.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/tribool.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/google_api_keys.h"
#include "net/base/isolation_info.h"
#include "net/base/load_flags.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/oblivious_http_request.mojom.h"
#include "services/network/public/mojom/trust_tokens.mojom.h"
namespace {
constexpr base::TimeDelta kRequestTimeout = base::Seconds(5);
constexpr base::TimeDelta kRequestMargin = base::Minutes(5);
constexpr base::TimeDelta kKeyCacheDuration = base::Hours(24);
constexpr int kMaxRetries = 5;
constexpr size_t kMaxQueueSize = 400;
// TODO(behamilton): Allow the KAnonType to be specified by the client.
const char kKAnonType[] = "fledge";
const char kKAnonymityServiceStoragePath[] = "KAnonymityService";
constexpr net::NetworkTrafficAnnotationTag
kKAnonymityServiceJoinSetTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("k_anonymity_service_join_set",
R"(
semantics {
sender: "Chrome k-Anonymity Service Client"
description:
"Request to the Chrome k-Anonymity Join server to notify it of use "
"of a k-anonymity protected element."
trigger:
"Use of a k-anonymity protected element."
data:
"Hash of a feature protected by k-anonymity, such as the URL of a "
"FLEDGE ad. Also contains a trust token issued by the k-Anonymity Auth "
"server."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"Disable features using k-anonymity, such as FLEDGE and Attribution "
"Reporting."
chrome_policy {
}
}
comments:
""
)");
constexpr net::NetworkTrafficAnnotationTag
kKAnonymityServiceQuerySetTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("k_anonymity_service_query_set",
R"(
semantics {
sender: "Chrome k-Anonymity Service Client"
description:
"Request to the Chrome k-Anonymity Query server to query if "
"k-anonymity protected element is k-anonymous. These results are "
"typically cached."
trigger:
"Expected use of a k-anonymity protected element."
data:
"Hash of a feature protected by k-anonymity, such as the URL of a "
"FLEDGE ad."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"Disable features using k-anonymity, such as FLEDGE and Attribution "
"Reporting."
chrome_policy {
}
}
comments:
""
)");
// KAnonObliviousHttpClient accepts OnCompleted calls and forwards them to the
// provided callback. It also calls the callback if it is destroyed before the
// callback is called.
class KAnonObliviousHttpClient : public network::mojom::ObliviousHttpClient {
public:
using OnCompletedCallback =
base::OnceCallback<void(const std::optional<std::string>&, int)>;
explicit KAnonObliviousHttpClient(OnCompletedCallback callback)
: callback_(std::move(callback)) {}
~KAnonObliviousHttpClient() override {
if (!called_) {
std::move(callback_).Run(std::nullopt, net::ERR_FAILED);
}
}
void OnCompleted(
network::mojom::ObliviousHttpCompletionResultPtr status) override {
if (called_) {
mojo::ReportBadMessage("OnCompleted called more than once");
return;
}
called_ = true;
if (status->is_net_error()) {
std::move(callback_).Run(std::nullopt, status->get_net_error());
} else if (status->is_outer_response_error_code()) {
std::move(callback_).Run(std::nullopt,
net::ERR_HTTP_RESPONSE_CODE_FAILURE);
} else {
DCHECK(status->is_inner_response());
if (status->get_inner_response()->response_code != net::HTTP_OK) {
std::move(callback_).Run(std::nullopt,
net::ERR_HTTP_RESPONSE_CODE_FAILURE);
} else {
std::move(callback_).Run(status->get_inner_response()->response_body,
net::OK);
}
}
}
private:
bool called_ = false;
OnCompletedCallback callback_;
};
} // namespace
KAnonymityServiceClient::PendingJoinRequest::PendingJoinRequest(
std::string set_id,
base::OnceCallback<void(bool)> callback)
: id(std::move(set_id)),
request_start(base::TimeTicks::Now()),
callback(std::move(callback)) {}
KAnonymityServiceClient::PendingJoinRequest::~PendingJoinRequest() = default;
KAnonymityServiceClient::PendingQueryRequest::PendingQueryRequest(
std::vector<std::string> set_ids,
base::OnceCallback<void(std::vector<bool>)> callback)
: ids(std::move(set_ids)),
request_start(base::TimeTicks::Now()),
callback(std::move(callback)) {}
KAnonymityServiceClient::PendingQueryRequest::~PendingQueryRequest() = default;
KAnonymityServiceClient::KAnonymityServiceClient(Profile* profile)
: url_loader_factory_(profile->GetURLLoaderFactory()),
enable_ohttp_requests_(base::FeatureList::IsEnabled(
features::kKAnonymityServiceOHTTPRequests)),
storage_(
(base::FeatureList::IsEnabled(features::kKAnonymityServiceStorage) &&
profile && !profile->IsOffTheRecord())
? CreateKAnonymitySqlStorageForPath(
profile->GetDefaultStoragePartition()
->GetPath()
.AppendASCII(kKAnonymityServiceStoragePath))
: std::make_unique<KAnonymityServiceMemoryStorage>()),
// Pass the auth server origin as if it is our "top frame".
trust_token_answerer_(url::Origin::Create(GURL(
features::kKAnonymityServiceAuthServer.Get())),
profile),
token_getter_(IdentityManagerFactory::GetForProfile(profile),
url_loader_factory_,
&trust_token_answerer_,
storage_.get()),
profile_(profile) {
join_origin_ =
url::Origin::Create(GURL(features::kKAnonymityServiceJoinServer.Get()));
DCHECK(!join_origin_.opaque());
query_origin_ =
url::Origin::Create(GURL(features::kKAnonymityServiceQueryServer.Get()));
DCHECK(!query_origin_.opaque());
}
KAnonymityServiceClient::~KAnonymityServiceClient() = default;
bool KAnonymityServiceClient::CanUseKAnonymityService(Profile* profile) {
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
if (!identity_manager) {
return false;
}
const AccountInfo account_info = identity_manager->FindExtendedAccountInfo(
identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin));
auto capability =
account_info.capabilities.can_run_chrome_privacy_sandbox_trials();
return capability == signin::Tribool::kTrue;
}
void KAnonymityServiceClient::JoinSet(std::string id,
base::OnceCallback<void(bool)> callback) {
if (!CanUseKAnonymityService(profile_)) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), false));
return;
}
RecordJoinSetAction(KAnonymityServiceJoinSetAction::kJoinSet);
// Fail immediately if the queue is full.
if (join_queue_.size() >= kMaxQueueSize) {
RecordJoinSetAction(KAnonymityServiceJoinSetAction::kJoinSetQueueFull);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), false));
return;
}
// Add to the queue. If this is the only request in the queue, start it.
join_queue_.push_back(
std::make_unique<PendingJoinRequest>(std::move(id), std::move(callback)));
if (join_queue_.size() > 1)
return;
storage_->WaitUntilReady(
base::BindOnce(&KAnonymityServiceClient::JoinSetOnStorageReady,
weak_ptr_factory_.GetWeakPtr()));
}
void KAnonymityServiceClient::JoinSetOnStorageReady(
KAnonymityServiceStorage::InitStatus status) {
if (status != KAnonymityServiceStorage::InitStatus::kInitOk) {
FailJoinSetRequests();
return;
}
JoinSetStartNextQueued();
}
void KAnonymityServiceClient::JoinSetStartNextQueued() {
DCHECK(!join_queue_.empty());
JoinSetCheckOHTTPKey();
}
void KAnonymityServiceClient::JoinSetCheckOHTTPKey() {
// We need the OHTTP key to send the OHTTP request.
std::optional<OHTTPKeyAndExpiration> ohttp_key =
storage_->GetOHTTPKeyFor(join_origin_);
if (enable_ohttp_requests_ &&
(!ohttp_key ||
ohttp_key->expiration <= base::Time::Now() + kRequestMargin)) {
RequestJoinSetOHTTPKey();
return;
}
JoinSetCheckTrustTokens(
std::move(ohttp_key).value_or(OHTTPKeyAndExpiration{}));
}
void KAnonymityServiceClient::RequestJoinSetOHTTPKey() {
RecordJoinSetAction(KAnonymityServiceJoinSetAction::kFetchJoinSetOHTTPKey);
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = join_origin_.GetURL().Resolve(
base::StrCat({kJoinSetOhttpPath, google_apis::GetAPIKey()}));
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
resource_request->trusted_params.emplace();
resource_request->trusted_params->isolation_info = isolation_info_;
join_url_loader_ = network::SimpleURLLoader::Create(
std::move(resource_request), kKAnonymityServiceJoinSetTrafficAnnotation);
join_url_loader_->SetTimeoutDuration(kRequestTimeout);
join_url_loader_->DownloadToString(
url_loader_factory_.get(),
base::BindOnce(&KAnonymityServiceClient::OnGotJoinSetOHTTPKey,
weak_ptr_factory_.GetWeakPtr()),
/*max_body_size=*/1024);
}
void KAnonymityServiceClient::OnGotJoinSetOHTTPKey(
std::unique_ptr<std::string> response) {
join_url_loader_.reset();
if (!response) {
RecordJoinSetAction(
KAnonymityServiceJoinSetAction::kFetchJoinSetOHTTPKeyFailed);
FailJoinSetRequests();
return;
}
OHTTPKeyAndExpiration ohttp_key{*response,
base::Time::Now() + kKeyCacheDuration};
storage_->UpdateOHTTPKeyFor(join_origin_, ohttp_key);
JoinSetCheckTrustTokens(std::move(ohttp_key));
}
void KAnonymityServiceClient::JoinSetCheckTrustTokens(
OHTTPKeyAndExpiration ohttp_key) {
token_getter_.TryGetTrustTokenAndKey(
base::BindOnce(&KAnonymityServiceClient::OnMaybeHasTrustTokens,
weak_ptr_factory_.GetWeakPtr(), std::move(ohttp_key)));
}
void KAnonymityServiceClient::OnMaybeHasTrustTokens(
OHTTPKeyAndExpiration ohttp_key,
std::optional<KeyAndNonUniqueUserId> maybe_key_and_id) {
if (!maybe_key_and_id) {
FailJoinSetRequests();
return;
}
if (!enable_ohttp_requests_) {
CompleteJoinSetRequest();
return;
}
// Once we know we have a trust token and have the OHTTP key we can send the
// request.
JoinSetSendRequest(std::move(ohttp_key), std::move(*maybe_key_and_id));
}
void KAnonymityServiceClient::JoinSetSendRequest(
OHTTPKeyAndExpiration ohttp_key,
KeyAndNonUniqueUserId key_and_id) {
RecordJoinSetAction(KAnonymityServiceJoinSetAction::kSendJoinSetRequest);
std::string encoded_id;
base::Base64UrlEncode(join_queue_.front()->id,
base::Base64UrlEncodePolicy::OMIT_PADDING, &encoded_id);
network::mojom::ObliviousHttpRequestPtr request =
network::mojom::ObliviousHttpRequest::New();
request->relay_url = GURL(features::kKAnonymityServiceJoinRelayServer.Get());
request->traffic_annotation = net::MutableNetworkTrafficAnnotationTag(
kKAnonymityServiceJoinSetTrafficAnnotation);
request->key_config = ohttp_key.key;
request->resource_url = join_origin_.GetURL().Resolve(
base::StringPrintf(kJoinSetPathFmt, kKAnonType, encoded_id.c_str(),
google_apis::GetAPIKey().c_str()));
request->method = net::HttpRequestHeaders::kPostMethod;
std::string payload = base::StringPrintf(
"{name: 'type/%s/sets/%s', shortClientIdentifier: %d}", kKAnonType,
encoded_id.c_str(), key_and_id.non_unique_user_id);
request->request_body = network::mojom::ObliviousHttpRequestBody::New(
payload, /*content_type=*/"application/json");
// Add padding to reduce the exposure through traffic analysis.
request->padding_params =
network::mojom::ObliviousHttpPaddingParameters::New();
request->padding_params->add_exponential_pad = false;
request->padding_params->pad_to_next_power_of_two = true;
// We want to send the redemption request to the join_origin, but the tokens
// are scoped to auth_origin. That means we need to specify auth_origin as the
// issuer.
url::Origin auth_origin =
url::Origin::Create(GURL(features::kKAnonymityServiceAuthServer.Get()));
network::mojom::TrustTokenParamsPtr params =
network::mojom::TrustTokenParams::New();
params->operation = network::mojom::TrustTokenOperationType::kRedemption;
params->refresh_policy = network::mojom::TrustTokenRefreshPolicy::kRefresh;
params->custom_key_commitment = key_and_id.key_commitment;
params->custom_issuer = auth_origin;
params->issuers.push_back(auth_origin);
request->trust_token_params = std::move(params);
mojo::PendingReceiver<network::mojom::ObliviousHttpClient> pending_receiver;
profile_->GetDefaultStoragePartition()
->GetNetworkContext()
->GetViaObliviousHttp(std::move(request),
pending_receiver.InitWithNewPipeAndPassRemote());
ohttp_client_receivers_.Add(
std::make_unique<KAnonObliviousHttpClient>(
base::BindOnce(&KAnonymityServiceClient::JoinSetOnGotResponse,
weak_ptr_factory_.GetWeakPtr())),
std::move(pending_receiver));
}
void KAnonymityServiceClient::JoinSetOnGotResponse(
const std::optional<std::string>& response,
int error_code) {
if (error_code != net::OK) {
// If failure was because we didn't have the trust token (it was used before
// we could get it) then retry. We don't need to back off because getting
// this error implies that the server is not overloaded.
if (error_code == net::ERR_TRUST_TOKEN_OPERATION_FAILED &&
join_queue_.front()->retries++ < kMaxRetries) {
// Retry from checking the OHTTP Key. This will also get a trust token and
// send the request again.
JoinSetCheckOHTTPKey();
return;
}
RecordJoinSetAction(KAnonymityServiceJoinSetAction::kJoinSetRequestFailed);
FailJoinSetRequests();
return;
}
// Only record latency for successful requests.
RecordJoinSetLatency(join_queue_.front()->request_start,
base::TimeTicks::Now());
CompleteJoinSetRequest();
}
void KAnonymityServiceClient::FailJoinSetRequests() {
while (!join_queue_.empty()) {
DoJoinSetCallback(false);
}
}
void KAnonymityServiceClient::CompleteJoinSetRequest() {
RecordJoinSetAction(KAnonymityServiceJoinSetAction::kJoinSetSuccess);
DoJoinSetCallback(true);
// If we have a request queued, process that one.
if (!join_queue_.empty())
JoinSetStartNextQueued();
}
void KAnonymityServiceClient::DoJoinSetCallback(bool status) {
DCHECK(!join_queue_.empty());
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(join_queue_.front()->callback), status));
join_queue_.pop_front();
}
void KAnonymityServiceClient::QuerySets(
std::vector<std::string> set_ids,
base::OnceCallback<void(std::vector<bool>)> callback) {
if (!CanUseKAnonymityService(profile_)) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::vector<bool>()));
return;
}
RecordQuerySetAction(KAnonymityServiceQuerySetAction::kQuerySet);
RecordQuerySetSize(set_ids.size());
// Fail immediately if the queue is full.
if (query_queue_.size() >= kMaxQueueSize || set_ids.empty()) {
RecordQuerySetAction(KAnonymityServiceQuerySetAction::kQuerySetQueueFull);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::vector<bool>()));
return;
}
if (!enable_ohttp_requests_) {
// Trigger a "successful" callback.
RecordQuerySetAction(KAnonymityServiceQuerySetAction::kQuerySetsSuccess);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
std::vector<bool>(set_ids.size(), false)));
return;
}
query_queue_.push_back(std::make_unique<PendingQueryRequest>(
std::move(set_ids), std::move(callback)));
// We only process one query at a time for simplicity.
if (query_queue_.size() > 1)
return;
storage_->WaitUntilReady(
base::BindOnce(&KAnonymityServiceClient::QuerySetsOnStorageReady,
weak_ptr_factory_.GetWeakPtr()));
}
void KAnonymityServiceClient::QuerySetsOnStorageReady(
KAnonymityServiceStorage::InitStatus status) {
if (status != KAnonymityServiceStorage::InitStatus::kInitOk) {
FailQuerySetsRequests();
return;
}
QuerySetsCheckOHTTPKey();
}
void KAnonymityServiceClient::QuerySetsCheckOHTTPKey() {
std::optional<OHTTPKeyAndExpiration> ohttp_key =
storage_->GetOHTTPKeyFor(query_origin_);
if (!ohttp_key ||
ohttp_key->expiration <= base::Time::Now() + kRequestMargin) {
RequestQuerySetOHTTPKey();
return;
}
QuerySetsSendRequest(std::move(ohttp_key.value()));
}
void KAnonymityServiceClient::RequestQuerySetOHTTPKey() {
DCHECK(!query_url_loader_);
RecordQuerySetAction(KAnonymityServiceQuerySetAction::kFetchQuerySetOHTTPKey);
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = query_origin_.GetURL().Resolve(
base::StrCat({kQuerySetOhttpPath, google_apis::GetAPIKey()}));
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
resource_request->trusted_params.emplace();
resource_request->trusted_params->isolation_info = isolation_info_;
query_url_loader_ = network::SimpleURLLoader::Create(
std::move(resource_request), kKAnonymityServiceQuerySetTrafficAnnotation);
query_url_loader_->SetTimeoutDuration(kRequestTimeout);
query_url_loader_->DownloadToString(
url_loader_factory_.get(),
base::BindOnce(&KAnonymityServiceClient::OnGotQuerySetOHTTPKey,
weak_ptr_factory_.GetWeakPtr()),
/*max_body_size=*/1024);
}
void KAnonymityServiceClient::OnGotQuerySetOHTTPKey(
std::unique_ptr<std::string> response) {
query_url_loader_.reset();
if (!response) {
RecordQuerySetAction(
KAnonymityServiceQuerySetAction::kFetchQuerySetOHTTPKeyFailed);
FailQuerySetsRequests();
return;
}
OHTTPKeyAndExpiration ohttp_key{*response,
base::Time::Now() + kKeyCacheDuration};
storage_->UpdateOHTTPKeyFor(query_origin_, ohttp_key);
QuerySetsSendRequest(std::move(ohttp_key));
}
void KAnonymityServiceClient::QuerySetsSendRequest(
OHTTPKeyAndExpiration ohttp_key) {
DCHECK(!query_url_loader_);
RecordQuerySetAction(KAnonymityServiceQuerySetAction::kSendQuerySetRequest);
// Request looks like this:
// { setsForType: [
// { type: "t1", hashes: ["a", "b", "c"]},
// { type: "t1", hashes: ["d', "e", "f", "f", "c"]},
// { type: "t2"}
// ]}
base::Value::List request_hashes;
for (const auto& id : query_queue_.front()->ids) {
request_hashes.Append(base::Base64Encode(id));
}
base::Value::Dict sets_for_type;
sets_for_type.Set("type", kKAnonType);
sets_for_type.Set("hashes", std::move(request_hashes));
base::Value::List types;
types.Append(std::move(sets_for_type));
base::Value::Dict request_dict;
request_dict.Set("setsForType", std::move(types));
std::string request_body;
base::JSONWriter::Write(request_dict, &request_body);
network::mojom::ObliviousHttpRequestPtr request =
network::mojom::ObliviousHttpRequest::New();
request->relay_url = GURL(features::kKAnonymityServiceQueryRelayServer.Get());
request->traffic_annotation = net::MutableNetworkTrafficAnnotationTag(
kKAnonymityServiceQuerySetTrafficAnnotation);
request->key_config = ohttp_key.key;
request->resource_url = query_origin_.GetURL().Resolve(
base::StrCat({kQuerySetsPath, google_apis::GetAPIKey()}));
request->method = net::HttpRequestHeaders::kPostMethod;
request->request_body = network::mojom::ObliviousHttpRequestBody::New(
request_body, /*content_type=*/"application/json");
// Add padding to reduce the exposure through traffic analysis.
request->padding_params =
network::mojom::ObliviousHttpPaddingParameters::New();
request->padding_params->add_exponential_pad = false;
request->padding_params->pad_to_next_power_of_two = true;
mojo::PendingReceiver<network::mojom::ObliviousHttpClient> pending_receiver;
profile_->GetDefaultStoragePartition()
->GetNetworkContext()
->GetViaObliviousHttp(std::move(request),
pending_receiver.InitWithNewPipeAndPassRemote());
ohttp_client_receivers_.Add(
std::make_unique<KAnonObliviousHttpClient>(
base::BindOnce(&KAnonymityServiceClient::QuerySetsOnGotResponse,
weak_ptr_factory_.GetWeakPtr())),
std::move(pending_receiver));
}
void KAnonymityServiceClient::QuerySetsOnGotResponse(
const std::optional<std::string>& response,
int error_code) {
if (error_code != net::OK) {
RecordQuerySetAction(
KAnonymityServiceQuerySetAction::kQuerySetRequestFailed);
FailQuerySetsRequests();
return;
}
data_decoder::DataDecoder::ParseJsonIsolated(
*response,
base::BindOnce(&KAnonymityServiceClient::QuerySetsOnParsedResponse,
weak_ptr_factory_.GetWeakPtr()));
}
void KAnonymityServiceClient::QuerySetsOnParsedResponse(
data_decoder::DataDecoder::ValueOrError result) {
if (!result.has_value()) {
RecordQuerySetAction(
KAnonymityServiceQuerySetAction::kQuerySetRequestParseError);
FailQuerySetsRequests();
return;
}
// Response has the form:
// { kAnonymousSets: [
// { type: "t1", hashes: ["c", "f"]}
// ]}
const base::Value::Dict* response_dict = result->GetIfDict();
if (!response_dict) {
RecordQuerySetAction(
KAnonymityServiceQuerySetAction::kQuerySetRequestParseError);
FailQuerySetsRequests();
return;
}
const base::Value::List* set_types =
response_dict->FindList("kAnonymousSets");
if (!set_types) {
RecordQuerySetAction(
KAnonymityServiceQuerySetAction::kQuerySetRequestParseError);
FailQuerySetsRequests();
return;
}
std::vector<std::string> returned_hashes;
for (const auto& set_type_value : *set_types) {
const base::Value::Dict* set_type_dict = set_type_value.GetIfDict();
if (!set_type_dict) {
RecordQuerySetAction(
KAnonymityServiceQuerySetAction::kQuerySetRequestParseError);
FailQuerySetsRequests();
return;
}
const std::string* type = set_type_dict->FindString("type");
if (!type || *type != kKAnonType) {
RecordQuerySetAction(
KAnonymityServiceQuerySetAction::kQuerySetRequestParseError);
FailQuerySetsRequests();
return;
}
const base::Value::List* hashes = set_type_dict->FindList("hashes");
if (!hashes) {
RecordQuerySetAction(
KAnonymityServiceQuerySetAction::kQuerySetRequestParseError);
FailQuerySetsRequests();
return;
}
for (const base::Value& val : *hashes) {
const std::string* string_val = val.GetIfString();
if (!string_val) {
RecordQuerySetAction(
KAnonymityServiceQuerySetAction::kQuerySetRequestParseError);
FailQuerySetsRequests();
return;
}
std::string decoded_value;
if (!base::Base64Decode(*string_val, &decoded_value)) {
FailQuerySetsRequests();
RecordQuerySetAction(
KAnonymityServiceQuerySetAction::kQuerySetRequestParseError);
return;
}
returned_hashes.emplace_back(std::move(decoded_value));
}
}
base::flat_set<std::string> k_anon_set(std::move(returned_hashes));
std::vector<bool> output;
output.reserve(query_queue_.front()->ids.size());
for (const auto& id : query_queue_.front()->ids) {
output.push_back(k_anon_set.contains(id));
}
// Only record latency for successful requests.
RecordQuerySetLatency(query_queue_.front()->request_start,
base::TimeTicks::Now());
CompleteQuerySetsRequest(std::move(output));
}
void KAnonymityServiceClient::CompleteQuerySetsRequest(
std::vector<bool> result) {
RecordQuerySetAction(KAnonymityServiceQuerySetAction::kQuerySetsSuccess);
DoQuerySetsCallback(std::move(result));
if (!query_queue_.empty()) {
QuerySetsCheckOHTTPKey();
}
}
void KAnonymityServiceClient::FailQuerySetsRequests() {
// Callback with empty result indicating failure.
while (!query_queue_.empty()) {
DoQuerySetsCallback(std::vector<bool>());
}
}
void KAnonymityServiceClient::DoQuerySetsCallback(std::vector<bool> result) {
DCHECK(!query_queue_.empty());
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(query_queue_.front()->callback),
std::move(result)));
query_queue_.pop_front();
}
base::TimeDelta KAnonymityServiceClient::GetJoinInterval() {
return features::kKAnonymityServiceJoinInterval.Get();
}
base::TimeDelta KAnonymityServiceClient::GetQueryInterval() {
return features::kKAnonymityServiceQueryInterval.Get();
}