Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / safe_browsing / incident_reporting / incident_reporting_service.cc
blob8af46f12cc5ba793de761e6dec9175702f1d7a15
1 // Copyright 2014 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.
5 #include "chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.h"
7 #include <math.h>
9 #include <algorithm>
10 #include <vector>
12 #include "base/metrics/histogram.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/prefs/scoped_user_pref_update.h"
15 #include "base/process/process_info.h"
16 #include "base/single_thread_task_runner.h"
17 #include "base/stl_util.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/thread_task_runner_handle.h"
20 #include "base/threading/sequenced_worker_pool.h"
21 #include "base/values.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/chrome_notification_types.h"
24 #include "chrome/browser/prefs/tracked/tracked_preference_validation_delegate.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/safe_browsing/database_manager.h"
27 #include "chrome/browser/safe_browsing/incident_reporting/binary_integrity_incident_handlers.h"
28 #include "chrome/browser/safe_browsing/incident_reporting/blacklist_load_incident_handlers.h"
29 #include "chrome/browser/safe_browsing/incident_reporting/environment_data_collection.h"
30 #include "chrome/browser/safe_browsing/incident_reporting/incident_report_uploader_impl.h"
31 #include "chrome/browser/safe_browsing/incident_reporting/preference_validation_delegate.h"
32 #include "chrome/browser/safe_browsing/incident_reporting/tracked_preference_incident_handlers.h"
33 #include "chrome/browser/safe_browsing/incident_reporting/variations_seed_signature_incident_handlers.h"
34 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
35 #include "chrome/common/pref_names.h"
36 #include "chrome/common/safe_browsing/csd.pb.h"
37 #include "content/public/browser/browser_thread.h"
38 #include "content/public/browser/notification_service.h"
39 #include "net/url_request/url_request_context_getter.h"
41 namespace safe_browsing {
43 namespace {
45 // The type of an incident. Used for user metrics and for pruning of
46 // previously-reported incidents.
47 enum IncidentType {
48 // Start with 1 rather than zero; otherwise there won't be enough buckets for
49 // the histogram.
50 TRACKED_PREFERENCE = 1,
51 BINARY_INTEGRITY = 2,
52 BLACKLIST_LOAD = 3,
53 VARIATIONS_SEED_SIGNATURE = 4,
54 // Values for new incident types go here.
55 NUM_INCIDENT_TYPES = 5
58 // The action taken for an incident; used for user metrics (see
59 // LogIncidentDataType).
60 enum IncidentDisposition {
61 DROPPED,
62 ACCEPTED,
65 // The state persisted for a specific instance of an incident to enable pruning
66 // of previously-reported incidents.
67 struct PersistentIncidentState {
68 // The type of the incident.
69 IncidentType type;
71 // The key for a specific instance of an incident.
72 std::string key;
74 // A hash digest representing a specific instance of an incident.
75 uint32_t digest;
78 // The amount of time the service will wait to collate incidents.
79 const int64 kDefaultUploadDelayMs = 1000 * 60; // one minute
81 // The amount of time between running delayed analysis callbacks.
82 const int64 kDefaultCallbackIntervalMs = 1000 * 20;
84 // Returns the number of incidents contained in |incident|. The result is
85 // expected to be 1. Used in DCHECKs.
86 size_t CountIncidents(const ClientIncidentReport_IncidentData& incident) {
87 size_t result = 0;
88 if (incident.has_tracked_preference())
89 ++result;
90 if (incident.has_binary_integrity())
91 ++result;
92 if (incident.has_blacklist_load())
93 ++result;
94 if (incident.has_variations_seed_signature())
95 ++result;
96 // Add detection for new incident types here.
97 return result;
100 // Returns the type of incident contained in |incident_data|.
101 IncidentType GetIncidentType(
102 const ClientIncidentReport_IncidentData& incident_data) {
103 if (incident_data.has_tracked_preference())
104 return TRACKED_PREFERENCE;
105 if (incident_data.has_binary_integrity())
106 return BINARY_INTEGRITY;
107 if (incident_data.has_blacklist_load())
108 return BLACKLIST_LOAD;
109 if (incident_data.has_variations_seed_signature())
110 return VARIATIONS_SEED_SIGNATURE;
112 // Add detection for new incident types here.
113 COMPILE_ASSERT(VARIATIONS_SEED_SIGNATURE + 1 == NUM_INCIDENT_TYPES,
114 add_support_for_new_types);
115 NOTREACHED();
116 return NUM_INCIDENT_TYPES;
119 // Logs the type of incident in |incident_data| to a user metrics histogram.
120 void LogIncidentDataType(
121 IncidentDisposition disposition,
122 const ClientIncidentReport_IncidentData& incident_data) {
123 IncidentType type = GetIncidentType(incident_data);
124 if (disposition == ACCEPTED) {
125 UMA_HISTOGRAM_ENUMERATION("SBIRS.Incident", type, NUM_INCIDENT_TYPES);
126 } else {
127 DCHECK_EQ(disposition, DROPPED);
128 UMA_HISTOGRAM_ENUMERATION("SBIRS.DroppedIncident", type,
129 NUM_INCIDENT_TYPES);
133 // Computes the persistent state for an incident.
134 PersistentIncidentState ComputeIncidentState(
135 const ClientIncidentReport_IncidentData& incident) {
136 PersistentIncidentState state = {GetIncidentType(incident)};
137 switch (state.type) {
138 case TRACKED_PREFERENCE:
139 state.key = GetTrackedPreferenceIncidentKey(incident);
140 state.digest = GetTrackedPreferenceIncidentDigest(incident);
141 break;
142 case BINARY_INTEGRITY:
143 state.key = GetBinaryIntegrityIncidentKey(incident);
144 state.digest = GetBinaryIntegrityIncidentDigest(incident);
145 break;
146 case BLACKLIST_LOAD:
147 state.key = GetBlacklistLoadIncidentKey(incident);
148 state.digest = GetBlacklistLoadIncidentDigest(incident);
149 break;
150 case VARIATIONS_SEED_SIGNATURE:
151 state.key = GetVariationsSeedSignatureIncidentKey(incident);
152 state.digest = GetVariationsSeedSignatureIncidentDigest(incident);
153 break;
154 // Add handling for new incident types here.
155 default:
156 COMPILE_ASSERT(VARIATIONS_SEED_SIGNATURE + 1 == NUM_INCIDENT_TYPES,
157 add_support_for_new_types);
158 NOTREACHED();
159 break;
161 return state;
164 // Returns true if the incident described by |state| has already been reported
165 // based on the bookkeeping in the |incidents_sent| preference dictionary.
166 bool IncidentHasBeenReported(const base::DictionaryValue* incidents_sent,
167 const PersistentIncidentState& state) {
168 const base::DictionaryValue* type_dict = NULL;
169 std::string digest_string;
170 return (incidents_sent &&
171 incidents_sent->GetDictionaryWithoutPathExpansion(
172 base::IntToString(state.type), &type_dict) &&
173 type_dict->GetStringWithoutPathExpansion(state.key, &digest_string) &&
174 digest_string == base::UintToString(state.digest));
177 // Marks the incidents described by |states| as having been reported
178 // in |incidents_set|.
179 void MarkIncidentsAsReported(const std::vector<PersistentIncidentState>& states,
180 base::DictionaryValue* incidents_sent) {
181 for (size_t i = 0; i < states.size(); ++i) {
182 const PersistentIncidentState& data = states[i];
183 base::DictionaryValue* type_dict = NULL;
184 const std::string type_string(base::IntToString(data.type));
185 if (!incidents_sent->GetDictionaryWithoutPathExpansion(type_string,
186 &type_dict)) {
187 type_dict = new base::DictionaryValue();
188 incidents_sent->SetWithoutPathExpansion(type_string, type_dict);
190 type_dict->SetStringWithoutPathExpansion(data.key,
191 base::UintToString(data.digest));
195 // Runs |callback| on the thread to which |thread_runner| belongs. The callback
196 // is run immediately if this function is called on |thread_runner|'s thread.
197 void AddIncidentOnOriginThread(
198 const AddIncidentCallback& callback,
199 scoped_refptr<base::SingleThreadTaskRunner> thread_runner,
200 scoped_ptr<ClientIncidentReport_IncidentData> incident) {
201 if (thread_runner->BelongsToCurrentThread())
202 callback.Run(incident.Pass());
203 else
204 thread_runner->PostTask(FROM_HERE,
205 base::Bind(callback, base::Passed(&incident)));
208 } // namespace
210 struct IncidentReportingService::ProfileContext {
211 ProfileContext();
212 ~ProfileContext();
214 // The incidents collected for this profile pending creation and/or upload.
215 ScopedVector<ClientIncidentReport_IncidentData> incidents;
217 // False until PROFILE_ADDED notification is received.
218 bool added;
220 private:
221 DISALLOW_COPY_AND_ASSIGN(ProfileContext);
224 class IncidentReportingService::UploadContext {
225 public:
226 typedef std::map<Profile*, std::vector<PersistentIncidentState> >
227 PersistentIncidentStateCollection;
229 explicit UploadContext(scoped_ptr<ClientIncidentReport> report);
230 ~UploadContext();
232 // The report being uploaded.
233 scoped_ptr<ClientIncidentReport> report;
235 // The uploader in use. This is NULL until the CSD killswitch is checked.
236 scoped_ptr<IncidentReportUploader> uploader;
238 // A mapping of profiles to the data to be persisted upon successful upload.
239 PersistentIncidentStateCollection profiles_to_state;
241 private:
242 DISALLOW_COPY_AND_ASSIGN(UploadContext);
245 IncidentReportingService::ProfileContext::ProfileContext() : added() {
248 IncidentReportingService::ProfileContext::~ProfileContext() {
251 IncidentReportingService::UploadContext::UploadContext(
252 scoped_ptr<ClientIncidentReport> report)
253 : report(report.Pass()) {
256 IncidentReportingService::UploadContext::~UploadContext() {
259 IncidentReportingService::IncidentReportingService(
260 SafeBrowsingService* safe_browsing_service,
261 const scoped_refptr<net::URLRequestContextGetter>& request_context_getter)
262 : database_manager_(safe_browsing_service ?
263 safe_browsing_service->database_manager() : NULL),
264 url_request_context_getter_(request_context_getter),
265 collect_environment_data_fn_(&CollectEnvironmentData),
266 environment_collection_task_runner_(
267 content::BrowserThread::GetBlockingPool()
268 ->GetTaskRunnerWithShutdownBehavior(
269 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
270 environment_collection_pending_(),
271 collation_timeout_pending_(),
272 collation_timer_(FROM_HERE,
273 base::TimeDelta::FromMilliseconds(kDefaultUploadDelayMs),
274 this,
275 &IncidentReportingService::OnCollationTimeout),
276 delayed_analysis_callbacks_(
277 base::TimeDelta::FromMilliseconds(kDefaultCallbackIntervalMs),
278 content::BrowserThread::GetBlockingPool()
279 ->GetTaskRunnerWithShutdownBehavior(
280 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
281 receiver_weak_ptr_factory_(this),
282 weak_ptr_factory_(this) {
283 notification_registrar_.Add(this,
284 chrome::NOTIFICATION_PROFILE_ADDED,
285 content::NotificationService::AllSources());
286 notification_registrar_.Add(this,
287 chrome::NOTIFICATION_PROFILE_DESTROYED,
288 content::NotificationService::AllSources());
291 IncidentReportingService::~IncidentReportingService() {
292 CancelIncidentCollection();
294 // Cancel all internal asynchronous tasks.
295 weak_ptr_factory_.InvalidateWeakPtrs();
297 CancelEnvironmentCollection();
298 CancelDownloadCollection();
299 CancelAllReportUploads();
301 STLDeleteValues(&profiles_);
304 AddIncidentCallback IncidentReportingService::GetAddIncidentCallback(
305 Profile* profile) {
306 // Force the context to be created so that incidents added before
307 // OnProfileAdded is called are held until the profile's preferences can be
308 // queried.
309 ignore_result(GetOrCreateProfileContext(profile));
311 return base::Bind(&IncidentReportingService::AddIncident,
312 receiver_weak_ptr_factory_.GetWeakPtr(),
313 profile);
316 scoped_ptr<TrackedPreferenceValidationDelegate>
317 IncidentReportingService::CreatePreferenceValidationDelegate(Profile* profile) {
318 DCHECK(thread_checker_.CalledOnValidThread());
320 if (profile->IsOffTheRecord())
321 return scoped_ptr<TrackedPreferenceValidationDelegate>();
322 return scoped_ptr<TrackedPreferenceValidationDelegate>(
323 new PreferenceValidationDelegate(GetAddIncidentCallback(profile)));
326 void IncidentReportingService::RegisterDelayedAnalysisCallback(
327 const DelayedAnalysisCallback& callback) {
328 DCHECK(thread_checker_.CalledOnValidThread());
330 // |callback| will be run on the blocking pool, so it will likely run the
331 // AddIncidentCallback there as well. Bounce the run of that callback back to
332 // the current thread via AddIncidentOnOriginThread.
333 delayed_analysis_callbacks_.RegisterCallback(
334 base::Bind(callback,
335 base::Bind(&AddIncidentOnOriginThread,
336 GetAddIncidentCallback(NULL),
337 base::ThreadTaskRunnerHandle::Get())));
339 // Start running the callbacks if any profiles are participating in safe
340 // browsing. If none are now, running will commence if/when a participaing
341 // profile is added.
342 if (FindEligibleProfile())
343 delayed_analysis_callbacks_.Start();
346 IncidentReportingService::IncidentReportingService(
347 SafeBrowsingService* safe_browsing_service,
348 const scoped_refptr<net::URLRequestContextGetter>& request_context_getter,
349 base::TimeDelta delayed_task_interval,
350 const scoped_refptr<base::TaskRunner>& delayed_task_runner)
351 : database_manager_(safe_browsing_service ?
352 safe_browsing_service->database_manager() : NULL),
353 url_request_context_getter_(request_context_getter),
354 collect_environment_data_fn_(&CollectEnvironmentData),
355 environment_collection_task_runner_(
356 content::BrowserThread::GetBlockingPool()
357 ->GetTaskRunnerWithShutdownBehavior(
358 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
359 environment_collection_pending_(),
360 collation_timeout_pending_(),
361 collation_timer_(FROM_HERE,
362 base::TimeDelta::FromMilliseconds(kDefaultUploadDelayMs),
363 this,
364 &IncidentReportingService::OnCollationTimeout),
365 delayed_analysis_callbacks_(delayed_task_interval, delayed_task_runner),
366 receiver_weak_ptr_factory_(this),
367 weak_ptr_factory_(this) {
368 notification_registrar_.Add(this,
369 chrome::NOTIFICATION_PROFILE_ADDED,
370 content::NotificationService::AllSources());
371 notification_registrar_.Add(this,
372 chrome::NOTIFICATION_PROFILE_DESTROYED,
373 content::NotificationService::AllSources());
376 void IncidentReportingService::SetCollectEnvironmentHook(
377 CollectEnvironmentDataFn collect_environment_data_hook,
378 const scoped_refptr<base::TaskRunner>& task_runner) {
379 if (collect_environment_data_hook) {
380 collect_environment_data_fn_ = collect_environment_data_hook;
381 environment_collection_task_runner_ = task_runner;
382 } else {
383 collect_environment_data_fn_ = &CollectEnvironmentData;
384 environment_collection_task_runner_ =
385 content::BrowserThread::GetBlockingPool()
386 ->GetTaskRunnerWithShutdownBehavior(
387 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
391 void IncidentReportingService::OnProfileAdded(Profile* profile) {
392 DCHECK(thread_checker_.CalledOnValidThread());
394 // Track the addition of all profiles even when no report is being assembled
395 // so that the service can determine whether or not it can evaluate a
396 // profile's preferences at the time of incident addition.
397 ProfileContext* context = GetOrCreateProfileContext(profile);
398 context->added = true;
400 const bool safe_browsing_enabled =
401 profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled);
403 // Start processing delayed analysis callbacks if this new profile
404 // participates in safe browsing. Start is idempotent, so this is safe even if
405 // they're already running.
406 if (safe_browsing_enabled)
407 delayed_analysis_callbacks_.Start();
409 // Start a new report if this profile participates in safe browsing and there
410 // are process-wide incidents.
411 if (safe_browsing_enabled && GetProfileContext(NULL) &&
412 GetProfileContext(NULL)->incidents.size()) {
413 BeginReportProcessing();
416 // TODO(grt): register for pref change notifications to start delayed analysis
417 // and/or report processing if sb is currently disabled but subsequently
418 // enabled.
420 // Nothing else to do if a report is not being assembled.
421 if (!report_)
422 return;
424 // Drop all incidents associated with this profile that were received prior to
425 // its addition if the profile is not participating in safe browsing.
426 if (!context->incidents.empty() && !safe_browsing_enabled) {
427 for (size_t i = 0; i < context->incidents.size(); ++i)
428 LogIncidentDataType(DROPPED, *context->incidents[i]);
429 context->incidents.clear();
432 // Take another stab at finding the most recent download if a report is being
433 // assembled and one hasn't been found yet (the LastDownloadFinder operates
434 // only on profiles that have been added to the ProfileManager).
435 BeginDownloadCollection();
438 scoped_ptr<LastDownloadFinder> IncidentReportingService::CreateDownloadFinder(
439 const LastDownloadFinder::LastDownloadCallback& callback) {
440 return LastDownloadFinder::Create(callback).Pass();
443 scoped_ptr<IncidentReportUploader> IncidentReportingService::StartReportUpload(
444 const IncidentReportUploader::OnResultCallback& callback,
445 const scoped_refptr<net::URLRequestContextGetter>& request_context_getter,
446 const ClientIncidentReport& report) {
447 return IncidentReportUploaderImpl::UploadReport(
448 callback, request_context_getter, report).Pass();
451 bool IncidentReportingService::IsProcessingReport() const {
452 return report_ != NULL;
455 IncidentReportingService::ProfileContext*
456 IncidentReportingService::GetOrCreateProfileContext(Profile* profile) {
457 ProfileContextCollection::iterator it =
458 profiles_.insert(ProfileContextCollection::value_type(profile, NULL))
459 .first;
460 if (!it->second)
461 it->second = new ProfileContext();
462 return it->second;
465 IncidentReportingService::ProfileContext*
466 IncidentReportingService::GetProfileContext(Profile* profile) {
467 ProfileContextCollection::iterator it = profiles_.find(profile);
468 return it == profiles_.end() ? NULL : it->second;
471 void IncidentReportingService::OnProfileDestroyed(Profile* profile) {
472 DCHECK(thread_checker_.CalledOnValidThread());
474 ProfileContextCollection::iterator it = profiles_.find(profile);
475 if (it == profiles_.end())
476 return;
478 // TODO(grt): Persist incidents for upload on future profile load.
480 // Forget about this profile. Incidents not yet sent for upload are lost.
481 // No new incidents will be accepted for it.
482 delete it->second;
483 profiles_.erase(it);
485 // Remove the association with this profile from all pending uploads.
486 for (size_t i = 0; i < uploads_.size(); ++i)
487 uploads_[i]->profiles_to_state.erase(profile);
490 Profile* IncidentReportingService::FindEligibleProfile() const {
491 Profile* candidate = NULL;
492 for (ProfileContextCollection::const_iterator scan = profiles_.begin();
493 scan != profiles_.end();
494 ++scan) {
495 // Skip over profiles that have yet to be added to the profile manager.
496 // This will also skip over the NULL-profile context used to hold
497 // process-wide incidents.
498 if (!scan->second->added)
499 continue;
500 PrefService* prefs = scan->first->GetPrefs();
501 if (prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) {
502 if (!candidate)
503 candidate = scan->first;
504 if (prefs->GetBoolean(prefs::kSafeBrowsingExtendedReportingEnabled)) {
505 candidate = scan->first;
506 break;
510 return candidate;
513 void IncidentReportingService::AddIncident(
514 Profile* profile,
515 scoped_ptr<ClientIncidentReport_IncidentData> incident_data) {
516 DCHECK(thread_checker_.CalledOnValidThread());
517 DCHECK_EQ(1U, CountIncidents(*incident_data));
519 ProfileContext* context = GetProfileContext(profile);
520 // It is forbidden to call this function with a destroyed profile.
521 DCHECK(context);
522 // If this is a process-wide incident, the context must not indicate that the
523 // profile (which is NULL) has been added to the profile manager.
524 DCHECK(profile || !context->added);
526 // Drop the incident immediately if the profile has already been added to the
527 // manager and is not participating in safe browsing. Preference evaluation is
528 // deferred until OnProfileAdded() otherwise.
529 if (context->added &&
530 !profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) {
531 LogIncidentDataType(DROPPED, *incident_data);
532 return;
535 // Provide time to the new incident if the caller didn't do so.
536 if (!incident_data->has_incident_time_msec())
537 incident_data->set_incident_time_msec(base::Time::Now().ToJavaTime());
539 // Take ownership of the incident.
540 context->incidents.push_back(incident_data.release());
542 // Remember when the first incident for this report arrived.
543 if (first_incident_time_.is_null())
544 first_incident_time_ = base::Time::Now();
545 // Log the time between the previous incident and this one.
546 if (!last_incident_time_.is_null()) {
547 UMA_HISTOGRAM_TIMES("SBIRS.InterIncidentTime",
548 base::TimeTicks::Now() - last_incident_time_);
550 last_incident_time_ = base::TimeTicks::Now();
552 // Persist the incident data.
554 // Start assembling a new report if this is the first incident ever or the
555 // first since the last upload.
556 BeginReportProcessing();
559 void IncidentReportingService::BeginReportProcessing() {
560 DCHECK(thread_checker_.CalledOnValidThread());
562 // Creates a new report if needed.
563 if (!report_)
564 report_.reset(new ClientIncidentReport());
566 // Ensure that collection tasks are running (calls are idempotent).
567 BeginIncidentCollation();
568 BeginEnvironmentCollection();
569 BeginDownloadCollection();
572 void IncidentReportingService::BeginIncidentCollation() {
573 // Restart the delay timer to send the report upon expiration.
574 collation_timeout_pending_ = true;
575 collation_timer_.Reset();
578 void IncidentReportingService::BeginEnvironmentCollection() {
579 DCHECK(thread_checker_.CalledOnValidThread());
580 DCHECK(report_);
581 // Nothing to do if environment collection is pending or has already
582 // completed.
583 if (environment_collection_pending_ || report_->has_environment())
584 return;
586 environment_collection_begin_ = base::TimeTicks::Now();
587 ClientIncidentReport_EnvironmentData* environment_data =
588 new ClientIncidentReport_EnvironmentData();
589 environment_collection_pending_ =
590 environment_collection_task_runner_->PostTaskAndReply(
591 FROM_HERE,
592 base::Bind(collect_environment_data_fn_, environment_data),
593 base::Bind(&IncidentReportingService::OnEnvironmentDataCollected,
594 weak_ptr_factory_.GetWeakPtr(),
595 base::Passed(make_scoped_ptr(environment_data))));
597 // Posting the task will fail if the runner has been shut down. This should
598 // never happen since the blocking pool is shut down after this service.
599 DCHECK(environment_collection_pending_);
602 bool IncidentReportingService::WaitingForEnvironmentCollection() {
603 return environment_collection_pending_;
606 void IncidentReportingService::CancelEnvironmentCollection() {
607 environment_collection_begin_ = base::TimeTicks();
608 environment_collection_pending_ = false;
609 if (report_)
610 report_->clear_environment();
613 void IncidentReportingService::OnEnvironmentDataCollected(
614 scoped_ptr<ClientIncidentReport_EnvironmentData> environment_data) {
615 DCHECK(thread_checker_.CalledOnValidThread());
616 DCHECK(environment_collection_pending_);
617 DCHECK(report_ && !report_->has_environment());
618 environment_collection_pending_ = false;
620 // CurrentProcessInfo::CreationTime() is missing on some platforms.
621 #if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX)
622 base::TimeDelta uptime =
623 first_incident_time_ - base::CurrentProcessInfo::CreationTime();
624 environment_data->mutable_process()->set_uptime_msec(uptime.InMilliseconds());
625 #endif
627 report_->set_allocated_environment(environment_data.release());
629 UMA_HISTOGRAM_TIMES("SBIRS.EnvCollectionTime",
630 base::TimeTicks::Now() - environment_collection_begin_);
631 environment_collection_begin_ = base::TimeTicks();
633 UploadIfCollectionComplete();
636 bool IncidentReportingService::WaitingToCollateIncidents() {
637 return collation_timeout_pending_;
640 void IncidentReportingService::CancelIncidentCollection() {
641 collation_timeout_pending_ = false;
642 last_incident_time_ = base::TimeTicks();
643 report_.reset();
646 void IncidentReportingService::OnCollationTimeout() {
647 DCHECK(thread_checker_.CalledOnValidThread());
649 // Exit early if collection was cancelled.
650 if (!collation_timeout_pending_)
651 return;
653 // Wait another round if profile-bound incidents have come in from a profile
654 // that has yet to complete creation.
655 for (ProfileContextCollection::iterator scan = profiles_.begin();
656 scan != profiles_.end();
657 ++scan) {
658 if (scan->first && !scan->second->added &&
659 !scan->second->incidents.empty()) {
660 collation_timer_.Reset();
661 return;
665 collation_timeout_pending_ = false;
667 UploadIfCollectionComplete();
670 void IncidentReportingService::BeginDownloadCollection() {
671 DCHECK(thread_checker_.CalledOnValidThread());
672 DCHECK(report_);
673 // Nothing to do if a search for the most recent download is already pending
674 // or if one has already been found.
675 if (last_download_finder_ || report_->has_download())
676 return;
678 last_download_begin_ = base::TimeTicks::Now();
679 last_download_finder_ = CreateDownloadFinder(
680 base::Bind(&IncidentReportingService::OnLastDownloadFound,
681 weak_ptr_factory_.GetWeakPtr()));
682 // No instance is returned if there are no eligible loaded profiles. Another
683 // search will be attempted in OnProfileAdded() if another profile appears on
684 // the scene.
685 if (!last_download_finder_)
686 last_download_begin_ = base::TimeTicks();
689 bool IncidentReportingService::WaitingForMostRecentDownload() {
690 DCHECK(report_); // Only call this when a report is being assembled.
691 // The easy case: not waiting if a download has already been found.
692 if (report_->has_download())
693 return false;
694 // The next easy case: waiting if the finder is operating.
695 if (last_download_finder_)
696 return true;
697 // The harder case: waiting if a non-NULL profile has not yet been added.
698 for (ProfileContextCollection::const_iterator scan = profiles_.begin();
699 scan != profiles_.end();
700 ++scan) {
701 if (scan->first && !scan->second->added)
702 return true;
704 // There is no most recent download and there's nothing more to wait for.
705 return false;
708 void IncidentReportingService::CancelDownloadCollection() {
709 last_download_finder_.reset();
710 last_download_begin_ = base::TimeTicks();
711 if (report_)
712 report_->clear_download();
715 void IncidentReportingService::OnLastDownloadFound(
716 scoped_ptr<ClientIncidentReport_DownloadDetails> last_download) {
717 DCHECK(thread_checker_.CalledOnValidThread());
718 DCHECK(report_);
720 UMA_HISTOGRAM_TIMES("SBIRS.FindDownloadedBinaryTime",
721 base::TimeTicks::Now() - last_download_begin_);
722 last_download_begin_ = base::TimeTicks();
724 // Harvest the finder.
725 last_download_finder_.reset();
727 if (last_download)
728 report_->set_allocated_download(last_download.release());
730 UploadIfCollectionComplete();
733 void IncidentReportingService::UploadIfCollectionComplete() {
734 DCHECK(report_);
735 // Bail out if there are still outstanding collection tasks. Completion of any
736 // of these will start another upload attempt.
737 if (WaitingForEnvironmentCollection() ||
738 WaitingToCollateIncidents() ||
739 WaitingForMostRecentDownload()) {
740 return;
743 // Take ownership of the report and clear things for future reports.
744 scoped_ptr<ClientIncidentReport> report(report_.Pass());
745 first_incident_time_ = base::Time();
746 last_incident_time_ = base::TimeTicks();
748 // Drop the report if no executable download was found.
749 if (!report->has_download()) {
750 UMA_HISTOGRAM_ENUMERATION("SBIRS.UploadResult",
751 IncidentReportUploader::UPLOAD_NO_DOWNLOAD,
752 IncidentReportUploader::NUM_UPLOAD_RESULTS);
753 return;
756 ClientIncidentReport_EnvironmentData_Process* process =
757 report->mutable_environment()->mutable_process();
759 // Not all platforms have a metrics reporting preference.
760 if (g_browser_process->local_state()->FindPreference(
761 prefs::kMetricsReportingEnabled)) {
762 process->set_metrics_consent(g_browser_process->local_state()->GetBoolean(
763 prefs::kMetricsReportingEnabled));
766 // Find the profile that benefits from the strongest protections.
767 Profile* eligible_profile = FindEligibleProfile();
768 process->set_extended_consent(
769 eligible_profile ? eligible_profile->GetPrefs()->GetBoolean(
770 prefs::kSafeBrowsingExtendedReportingEnabled) :
771 false);
773 // Associate process-wide incidents with the profile that benefits from the
774 // strongest safe browsing protections.
775 ProfileContext* null_context = GetProfileContext(NULL);
776 if (null_context && !null_context->incidents.empty() && eligible_profile) {
777 ProfileContext* eligible_context = GetProfileContext(eligible_profile);
778 // Move the incidents to the target context.
779 eligible_context->incidents.insert(eligible_context->incidents.end(),
780 null_context->incidents.begin(),
781 null_context->incidents.end());
782 null_context->incidents.weak_clear();
785 // Collect incidents across all profiles participating in safe browsing. Drop
786 // incidents if the profile stopped participating before collection completed.
787 // Prune previously submitted incidents.
788 // Associate the profiles and their incident data with the upload.
789 size_t prune_count = 0;
790 UploadContext::PersistentIncidentStateCollection profiles_to_state;
791 for (ProfileContextCollection::iterator scan = profiles_.begin();
792 scan != profiles_.end();
793 ++scan) {
794 // Bypass process-wide incidents that have not yet been associated with a
795 // profile.
796 if (!scan->first)
797 continue;
798 PrefService* prefs = scan->first->GetPrefs();
799 ProfileContext* context = scan->second;
800 if (context->incidents.empty())
801 continue;
802 if (!prefs->GetBoolean(prefs::kSafeBrowsingEnabled)) {
803 for (size_t i = 0; i < context->incidents.size(); ++i) {
804 LogIncidentDataType(DROPPED, *context->incidents[i]);
806 context->incidents.clear();
807 continue;
809 std::vector<PersistentIncidentState> states;
810 const base::DictionaryValue* incidents_sent =
811 prefs->GetDictionary(prefs::kSafeBrowsingIncidentsSent);
812 // Prep persistent data and prune any incidents already sent.
813 for (size_t i = 0; i < context->incidents.size(); ++i) {
814 ClientIncidentReport_IncidentData* incident = context->incidents[i];
815 const PersistentIncidentState state = ComputeIncidentState(*incident);
816 if (IncidentHasBeenReported(incidents_sent, state)) {
817 ++prune_count;
818 delete context->incidents[i];
819 context->incidents[i] = NULL;
820 } else {
821 states.push_back(state);
824 if (prefs->GetBoolean(prefs::kSafeBrowsingIncidentReportSent)) {
825 // Prune all incidents as if they had been reported, migrating to the new
826 // technique. TODO(grt): remove this branch after it has shipped.
827 for (size_t i = 0; i < context->incidents.size(); ++i) {
828 if (context->incidents[i])
829 ++prune_count;
831 context->incidents.clear();
832 prefs->ClearPref(prefs::kSafeBrowsingIncidentReportSent);
833 DictionaryPrefUpdate pref_update(prefs,
834 prefs::kSafeBrowsingIncidentsSent);
835 MarkIncidentsAsReported(states, pref_update.Get());
836 } else {
837 for (size_t i = 0; i < context->incidents.size(); ++i) {
838 ClientIncidentReport_IncidentData* incident = context->incidents[i];
839 if (incident) {
840 LogIncidentDataType(ACCEPTED, *incident);
841 // Ownership of the incident is passed to the report.
842 report->mutable_incident()->AddAllocated(incident);
845 context->incidents.weak_clear();
846 std::vector<PersistentIncidentState>& profile_states =
847 profiles_to_state[scan->first];
848 profile_states.swap(states);
852 const int count = report->incident_size();
853 // Abandon the request if all incidents were dropped with none pruned.
854 if (!count && !prune_count)
855 return;
857 UMA_HISTOGRAM_COUNTS_100("SBIRS.IncidentCount", count + prune_count);
860 double prune_pct = static_cast<double>(prune_count);
861 prune_pct = prune_pct * 100.0 / (count + prune_count);
862 prune_pct = round(prune_pct);
863 UMA_HISTOGRAM_PERCENTAGE("SBIRS.PruneRatio", static_cast<int>(prune_pct));
865 // Abandon the report if all incidents were pruned.
866 if (!count)
867 return;
869 scoped_ptr<UploadContext> context(new UploadContext(report.Pass()));
870 context->profiles_to_state.swap(profiles_to_state);
871 if (!database_manager_.get()) {
872 // No database manager during testing. Take ownership of the context and
873 // continue processing.
874 UploadContext* temp_context = context.get();
875 uploads_.push_back(context.release());
876 IncidentReportingService::OnKillSwitchResult(temp_context, false);
877 } else {
878 if (content::BrowserThread::PostTaskAndReplyWithResult(
879 content::BrowserThread::IO,
880 FROM_HERE,
881 base::Bind(&SafeBrowsingDatabaseManager::IsCsdWhitelistKillSwitchOn,
882 database_manager_),
883 base::Bind(&IncidentReportingService::OnKillSwitchResult,
884 weak_ptr_factory_.GetWeakPtr(),
885 context.get()))) {
886 uploads_.push_back(context.release());
887 } // else should not happen. Let the context be deleted automatically.
891 void IncidentReportingService::CancelAllReportUploads() {
892 for (size_t i = 0; i < uploads_.size(); ++i) {
893 UMA_HISTOGRAM_ENUMERATION("SBIRS.UploadResult",
894 IncidentReportUploader::UPLOAD_CANCELLED,
895 IncidentReportUploader::NUM_UPLOAD_RESULTS);
897 uploads_.clear();
900 void IncidentReportingService::OnKillSwitchResult(UploadContext* context,
901 bool is_killswitch_on) {
902 DCHECK(thread_checker_.CalledOnValidThread());
903 if (!is_killswitch_on) {
904 // Initiate the upload.
905 context->uploader =
906 StartReportUpload(
907 base::Bind(&IncidentReportingService::OnReportUploadResult,
908 weak_ptr_factory_.GetWeakPtr(),
909 context),
910 url_request_context_getter_,
911 *context->report).Pass();
912 if (!context->uploader) {
913 OnReportUploadResult(context,
914 IncidentReportUploader::UPLOAD_INVALID_REQUEST,
915 scoped_ptr<ClientIncidentResponse>());
917 } else {
918 OnReportUploadResult(context,
919 IncidentReportUploader::UPLOAD_SUPPRESSED,
920 scoped_ptr<ClientIncidentResponse>());
924 void IncidentReportingService::HandleResponse(const UploadContext& context) {
925 for (UploadContext::PersistentIncidentStateCollection::const_iterator scan =
926 context.profiles_to_state.begin();
927 scan != context.profiles_to_state.end();
928 ++scan) {
929 DictionaryPrefUpdate pref_update(scan->first->GetPrefs(),
930 prefs::kSafeBrowsingIncidentsSent);
931 MarkIncidentsAsReported(scan->second, pref_update.Get());
935 void IncidentReportingService::OnReportUploadResult(
936 UploadContext* context,
937 IncidentReportUploader::Result result,
938 scoped_ptr<ClientIncidentResponse> response) {
939 DCHECK(thread_checker_.CalledOnValidThread());
941 UMA_HISTOGRAM_ENUMERATION(
942 "SBIRS.UploadResult", result, IncidentReportUploader::NUM_UPLOAD_RESULTS);
944 // The upload is no longer outstanding, so take ownership of the context (from
945 // the collection of outstanding uploads) in this scope.
946 ScopedVector<UploadContext>::iterator it(
947 std::find(uploads_.begin(), uploads_.end(), context));
948 DCHECK(it != uploads_.end());
949 scoped_ptr<UploadContext> upload(context); // == *it
950 *it = uploads_.back();
951 uploads_.weak_erase(uploads_.end() - 1);
953 if (result == IncidentReportUploader::UPLOAD_SUCCESS)
954 HandleResponse(*upload);
955 // else retry?
958 void IncidentReportingService::Observe(
959 int type,
960 const content::NotificationSource& source,
961 const content::NotificationDetails& details) {
962 switch (type) {
963 case chrome::NOTIFICATION_PROFILE_ADDED: {
964 Profile* profile = content::Source<Profile>(source).ptr();
965 if (!profile->IsOffTheRecord())
966 OnProfileAdded(profile);
967 break;
969 case chrome::NOTIFICATION_PROFILE_DESTROYED: {
970 Profile* profile = content::Source<Profile>(source).ptr();
971 if (!profile->IsOffTheRecord())
972 OnProfileDestroyed(profile);
973 break;
975 default:
976 break;
980 } // namespace safe_browsing