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