blob: 3e848fbcd673525fa1c5c0fe6c3e6f86fe4f96f9 [file] [log] [blame]
// 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.
#ifndef CONTENT_BROWSER_INTEREST_GROUP_TRUSTED_SIGNALS_FETCHER_H_
#define CONTENT_BROWSER_INTEREST_GROUP_TRUSTED_SIGNALS_FETCHER_H_
#include <stdint.h>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <vector>
#include "base/containers/flat_set.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/types/expected.h"
#include "base/unguessable_token.h"
#include "base/values.h"
#include "content/browser/interest_group/data_decoder_manager.h"
#include "content/browser/interest_group/devtools_enums.h"
#include "content/common/content_export.h"
#include "content/public/browser/frame_tree_node_id.h"
#include "content/services/auction_worklet/public/mojom/trusted_signals_cache.mojom.h"
#include "net/http/http_response_headers.h"
#include "net/third_party/quiche/src/quiche/oblivious_http/buffers/oblivious_http_request.h"
#include "services/data_decoder/public/cpp/data_decoder.h"
#include "services/network/public/mojom/ip_address_space.mojom-forward.h"
#include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
#include "services/network/public/mojom/url_response_head.mojom-forward.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace auction_worklet {
class AuctionDownloader;
}
namespace content {
struct BiddingAndAuctionServerKey;
// Single-use network fetcher for versions 2+ of the key-value server API.
// It takes a list compression groups and partitions, and asynchronously returns
// a set of responses, one per compression group. The responses are provided as
// still compressed compression group bodies, so the cache layer can store
// compressed responses and to minimize IPC size. The responses will be
// decompressed before use in the appropriate Javascript process.
//
// Bidding and scoring signals need different structs when sending requests, but
// they use the same response format, since it's only the compressed data itself
// that varies based on signals type.
//
// TODO(https://crbug.com/333445540): This is currently only an API, with no
// implementation. Need to actually implement the API.
class CONTENT_EXPORT TrustedSignalsFetcher {
public:
static constexpr std::string_view kRequestMediaType =
"message/ad-auction-trusted-signals-request";
static constexpr std::string_view kResponseMediaType =
"message/ad-auction-trusted-signals-response";
// All the data needed to request a particular bidding signals partition.
struct CONTENT_EXPORT BiddingPartition {
// Pointer arguments must remain valid until the BiddingPartition is
// destroyed.
BiddingPartition(int partition_id,
const std::set<std::string>* interest_group_names,
const std::set<std::string>* keys,
const base::Value::Dict* additional_params,
const std::string* buyer_tkv_signals);
BiddingPartition(BiddingPartition&&);
~BiddingPartition();
BiddingPartition& operator=(BiddingPartition&&);
int partition_id;
base::raw_ref<const std::set<std::string>> interest_group_names;
base::raw_ref<const std::set<std::string>> keys;
// At the moment, valid keys are "experimentGroupId", "slotSize", and
// "allSlotsRequestedSizes". We could take them separately, but seems better
// to take one field rather than several?
base::raw_ref<const base::Value::Dict> additional_params;
raw_ptr<const std::string> buyer_tkv_signals;
};
// All the data needed to request a particular scoring signals partition.
struct CONTENT_EXPORT ScoringPartition {
// Pointer arguments must remain valid until the ScoringPartition is
// destroyed.
ScoringPartition(int partition_id,
const GURL* render_url,
const std::set<GURL>* component_render_urls,
const base::Value::Dict* additional_params,
const std::string* seller_tkv_signals);
ScoringPartition(ScoringPartition&&);
~ScoringPartition();
ScoringPartition& operator=(ScoringPartition&&);
int partition_id;
// Currently, TrustedSignalsCacheImpl puts the values from each bid in its
// own partition, so there will always be only one `render_url`.
base::raw_ref<const GURL> render_url;
base::raw_ref<const std::set<GURL>> component_render_urls;
// At the moment, valid keys are "experimentGroupId", "slotSize", and
// "allSlotsRequestedSizes". We could take them separately, but seems better
// to take one field rather than several?
base::raw_ref<const base::Value::Dict> additional_params;
raw_ptr<const std::string> seller_tkv_signals;
};
// While buying and scoring signals partitions need different structs when
// sending requests, the responses use the same format.
// The received result for a particular compression group. Only returned on
// success.
struct CONTENT_EXPORT CompressionGroupResult {
CompressionGroupResult();
CompressionGroupResult(CompressionGroupResult&&);
~CompressionGroupResult();
CompressionGroupResult& operator=(CompressionGroupResult&&);
// The compression scheme used by `compression_group_data`, as indicated by
// the server.
auction_worklet::mojom::TrustedSignalsCompressionScheme compression_scheme;
// The still-compressed data for the compression group.
base::Value::BlobStorage compression_group_data;
// Time until the response expires.
base::TimeDelta ttl;
};
// A map of compression group ids to results, in the case of success.
using CompressionGroupResultMap = std::map<int, CompressionGroupResult>;
// The result of a fetch. Either the entire fetch succeeds or it fails with a
// single error.
using SignalsFetchResult =
base::expected<CompressionGroupResultMap, std::string>;
using Callback = base::OnceCallback<void(SignalsFetchResult)>;
TrustedSignalsFetcher();
// Virtual for tests.
virtual ~TrustedSignalsFetcher();
TrustedSignalsFetcher(const TrustedSignalsFetcher&) = delete;
TrustedSignalsFetcher& operator=(const TrustedSignalsFetcher&) = delete;
// `data_decoder_manager` must outlive the fetcher.
//
// `frame_tree_node_id` and `devtools_auction_ids` are used to log events for
// devtools, if needed.
//
// `main_frame_origin` and `network_partition_nonce` are used to create an
// IsolationInfo identifying the network partition to use.
// `main_frame_origin`'s host is also sent as part of the encrypted request.
//
// `ip_address_space` is the IPAddressSpace of the frame that's running the
// auction. It's used to create a ClientSecurityState that has a
// PrivateNetworkRequestPolicy that will block the signals fetch if the
// signals URL maps to an IP on a less public address space. The other members
// of ClientSecurityState use default values. Default values are safe since
// these are credentialless requests. Any data taken from the frame would also
// potentially be a leak.
//
// `script_origin` is the owner of the interest group the request is for. Used
// as the initiator for CORS.
//
// `compression_groups` is a map of all partitions in the request, indexed by
// compression group id. Virtual for tests.
virtual void FetchBiddingSignals(
DataDecoderManager& data_decoder_manager,
network::mojom::URLLoaderFactory* url_loader_factory,
FrameTreeNodeId frame_tree_node_id,
base::flat_set<std::string> devtools_auction_ids,
const url::Origin& main_frame_origin,
network::mojom::IPAddressSpace ip_address_space,
base::UnguessableToken network_partition_nonce,
const url::Origin& script_origin,
const GURL& trusted_bidding_signals_url,
const BiddingAndAuctionServerKey& bidding_and_auction_key,
const std::map<int, std::vector<BiddingPartition>>& compression_groups,
Callback callback);
// `data_decoder_manager` must outlive the fetcher.
//
// `frame_tree_node_id` and `devtools_auction_ids` are used to log events for
// devtools, if needed.
//
// `main_frame_origin` and `network_partition_nonce` are used to create an
// IsolationInfo identifying the network partition to use.
// `main_frame_origin`'s host is also sent as part of the encrypted request.
//
// `ip_address_space` is the IPAddressSpace of the frame that's running the
// auction. It's used to create a ClientSecurityState that has a
// PrivateNetworkRequestPolicy that will block the signals fetch if the
// signals URL maps to an IP on a less public address space. The other members
// of ClientSecurityState use default values. Default values are safe since
// these are credentialless requests. Any data taken from the frame would also
// potentially be a leak.
//
// `script_origin` is the seller for the auction. Used as the initiator for
// CORS.
//
// `compression_groups` is a map of all partitions in the request, indexed by
// compression group id. Virtual for tests.
virtual void FetchScoringSignals(
DataDecoderManager& data_decoder_manager,
network::mojom::URLLoaderFactory* url_loader_factory,
FrameTreeNodeId frame_tree_node_id,
base::flat_set<std::string> devtools_auction_ids,
const url::Origin& main_frame_origin,
network::mojom::IPAddressSpace ip_address_space,
base::UnguessableToken network_partition_nonce,
const url::Origin& script_origin,
const GURL& trusted_scoring_signals_url,
const BiddingAndAuctionServerKey& bidding_and_auction_key,
const std::map<int, std::vector<ScoringPartition>>& compression_groups,
Callback callback);
private:
// Encrypts `plaintext_body` using `bidding_and_auction_key`, and then creates
// a SimpleURLLoader and starts a request. Once the request body has been
// created, everything else (including response body parsing) is identical for
// bidding and scoring signals, as only the data inside compression groups is
// different for bidding and scoring signals, and that layer is not parsed by
// this class.
void EncryptRequestBodyAndStart(
DataDecoderManager& data_decoder_manager,
network::mojom::URLLoaderFactory* url_loader_factory,
InterestGroupAuctionFetchType fetch_type,
FrameTreeNodeId frame_tree_node_id,
base::flat_set<std::string> devtools_auction_ids,
const url::Origin& main_frame_origin,
network::mojom::IPAddressSpace ip_address_space,
base::UnguessableToken network_partition_nonce,
const url::Origin& script_origin,
const GURL& trusted_signals_url,
const BiddingAndAuctionServerKey& bidding_and_auction_key,
std::string plaintext_request_body,
Callback callback);
void OnRequestComplete(std::unique_ptr<std::string> response_body,
scoped_refptr<net::HttpResponseHeaders> headers,
std::optional<std::string> error);
void OnCborParsed(data_decoder::DataDecoder::ValueOrError value_or_error);
// Attempts to parse the base::Value result from having the DataDecoder parse
// the CBOR contents of the fetch.
SignalsFetchResult ParseDataDecoderResult(
data_decoder::DataDecoder::ValueOrError value_or_error);
// Attempts to parse a single compression group object.
// `compression_group_value` should be a value from the `compressionGroups`
// array of the parsed CBOR value. On success, returns a
// CompressionGroupResult and sets `compression_group_id` to the ID from the
// passed in value. On failure, leaves `compression_group_id` alone, and
// returns a string.
base::expected<CompressionGroupResult, std::string> ParseCompressionGroup(
base::Value compression_group_value,
int& compression_group_id);
// Returns a string error message, prefixing the passed in message with the
// URL.
std::string CreateError(const std::string& error_message);
// The URL being fetched. Cached for using in error strings.
GURL trusted_signals_url_;
Callback callback_;
std::unique_ptr<auction_worklet::AuctionDownloader> auction_downloader_;
// Context needed to decrypt the response. Initialized while encrypting the
// request body.
std::unique_ptr<quiche::ObliviousHttpRequest::Context> ohttp_context_;
// Used to parse the CBOR response. Created when fetch starts, to pre-warm the
// decoder process.
std::unique_ptr<DataDecoderManager::Handle> decoder_handle_;
// Compression scheme used by all compression groups. Populated when reading
// the response.
auction_worklet::mojom::TrustedSignalsCompressionScheme compression_scheme_ =
auction_worklet::mojom::TrustedSignalsCompressionScheme::kNone;
base::WeakPtrFactory<TrustedSignalsFetcher> weak_ptr_factory_{this};
};
} // namespace content
#endif // CONTENT_BROWSER_INTEREST_GROUP_TRUSTED_SIGNALS_FETCHER_H_