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