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