// Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef NET_REPORTING_REPORTING_CACHE_IMPL_H_ #define NET_REPORTING_REPORTING_CACHE_IMPL_H_ #include #include #include #include #include #include #include #include "base/containers/flat_map.h" #include "base/containers/flat_set.h" #include "base/containers/unique_ptr_adapters.h" #include "base/memory/raw_ptr.h" #include "base/sequence_checker.h" #include "base/time/time.h" #include "base/unguessable_token.h" #include "base/values.h" #include "net/base/isolation_info.h" #include "net/reporting/reporting_cache.h" #include "net/reporting/reporting_context.h" #include "net/reporting/reporting_endpoint.h" #include "net/reporting/reporting_header_parser.h" #include "net/reporting/reporting_report.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "url/gurl.h" #include "url/origin.h" namespace net { class ReportingCacheImpl : public ReportingCache { public: explicit ReportingCacheImpl(ReportingContext* context); ReportingCacheImpl(const ReportingCacheImpl&) = delete; ReportingCacheImpl& operator=(const ReportingCacheImpl&) = delete; ~ReportingCacheImpl() override; // ReportingCache implementation void AddReport(const absl::optional& reporting_source, const NetworkIsolationKey& network_isolation_key, const GURL& url, const std::string& user_agent, const std::string& group_name, const std::string& type, base::Value::Dict body, int depth, base::TimeTicks queued, int attempts) override; void GetReports( std::vector* reports_out) const override; base::Value GetReportsAsValue() const override; std::vector GetReportsToDeliver() override; std::vector GetReportsToDeliverForSource( const base::UnguessableToken& reporting_source) override; void ClearReportsPending( const std::vector& reports) override; void IncrementReportsAttempts( const std::vector& reports) override; base::flat_map> GetV1ReportingEndpointsByOrigin() const override; void IncrementEndpointDeliveries(const ReportingEndpointGroupKey& group_key, const GURL& url, int reports_delivered, bool successful) override; void SetExpiredSource( const base::UnguessableToken& reporting_source) override; const base::flat_set& GetExpiredSources() const override; void RemoveReports( const std::vector& reports) override; void RemoveReports(const std::vector& reports, bool delivery_success) override; void RemoveAllReports() override; size_t GetFullReportCountForTesting() const override; size_t GetReportCountWithStatusForTesting( ReportingReport::Status status) const override; bool IsReportPendingForTesting(const ReportingReport* report) const override; bool IsReportDoomedForTesting(const ReportingReport* report) const override; void OnParsedHeader( const NetworkIsolationKey& network_isolation_key, const url::Origin& origin, std::vector parsed_header) override; void OnParsedReportingEndpointsHeader( const base::UnguessableToken& reporting_source, const IsolationInfo& isolation_info, std::vector parsed_header) override; std::set GetAllOrigins() const override; void RemoveClient(const NetworkIsolationKey& network_isolation_key, const url::Origin& origin) override; void RemoveClientsForOrigin(const url::Origin& origin) override; void RemoveAllClients() override; void RemoveEndpointGroup(const ReportingEndpointGroupKey& group_key) override; void RemoveEndpointsForUrl(const GURL& url) override; void RemoveSourceAndEndpoints( const base::UnguessableToken& reporting_source) override; void AddClientsLoadedFromStore( std::vector loaded_endpoints, std::vector loaded_endpoint_groups) override; std::vector GetCandidateEndpointsForDelivery( const ReportingEndpointGroupKey& group_key) override; base::Value GetClientsAsValue() const override; size_t GetEndpointCount() const override; void Flush() override; ReportingEndpoint GetV1EndpointForTesting( const base::UnguessableToken& reporting_source, const std::string& endpoint_name) const override; ReportingEndpoint GetEndpointForTesting( const ReportingEndpointGroupKey& group_key, const GURL& url) const override; bool EndpointGroupExistsForTesting(const ReportingEndpointGroupKey& group_key, OriginSubdomains include_subdomains, base::Time expires) const override; bool ClientExistsForTesting(const NetworkIsolationKey& network_isolation_key, const url::Origin& origin) const override; size_t GetEndpointGroupCountForTesting() const override; size_t GetClientCountForTesting() const override; size_t GetReportingSourceCountForTesting() const override; void SetEndpointForTesting(const ReportingEndpointGroupKey& group_key, const GURL& url, OriginSubdomains include_subdomains, base::Time expires, int priority, int weight) override; void SetV1EndpointForTesting(const ReportingEndpointGroupKey& group_key, const base::UnguessableToken& reporting_source, const IsolationInfo& isolation_info, const GURL& url) override; IsolationInfo GetIsolationInfoForEndpoint( const ReportingEndpoint& endpoint) const override; private: // Represents the entire Report-To configuration for a (NIK, origin) pair. struct Client { Client(const NetworkIsolationKey& network_isolation_key, const url::Origin& origin); Client(const Client& other); Client(Client&& other); Client& operator=(const Client& other); Client& operator=(Client&& other); ~Client(); // NIK of the context associated with this client. Needed to prevent leaking // third party contexts across sites. NetworkIsolationKey network_isolation_key; // Origin that configured this client. url::Origin origin; // Total number of endpoints for this origin. Should stay in sync with the // sum of endpoint counts for all the groups within this client. size_t endpoint_count = 0; // Last time that any of the groups for this origin was accessed for a // delivery or updated via a new header. Should stay in sync with the latest // |last_used| of all the groups within this client. base::Time last_used; // Set of endpoint group names for this origin. std::set endpoint_group_names; }; using ReportSet = base::flat_set, base::UniquePtrComparator>; using ClientMap = std::multimap; using EndpointGroupMap = std::map; using EndpointMap = std::multimap; ReportSet::const_iterator FindReportToEvict() const; // Consistency-checks the entire data structure of clients, groups, and // endpoints, if DCHECK is on. The cached clients should pass this consistency // check after completely parsing a header (i.e. not after the intermediate // steps), and before and after any of the public methods that remove or // retrieve client info. Also calls |sequence_checker_| to DCHECK that we are // being called on a valid sequence. void ConsistencyCheckClients() const; // Helper methods for ConsistencyCheckClients(): #if DCHECK_IS_ON() // Returns number of endpoint groups found in |client|. size_t ConsistencyCheckClient(const std::string& domain, const Client& client) const; // Returns the number of endpoints found in |group|. size_t ConsistencyCheckEndpointGroup( const ReportingEndpointGroupKey& key, const CachedReportingEndpointGroup& group) const; void ConsistencyCheckEndpoint(const ReportingEndpointGroupKey& key, const ReportingEndpoint& endpoint, EndpointMap::const_iterator endpoint_it) const; #endif // DCHECK_IS_ON() // Finds iterator to the client with the given |network_isolation_key| and // |origin|, if one exists. Returns |clients_.end()| if none is found. ClientMap::iterator FindClientIt( const NetworkIsolationKey& network_isolation_key, const url::Origin& origin); // Overload that takes a ReportingEndpointGroupKey and finds the client // to which a group specified by the |group_key| would belong. The group name // of the key is ignored. ClientMap::iterator FindClientIt(const ReportingEndpointGroupKey& group_key); // Finds iterator to the endpoint group identified by |group_key| (origin and // name), if one exists. Returns |endpoint_groups_.end()| if none is found. EndpointGroupMap::iterator FindEndpointGroupIt( const ReportingEndpointGroupKey& group_key); // Finds iterator to the endpoint for the given |group_key| (origin and group // name) and |url|, if one exists. Returns |endpoints_.end()| if none is // found. EndpointMap::iterator FindEndpointIt( const ReportingEndpointGroupKey& group_key, const GURL& url); // Adds a new client, endpoint group, or endpoint to the cache, if none // exists. If one already exists, updates the existing entry to match the new // one. Returns iterator to newly added client. ClientMap::iterator AddOrUpdateClient(Client new_client); void AddOrUpdateEndpointGroup(CachedReportingEndpointGroup new_group); void AddOrUpdateEndpoint(ReportingEndpoint new_endpoint); // Remove all the endpoints configured for |origin| and |group| whose urls are // not in |endpoints_to_keep_urls|. Does not guarantee that all the endpoints // in |endpoints_to_keep_urls| exist in the cache for that group. void RemoveEndpointsInGroupOtherThan( const ReportingEndpointGroupKey& group_key, const std::set& endpoints_to_keep_urls); // Remove all the endpoint groups for the NIK and origin whose names are not // in |groups_to_keep_names|. Does not guarantee that all the groups in // |groups_to_keep_names| exist in the cache for that client. void RemoveEndpointGroupsForClientOtherThan( const NetworkIsolationKey& network_isolation_key, const url::Origin& origin, const std::set& groups_to_keep_names); // Gets the endpoints in the given group. std::vector GetEndpointsInGroup( const ReportingEndpointGroupKey& group_key) const; // Gets the number of endpoints for the given origin and group. size_t GetEndpointCountInGroup( const ReportingEndpointGroupKey& group_key) const; // Updates the last_used time for the given origin and endpoint group. void MarkEndpointGroupAndClientUsed(ClientMap::iterator client_it, EndpointGroupMap::iterator group_it, base::Time now); // Removes the endpoint at the given iterator, which must exist in the cache. // Also takes iterators to the client and endpoint group to avoid repeated // lookups. May cause the client and/or group to be removed if they become // empty, which would invalidate those iterators. // Returns the iterator following the endpoint removed, or absl::nullopt if // either of |group_it| or |client_it| were invalidated. (If |client_it| is // invalidated, then so must |group_it|). absl::optional RemoveEndpointInternal( ClientMap::iterator client_it, EndpointGroupMap::iterator group_it, EndpointMap::iterator endpoint_it); // Removes the endpoint group at the given iterator (which must exist in the // cache). Also takes iterator to the client to avoid repeated lookups. May // cause the client to be removed if it becomes empty, which would // invalidate |client_it|. If |num_endpoints_removed| is not null, then // |*num_endpoints_removed| is incremented by the number of endpoints // removed. // Returns the iterator following the endpoint group removed, or absl::nullopt // if |client_it| was invalidated. absl::optional RemoveEndpointGroupInternal( ClientMap::iterator client_it, EndpointGroupMap::iterator group_it, size_t* num_endpoints_removed = nullptr); // Removes the client at the given iterator (which must exist in the cache), // along with all of its endpoint groups and endpoints. Invalidates // |client_it|. // Returns the iterator following the client removed. ClientMap::iterator RemoveClientInternal(ClientMap::iterator client_it); // Evict endpoints from the specified client and globally, if necessary to // obey the per-client and global endpoint limits set in the ReportingPolicy. // // To evict from a client: First evicts any stale or expired groups for that // origin. If that removes enough endpoints, then stop. Otherwise, find the // stalest group (which has not been accessed for a delivery in the longest // time) with the most endpoints, and evict the least important endpoints from // that group. // To evict globally: Find the stalest client with the most endpoints and do // the above. void EnforcePerClientAndGlobalEndpointLimits(ClientMap::iterator client_it); // Evicts endpoints from a client until it has evicted |endpoints_to_evict| // endpoints. First tries to remove expired and stale groups. If that fails to // satisfy the limit, finds the stalest group with the most endpoints and // evicts the least important endpoints from it. void EvictEndpointsFromClient(ClientMap::iterator client_it, size_t endpoints_to_evict); // Evicts the least important endpoint from a group (the endpoint with lowest // priority and lowest weight). May cause the group and/or client to be // deleted and the iterators invalidated. void EvictEndpointFromGroup(ClientMap::iterator client_it, EndpointGroupMap::iterator group_it); // Removes all expired or stale groups from the given client. May delete the // client and invalidate |client_it| if it becomes empty. // Increments |*num_endpoints_removed| by the number of endpoints removed. // Returns true if |client_it| was invalidated. bool RemoveExpiredOrStaleGroups(ClientMap::iterator client_it, size_t* num_endpoints_removed); // Adds/removes (if it exists) |endpoint_it| from |endpoint_its_by_url_|. void AddEndpointItToIndex(EndpointMap::iterator endpoint_it); void RemoveEndpointItFromIndex(EndpointMap::iterator endpoint_it); // Helper method for IncrementEndpointDeliveries ReportingEndpoint::Statistics* GetEndpointStats( const ReportingEndpointGroupKey& group_key, const GURL& url); // Helper methods for GetClientsAsValue(). base::Value GetClientAsValue(const Client& client) const; base::Value GetEndpointGroupAsValue( const CachedReportingEndpointGroup& group) const; base::Value GetEndpointAsValue(const ReportingEndpoint& endpoint) const; // Convenience methods for fetching things from the context_. const base::Clock& clock() const { return context_->clock(); } const base::TickClock& tick_clock() const { return context_->tick_clock(); } PersistentReportingStore* store() { return context_->store(); } raw_ptr context_; // Reports that have not yet been successfully uploaded. ReportSet reports_; // Reporting API V0 Cache: // The |clients_|, |endpoint_groups_| and |endpoints_| members all hold // endpoint group configuration for the V0 API. These endpoint groups are // configured through the Report-To HTTP header, and are currently used for // both document and network reports. // Map of clients for all configured origins and NIKs, keyed on domain name // (there may be multiple NIKs and origins per domain name). ClientMap clients_; // Map of endpoint groups, keyed on origin and group name. Keys and values // must only contain V0 endpoint group keys. EndpointGroupMap endpoint_groups_; // Map of endpoints, keyed on origin and group name (there may be multiple // endpoints for a given origin and group, with different urls). Keys must // only contain V0 endpoint group keys. EndpointMap endpoints_; // Index of endpoints stored in |endpoints_| keyed on URL, for easier lookup // during RemoveEndpointsForUrl(). Should stay in sync with |endpoints_|. std::multimap endpoint_its_by_url_; // Reporting API V1 Cache: // The `document_endpoints_` member holds endpoint configuration for the V1 // API, configured through the Reporting-Endpoints HTTP header. These // endpoints are strongly associated with the resource which configured them, // and are only used for document reports. // Map of endpoints for each reporting source, keyed on the reporting source // token. This contains only V1 document endpoints. std::map> document_endpoints_; // Isolation info for each reporting source. Used for determining credentials // to send when delivering reports. This contains only V1 document endpoints. std::map isolation_info_; // Reporting source tokens representing sources which have been destroyed. // The configuration in `document_endpoints_` and `isolation_info_` for these // sources can be removed once all outstanding reports are delivered (or // expired). base::flat_set expired_sources_; SEQUENCE_CHECKER(sequence_checker_); }; } // namespace net #endif // NET_REPORTING_REPORTING_CACHE_IMPL_H_