xref: /aosp_15_r20/external/cronet/net/reporting/reporting_cache_impl.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/reporting/reporting_cache_impl.h"
6 
7 #include <algorithm>
8 #include <optional>
9 #include <unordered_map>
10 #include <unordered_set>
11 #include <utility>
12 
13 #include "base/containers/contains.h"
14 #include "base/memory/raw_ptr.h"
15 #include "base/ranges/algorithm.h"
16 #include "base/stl_util.h"
17 #include "base/time/clock.h"
18 #include "base/time/tick_clock.h"
19 #include "net/base/network_anonymization_key.h"
20 #include "net/base/url_util.h"
21 #include "net/log/net_log.h"
22 
23 namespace net {
24 
ReportingCacheImpl(ReportingContext * context)25 ReportingCacheImpl::ReportingCacheImpl(ReportingContext* context)
26     : context_(context) {
27   DCHECK(context_);
28 }
29 
30 ReportingCacheImpl::~ReportingCacheImpl() = default;
31 
AddReport(const std::optional<base::UnguessableToken> & reporting_source,const NetworkAnonymizationKey & network_anonymization_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)32 void ReportingCacheImpl::AddReport(
33     const std::optional<base::UnguessableToken>& reporting_source,
34     const NetworkAnonymizationKey& network_anonymization_key,
35     const GURL& url,
36     const std::string& user_agent,
37     const std::string& group_name,
38     const std::string& type,
39     base::Value::Dict body,
40     int depth,
41     base::TimeTicks queued,
42     int attempts) {
43   // If |reporting_source| is present, it must not be empty.
44   DCHECK(!(reporting_source.has_value() && reporting_source->is_empty()));
45   // Drop the report if its reporting source is already marked as expired.
46   // This should only happen in testing as reporting source is only marked
47   // expiring when the document that can generate report is gone.
48   if (reporting_source.has_value() &&
49       expired_sources_.find(reporting_source.value()) !=
50           expired_sources_.end()) {
51     return;
52   }
53 
54   auto report = std::make_unique<ReportingReport>(
55       reporting_source, network_anonymization_key, url, user_agent, group_name,
56       type, std::move(body), depth, queued, attempts);
57 
58   auto inserted = reports_.insert(std::move(report));
59   DCHECK(inserted.second);
60 
61   if (reports_.size() > context_->policy().max_report_count) {
62     // There should be at most one extra report (the one added above).
63     DCHECK_EQ(context_->policy().max_report_count + 1, reports_.size());
64     ReportSet::const_iterator to_evict = FindReportToEvict();
65     DCHECK(to_evict != reports_.end());
66     // The newly-added report isn't pending, so even if all other reports are
67     // pending, the cache should have a report to evict.
68     DCHECK(!to_evict->get()->IsUploadPending());
69     if (to_evict != inserted.first) {
70       context_->NotifyReportAdded(inserted.first->get());
71     }
72     reports_.erase(to_evict);
73   } else {
74     context_->NotifyReportAdded(inserted.first->get());
75   }
76 
77   context_->NotifyCachedReportsUpdated();
78 }
79 
GetReports(std::vector<raw_ptr<const ReportingReport,VectorExperimental>> * reports_out) const80 void ReportingCacheImpl::GetReports(
81     std::vector<raw_ptr<const ReportingReport, VectorExperimental>>*
82         reports_out) const {
83   reports_out->clear();
84   for (const auto& report : reports_) {
85     if (report->status != ReportingReport::Status::DOOMED &&
86         report->status != ReportingReport::Status::SUCCESS) {
87       reports_out->push_back(report.get());
88     }
89   }
90 }
91 
GetReportsAsValue() const92 base::Value ReportingCacheImpl::GetReportsAsValue() const {
93   // Sort all unsent reports by origin and timestamp.
94   std::vector<const ReportingReport*> sorted_reports;
95   sorted_reports.reserve(reports_.size());
96   for (const auto& report : reports_) {
97     sorted_reports.push_back(report.get());
98   }
99   std::sort(sorted_reports.begin(), sorted_reports.end(),
100             [](const ReportingReport* report1, const ReportingReport* report2) {
101               return std::tie(report1->queued, report1->url) <
102                      std::tie(report2->queued, report2->url);
103             });
104 
105   base::Value::List report_list;
106   for (const ReportingReport* report : sorted_reports) {
107     base::Value::Dict report_dict;
108     report_dict.Set("network_anonymization_key",
109                     report->network_anonymization_key.ToDebugString());
110     report_dict.Set("url", report->url.spec());
111     report_dict.Set("group", report->group);
112     report_dict.Set("type", report->type);
113     report_dict.Set("depth", report->depth);
114     report_dict.Set("queued", NetLog::TickCountToString(report->queued));
115     report_dict.Set("attempts", report->attempts);
116     report_dict.Set("body", report->body.Clone());
117     switch (report->status) {
118       case ReportingReport::Status::DOOMED:
119         report_dict.Set("status", "doomed");
120         break;
121       case ReportingReport::Status::PENDING:
122         report_dict.Set("status", "pending");
123         break;
124       case ReportingReport::Status::QUEUED:
125         report_dict.Set("status", "queued");
126         break;
127       case ReportingReport::Status::SUCCESS:
128         report_dict.Set("status", "success");
129         break;
130     }
131     report_list.Append(std::move(report_dict));
132   }
133   return base::Value(std::move(report_list));
134 }
135 
136 std::vector<raw_ptr<const ReportingReport, VectorExperimental>>
GetReportsToDeliver()137 ReportingCacheImpl::GetReportsToDeliver() {
138   std::vector<raw_ptr<const ReportingReport, VectorExperimental>> reports_out;
139   for (const auto& report : reports_) {
140     if (report->IsUploadPending())
141       continue;
142     report->status = ReportingReport::Status::PENDING;
143     context_->NotifyReportUpdated(report.get());
144     reports_out.push_back(report.get());
145   }
146   return reports_out;
147 }
148 
149 std::vector<raw_ptr<const ReportingReport, VectorExperimental>>
GetReportsToDeliverForSource(const base::UnguessableToken & reporting_source)150 ReportingCacheImpl::GetReportsToDeliverForSource(
151     const base::UnguessableToken& reporting_source) {
152   DCHECK(!reporting_source.is_empty());
153   std::vector<raw_ptr<const ReportingReport, VectorExperimental>> reports_out;
154   for (const auto& report : reports_) {
155     if (report->reporting_source == reporting_source) {
156       if (report->IsUploadPending())
157         continue;
158       report->status = ReportingReport::Status::PENDING;
159       context_->NotifyReportUpdated(report.get());
160       reports_out.push_back(report.get());
161     }
162   }
163   return reports_out;
164 }
165 
ClearReportsPending(const std::vector<raw_ptr<const ReportingReport,VectorExperimental>> & reports)166 void ReportingCacheImpl::ClearReportsPending(
167     const std::vector<raw_ptr<const ReportingReport, VectorExperimental>>&
168         reports) {
169   for (const ReportingReport* report : reports) {
170     auto it = reports_.find(report);
171     DCHECK(it != reports_.end());
172     if (it->get()->status == ReportingReport::Status::DOOMED ||
173         it->get()->status == ReportingReport::Status::SUCCESS) {
174       reports_.erase(it);
175     } else {
176       DCHECK_EQ(ReportingReport::Status::PENDING, it->get()->status);
177       it->get()->status = ReportingReport::Status::QUEUED;
178       context_->NotifyReportUpdated(it->get());
179     }
180   }
181 }
182 
IncrementReportsAttempts(const std::vector<raw_ptr<const ReportingReport,VectorExperimental>> & reports)183 void ReportingCacheImpl::IncrementReportsAttempts(
184     const std::vector<raw_ptr<const ReportingReport, VectorExperimental>>&
185         reports) {
186   for (const ReportingReport* report : reports) {
187     auto it = reports_.find(report);
188     DCHECK(it != reports_.end());
189     it->get()->attempts++;
190     context_->NotifyReportUpdated(it->get());
191   }
192 
193   context_->NotifyCachedReportsUpdated();
194 }
195 
FilterEndpointsByOrigin(const std::map<base::UnguessableToken,std::vector<ReportingEndpoint>> & document_endpoints,const url::Origin & origin)196 std::vector<ReportingEndpoint> FilterEndpointsByOrigin(
197     const std::map<base::UnguessableToken, std::vector<ReportingEndpoint>>&
198         document_endpoints,
199     const url::Origin& origin) {
200   std::set<std::string> group_names;
201   std::vector<ReportingEndpoint> result;
202   for (const auto& token_and_endpoints : document_endpoints) {
203     for (const auto& endpoint : token_and_endpoints.second) {
204       if (endpoint.group_key.origin == origin) {
205         if (group_names.insert(endpoint.group_key.group_name).second) {
206           // Push the endpoint only when the insertion succeeds.
207           result.push_back(endpoint);
208         }
209       }
210     }
211   }
212   return result;
213 }
214 
215 base::flat_map<url::Origin, std::vector<ReportingEndpoint>>
GetV1ReportingEndpointsByOrigin() const216 ReportingCacheImpl::GetV1ReportingEndpointsByOrigin() const {
217   base::flat_map<url::Origin, std::vector<ReportingEndpoint>> result;
218   base::flat_map<url::Origin, base::flat_set<std::string>> group_name_helper;
219   for (const auto& token_and_endpoints : document_endpoints_) {
220     for (const auto& endpoint : token_and_endpoints.second) {
221       auto origin = endpoint.group_key.origin;
222       if (result.count(origin)) {
223         if (group_name_helper.at(origin)
224                 .insert(endpoint.group_key.group_name)
225                 .second) {
226           // Push the endpoint only when the insertion succeeds.
227           result.at(origin).push_back(endpoint);
228         }
229       } else {
230         std::vector<ReportingEndpoint> endpoints_for_origin;
231         endpoints_for_origin.push_back(endpoint);
232         result.emplace(origin, endpoints_for_origin);
233 
234         base::flat_set<std::string> group_names;
235         group_names.insert(endpoint.group_key.group_name);
236         group_name_helper.emplace(origin, group_names);
237       }
238     }
239   }
240   return result;
241 }
242 
GetEndpointStats(const ReportingEndpointGroupKey & group_key,const GURL & url)243 ReportingEndpoint::Statistics* ReportingCacheImpl::GetEndpointStats(
244     const ReportingEndpointGroupKey& group_key,
245     const GURL& url) {
246   if (group_key.IsDocumentEndpoint()) {
247     const auto document_endpoints_source_it =
248         document_endpoints_.find(group_key.reporting_source.value());
249     // The reporting source may have been removed while the upload was in
250     // progress. In that case, we no longer care about the stats for the
251     // endpoint associated with the destroyed reporting source.
252     if (document_endpoints_source_it == document_endpoints_.end())
253       return nullptr;
254     const auto document_endpoint_it =
255         base::ranges::find(document_endpoints_source_it->second, group_key,
256                            &ReportingEndpoint::group_key);
257     // The endpoint may have been removed while the upload was in progress. In
258     // that case, we no longer care about the stats for the removed endpoint.
259     if (document_endpoint_it == document_endpoints_source_it->second.end())
260       return nullptr;
261     return &document_endpoint_it->stats;
262   } else {
263     EndpointMap::iterator endpoint_it = FindEndpointIt(group_key, url);
264     // The endpoint may have been removed while the upload was in progress. In
265     // that case, we no longer care about the stats for the removed endpoint.
266     if (endpoint_it == endpoints_.end())
267       return nullptr;
268     return &endpoint_it->second.stats;
269   }
270 }
271 
IncrementEndpointDeliveries(const ReportingEndpointGroupKey & group_key,const GURL & url,int reports_delivered,bool successful)272 void ReportingCacheImpl::IncrementEndpointDeliveries(
273     const ReportingEndpointGroupKey& group_key,
274     const GURL& url,
275     int reports_delivered,
276     bool successful) {
277   ReportingEndpoint::Statistics* stats = GetEndpointStats(group_key, url);
278   if (!stats)
279     return;
280 
281   ++stats->attempted_uploads;
282   stats->attempted_reports += reports_delivered;
283   if (successful) {
284     ++stats->successful_uploads;
285     stats->successful_reports += reports_delivered;
286   }
287 }
288 
SetExpiredSource(const base::UnguessableToken & reporting_source)289 void ReportingCacheImpl::SetExpiredSource(
290     const base::UnguessableToken& reporting_source) {
291   DCHECK(!reporting_source.is_empty());
292   expired_sources_.insert(reporting_source);
293 }
294 
295 const base::flat_set<base::UnguessableToken>&
GetExpiredSources() const296 ReportingCacheImpl::GetExpiredSources() const {
297   return expired_sources_;
298 }
299 
RemoveReports(const std::vector<raw_ptr<const ReportingReport,VectorExperimental>> & reports)300 void ReportingCacheImpl::RemoveReports(
301     const std::vector<raw_ptr<const ReportingReport, VectorExperimental>>&
302         reports) {
303   RemoveReports(reports, false);
304 }
305 
RemoveReports(const std::vector<raw_ptr<const ReportingReport,VectorExperimental>> & reports,bool delivery_success)306 void ReportingCacheImpl::RemoveReports(
307     const std::vector<raw_ptr<const ReportingReport, VectorExperimental>>&
308         reports,
309     bool delivery_success) {
310   for (const ReportingReport* report : reports) {
311     auto it = reports_.find(report);
312     DCHECK(it != reports_.end());
313 
314     switch (it->get()->status) {
315       case ReportingReport::Status::DOOMED:
316         if (delivery_success) {
317           it->get()->status = ReportingReport::Status::SUCCESS;
318           context_->NotifyReportUpdated(it->get());
319         }
320         break;
321       case ReportingReport::Status::PENDING:
322         it->get()->status = delivery_success ? ReportingReport::Status::SUCCESS
323                                              : ReportingReport::Status::DOOMED;
324         context_->NotifyReportUpdated(it->get());
325         break;
326       case ReportingReport::Status::QUEUED:
327         it->get()->status = delivery_success ? ReportingReport::Status::SUCCESS
328                                              : ReportingReport::Status::DOOMED;
329         context_->NotifyReportUpdated(it->get());
330         reports_.erase(it);
331         break;
332       case ReportingReport::Status::SUCCESS:
333         break;
334     }
335   }
336   context_->NotifyCachedReportsUpdated();
337 }
338 
RemoveAllReports()339 void ReportingCacheImpl::RemoveAllReports() {
340   std::vector<raw_ptr<const ReportingReport, VectorExperimental>>
341       reports_to_remove;
342   GetReports(&reports_to_remove);
343   RemoveReports(reports_to_remove);
344 }
345 
GetFullReportCountForTesting() const346 size_t ReportingCacheImpl::GetFullReportCountForTesting() const {
347   return reports_.size();
348 }
349 
GetReportCountWithStatusForTesting(ReportingReport::Status status) const350 size_t ReportingCacheImpl::GetReportCountWithStatusForTesting(
351     ReportingReport::Status status) const {
352   size_t count = 0;
353   for (const auto& report : reports_) {
354     if (report->status == status)
355       ++count;
356   }
357   return count;
358 }
359 
IsReportPendingForTesting(const ReportingReport * report) const360 bool ReportingCacheImpl::IsReportPendingForTesting(
361     const ReportingReport* report) const {
362   DCHECK(report);
363   DCHECK(reports_.find(report) != reports_.end());
364   return report->IsUploadPending();
365 }
366 
IsReportDoomedForTesting(const ReportingReport * report) const367 bool ReportingCacheImpl::IsReportDoomedForTesting(
368     const ReportingReport* report) const {
369   DCHECK(report);
370   DCHECK(reports_.find(report) != reports_.end());
371   return report->status == ReportingReport::Status::DOOMED ||
372          report->status == ReportingReport::Status::SUCCESS;
373 }
374 
OnParsedHeader(const NetworkAnonymizationKey & network_anonymization_key,const url::Origin & origin,std::vector<ReportingEndpointGroup> parsed_header)375 void ReportingCacheImpl::OnParsedHeader(
376     const NetworkAnonymizationKey& network_anonymization_key,
377     const url::Origin& origin,
378     std::vector<ReportingEndpointGroup> parsed_header) {
379   ConsistencyCheckClients();
380 
381   Client new_client(network_anonymization_key, origin);
382   base::Time now = clock().Now();
383   new_client.last_used = now;
384 
385   std::map<ReportingEndpointGroupKey, std::set<GURL>> endpoints_per_group;
386 
387   for (const auto& parsed_endpoint_group : parsed_header) {
388     new_client.endpoint_group_names.insert(
389         parsed_endpoint_group.group_key.group_name);
390 
391     // Creates an endpoint group and sets its |last_used| to |now|.
392     CachedReportingEndpointGroup new_group(parsed_endpoint_group, now);
393 
394     // Consistency check: the new client should have the same NAK and origin as
395     // all groups parsed from this header.
396     DCHECK(new_group.group_key.network_anonymization_key ==
397            new_client.network_anonymization_key);
398     DCHECK_EQ(new_group.group_key.origin, new_client.origin);
399 
400     for (const auto& parsed_endpoint_info : parsed_endpoint_group.endpoints) {
401       endpoints_per_group[new_group.group_key].insert(parsed_endpoint_info.url);
402       ReportingEndpoint new_endpoint(new_group.group_key,
403                                      std::move(parsed_endpoint_info));
404       AddOrUpdateEndpoint(std::move(new_endpoint));
405     }
406 
407     AddOrUpdateEndpointGroup(std::move(new_group));
408   }
409 
410   // Compute the total endpoint count for this origin. We can't just count the
411   // number of endpoints per group because there may be duplicate endpoint URLs,
412   // which we ignore. See http://crbug.com/983000 for discussion.
413   // TODO(crbug.com/983000): Allow duplicate endpoint URLs.
414   for (const auto& group_key_and_endpoint_set : endpoints_per_group) {
415     new_client.endpoint_count += group_key_and_endpoint_set.second.size();
416 
417     // Remove endpoints that may have been previously configured for this group,
418     // but which were not specified in the current header.
419     // This must be done all at once after all the groups in the header have
420     // been processed, rather than after each individual group, otherwise
421     // headers with multiple groups of the same name will clobber previous parts
422     // of themselves. See crbug.com/1116529.
423     RemoveEndpointsInGroupOtherThan(group_key_and_endpoint_set.first,
424                                     group_key_and_endpoint_set.second);
425   }
426 
427   // Remove endpoint groups that may have been configured for an existing client
428   // for |origin|, but which are not specified in the current header.
429   RemoveEndpointGroupsForClientOtherThan(network_anonymization_key, origin,
430                                          new_client.endpoint_group_names);
431 
432   EnforcePerClientAndGlobalEndpointLimits(
433       AddOrUpdateClient(std::move(new_client)));
434   ConsistencyCheckClients();
435 
436   context_->NotifyCachedClientsUpdated();
437 }
438 
RemoveSourceAndEndpoints(const base::UnguessableToken & reporting_source)439 void ReportingCacheImpl::RemoveSourceAndEndpoints(
440     const base::UnguessableToken& reporting_source) {
441   DCHECK(!reporting_source.is_empty());
442   // Sanity checks: The source must be in the list of expired sources, and
443   // there must be no more cached reports for it (except reports already marked
444   // as doomed, as they will be garbage collected soon).
445   DCHECK(expired_sources_.contains(reporting_source));
446   DCHECK(
447       base::ranges::none_of(reports_, [reporting_source](const auto& report) {
448         return report->reporting_source == reporting_source &&
449                report->status != ReportingReport::Status::DOOMED &&
450                report->status != ReportingReport::Status::SUCCESS;
451       }));
452   url::Origin origin;
453   if (document_endpoints_.count(reporting_source) > 0) {
454     origin = document_endpoints_.at(reporting_source)[0].group_key.origin;
455   }
456   document_endpoints_.erase(reporting_source);
457   isolation_info_.erase(reporting_source);
458   expired_sources_.erase(reporting_source);
459   context_->NotifyEndpointsUpdatedForOrigin(
460       FilterEndpointsByOrigin(document_endpoints_, origin));
461 }
462 
OnParsedReportingEndpointsHeader(const base::UnguessableToken & reporting_source,const IsolationInfo & isolation_info,std::vector<ReportingEndpoint> endpoints)463 void ReportingCacheImpl::OnParsedReportingEndpointsHeader(
464     const base::UnguessableToken& reporting_source,
465     const IsolationInfo& isolation_info,
466     std::vector<ReportingEndpoint> endpoints) {
467   DCHECK(!reporting_source.is_empty());
468   DCHECK(!endpoints.empty());
469   DCHECK_EQ(0u, document_endpoints_.count(reporting_source));
470   DCHECK_EQ(0u, isolation_info_.count(reporting_source));
471   url::Origin origin = endpoints[0].group_key.origin;
472   document_endpoints_.insert({reporting_source, std::move(endpoints)});
473   isolation_info_.insert({reporting_source, isolation_info});
474   context_->NotifyEndpointsUpdatedForOrigin(
475       FilterEndpointsByOrigin(document_endpoints_, origin));
476 }
477 
GetAllOrigins() const478 std::set<url::Origin> ReportingCacheImpl::GetAllOrigins() const {
479   ConsistencyCheckClients();
480   std::set<url::Origin> origins_out;
481   for (const auto& domain_and_client : clients_) {
482     origins_out.insert(domain_and_client.second.origin);
483   }
484   return origins_out;
485 }
486 
RemoveClient(const NetworkAnonymizationKey & network_anonymization_key,const url::Origin & origin)487 void ReportingCacheImpl::RemoveClient(
488     const NetworkAnonymizationKey& network_anonymization_key,
489     const url::Origin& origin) {
490   ConsistencyCheckClients();
491   ClientMap::iterator client_it =
492       FindClientIt(network_anonymization_key, origin);
493   if (client_it == clients_.end())
494     return;
495   RemoveClientInternal(client_it);
496   ConsistencyCheckClients();
497   context_->NotifyCachedClientsUpdated();
498 }
499 
RemoveClientsForOrigin(const url::Origin & origin)500 void ReportingCacheImpl::RemoveClientsForOrigin(const url::Origin& origin) {
501   ConsistencyCheckClients();
502   std::string domain = origin.host();
503   const auto domain_range = clients_.equal_range(domain);
504   ClientMap::iterator it = domain_range.first;
505   while (it != domain_range.second) {
506     if (it->second.origin == origin) {
507       it = RemoveClientInternal(it);
508       continue;
509     }
510     ++it;
511   }
512   ConsistencyCheckClients();
513   context_->NotifyCachedClientsUpdated();
514 }
515 
RemoveAllClients()516 void ReportingCacheImpl::RemoveAllClients() {
517   ConsistencyCheckClients();
518 
519   auto remove_it = clients_.begin();
520   while (remove_it != clients_.end()) {
521     remove_it = RemoveClientInternal(remove_it);
522   }
523 
524   DCHECK(clients_.empty());
525   DCHECK(endpoint_groups_.empty());
526   DCHECK(endpoints_.empty());
527   DCHECK(endpoint_its_by_url_.empty());
528 
529   ConsistencyCheckClients();
530   context_->NotifyCachedClientsUpdated();
531 }
532 
RemoveEndpointGroup(const ReportingEndpointGroupKey & group_key)533 void ReportingCacheImpl::RemoveEndpointGroup(
534     const ReportingEndpointGroupKey& group_key) {
535   ConsistencyCheckClients();
536   EndpointGroupMap::iterator group_it = FindEndpointGroupIt(group_key);
537   if (group_it == endpoint_groups_.end())
538     return;
539   ClientMap::iterator client_it = FindClientIt(group_key);
540   DCHECK(client_it != clients_.end());
541 
542   RemoveEndpointGroupInternal(client_it, group_it);
543   ConsistencyCheckClients();
544   context_->NotifyCachedClientsUpdated();
545 }
546 
RemoveEndpointsForUrl(const GURL & url)547 void ReportingCacheImpl::RemoveEndpointsForUrl(const GURL& url) {
548   ConsistencyCheckClients();
549 
550   auto url_range = endpoint_its_by_url_.equal_range(url);
551   if (url_range.first == url_range.second)
552     return;
553 
554   // Make a copy of the EndpointMap::iterators matching |url|, to avoid deleting
555   // while iterating
556   std::vector<EndpointMap::iterator> endpoint_its_to_remove;
557   for (auto index_it = url_range.first; index_it != url_range.second;
558        ++index_it) {
559     endpoint_its_to_remove.push_back(index_it->second);
560   }
561   DCHECK_GT(endpoint_its_to_remove.size(), 0u);
562 
563   // Delete from the index, since we have the |url_range| already. This saves
564   // us from having to remove them one by one, which would involve
565   // iterating over the |url_range| on each call to RemoveEndpointInternal().
566   endpoint_its_by_url_.erase(url_range.first, url_range.second);
567 
568   for (EndpointMap::iterator endpoint_it : endpoint_its_to_remove) {
569     DCHECK(endpoint_it->second.info.url == url);
570     const ReportingEndpointGroupKey& group_key = endpoint_it->first;
571     ClientMap::iterator client_it = FindClientIt(group_key);
572     DCHECK(client_it != clients_.end());
573     EndpointGroupMap::iterator group_it = FindEndpointGroupIt(group_key);
574     DCHECK(group_it != endpoint_groups_.end());
575     RemoveEndpointInternal(client_it, group_it, endpoint_it);
576   }
577 
578   ConsistencyCheckClients();
579   context_->NotifyCachedClientsUpdated();
580 }
581 
582 // Reconstruct an Client from the loaded endpoint groups, and add the
583 // loaded endpoints and endpoint groups into the cache.
AddClientsLoadedFromStore(std::vector<ReportingEndpoint> loaded_endpoints,std::vector<CachedReportingEndpointGroup> loaded_endpoint_groups)584 void ReportingCacheImpl::AddClientsLoadedFromStore(
585     std::vector<ReportingEndpoint> loaded_endpoints,
586     std::vector<CachedReportingEndpointGroup> loaded_endpoint_groups) {
587   DCHECK(context_->IsClientDataPersisted());
588 
589   std::sort(loaded_endpoints.begin(), loaded_endpoints.end(),
590             [](const ReportingEndpoint& a, const ReportingEndpoint& b) -> bool {
591               return a.group_key < b.group_key;
592             });
593   std::sort(loaded_endpoint_groups.begin(), loaded_endpoint_groups.end(),
594             [](const CachedReportingEndpointGroup& a,
595                const CachedReportingEndpointGroup& b) -> bool {
596               return a.group_key < b.group_key;
597             });
598 
599   // If using a persistent store, cache should be empty before loading finishes.
600   DCHECK(clients_.empty());
601   DCHECK(endpoint_groups_.empty());
602   DCHECK(endpoints_.empty());
603   DCHECK(endpoint_its_by_url_.empty());
604 
605   // |loaded_endpoints| and |loaded_endpoint_groups| should both be sorted by
606   // origin and group name.
607   auto endpoints_it = loaded_endpoints.begin();
608   auto endpoint_groups_it = loaded_endpoint_groups.begin();
609 
610   std::optional<Client> client;
611 
612   while (endpoint_groups_it != loaded_endpoint_groups.end() &&
613          endpoints_it != loaded_endpoints.end()) {
614     const CachedReportingEndpointGroup& group = *endpoint_groups_it;
615     const ReportingEndpointGroupKey& group_key = group.group_key;
616 
617     // These things should probably never happen:
618     if (group_key < endpoints_it->group_key) {
619       // This endpoint group has no associated endpoints, so move on to the next
620       // endpoint group.
621       ++endpoint_groups_it;
622       continue;
623     } else if (group_key > endpoints_it->group_key) {
624       // This endpoint has no associated endpoint group, so move on to the next
625       // endpoint.
626       ++endpoints_it;
627       continue;
628     }
629 
630     DCHECK_EQ(group_key, endpoints_it->group_key);
631 
632     size_t cur_group_endpoints_count = 0;
633 
634     // Insert the endpoints corresponding to this group.
635     while (endpoints_it != loaded_endpoints.end() &&
636            endpoints_it->group_key == group_key) {
637       if (FindEndpointIt(group_key, endpoints_it->info.url) !=
638           endpoints_.end()) {
639         // This endpoint is duplicated in the store, so discard it and move on
640         // to the next endpoint. This should not happen unless the store is
641         // corrupted.
642         ++endpoints_it;
643         continue;
644       }
645       EndpointMap::iterator inserted =
646           endpoints_.emplace(group_key, std::move(*endpoints_it));
647       endpoint_its_by_url_.emplace(inserted->second.info.url, inserted);
648       ++cur_group_endpoints_count;
649       ++endpoints_it;
650     }
651 
652     if (!client ||
653         client->network_anonymization_key !=
654             group_key.network_anonymization_key ||
655         client->origin != group_key.origin) {
656       // Store the old client and start a new one.
657       if (client) {
658         ClientMap::iterator client_it =
659             clients_.emplace(client->origin.host(), std::move(*client));
660         EnforcePerClientAndGlobalEndpointLimits(client_it);
661       }
662       DCHECK(FindClientIt(group_key) == clients_.end());
663       client = std::make_optional(
664           Client(group_key.network_anonymization_key, group_key.origin));
665     }
666     DCHECK(client.has_value());
667     client->endpoint_group_names.insert(group_key.group_name);
668     client->endpoint_count += cur_group_endpoints_count;
669     client->last_used = std::max(client->last_used, group.last_used);
670 
671     endpoint_groups_.emplace(group_key, std::move(group));
672 
673     ++endpoint_groups_it;
674   }
675 
676   if (client) {
677     DCHECK(FindClientIt(client->network_anonymization_key, client->origin) ==
678            clients_.end());
679     ClientMap::iterator client_it =
680         clients_.emplace(client->origin.host(), std::move(*client));
681     EnforcePerClientAndGlobalEndpointLimits(client_it);
682   }
683 
684   ConsistencyCheckClients();
685 }
686 
687 // Until the V0 Reporting API is deprecated and removed, this method needs to
688 // handle endpoint groups configured by both the V0 Report-To header, which are
689 // persisted and used by any resource on the origin which defined them, as well
690 // as the V1 Reporting-Endpoints header, which defines ephemeral endpoints
691 // which can only be used by the resource which defines them.
692 // In order to properly isolate reports from different documents, any reports
693 // which can be sent to a V1 endpoint must be. V0 endpoints are selected only
694 // for those reports with no reporting source token, or when no matching V1
695 // endpoint has been configured.
696 // To achieve this, the reporting service continues to use the EndpointGroupKey
697 // structure, which uses the presence of an optional reporting source token to
698 // distinguish V1 endpoints from V0 endpoint groups.
699 std::vector<ReportingEndpoint>
GetCandidateEndpointsForDelivery(const ReportingEndpointGroupKey & group_key)700 ReportingCacheImpl::GetCandidateEndpointsForDelivery(
701     const ReportingEndpointGroupKey& group_key) {
702   base::Time now = clock().Now();
703   ConsistencyCheckClients();
704 
705   // If |group_key| has a defined |reporting_source| field, then this method is
706   // being called for reports with an associated source. We need to first look
707   // for a matching V1 endpoint, based on |reporting_source| and |group_name|.
708   if (group_key.IsDocumentEndpoint()) {
709     const auto it =
710         document_endpoints_.find(group_key.reporting_source.value());
711     if (it != document_endpoints_.end()) {
712       for (const ReportingEndpoint& endpoint : it->second) {
713         if (endpoint.group_key == group_key) {
714           return {endpoint};
715         }
716       }
717     }
718   }
719 
720   // Either |group_key| does not have a defined |reporting_source|, which means
721   // that this method was called for reports without a source (e.g. NEL), or
722   // we tried and failed to find an appropriate V1 endpoint. In either case, we
723   // now look for the appropriate V0 endpoints.
724 
725   // We need to clear out the |reporting_source| field to get a group key which
726   // can be compared to any V0 endpoint groups.
727   ReportingEndpointGroupKey v0_lookup_group_key(
728       group_key.network_anonymization_key, group_key.origin,
729       group_key.group_name);
730 
731   // Look for an exact origin match for |origin| and |group|.
732   EndpointGroupMap::iterator group_it =
733       FindEndpointGroupIt(v0_lookup_group_key);
734   if (group_it != endpoint_groups_.end() && group_it->second.expires > now) {
735     ClientMap::iterator client_it = FindClientIt(v0_lookup_group_key);
736     MarkEndpointGroupAndClientUsed(client_it, group_it, now);
737     ConsistencyCheckClients();
738     context_->NotifyCachedClientsUpdated();
739     return GetEndpointsInGroup(group_it->first);
740   }
741 
742   // If no endpoints were found for an exact match, look for superdomain matches
743   // TODO(chlily): Limit the number of labels to go through when looking for a
744   // superdomain match.
745   std::string domain = v0_lookup_group_key.origin.host();
746   while (!domain.empty()) {
747     const auto domain_range = clients_.equal_range(domain);
748     for (auto client_it = domain_range.first; client_it != domain_range.second;
749          ++client_it) {
750       // Client for a superdomain of |origin|
751       const Client& client = client_it->second;
752       if (client.network_anonymization_key !=
753           v0_lookup_group_key.network_anonymization_key) {
754         continue;
755       }
756       ReportingEndpointGroupKey superdomain_lookup_group_key(
757           v0_lookup_group_key.network_anonymization_key, client.origin,
758           v0_lookup_group_key.group_name);
759       group_it = FindEndpointGroupIt(superdomain_lookup_group_key);
760 
761       if (group_it == endpoint_groups_.end())
762         continue;
763 
764       const CachedReportingEndpointGroup& endpoint_group = group_it->second;
765       // Check if the group is valid (unexpired and includes subdomains).
766       if (endpoint_group.include_subdomains == OriginSubdomains::INCLUDE &&
767           endpoint_group.expires > now) {
768         MarkEndpointGroupAndClientUsed(client_it, group_it, now);
769         ConsistencyCheckClients();
770         context_->NotifyCachedClientsUpdated();
771         return GetEndpointsInGroup(superdomain_lookup_group_key);
772       }
773     }
774     domain = GetSuperdomain(domain);
775   }
776   return std::vector<ReportingEndpoint>();
777 }
778 
GetClientsAsValue() const779 base::Value ReportingCacheImpl::GetClientsAsValue() const {
780   ConsistencyCheckClients();
781   base::Value::List client_list;
782   for (const auto& domain_and_client : clients_) {
783     const Client& client = domain_and_client.second;
784     client_list.Append(GetClientAsValue(client));
785   }
786   return base::Value(std::move(client_list));
787 }
788 
GetEndpointCount() const789 size_t ReportingCacheImpl::GetEndpointCount() const {
790   return endpoints_.size();
791 }
792 
Flush()793 void ReportingCacheImpl::Flush() {
794   if (context_->IsClientDataPersisted())
795     store()->Flush();
796 }
797 
GetV1EndpointForTesting(const base::UnguessableToken & reporting_source,const std::string & endpoint_name) const798 ReportingEndpoint ReportingCacheImpl::GetV1EndpointForTesting(
799     const base::UnguessableToken& reporting_source,
800     const std::string& endpoint_name) const {
801   DCHECK(!reporting_source.is_empty());
802   const auto it = document_endpoints_.find(reporting_source);
803   if (it != document_endpoints_.end()) {
804     for (const ReportingEndpoint& endpoint : it->second) {
805       if (endpoint_name == endpoint.group_key.group_name)
806         return endpoint;
807     }
808   }
809   return ReportingEndpoint();
810 }
811 
GetEndpointForTesting(const ReportingEndpointGroupKey & group_key,const GURL & url) const812 ReportingEndpoint ReportingCacheImpl::GetEndpointForTesting(
813     const ReportingEndpointGroupKey& group_key,
814     const GURL& url) const {
815   ConsistencyCheckClients();
816   for (const auto& group_key_and_endpoint : endpoints_) {
817     const ReportingEndpoint& endpoint = group_key_and_endpoint.second;
818     if (endpoint.group_key == group_key && endpoint.info.url == url)
819       return endpoint;
820   }
821   return ReportingEndpoint();
822 }
823 
EndpointGroupExistsForTesting(const ReportingEndpointGroupKey & group_key,OriginSubdomains include_subdomains,base::Time expires) const824 bool ReportingCacheImpl::EndpointGroupExistsForTesting(
825     const ReportingEndpointGroupKey& group_key,
826     OriginSubdomains include_subdomains,
827     base::Time expires) const {
828   ConsistencyCheckClients();
829   for (const auto& key_and_group : endpoint_groups_) {
830     const CachedReportingEndpointGroup& endpoint_group = key_and_group.second;
831     if (endpoint_group.group_key == group_key &&
832         endpoint_group.include_subdomains == include_subdomains) {
833       if (expires != base::Time())
834         return endpoint_group.expires == expires;
835       return true;
836     }
837   }
838   return false;
839 }
840 
ClientExistsForTesting(const NetworkAnonymizationKey & network_anonymization_key,const url::Origin & origin) const841 bool ReportingCacheImpl::ClientExistsForTesting(
842     const NetworkAnonymizationKey& network_anonymization_key,
843     const url::Origin& origin) const {
844   ConsistencyCheckClients();
845   for (const auto& domain_and_client : clients_) {
846     const Client& client = domain_and_client.second;
847     DCHECK_EQ(client.origin.host(), domain_and_client.first);
848     if (client.network_anonymization_key == network_anonymization_key &&
849         client.origin == origin) {
850       return true;
851     }
852   }
853   return false;
854 }
855 
GetEndpointGroupCountForTesting() const856 size_t ReportingCacheImpl::GetEndpointGroupCountForTesting() const {
857   return endpoint_groups_.size();
858 }
859 
GetClientCountForTesting() const860 size_t ReportingCacheImpl::GetClientCountForTesting() const {
861   return clients_.size();
862 }
863 
GetReportingSourceCountForTesting() const864 size_t ReportingCacheImpl::GetReportingSourceCountForTesting() const {
865   return document_endpoints_.size();
866 }
867 
SetV1EndpointForTesting(const ReportingEndpointGroupKey & group_key,const base::UnguessableToken & reporting_source,const IsolationInfo & isolation_info,const GURL & url)868 void ReportingCacheImpl::SetV1EndpointForTesting(
869     const ReportingEndpointGroupKey& group_key,
870     const base::UnguessableToken& reporting_source,
871     const IsolationInfo& isolation_info,
872     const GURL& url) {
873   DCHECK(!reporting_source.is_empty());
874   DCHECK(group_key.IsDocumentEndpoint());
875   DCHECK_EQ(reporting_source, group_key.reporting_source.value());
876   DCHECK(group_key.network_anonymization_key ==
877          isolation_info.network_anonymization_key());
878 
879   ReportingEndpoint::EndpointInfo info;
880   info.url = url;
881   ReportingEndpoint new_endpoint(group_key, info);
882   if (document_endpoints_.count(reporting_source) > 0) {
883     // The endpoints list is const, so remove and replace with an updated list.
884     std::vector<ReportingEndpoint> endpoints =
885         document_endpoints_.at(reporting_source);
886     endpoints.push_back(std::move(new_endpoint));
887     document_endpoints_.erase(reporting_source);
888     document_endpoints_.insert({reporting_source, std::move(endpoints)});
889   } else {
890     document_endpoints_.insert({reporting_source, {std::move(new_endpoint)}});
891   }
892   // If this is the first time we've used this reporting_source, then add the
893   // isolation info. Otherwise, ensure that it is the same as what was used
894   // previously.
895   if (isolation_info_.count(reporting_source) == 0) {
896     isolation_info_.insert({reporting_source, isolation_info});
897   } else {
898     DCHECK(isolation_info_.at(reporting_source)
899                .IsEqualForTesting(isolation_info));  // IN-TEST
900   }
901   context_->NotifyEndpointsUpdatedForOrigin(
902       FilterEndpointsByOrigin(document_endpoints_, group_key.origin));
903 }
904 
SetEndpointForTesting(const ReportingEndpointGroupKey & group_key,const GURL & url,OriginSubdomains include_subdomains,base::Time expires,int priority,int weight)905 void ReportingCacheImpl::SetEndpointForTesting(
906     const ReportingEndpointGroupKey& group_key,
907     const GURL& url,
908     OriginSubdomains include_subdomains,
909     base::Time expires,
910     int priority,
911     int weight) {
912   ClientMap::iterator client_it = FindClientIt(group_key);
913   // If the client doesn't yet exist, add it.
914   if (client_it == clients_.end()) {
915     Client new_client(group_key.network_anonymization_key, group_key.origin);
916     std::string domain = group_key.origin.host();
917     client_it = clients_.emplace(domain, std::move(new_client));
918   }
919 
920   base::Time now = clock().Now();
921 
922   EndpointGroupMap::iterator group_it = FindEndpointGroupIt(group_key);
923   // If the endpoint group doesn't yet exist, add it.
924   if (group_it == endpoint_groups_.end()) {
925     CachedReportingEndpointGroup new_group(group_key, include_subdomains,
926                                            expires, now);
927     group_it = endpoint_groups_.emplace(group_key, std::move(new_group)).first;
928     client_it->second.endpoint_group_names.insert(group_key.group_name);
929   } else {
930     // Otherwise, update the existing entry
931     group_it->second.include_subdomains = include_subdomains;
932     group_it->second.expires = expires;
933     group_it->second.last_used = now;
934   }
935 
936   MarkEndpointGroupAndClientUsed(client_it, group_it, now);
937 
938   EndpointMap::iterator endpoint_it = FindEndpointIt(group_key, url);
939   // If the endpoint doesn't yet exist, add it.
940   if (endpoint_it == endpoints_.end()) {
941     ReportingEndpoint::EndpointInfo info;
942     info.url = std::move(url);
943     info.priority = priority;
944     info.weight = weight;
945     ReportingEndpoint new_endpoint(group_key, info);
946     endpoint_it = endpoints_.emplace(group_key, std::move(new_endpoint));
947     AddEndpointItToIndex(endpoint_it);
948     ++client_it->second.endpoint_count;
949   } else {
950     // Otherwise, update the existing entry
951     endpoint_it->second.info.priority = priority;
952     endpoint_it->second.info.weight = weight;
953   }
954 
955   EnforcePerClientAndGlobalEndpointLimits(client_it);
956   ConsistencyCheckClients();
957   context_->NotifyCachedClientsUpdated();
958 }
959 
GetIsolationInfoForEndpoint(const ReportingEndpoint & endpoint) const960 IsolationInfo ReportingCacheImpl::GetIsolationInfoForEndpoint(
961     const ReportingEndpoint& endpoint) const {
962   // V0 endpoint groups do not support credentials.
963   if (!endpoint.group_key.reporting_source.has_value()) {
964     // TODO(crbug/1372769): Remove this and have a better way to get an correct
965     // IsolationInfo here.
966     return IsolationInfo::DoNotUseCreatePartialFromNak(
967         endpoint.group_key.network_anonymization_key);
968   }
969   const auto it =
970       isolation_info_.find(endpoint.group_key.reporting_source.value());
971   DCHECK(it != isolation_info_.end());
972   return it->second;
973 }
974 
Client(const NetworkAnonymizationKey & network_anonymization_key,const url::Origin & origin)975 ReportingCacheImpl::Client::Client(
976     const NetworkAnonymizationKey& network_anonymization_key,
977     const url::Origin& origin)
978     : network_anonymization_key(network_anonymization_key), origin(origin) {}
979 
980 ReportingCacheImpl::Client::Client(const Client& other) = default;
981 
982 ReportingCacheImpl::Client::Client(Client&& other) = default;
983 
984 ReportingCacheImpl::Client& ReportingCacheImpl::Client::operator=(
985     const Client& other) = default;
986 
987 ReportingCacheImpl::Client& ReportingCacheImpl::Client::operator=(
988     Client&& other) = default;
989 
990 ReportingCacheImpl::Client::~Client() = default;
991 
992 ReportingCacheImpl::ReportSet::const_iterator
FindReportToEvict() const993 ReportingCacheImpl::FindReportToEvict() const {
994   ReportSet::const_iterator to_evict = reports_.end();
995 
996   for (auto it = reports_.begin(); it != reports_.end(); ++it) {
997     // Don't evict pending or doomed reports.
998     if (it->get()->IsUploadPending())
999       continue;
1000     if (to_evict == reports_.end() ||
1001         it->get()->queued < to_evict->get()->queued) {
1002       to_evict = it;
1003     }
1004   }
1005 
1006   return to_evict;
1007 }
1008 
ConsistencyCheckClients() const1009 void ReportingCacheImpl::ConsistencyCheckClients() const {
1010   // TODO(crbug.com/1165308): Remove this CHECK once the investigation is done.
1011   CHECK_LE(endpoint_groups_.size(), context_->policy().max_endpoint_count);
1012 #if DCHECK_IS_ON()
1013   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
1014 
1015   size_t total_endpoint_count = 0;
1016   size_t total_endpoint_group_count = 0;
1017   std::set<std::pair<NetworkAnonymizationKey, url::Origin>>
1018       nik_origin_pairs_in_cache;
1019 
1020   for (const auto& domain_and_client : clients_) {
1021     const std::string& domain = domain_and_client.first;
1022     const Client& client = domain_and_client.second;
1023     total_endpoint_count += client.endpoint_count;
1024     total_endpoint_group_count += ConsistencyCheckClient(domain, client);
1025 
1026     auto inserted = nik_origin_pairs_in_cache.emplace(
1027         client.network_anonymization_key, client.origin);
1028     // We have not seen a duplicate client with the same NAK and origin.
1029     DCHECK(inserted.second);
1030   }
1031 
1032   // Global endpoint cap is respected.
1033   DCHECK_LE(GetEndpointCount(), context_->policy().max_endpoint_count);
1034   // The number of endpoint groups must not exceed the number of endpoints.
1035   DCHECK_LE(endpoint_groups_.size(), GetEndpointCount());
1036 
1037   // All the endpoints and endpoint groups are accounted for.
1038   DCHECK_EQ(total_endpoint_count, endpoints_.size());
1039   DCHECK_EQ(total_endpoint_group_count, endpoint_groups_.size());
1040 
1041   // All the endpoints are indexed properly.
1042   DCHECK_EQ(total_endpoint_count, endpoint_its_by_url_.size());
1043   for (const auto& url_and_endpoint_it : endpoint_its_by_url_) {
1044     DCHECK_EQ(url_and_endpoint_it.first,
1045               url_and_endpoint_it.second->second.info.url);
1046   }
1047 }
1048 
ConsistencyCheckClient(const std::string & domain,const Client & client) const1049 size_t ReportingCacheImpl::ConsistencyCheckClient(const std::string& domain,
1050                                                   const Client& client) const {
1051   // Each client is keyed by its domain name.
1052   DCHECK_EQ(domain, client.origin.host());
1053   // Client is not empty (has at least one group)
1054   DCHECK(!client.endpoint_group_names.empty());
1055 
1056   size_t endpoint_count_in_client = 0;
1057   size_t endpoint_group_count_in_client = 0;
1058 
1059   for (const std::string& group_name : client.endpoint_group_names) {
1060     size_t groups_with_name = 0;
1061     for (const auto& key_and_group : endpoint_groups_) {
1062       const ReportingEndpointGroupKey& key = key_and_group.first;
1063       // There should not be any V1 document endpoints; this is a V0 endpoint
1064       // group.
1065       DCHECK(!key_and_group.first.IsDocumentEndpoint());
1066       if (key.origin == client.origin &&
1067           key.network_anonymization_key == client.network_anonymization_key &&
1068           key.group_name == group_name) {
1069         ++endpoint_group_count_in_client;
1070         ++groups_with_name;
1071         endpoint_count_in_client +=
1072             ConsistencyCheckEndpointGroup(key, key_and_group.second);
1073       }
1074     }
1075     DCHECK_EQ(1u, groups_with_name);
1076   }
1077   // Client has the correct endpoint count.
1078   DCHECK_EQ(client.endpoint_count, endpoint_count_in_client);
1079   // Per-client endpoint cap is respected.
1080   DCHECK_LE(client.endpoint_count, context_->policy().max_endpoints_per_origin);
1081 
1082   // Note: Not checking last_used time here because base::Time is not
1083   // guaranteed to be monotonically non-decreasing.
1084 
1085   return endpoint_group_count_in_client;
1086 }
1087 
ConsistencyCheckEndpointGroup(const ReportingEndpointGroupKey & key,const CachedReportingEndpointGroup & group) const1088 size_t ReportingCacheImpl::ConsistencyCheckEndpointGroup(
1089     const ReportingEndpointGroupKey& key,
1090     const CachedReportingEndpointGroup& group) const {
1091   size_t endpoint_count_in_group = 0;
1092 
1093   // Each group is keyed by its origin and name.
1094   DCHECK(key == group.group_key);
1095 
1096   // Group is not empty (has at least one endpoint)
1097   DCHECK_LE(0u, GetEndpointCountInGroup(group.group_key));
1098 
1099   // Note: Not checking expiry here because expired groups are allowed to
1100   // linger in the cache until they are garbage collected.
1101 
1102   std::set<GURL> endpoint_urls_in_group;
1103 
1104   const auto group_range = endpoints_.equal_range(key);
1105   for (auto it = group_range.first; it != group_range.second; ++it) {
1106     const ReportingEndpoint& endpoint = it->second;
1107 
1108     ConsistencyCheckEndpoint(key, endpoint, it);
1109 
1110     auto inserted = endpoint_urls_in_group.insert(endpoint.info.url);
1111     // We have not seen a duplicate endpoint with the same URL in this
1112     // group.
1113     DCHECK(inserted.second);
1114 
1115     ++endpoint_count_in_group;
1116   }
1117 
1118   return endpoint_count_in_group;
1119 }
1120 
ConsistencyCheckEndpoint(const ReportingEndpointGroupKey & key,const ReportingEndpoint & endpoint,EndpointMap::const_iterator endpoint_it) const1121 void ReportingCacheImpl::ConsistencyCheckEndpoint(
1122     const ReportingEndpointGroupKey& key,
1123     const ReportingEndpoint& endpoint,
1124     EndpointMap::const_iterator endpoint_it) const {
1125   // Origin and group name match.
1126   DCHECK(key == endpoint.group_key);
1127 
1128   // Priority and weight are nonnegative integers.
1129   DCHECK_LE(0, endpoint.info.priority);
1130   DCHECK_LE(0, endpoint.info.weight);
1131 
1132   // The endpoint is in the |endpoint_its_by_url_| index.
1133   DCHECK(base::Contains(endpoint_its_by_url_, endpoint.info.url));
1134   auto url_range = endpoint_its_by_url_.equal_range(endpoint.info.url);
1135   std::vector<EndpointMap::iterator> endpoint_its_for_url;
1136   for (auto index_it = url_range.first; index_it != url_range.second;
1137        ++index_it) {
1138     endpoint_its_for_url.push_back(index_it->second);
1139   }
1140   DCHECK(base::Contains(endpoint_its_for_url, endpoint_it));
1141 #endif  // DCHECK_IS_ON()
1142 }
1143 
FindClientIt(const NetworkAnonymizationKey & network_anonymization_key,const url::Origin & origin)1144 ReportingCacheImpl::ClientMap::iterator ReportingCacheImpl::FindClientIt(
1145     const NetworkAnonymizationKey& network_anonymization_key,
1146     const url::Origin& origin) {
1147   // TODO(chlily): Limit the number of clients per domain to prevent an attacker
1148   // from installing many Reporting policies for different port numbers on the
1149   // same host.
1150   const auto domain_range = clients_.equal_range(origin.host());
1151   for (auto it = domain_range.first; it != domain_range.second; ++it) {
1152     if (it->second.network_anonymization_key == network_anonymization_key &&
1153         it->second.origin == origin) {
1154       return it;
1155     }
1156   }
1157   return clients_.end();
1158 }
1159 
FindClientIt(const ReportingEndpointGroupKey & group_key)1160 ReportingCacheImpl::ClientMap::iterator ReportingCacheImpl::FindClientIt(
1161     const ReportingEndpointGroupKey& group_key) {
1162   return FindClientIt(group_key.network_anonymization_key, group_key.origin);
1163 }
1164 
1165 ReportingCacheImpl::EndpointGroupMap::iterator
FindEndpointGroupIt(const ReportingEndpointGroupKey & group_key)1166 ReportingCacheImpl::FindEndpointGroupIt(
1167     const ReportingEndpointGroupKey& group_key) {
1168   return endpoint_groups_.find(group_key);
1169 }
1170 
FindEndpointIt(const ReportingEndpointGroupKey & group_key,const GURL & url)1171 ReportingCacheImpl::EndpointMap::iterator ReportingCacheImpl::FindEndpointIt(
1172     const ReportingEndpointGroupKey& group_key,
1173     const GURL& url) {
1174   const auto group_range = endpoints_.equal_range(group_key);
1175   for (auto it = group_range.first; it != group_range.second; ++it) {
1176     if (it->second.info.url == url)
1177       return it;
1178   }
1179   return endpoints_.end();
1180 }
1181 
AddOrUpdateClient(Client new_client)1182 ReportingCacheImpl::ClientMap::iterator ReportingCacheImpl::AddOrUpdateClient(
1183     Client new_client) {
1184   ClientMap::iterator client_it =
1185       FindClientIt(new_client.network_anonymization_key, new_client.origin);
1186 
1187   // Add a new client for this NAK and origin.
1188   if (client_it == clients_.end()) {
1189     std::string domain = new_client.origin.host();
1190     client_it = clients_.emplace(std::move(domain), std::move(new_client));
1191   } else {
1192     // If an entry already existed, just update it.
1193     Client& old_client = client_it->second;
1194     old_client.endpoint_count = new_client.endpoint_count;
1195     old_client.endpoint_group_names =
1196         std::move(new_client.endpoint_group_names);
1197     old_client.last_used = new_client.last_used;
1198   }
1199 
1200   // Note: ConsistencyCheckClients() may fail here because we may be over the
1201   // global/per-origin endpoint limits.
1202   return client_it;
1203 }
1204 
AddOrUpdateEndpointGroup(CachedReportingEndpointGroup new_group)1205 void ReportingCacheImpl::AddOrUpdateEndpointGroup(
1206     CachedReportingEndpointGroup new_group) {
1207   EndpointGroupMap::iterator group_it =
1208       FindEndpointGroupIt(new_group.group_key);
1209 
1210   // Add a new endpoint group for this origin and group name.
1211   if (group_it == endpoint_groups_.end()) {
1212     if (context_->IsClientDataPersisted())
1213       store()->AddReportingEndpointGroup(new_group);
1214 
1215     endpoint_groups_.emplace(new_group.group_key, std::move(new_group));
1216     return;
1217   }
1218 
1219   // If an entry already existed, just update it.
1220   CachedReportingEndpointGroup& old_group = group_it->second;
1221   old_group.include_subdomains = new_group.include_subdomains;
1222   old_group.expires = new_group.expires;
1223   old_group.last_used = new_group.last_used;
1224 
1225   if (context_->IsClientDataPersisted())
1226     store()->UpdateReportingEndpointGroupDetails(new_group);
1227 
1228   // Note: ConsistencyCheckClients() may fail here because we have not yet
1229   // added/updated the Client yet.
1230 }
1231 
AddOrUpdateEndpoint(ReportingEndpoint new_endpoint)1232 void ReportingCacheImpl::AddOrUpdateEndpoint(ReportingEndpoint new_endpoint) {
1233   EndpointMap::iterator endpoint_it =
1234       FindEndpointIt(new_endpoint.group_key, new_endpoint.info.url);
1235 
1236   // Add a new endpoint for this origin, group, and url.
1237   if (endpoint_it == endpoints_.end()) {
1238     if (context_->IsClientDataPersisted())
1239       store()->AddReportingEndpoint(new_endpoint);
1240 
1241     endpoint_it =
1242         endpoints_.emplace(new_endpoint.group_key, std::move(new_endpoint));
1243     AddEndpointItToIndex(endpoint_it);
1244 
1245     // If the client already exists, update its endpoint count.
1246     ClientMap::iterator client_it = FindClientIt(endpoint_it->second.group_key);
1247     if (client_it != clients_.end())
1248       ++client_it->second.endpoint_count;
1249     return;
1250   }
1251 
1252   // If an entry already existed, just update it.
1253   ReportingEndpoint& old_endpoint = endpoint_it->second;
1254   old_endpoint.info.priority = new_endpoint.info.priority;
1255   old_endpoint.info.weight = new_endpoint.info.weight;
1256   // |old_endpoint.stats| stays the same.
1257 
1258   if (context_->IsClientDataPersisted())
1259     store()->UpdateReportingEndpointDetails(new_endpoint);
1260 
1261   // Note: ConsistencyCheckClients() may fail here because we have not yet
1262   // added/updated the Client yet.
1263 }
1264 
RemoveEndpointsInGroupOtherThan(const ReportingEndpointGroupKey & group_key,const std::set<GURL> & endpoints_to_keep_urls)1265 void ReportingCacheImpl::RemoveEndpointsInGroupOtherThan(
1266     const ReportingEndpointGroupKey& group_key,
1267     const std::set<GURL>& endpoints_to_keep_urls) {
1268   EndpointGroupMap::iterator group_it = FindEndpointGroupIt(group_key);
1269   if (group_it == endpoint_groups_.end())
1270     return;
1271   ClientMap::iterator client_it = FindClientIt(group_key);
1272   // Normally a group would not exist without a client for that origin, but
1273   // this can actually happen during header parsing if a header for an origin
1274   // without a pre-existing configuration erroneously contains multiple groups
1275   // with the same name. In that case, we assume here that they meant to set all
1276   // of those same-name groups as one group, so we don't remove anything.
1277   if (client_it == clients_.end())
1278     return;
1279 
1280   const auto group_range = endpoints_.equal_range(group_key);
1281   for (auto it = group_range.first; it != group_range.second;) {
1282     if (base::Contains(endpoints_to_keep_urls, it->second.info.url)) {
1283       ++it;
1284       continue;
1285     }
1286 
1287     // This may invalidate |group_it| (and also possibly |client_it|), but only
1288     // if we are processing the last remaining endpoint in the group.
1289     std::optional<EndpointMap::iterator> next_it =
1290         RemoveEndpointInternal(client_it, group_it, it);
1291     if (!next_it.has_value())
1292       return;
1293     it = next_it.value();
1294   }
1295 }
1296 
RemoveEndpointGroupsForClientOtherThan(const NetworkAnonymizationKey & network_anonymization_key,const url::Origin & origin,const std::set<std::string> & groups_to_keep_names)1297 void ReportingCacheImpl::RemoveEndpointGroupsForClientOtherThan(
1298     const NetworkAnonymizationKey& network_anonymization_key,
1299     const url::Origin& origin,
1300     const std::set<std::string>& groups_to_keep_names) {
1301   ClientMap::iterator client_it =
1302       FindClientIt(network_anonymization_key, origin);
1303   if (client_it == clients_.end())
1304     return;
1305 
1306   std::set<std::string>& old_group_names =
1307       client_it->second.endpoint_group_names;
1308   std::vector<std::string> groups_to_remove_names =
1309       base::STLSetDifference<std::vector<std::string>>(old_group_names,
1310                                                        groups_to_keep_names);
1311 
1312   for (const std::string& group_name : groups_to_remove_names) {
1313     EndpointGroupMap::iterator group_it =
1314         FindEndpointGroupIt(ReportingEndpointGroupKey(network_anonymization_key,
1315                                                       origin, group_name));
1316     RemoveEndpointGroupInternal(client_it, group_it);
1317   }
1318 }
1319 
GetEndpointsInGroup(const ReportingEndpointGroupKey & group_key) const1320 std::vector<ReportingEndpoint> ReportingCacheImpl::GetEndpointsInGroup(
1321     const ReportingEndpointGroupKey& group_key) const {
1322   const auto group_range = endpoints_.equal_range(group_key);
1323   std::vector<ReportingEndpoint> endpoints_out;
1324   for (auto it = group_range.first; it != group_range.second; ++it) {
1325     endpoints_out.push_back(it->second);
1326   }
1327   return endpoints_out;
1328 }
1329 
GetEndpointCountInGroup(const ReportingEndpointGroupKey & group_key) const1330 size_t ReportingCacheImpl::GetEndpointCountInGroup(
1331     const ReportingEndpointGroupKey& group_key) const {
1332   return endpoints_.count(group_key);
1333 }
1334 
MarkEndpointGroupAndClientUsed(ClientMap::iterator client_it,EndpointGroupMap::iterator group_it,base::Time now)1335 void ReportingCacheImpl::MarkEndpointGroupAndClientUsed(
1336     ClientMap::iterator client_it,
1337     EndpointGroupMap::iterator group_it,
1338     base::Time now) {
1339   group_it->second.last_used = now;
1340   client_it->second.last_used = now;
1341   if (context_->IsClientDataPersisted())
1342     store()->UpdateReportingEndpointGroupAccessTime(group_it->second);
1343 }
1344 
1345 std::optional<ReportingCacheImpl::EndpointMap::iterator>
RemoveEndpointInternal(ClientMap::iterator client_it,EndpointGroupMap::iterator group_it,EndpointMap::iterator endpoint_it)1346 ReportingCacheImpl::RemoveEndpointInternal(ClientMap::iterator client_it,
1347                                            EndpointGroupMap::iterator group_it,
1348                                            EndpointMap::iterator endpoint_it) {
1349   DCHECK(client_it != clients_.end());
1350   DCHECK(group_it != endpoint_groups_.end());
1351   DCHECK(endpoint_it != endpoints_.end());
1352 
1353   const ReportingEndpointGroupKey& group_key = endpoint_it->first;
1354   // If this is the only endpoint in the group, then removing it will cause the
1355   // group to become empty, so just remove the whole group. The client may also
1356   // be removed if it becomes empty.
1357   if (endpoints_.count(group_key) == 1) {
1358     RemoveEndpointGroupInternal(client_it, group_it);
1359     return std::nullopt;
1360   }
1361   // Otherwise, there are other endpoints in the group, so there is no chance
1362   // of needing to remove the group/client. Just remove this endpoint and
1363   // update the client's endpoint count.
1364   DCHECK_GT(client_it->second.endpoint_count, 1u);
1365   RemoveEndpointItFromIndex(endpoint_it);
1366   --client_it->second.endpoint_count;
1367   if (context_->IsClientDataPersisted())
1368     store()->DeleteReportingEndpoint(endpoint_it->second);
1369   return endpoints_.erase(endpoint_it);
1370 }
1371 
1372 std::optional<ReportingCacheImpl::EndpointGroupMap::iterator>
RemoveEndpointGroupInternal(ClientMap::iterator client_it,EndpointGroupMap::iterator group_it,size_t * num_endpoints_removed)1373 ReportingCacheImpl::RemoveEndpointGroupInternal(
1374     ClientMap::iterator client_it,
1375     EndpointGroupMap::iterator group_it,
1376     size_t* num_endpoints_removed) {
1377   DCHECK(client_it != clients_.end());
1378   DCHECK(group_it != endpoint_groups_.end());
1379   const ReportingEndpointGroupKey& group_key = group_it->first;
1380 
1381   // Remove the endpoints for this group.
1382   const auto group_range = endpoints_.equal_range(group_key);
1383   size_t endpoints_removed =
1384       std::distance(group_range.first, group_range.second);
1385   DCHECK_GT(endpoints_removed, 0u);
1386   if (num_endpoints_removed)
1387     *num_endpoints_removed += endpoints_removed;
1388   for (auto it = group_range.first; it != group_range.second; ++it) {
1389     if (context_->IsClientDataPersisted())
1390       store()->DeleteReportingEndpoint(it->second);
1391 
1392     RemoveEndpointItFromIndex(it);
1393   }
1394   endpoints_.erase(group_range.first, group_range.second);
1395 
1396   // Update the client's endpoint count.
1397   Client& client = client_it->second;
1398   client.endpoint_count -= endpoints_removed;
1399 
1400   // Remove endpoint group from client.
1401   size_t erased_from_client =
1402       client.endpoint_group_names.erase(group_key.group_name);
1403   DCHECK_EQ(1u, erased_from_client);
1404 
1405   if (context_->IsClientDataPersisted())
1406     store()->DeleteReportingEndpointGroup(group_it->second);
1407 
1408   EndpointGroupMap::iterator rv = endpoint_groups_.erase(group_it);
1409 
1410   // Delete client if empty.
1411   if (client.endpoint_count == 0) {
1412     DCHECK(client.endpoint_group_names.empty());
1413     clients_.erase(client_it);
1414     return std::nullopt;
1415   }
1416   return rv;
1417 }
1418 
1419 ReportingCacheImpl::ClientMap::iterator
RemoveClientInternal(ClientMap::iterator client_it)1420 ReportingCacheImpl::RemoveClientInternal(ClientMap::iterator client_it) {
1421   DCHECK(client_it != clients_.end());
1422   const Client& client = client_it->second;
1423 
1424   // Erase all groups in this client, and all endpoints in those groups.
1425   for (const std::string& group_name : client.endpoint_group_names) {
1426     ReportingEndpointGroupKey group_key(client.network_anonymization_key,
1427                                         client.origin, group_name);
1428     EndpointGroupMap::iterator group_it = FindEndpointGroupIt(group_key);
1429     if (context_->IsClientDataPersisted())
1430       store()->DeleteReportingEndpointGroup(group_it->second);
1431     endpoint_groups_.erase(group_it);
1432 
1433     const auto group_range = endpoints_.equal_range(group_key);
1434     for (auto it = group_range.first; it != group_range.second; ++it) {
1435       if (context_->IsClientDataPersisted())
1436         store()->DeleteReportingEndpoint(it->second);
1437 
1438       RemoveEndpointItFromIndex(it);
1439     }
1440     endpoints_.erase(group_range.first, group_range.second);
1441   }
1442 
1443   return clients_.erase(client_it);
1444 }
1445 
EnforcePerClientAndGlobalEndpointLimits(ClientMap::iterator client_it)1446 void ReportingCacheImpl::EnforcePerClientAndGlobalEndpointLimits(
1447     ClientMap::iterator client_it) {
1448   DCHECK(client_it != clients_.end());
1449   size_t client_endpoint_count = client_it->second.endpoint_count;
1450   // TODO(chlily): This is actually a limit on the endpoints for a given client
1451   // (for a NAK, origin pair). Rename this.
1452   size_t max_endpoints_per_origin = context_->policy().max_endpoints_per_origin;
1453   if (client_endpoint_count > max_endpoints_per_origin) {
1454     EvictEndpointsFromClient(client_it,
1455                              client_endpoint_count - max_endpoints_per_origin);
1456   }
1457 
1458   size_t max_endpoint_count = context_->policy().max_endpoint_count;
1459   while (GetEndpointCount() > max_endpoint_count) {
1460     // Find the stalest client (arbitrarily pick the first one if there are
1461     // multiple).
1462     ClientMap::iterator to_evict = clients_.end();
1463     for (auto it = clients_.begin(); it != clients_.end(); ++it) {
1464       const Client& client = it->second;
1465       if (to_evict == clients_.end() ||
1466           client.last_used < to_evict->second.last_used) {
1467         to_evict = it;
1468       }
1469     }
1470 
1471     DCHECK(to_evict != clients_.end());
1472 
1473     // Evict endpoints from the chosen client.
1474     size_t num_to_evict = GetEndpointCount() - max_endpoint_count;
1475     EvictEndpointsFromClient(
1476         to_evict, std::min(to_evict->second.endpoint_count, num_to_evict));
1477   }
1478 }
1479 
EvictEndpointsFromClient(ClientMap::iterator client_it,size_t endpoints_to_evict)1480 void ReportingCacheImpl::EvictEndpointsFromClient(ClientMap::iterator client_it,
1481                                                   size_t endpoints_to_evict) {
1482   DCHECK_GT(endpoints_to_evict, 0u);
1483   DCHECK(client_it != clients_.end());
1484   const Client& client = client_it->second;
1485   // Cache this value as |client| may be deleted.
1486   size_t client_endpoint_count = client.endpoint_count;
1487   const NetworkAnonymizationKey& network_anonymization_key =
1488       client.network_anonymization_key;
1489   const url::Origin& origin = client.origin;
1490 
1491   DCHECK_GE(client_endpoint_count, endpoints_to_evict);
1492   if (endpoints_to_evict == client_endpoint_count) {
1493     RemoveClientInternal(client_it);
1494     return;
1495   }
1496 
1497   size_t endpoints_removed = 0;
1498   bool client_deleted =
1499       RemoveExpiredOrStaleGroups(client_it, &endpoints_removed);
1500   // If we deleted the whole client, there is nothing left to do.
1501   if (client_deleted) {
1502     DCHECK_EQ(endpoints_removed, client_endpoint_count);
1503     return;
1504   }
1505 
1506   DCHECK(!client.endpoint_group_names.empty());
1507 
1508   while (endpoints_removed < endpoints_to_evict) {
1509     DCHECK_GT(client_it->second.endpoint_count, 0u);
1510     // Find the stalest group with the most endpoints.
1511     EndpointGroupMap::iterator stalest_group_it = endpoint_groups_.end();
1512     size_t stalest_group_endpoint_count = 0;
1513     for (const std::string& group_name : client.endpoint_group_names) {
1514       ReportingEndpointGroupKey group_key(network_anonymization_key, origin,
1515                                           group_name);
1516       EndpointGroupMap::iterator group_it = FindEndpointGroupIt(group_key);
1517       size_t group_endpoint_count = GetEndpointCountInGroup(group_key);
1518 
1519       const CachedReportingEndpointGroup& group = group_it->second;
1520       if (stalest_group_it == endpoint_groups_.end() ||
1521           group.last_used < stalest_group_it->second.last_used ||
1522           (group.last_used == stalest_group_it->second.last_used &&
1523            group_endpoint_count > stalest_group_endpoint_count)) {
1524         stalest_group_it = group_it;
1525         stalest_group_endpoint_count = group_endpoint_count;
1526       }
1527     }
1528     DCHECK(stalest_group_it != endpoint_groups_.end());
1529 
1530     // Evict the least important (lowest priority, lowest weight) endpoint.
1531     EvictEndpointFromGroup(client_it, stalest_group_it);
1532     ++endpoints_removed;
1533   }
1534 }
1535 
EvictEndpointFromGroup(ClientMap::iterator client_it,EndpointGroupMap::iterator group_it)1536 void ReportingCacheImpl::EvictEndpointFromGroup(
1537     ClientMap::iterator client_it,
1538     EndpointGroupMap::iterator group_it) {
1539   const ReportingEndpointGroupKey& group_key = group_it->first;
1540   const auto group_range = endpoints_.equal_range(group_key);
1541   EndpointMap::iterator endpoint_to_evict_it = endpoints_.end();
1542   for (auto it = group_range.first; it != group_range.second; ++it) {
1543     const ReportingEndpoint& endpoint = it->second;
1544     if (endpoint_to_evict_it == endpoints_.end() ||
1545         // Lower priority = higher numerical value of |priority|.
1546         endpoint.info.priority > endpoint_to_evict_it->second.info.priority ||
1547         (endpoint.info.priority == endpoint_to_evict_it->second.info.priority &&
1548          endpoint.info.weight < endpoint_to_evict_it->second.info.weight)) {
1549       endpoint_to_evict_it = it;
1550     }
1551   }
1552   DCHECK(endpoint_to_evict_it != endpoints_.end());
1553 
1554   RemoveEndpointInternal(client_it, group_it, endpoint_to_evict_it);
1555 }
1556 
RemoveExpiredOrStaleGroups(ClientMap::iterator client_it,size_t * num_endpoints_removed)1557 bool ReportingCacheImpl::RemoveExpiredOrStaleGroups(
1558     ClientMap::iterator client_it,
1559     size_t* num_endpoints_removed) {
1560   base::Time now = clock().Now();
1561   // Make a copy of this because |client_it| may be invalidated.
1562   std::set<std::string> groups_in_client_names(
1563       client_it->second.endpoint_group_names);
1564 
1565   for (const std::string& group_name : groups_in_client_names) {
1566     EndpointGroupMap::iterator group_it = FindEndpointGroupIt(
1567         ReportingEndpointGroupKey(client_it->second.network_anonymization_key,
1568                                   client_it->second.origin, group_name));
1569     DCHECK(group_it != endpoint_groups_.end());
1570     const CachedReportingEndpointGroup& group = group_it->second;
1571     if (group.expires < now ||
1572         now - group.last_used > context_->policy().max_group_staleness) {
1573       // May delete the client, invalidating |client_it|, but only if we are
1574       // processing the last remaining group.
1575       if (!RemoveEndpointGroupInternal(client_it, group_it,
1576                                        num_endpoints_removed)
1577                .has_value()) {
1578         return true;
1579       }
1580     }
1581   }
1582 
1583   return false;
1584 }
1585 
AddEndpointItToIndex(EndpointMap::iterator endpoint_it)1586 void ReportingCacheImpl::AddEndpointItToIndex(
1587     EndpointMap::iterator endpoint_it) {
1588   const GURL& url = endpoint_it->second.info.url;
1589   endpoint_its_by_url_.emplace(url, endpoint_it);
1590 }
1591 
RemoveEndpointItFromIndex(EndpointMap::iterator endpoint_it)1592 void ReportingCacheImpl::RemoveEndpointItFromIndex(
1593     EndpointMap::iterator endpoint_it) {
1594   const GURL& url = endpoint_it->second.info.url;
1595   auto url_range = endpoint_its_by_url_.equal_range(url);
1596   for (auto it = url_range.first; it != url_range.second; ++it) {
1597     if (it->second == endpoint_it) {
1598       endpoint_its_by_url_.erase(it);
1599       return;
1600     }
1601   }
1602 }
1603 
GetClientAsValue(const Client & client) const1604 base::Value ReportingCacheImpl::GetClientAsValue(const Client& client) const {
1605   base::Value::Dict client_dict;
1606   client_dict.Set("network_anonymization_key",
1607                   client.network_anonymization_key.ToDebugString());
1608   client_dict.Set("origin", client.origin.Serialize());
1609 
1610   base::Value::List group_list;
1611   for (const std::string& group_name : client.endpoint_group_names) {
1612     ReportingEndpointGroupKey group_key(client.network_anonymization_key,
1613                                         client.origin, group_name);
1614     const CachedReportingEndpointGroup& group = endpoint_groups_.at(group_key);
1615     group_list.Append(GetEndpointGroupAsValue(group));
1616   }
1617 
1618   client_dict.Set("groups", std::move(group_list));
1619 
1620   return base::Value(std::move(client_dict));
1621 }
1622 
GetEndpointGroupAsValue(const CachedReportingEndpointGroup & group) const1623 base::Value ReportingCacheImpl::GetEndpointGroupAsValue(
1624     const CachedReportingEndpointGroup& group) const {
1625   base::Value::Dict group_dict;
1626   group_dict.Set("name", group.group_key.group_name);
1627   group_dict.Set("expires", NetLog::TimeToString(group.expires));
1628   group_dict.Set("includeSubdomains",
1629                  group.include_subdomains == OriginSubdomains::INCLUDE);
1630 
1631   base::Value::List endpoint_list;
1632 
1633   const auto group_range = endpoints_.equal_range(group.group_key);
1634   for (auto it = group_range.first; it != group_range.second; ++it) {
1635     const ReportingEndpoint& endpoint = it->second;
1636     endpoint_list.Append(GetEndpointAsValue(endpoint));
1637   }
1638 
1639   group_dict.Set("endpoints", std::move(endpoint_list));
1640 
1641   return base::Value(std::move(group_dict));
1642 }
1643 
GetEndpointAsValue(const ReportingEndpoint & endpoint) const1644 base::Value ReportingCacheImpl::GetEndpointAsValue(
1645     const ReportingEndpoint& endpoint) const {
1646   base::Value::Dict endpoint_dict;
1647   endpoint_dict.Set("url", endpoint.info.url.spec());
1648   endpoint_dict.Set("priority", endpoint.info.priority);
1649   endpoint_dict.Set("weight", endpoint.info.weight);
1650 
1651   const ReportingEndpoint::Statistics& stats = endpoint.stats;
1652   base::Value::Dict successful_dict;
1653   successful_dict.Set("uploads", stats.successful_uploads);
1654   successful_dict.Set("reports", stats.successful_reports);
1655   endpoint_dict.Set("successful", std::move(successful_dict));
1656 
1657   base::Value::Dict failed_dict;
1658   failed_dict.Set("uploads",
1659                   stats.attempted_uploads - stats.successful_uploads);
1660   failed_dict.Set("reports",
1661                   stats.attempted_reports - stats.successful_reports);
1662   endpoint_dict.Set("failed", std::move(failed_dict));
1663 
1664   return base::Value(std::move(endpoint_dict));
1665 }
1666 
1667 }  // namespace net
1668