reporting_cache_impl.cc 62 KB

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