Chromium Code Reviews| Index: chrome/browser/safe_browsing/incident_reporting_service.cc |
| diff --git a/chrome/browser/safe_browsing/incident_reporting_service.cc b/chrome/browser/safe_browsing/incident_reporting_service.cc |
| index 46cb9f3a35d4507215a42bea61766fe747feb069..4b6132f77175cecef66a4dc8ac3b35dab39bf989 100644 |
| --- a/chrome/browser/safe_browsing/incident_reporting_service.cc |
| +++ b/chrome/browser/safe_browsing/incident_reporting_service.cc |
| @@ -9,11 +9,15 @@ |
| #include <algorithm> |
| #include <vector> |
| +#include "base/hash.h" |
| #include "base/metrics/histogram.h" |
| #include "base/prefs/pref_service.h" |
| +#include "base/prefs/scoped_user_pref_update.h" |
| #include "base/process/process_info.h" |
| #include "base/stl_util.h" |
| +#include "base/strings/string_number_conversions.h" |
| #include "base/threading/sequenced_worker_pool.h" |
| +#include "base/values.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/prefs/tracked/tracked_preference_validation_delegate.h" |
| @@ -45,16 +49,94 @@ enum IncidentDisposition { |
| ACCEPTED, |
| }; |
| +// The state persisted for a specific instance of an incident to enable pruning |
| +// of previously-reported incidents. |
| +struct PersistentIncidentState { |
| + // The type of the incident. |
| + IncidentType type; |
| + |
| + // The key for a specific instance of an incident. |
| + std::string key; |
| + |
| + // A hash digest representing a specific instance of an incident. |
| + uint32_t digest; |
| +}; |
| + |
| const int64 kDefaultUploadDelayMs = 1000 * 60; // one minute |
| -void LogIncidentDataType( |
| - IncidentDisposition disposition, |
| +// Handlers for a specific type of incident. |
| +struct IncidentTypeHandlers { |
| + // The type of incident to which these handlers apply. |
| + IncidentType type; |
| + |
| + // A function that returns a unique key representing an incident. |
| + std::string (*get_key_fn)(const ClientIncidentReport_IncidentData&); |
| + |
| + // A function that returns a hash digest of an incident's data. |
| + uint32_t (*get_digest_fn)(const ClientIncidentReport_IncidentData&); |
| +}; |
| + |
| +// Forward declarations of incident type handler functions. |
| +std::string GetTrackedPreferenceIncidentKey( |
| + const ClientIncidentReport_IncidentData& incident_data); |
| +uint32_t GetTrackedPreferenceIncidentDigest( |
| + const ClientIncidentReport_IncidentData& incident_data); |
| + |
| +// The collection of handlers for all incident types. |
| +const IncidentTypeHandlers kIncidentTypeHandlers[] = { |
| + {TRACKED_PREFERENCE, |
| + &GetTrackedPreferenceIncidentKey, |
| + &GetTrackedPreferenceIncidentDigest}, |
| +}; |
| + |
| +// Returns the type of incident contained in |incident_data|. |
| +IncidentType GetIncidentType( |
| const ClientIncidentReport_IncidentData& incident_data) { |
| - IncidentType type = TRACKED_PREFERENCE; |
| + // Add logic once other types are supported. |
| + DCHECK(incident_data.has_tracked_preference()); |
| + |
| + return TRACKED_PREFERENCE; |
| +} |
| + |
| +// Returns the handlers for a given type of incident. |
| +const IncidentTypeHandlers* GetIncidentTypeHandlers(IncidentType type) { |
| + for (size_t i = 0; i < arraysize(kIncidentTypeHandlers); ++i) { |
| + if (kIncidentTypeHandlers[i].type == type) |
| + return &kIncidentTypeHandlers[i]; |
| + } |
| + NOTREACHED(); |
| + return NULL; |
| +} |
| - // Add a switch statement once other types are supported. |
| +// Computes a simple hash digest over the serialized form of |message|. |
| +uint32_t HashMessage(const google::protobuf::MessageLite& message) { |
| + std::string message_string; |
| + if (!message.SerializeToString(&message_string)) { |
| + NOTREACHED(); |
| + return 0; |
| + } |
| + return base::Hash(message_string); |
| +} |
| + |
| +// Returns the path of the tracked preference. |
| +std::string GetTrackedPreferenceIncidentKey( |
| + const ClientIncidentReport_IncidentData& incident_data) { |
| DCHECK(incident_data.has_tracked_preference()); |
| + return incident_data.tracked_preference().path(); |
| +} |
| +// Returns a digest computed over the tracked preference incident data. |
| +uint32_t GetTrackedPreferenceIncidentDigest( |
| + const ClientIncidentReport_IncidentData& incident_data) { |
| + DCHECK(incident_data.has_tracked_preference()); |
| + return HashMessage(incident_data.tracked_preference()); |
| +} |
| + |
| +// Logs the type of incident in |incident_data| to a user metrics histogram. |
| +void LogIncidentDataType( |
| + IncidentDisposition disposition, |
| + const ClientIncidentReport_IncidentData& incident_data) { |
| + IncidentType type = GetIncidentType(incident_data); |
| if (disposition == ACCEPTED) { |
| UMA_HISTOGRAM_ENUMERATION("SBIRS.Incident", type, NUM_INCIDENT_TYPES); |
| } else { |
| @@ -64,6 +146,49 @@ void LogIncidentDataType( |
| } |
| } |
| +// Computes the persistent state for an incident. |
| +PersistentIncidentState ComputeIncidentState( |
| + const ClientIncidentReport_IncidentData& incident) { |
| + PersistentIncidentState state; |
| + |
| + state.type = GetIncidentType(incident); |
| + const IncidentTypeHandlers* handlers = GetIncidentTypeHandlers(state.type); |
| + state.key = handlers->get_key_fn(incident); |
| + state.digest = handlers->get_digest_fn(incident); |
| + |
| + return state; |
| +} |
| + |
| +// Returns true if the incident described by |state| has already been reported |
| +// based on the bookkeeping in the |incidents_sent| preference dictionary. |
| +bool IncidentHasBeenReported(const base::DictionaryValue* incidents_sent, |
| + const PersistentIncidentState& state) { |
| + const base::DictionaryValue* type_dict = NULL; |
| + int digest = 0; |
| + return (incidents_sent && |
| + incidents_sent->GetDictionaryWithoutPathExpansion( |
| + base::IntToString(state.type), &type_dict) && |
| + type_dict->GetIntegerWithoutPathExpansion(state.key, &digest) && |
| + static_cast<uint32_t>(digest) == state.digest); |
| +} |
| + |
| +// Marks the incidents described by |states| as having been reported |
| +// in |incidents_set|. |
| +void MarkIncidentsAsReported(const std::vector<PersistentIncidentState>& states, |
| + base::DictionaryValue* incidents_sent) { |
| + for (size_t i = 0; i < states.size(); ++i) { |
| + const PersistentIncidentState& data = states[i]; |
|
gab
2014/07/23 13:58:10
Use a std::vector::const_iterator rather than loop
grt (UTC plus 2)
2014/07/23 14:45:18
I used to do that, but had the indexing hammered i
|
| + base::DictionaryValue* type_dict = NULL; |
| + std::string type_string(base::IntToString(data.type)); |
|
gab
2014/07/23 13:58:11
optional: const
(I know you sometimes don't like
grt (UTC plus 2)
2014/07/23 14:45:18
Yeah, I'm not too excited about that, but okay. ;-
|
| + if (!incidents_sent->GetDictionaryWithoutPathExpansion(type_string, |
| + &type_dict)) { |
| + type_dict = new base::DictionaryValue(); |
| + incidents_sent->SetWithoutPathExpansion(type_string, type_dict); |
| + } |
| + type_dict->SetIntegerWithoutPathExpansion(data.key, data.digest); |
|
gab
2014/07/23 13:58:10
In IncidentHasBeenReported() you explicitly cast f
grt (UTC plus 2)
2014/07/23 14:45:18
Good catch. I'm surprised the compiler didn't warn
|
| + } |
| +} |
| + |
| } // namespace |
| struct IncidentReportingService::ProfileContext { |
| @@ -82,6 +207,9 @@ struct IncidentReportingService::ProfileContext { |
| class IncidentReportingService::UploadContext { |
| public: |
| + typedef std::map<Profile*, std::vector<PersistentIncidentState> > |
| + PersistentIncidentStateCollection; |
| + |
| explicit UploadContext(scoped_ptr<ClientIncidentReport> report); |
| ~UploadContext(); |
| @@ -91,8 +219,8 @@ class IncidentReportingService::UploadContext { |
| // The uploader in use. This is NULL until the CSD killswitch is checked. |
| scoped_ptr<IncidentReportUploader> uploader; |
| - // The set of profiles from which incidents in |report| originated. |
| - std::vector<Profile*> profiles; |
| + // A mapping of profiles to the data to be persisted upon successful upload. |
| + PersistentIncidentStateCollection profiles_to_state; |
| private: |
| DISALLOW_COPY_AND_ASSIGN(UploadContext); |
| @@ -261,17 +389,9 @@ void IncidentReportingService::OnProfileDestroyed(Profile* profile) { |
| delete it->second; |
| profiles_.erase(it); |
| - // Remove the association with this profile from any pending uploads. |
| - for (size_t i = 0; i < uploads_.size(); ++i) { |
| - UploadContext* upload = uploads_[i]; |
| - std::vector<Profile*>::iterator it = |
| - std::find(upload->profiles.begin(), upload->profiles.end(), profile); |
| - if (it != upload->profiles.end()) { |
| - *it = upload->profiles.back(); |
| - upload->profiles.resize(upload->profiles.size() - 1); |
| - break; |
| - } |
| - } |
| + // Remove the association with this profile from all pending uploads. |
| + for (size_t i = 0; i < uploads_.size(); ++i) |
|
gab
2014/07/23 13:58:11
Use iterator?
|
| + uploads_[i]->profiles_to_state.erase(profile); |
| } |
| void IncidentReportingService::AddIncident( |
| @@ -516,10 +636,10 @@ void IncidentReportingService::UploadIfCollectionComplete() { |
| process->set_extended_consent(false); |
| // Collect incidents across all profiles participating in safe browsing. Drop |
| // incidents if the profile stopped participating before collection completed. |
| - // Prune incidents if the profile has already submitted any incidents. |
| - // Associate the participating profiles with the upload. |
| + // Prune previously submitted incidents. |
| + // Associate the profiles and their incident data with the upload. |
| size_t prune_count = 0; |
| - std::vector<Profile*> profiles; |
| + std::map<Profile*, std::vector<PersistentIncidentState> > profiles_to_state; |
| for (ProfileContextCollection::iterator scan = profiles_.begin(); |
| scan != profiles_.end(); |
| ++scan) { |
| @@ -537,21 +657,48 @@ void IncidentReportingService::UploadIfCollectionComplete() { |
| LogIncidentDataType(DROPPED, *context->incidents[i]); |
| } |
| context->incidents.clear(); |
| - } else if (prefs->GetBoolean(prefs::kSafeBrowsingIncidentReportSent)) { |
| - // Prune all incidents. |
| - // TODO(grt): Only prune previously submitted incidents; |
| - // http://crbug.com/383043. |
| - prune_count += context->incidents.size(); |
| + continue; |
| + } |
| + std::vector<PersistentIncidentState> states; |
| + const base::DictionaryValue* incidents_sent = |
| + prefs->GetDictionary(prefs::kSafeBrowsingIncidentsSent); |
| + // Prep persistent data and prune any incidents already sent. |
| + for (size_t i = 0; i < context->incidents.size(); ++i) { |
|
gab
2014/07/23 13:58:10
Iterator?
|
| + ClientIncidentReport_IncidentData* incident = context->incidents[i]; |
| + PersistentIncidentState state = ComputeIncidentState(*incident); |
|
gab
2014/07/23 13:58:11
const
grt (UTC plus 2)
2014/07/23 14:45:18
Done.
|
| + if (IncidentHasBeenReported(incidents_sent, state)) { |
| + ++prune_count; |
| + delete context->incidents[i]; |
| + context->incidents[i] = NULL; |
| + } else { |
| + states.push_back(state); |
| + } |
| + } |
| + if (prefs->GetBoolean(prefs::kSafeBrowsingIncidentReportSent)) { |
| + // Prune all incidents as if they had been reported, migrating to the new |
| + // technique. TODO(grt): remove this branch after it has shipped. |
| + for (size_t i = 0; i < context->incidents.size(); ++i) { |
| + if (context->incidents[i]) |
| + ++prune_count; |
| + } |
| context->incidents.clear(); |
| + prefs->ClearPref(prefs::kSafeBrowsingIncidentReportSent); |
| + DictionaryPrefUpdate pref_update(prefs, |
| + prefs::kSafeBrowsingIncidentsSent); |
| + MarkIncidentsAsReported(states, pref_update.Get()); |
| } else { |
| for (size_t i = 0; i < context->incidents.size(); ++i) { |
| ClientIncidentReport_IncidentData* incident = context->incidents[i]; |
| - LogIncidentDataType(ACCEPTED, *incident); |
| - // Ownership of the incident is passed to the report. |
| - report->mutable_incident()->AddAllocated(incident); |
| + if (incident) { |
| + LogIncidentDataType(ACCEPTED, *incident); |
| + // Ownership of the incident is passed to the report. |
| + report->mutable_incident()->AddAllocated(incident); |
| + } |
| } |
| context->incidents.weak_clear(); |
| - profiles.push_back(scan->first); |
| + std::vector<PersistentIncidentState>& profile_states = |
| + profiles_to_state[scan->first]; |
| + profile_states.swap(states); |
| } |
| } |
| @@ -573,7 +720,7 @@ void IncidentReportingService::UploadIfCollectionComplete() { |
| return; |
| scoped_ptr<UploadContext> context(new UploadContext(report.Pass())); |
| - context->profiles.swap(profiles); |
| + context->profiles_to_state.swap(profiles_to_state); |
| if (!database_manager_) { |
| // No database manager during testing. Take ownership of the context and |
| // continue processing. |
| @@ -628,9 +775,13 @@ void IncidentReportingService::OnKillSwitchResult(UploadContext* context, |
| } |
| void IncidentReportingService::HandleResponse(const UploadContext& context) { |
| - for (size_t i = 0; i < context.profiles.size(); ++i) { |
| - context.profiles[i]->GetPrefs()->SetBoolean( |
| - prefs::kSafeBrowsingIncidentReportSent, true); |
| + for (UploadContext::PersistentIncidentStateCollection::const_iterator scan = |
| + context.profiles_to_state.begin(); |
| + scan != context.profiles_to_state.end(); |
| + ++scan) { |
| + DictionaryPrefUpdate pref_update(scan->first->GetPrefs(), |
| + prefs::kSafeBrowsingIncidentsSent); |
| + MarkIncidentsAsReported(scan->second, pref_update.Get()); |
| } |
| } |