xref: /aosp_15_r20/external/cronet/net/reporting/reporting_delivery_agent.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2017 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_delivery_agent.h"
6 
7 #include <algorithm>
8 #include <map>
9 #include <set>
10 #include <string>
11 #include <utility>
12 #include <vector>
13 
14 #include "base/check.h"
15 #include "base/containers/contains.h"
16 #include "base/functional/bind.h"
17 #include "base/json/json_writer.h"
18 #include "base/memory/raw_ptr.h"
19 #include "base/metrics/histogram_functions.h"
20 #include "base/numerics/safe_conversions.h"
21 #include "base/time/tick_clock.h"
22 #include "base/timer/timer.h"
23 #include "base/values.h"
24 #include "net/base/isolation_info.h"
25 #include "net/base/network_anonymization_key.h"
26 #include "net/base/url_util.h"
27 #include "net/reporting/reporting_cache.h"
28 #include "net/reporting/reporting_cache_observer.h"
29 #include "net/reporting/reporting_context.h"
30 #include "net/reporting/reporting_delegate.h"
31 #include "net/reporting/reporting_endpoint_manager.h"
32 #include "net/reporting/reporting_report.h"
33 #include "net/reporting/reporting_uploader.h"
34 #include "url/gurl.h"
35 #include "url/origin.h"
36 
37 namespace net {
38 
39 namespace {
40 
41 using ReportList =
42     std::vector<raw_ptr<const ReportingReport, VectorExperimental>>;
43 using ReportingUploadHeaderType =
44     ReportingDeliveryAgent::ReportingUploadHeaderType;
45 
RecordReportingUploadHeaderType(ReportingUploadHeaderType header_type)46 void RecordReportingUploadHeaderType(ReportingUploadHeaderType header_type) {
47   base::UmaHistogramEnumeration("Net.Reporting.UploadHeaderType", header_type);
48 }
49 
SerializeReports(const ReportList & reports,base::TimeTicks now)50 std::string SerializeReports(const ReportList& reports, base::TimeTicks now) {
51   base::Value::List reports_value;
52 
53   for (const ReportingReport* report : reports) {
54     base::Value::Dict report_value;
55 
56     report_value.Set("age", base::saturated_cast<int>(
57                                 (now - report->queued).InMilliseconds()));
58     report_value.Set("type", report->type);
59     report_value.Set("url", report->url.spec());
60     report_value.Set("user_agent", report->user_agent);
61     report_value.Set("body", report->body.Clone());
62 
63     reports_value.Append(std::move(report_value));
64   }
65 
66   std::string json_out;
67   bool json_written = base::JSONWriter::Write(reports_value, &json_out);
68   DCHECK(json_written);
69   return json_out;
70 }
71 
CompareReportGroupKeys(const ReportingReport * lhs,const ReportingReport * rhs)72 bool CompareReportGroupKeys(const ReportingReport* lhs,
73                             const ReportingReport* rhs) {
74   return lhs->GetGroupKey() < rhs->GetGroupKey();
75 }
76 
77 // Each Delivery corresponds to one upload URLRequest.
78 class Delivery {
79  public:
80   // The target of a delivery. All reports uploaded together must share the
81   // same values for these parameters.
82   // Note that |origin| here (which matches the report's |origin|) is not
83   // necessarily the same as the |origin| of the ReportingEndpoint's group key
84   // (if the endpoint is configured to include subdomains). Reports with
85   // different group keys can be in the same delivery, as long as the NAK,
86   // report origin and reporting source are the same, and they all get assigned
87   // to the same endpoint URL.
88   // |isolation_info| is the IsolationInfo struct associated with the reporting
89   // endpoint, and is used to determine appropriate credentials for the upload.
90   // |network_anonymization_key| is the NAK from the ReportingEndpoint, which
91   // may have been cleared in the ReportingService if reports are not being
92   // partitioned by NAK. (This is why a separate parameter is used here, rather
93   // than simply using the computed NAK from |isolation_info|.)
94   struct Target {
Targetnet::__anon4ff9649f0111::Delivery::Target95     Target(const IsolationInfo& isolation_info,
96            const NetworkAnonymizationKey& network_anonymization_key,
97            const url::Origin& origin,
98            const GURL& endpoint_url,
99            const std::optional<base::UnguessableToken> reporting_source)
100         : isolation_info(isolation_info),
101           network_anonymization_key(network_anonymization_key),
102           origin(origin),
103           endpoint_url(endpoint_url),
104           reporting_source(reporting_source) {
105       DCHECK(network_anonymization_key.IsEmpty() ||
106              network_anonymization_key ==
107                  isolation_info.network_anonymization_key());
108     }
109 
110     ~Target() = default;
111 
operator <net::__anon4ff9649f0111::Delivery::Target112     bool operator<(const Target& other) const {
113       // Note that sorting by NAK here is required for V0 reports; V1 reports
114       // should not need this (but it doesn't hurt). We can remove that as a
115       // comparison key when V0 reporting endpoints are removed.
116       return std::tie(network_anonymization_key, origin, endpoint_url,
117                       reporting_source) <
118              std::tie(other.network_anonymization_key, other.origin,
119                       other.endpoint_url, other.reporting_source);
120     }
121 
122     IsolationInfo isolation_info;
123     NetworkAnonymizationKey network_anonymization_key;
124     url::Origin origin;
125     GURL endpoint_url;
126     std::optional<base::UnguessableToken> reporting_source;
127   };
128 
Delivery(const Target & target)129   explicit Delivery(const Target& target) : target_(target) {}
130 
131   ~Delivery() = default;
132 
133   // Add the reports in [reports_begin, reports_end) into this delivery.
134   // Modify the report counter for the |endpoint| to which this delivery is
135   // destined.
AddReports(const ReportingEndpoint & endpoint,const ReportList::const_iterator reports_begin,const ReportList::const_iterator reports_end)136   void AddReports(const ReportingEndpoint& endpoint,
137                   const ReportList::const_iterator reports_begin,
138                   const ReportList::const_iterator reports_end) {
139     DCHECK(reports_begin != reports_end);
140     DCHECK(endpoint.group_key.network_anonymization_key ==
141            network_anonymization_key());
142     DCHECK(IsSubdomainOf(target_.origin.host() /* subdomain */,
143                          endpoint.group_key.origin.host() /* superdomain */));
144     for (auto it = reports_begin; it != reports_end; ++it) {
145       DCHECK_EQ((*reports_begin)->GetGroupKey(), (*it)->GetGroupKey());
146       DCHECK((*it)->network_anonymization_key == network_anonymization_key());
147       DCHECK_EQ(url::Origin::Create((*it)->url), target_.origin);
148       DCHECK_EQ((*it)->group, endpoint.group_key.group_name);
149       // Report origin is equal to, or a subdomain of, the endpoint
150       // configuration's origin.
151       DCHECK(IsSubdomainOf((*it)->url.host_piece() /* subdomain */,
152                            endpoint.group_key.origin.host() /* superdomain */));
153     }
154 
155     reports_per_group_[endpoint.group_key] +=
156         std::distance(reports_begin, reports_end);
157     reports_.insert(reports_.end(), reports_begin, reports_end);
158   }
159 
160   // Records statistics for reports after an upload has completed.
161   // Either removes successfully delivered reports, or increments the failure
162   // counter if delivery was unsuccessful.
ProcessOutcome(ReportingCache * cache,bool success)163   void ProcessOutcome(ReportingCache* cache, bool success) {
164     for (const auto& group_name_and_count : reports_per_group_) {
165       cache->IncrementEndpointDeliveries(group_name_and_count.first,
166                                          target_.endpoint_url,
167                                          group_name_and_count.second, success);
168     }
169     if (success) {
170       ReportingUploadHeaderType upload_type =
171           target_.reporting_source.has_value()
172               ? ReportingUploadHeaderType::kReportingEndpoints
173               : ReportingUploadHeaderType::kReportTo;
174       for (size_t i = 0; i < reports_.size(); ++i) {
175         RecordReportingUploadHeaderType(upload_type);
176       }
177       cache->RemoveReports(reports_, /* delivery_success */ true);
178     } else {
179       cache->IncrementReportsAttempts(reports_);
180     }
181   }
182 
network_anonymization_key() const183   const NetworkAnonymizationKey& network_anonymization_key() const {
184     return target_.network_anonymization_key;
185   }
endpoint_url() const186   const GURL& endpoint_url() const { return target_.endpoint_url; }
reports() const187   const ReportList& reports() const { return reports_; }
188 
189  private:
190   const Target target_;
191   ReportList reports_;
192 
193   // Used to track statistics for each ReportingEndpoint.
194   // The endpoint is uniquely identified by the key in conjunction with
195   // |target_.endpoint_url|. See ProcessOutcome().
196   std::map<ReportingEndpointGroupKey, int> reports_per_group_;
197 };
198 
199 class ReportingDeliveryAgentImpl : public ReportingDeliveryAgent,
200                                    public ReportingCacheObserver {
201  public:
ReportingDeliveryAgentImpl(ReportingContext * context,const RandIntCallback & rand_callback)202   ReportingDeliveryAgentImpl(ReportingContext* context,
203                              const RandIntCallback& rand_callback)
204       : context_(context),
205         timer_(std::make_unique<base::OneShotTimer>()),
206         endpoint_manager_(
207             ReportingEndpointManager::Create(&context->policy(),
208                                              &context->tick_clock(),
209                                              context->delegate(),
210                                              context->cache(),
211                                              rand_callback)) {
212     context_->AddCacheObserver(this);
213   }
214 
215   ReportingDeliveryAgentImpl(const ReportingDeliveryAgentImpl&) = delete;
216   ReportingDeliveryAgentImpl& operator=(const ReportingDeliveryAgentImpl&) =
217       delete;
218 
219   // ReportingDeliveryAgent implementation:
220 
~ReportingDeliveryAgentImpl()221   ~ReportingDeliveryAgentImpl() override {
222     context_->RemoveCacheObserver(this);
223   }
224 
SetTimerForTesting(std::unique_ptr<base::OneShotTimer> timer)225   void SetTimerForTesting(std::unique_ptr<base::OneShotTimer> timer) override {
226     DCHECK(!timer_->IsRunning());
227     timer_ = std::move(timer);
228   }
229 
SendReportsForSource(base::UnguessableToken reporting_source)230   void SendReportsForSource(base::UnguessableToken reporting_source) override {
231     DCHECK(!reporting_source.is_empty());
232     ReportList reports =
233         cache()->GetReportsToDeliverForSource(reporting_source);
234     if (reports.empty())
235       return;
236     DoSendReports(std::move(reports));
237   }
238 
239   // ReportingCacheObserver implementation:
OnReportsUpdated()240   void OnReportsUpdated() override {
241     if (CacheHasReports() && !timer_->IsRunning()) {
242       SendReports();
243       StartTimer();
244     }
245   }
246 
247  private:
CacheHasReports()248   bool CacheHasReports() {
249     ReportList reports;
250     context_->cache()->GetReports(&reports);
251     return !reports.empty();
252   }
253 
StartTimer()254   void StartTimer() {
255     timer_->Start(FROM_HERE, policy().delivery_interval,
256                   base::BindOnce(&ReportingDeliveryAgentImpl::OnTimerFired,
257                                  base::Unretained(this)));
258   }
259 
OnTimerFired()260   void OnTimerFired() {
261     if (CacheHasReports()) {
262       SendReports();
263       StartTimer();
264     }
265   }
266 
SendReports()267   void SendReports() {
268     ReportList reports = cache()->GetReportsToDeliver();
269     if (reports.empty())
270       return;
271     DoSendReports(std::move(reports));
272   }
273 
DoSendReports(ReportList reports)274   void DoSendReports(ReportList reports) {
275     // First determine which origins we're allowed to upload reports about.
276     std::set<url::Origin> report_origins;
277     for (const ReportingReport* report : reports) {
278       report_origins.insert(url::Origin::Create(report->url));
279     }
280     delegate()->CanSendReports(
281         std::move(report_origins),
282         base::BindOnce(&ReportingDeliveryAgentImpl::OnSendPermissionsChecked,
283                        weak_factory_.GetWeakPtr(), std::move(reports)));
284   }
285 
OnSendPermissionsChecked(ReportList reports,std::set<url::Origin> allowed_report_origins)286   void OnSendPermissionsChecked(ReportList reports,
287                                 std::set<url::Origin> allowed_report_origins) {
288     DCHECK(!reports.empty());
289     std::map<Delivery::Target, std::unique_ptr<Delivery>> deliveries;
290 
291     // Sort by group key
292     std::sort(reports.begin(), reports.end(), &CompareReportGroupKeys);
293 
294     // Iterate over "buckets" of reports with the same group key.
295     for (auto bucket_it = reports.begin(); bucket_it != reports.end();) {
296       auto bucket_start = bucket_it;
297       // Set the iterator to the beginning of the next group bucket.
298       bucket_it = std::upper_bound(bucket_it, reports.end(), *bucket_it,
299                                    &CompareReportGroupKeys);
300 
301       // Skip this group if we don't have origin permissions for this origin.
302       const ReportingEndpointGroupKey& report_group_key =
303           (*bucket_start)->GetGroupKey();
304       if (!base::Contains(allowed_report_origins, report_group_key.origin))
305         continue;
306 
307       // Skip this group if there is already a pending upload for it.
308       // We don't allow multiple concurrent uploads for the same group.
309       if (base::Contains(pending_groups_, report_group_key))
310         continue;
311 
312       // Find an endpoint to deliver these reports to.
313       const ReportingEndpoint endpoint =
314           endpoint_manager_->FindEndpointForDelivery(report_group_key);
315       // TODO(chlily): Remove reports for which there are no valid delivery
316       // endpoints.
317       if (!endpoint)
318         continue;
319 
320       pending_groups_.insert(report_group_key);
321 
322       IsolationInfo isolation_info =
323           cache()->GetIsolationInfoForEndpoint(endpoint);
324 
325       // Add the reports to the appropriate delivery.
326       Delivery::Target target(isolation_info,
327                               report_group_key.network_anonymization_key,
328                               report_group_key.origin, endpoint.info.url,
329                               endpoint.group_key.reporting_source);
330       auto delivery_it = deliveries.find(target);
331       if (delivery_it == deliveries.end()) {
332         bool inserted;
333         auto new_delivery = std::make_unique<Delivery>(target);
334         std::tie(delivery_it, inserted) =
335             deliveries.emplace(std::move(target), std::move(new_delivery));
336         DCHECK(inserted);
337       }
338       delivery_it->second->AddReports(endpoint, bucket_start, bucket_it);
339     }
340 
341     // Keep track of which of these reports we don't queue for delivery; we'll
342     // need to mark them as not-pending.
343     std::set<const ReportingReport*> undelivered_reports(reports.begin(),
344                                                          reports.end());
345 
346     // Start an upload for each delivery.
347     for (auto& target_and_delivery : deliveries) {
348       const Delivery::Target& target = target_and_delivery.first;
349       std::unique_ptr<Delivery>& delivery = target_and_delivery.second;
350 
351       int max_depth = 0;
352       for (const ReportingReport* report : delivery->reports()) {
353         undelivered_reports.erase(report);
354         max_depth = std::max(report->depth, max_depth);
355       }
356 
357       std::string upload_data =
358           SerializeReports(delivery->reports(), tick_clock().NowTicks());
359 
360       // TODO: Calculate actual max depth.
361       uploader()->StartUpload(
362           target.origin, target.endpoint_url, target.isolation_info,
363           upload_data, max_depth,
364           /*eligible_for_credentials=*/target.reporting_source.has_value(),
365           base::BindOnce(&ReportingDeliveryAgentImpl::OnUploadComplete,
366                          weak_factory_.GetWeakPtr(), std::move(delivery)));
367     }
368 
369     cache()->ClearReportsPending(
370         {undelivered_reports.begin(), undelivered_reports.end()});
371   }
372 
OnUploadComplete(std::unique_ptr<Delivery> delivery,ReportingUploader::Outcome outcome)373   void OnUploadComplete(std::unique_ptr<Delivery> delivery,
374                         ReportingUploader::Outcome outcome) {
375     bool success = outcome == ReportingUploader::Outcome::SUCCESS;
376     delivery->ProcessOutcome(cache(), success);
377 
378     endpoint_manager_->InformOfEndpointRequest(
379         delivery->network_anonymization_key(), delivery->endpoint_url(),
380         success);
381 
382     // TODO(chlily): This leaks information across NAKs. If the endpoint URL is
383     // configured for both NAK1 and NAK2, and it responds with a 410 on a NAK1
384     // connection, then the change in configuration will be detectable on a NAK2
385     // connection.
386     // TODO(rodneyding): Handle Remove endpoint for Reporting-Endpoints header.
387     if (outcome == ReportingUploader::Outcome::REMOVE_ENDPOINT)
388       cache()->RemoveEndpointsForUrl(delivery->endpoint_url());
389 
390     for (const ReportingReport* report : delivery->reports()) {
391       pending_groups_.erase(report->GetGroupKey());
392     }
393 
394     cache()->ClearReportsPending(delivery->reports());
395   }
396 
policy() const397   const ReportingPolicy& policy() const { return context_->policy(); }
tick_clock() const398   const base::TickClock& tick_clock() const { return context_->tick_clock(); }
delegate()399   ReportingDelegate* delegate() { return context_->delegate(); }
cache()400   ReportingCache* cache() { return context_->cache(); }
uploader()401   ReportingUploader* uploader() { return context_->uploader(); }
402 
403   raw_ptr<ReportingContext> context_;
404 
405   std::unique_ptr<base::OneShotTimer> timer_;
406 
407   // Tracks endpoint groups for which there is a pending delivery running.
408   std::set<ReportingEndpointGroupKey> pending_groups_;
409 
410   std::unique_ptr<ReportingEndpointManager> endpoint_manager_;
411 
412   base::WeakPtrFactory<ReportingDeliveryAgentImpl> weak_factory_{this};
413 };
414 
415 }  // namespace
416 
417 // static
Create(ReportingContext * context,const RandIntCallback & rand_callback)418 std::unique_ptr<ReportingDeliveryAgent> ReportingDeliveryAgent::Create(
419     ReportingContext* context,
420     const RandIntCallback& rand_callback) {
421   return std::make_unique<ReportingDeliveryAgentImpl>(context, rand_callback);
422 }
423 
424 ReportingDeliveryAgent::~ReportingDeliveryAgent() = default;
425 
426 }  // namespace net
427