Home | History | Annotate | Download | only in variations
      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.
      4 
      5 #include "components/variations/study_filtering.h"
      6 
      7 #include <set>
      8 
      9 namespace variations {
     10 
     11 namespace {
     12 
     13 Study_Platform GetCurrentPlatform() {
     14 #if defined(OS_WIN)
     15   return Study_Platform_PLATFORM_WINDOWS;
     16 #elif defined(OS_IOS)
     17   return Study_Platform_PLATFORM_IOS;
     18 #elif defined(OS_MACOSX)
     19   return Study_Platform_PLATFORM_MAC;
     20 #elif defined(OS_CHROMEOS)
     21   return Study_Platform_PLATFORM_CHROMEOS;
     22 #elif defined(OS_ANDROID)
     23   return Study_Platform_PLATFORM_ANDROID;
     24 #elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
     25   // Default BSD and SOLARIS to Linux to not break those builds, although these
     26   // platforms are not officially supported by Chrome.
     27   return Study_Platform_PLATFORM_LINUX;
     28 #else
     29 #error Unknown platform
     30 #endif
     31 }
     32 
     33 // Converts |date_time| in Study date format to base::Time.
     34 base::Time ConvertStudyDateToBaseTime(int64 date_time) {
     35   return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(date_time);
     36 }
     37 
     38 }  // namespace
     39 
     40 namespace internal {
     41 
     42 bool CheckStudyChannel(const Study_Filter& filter, Study_Channel channel) {
     43   // An empty channel list matches all channels.
     44   if (filter.channel_size() == 0)
     45     return true;
     46 
     47   for (int i = 0; i < filter.channel_size(); ++i) {
     48     if (filter.channel(i) == channel)
     49       return true;
     50   }
     51   return false;
     52 }
     53 
     54 bool CheckStudyFormFactor(const Study_Filter& filter,
     55                           Study_FormFactor form_factor) {
     56   // An empty form factor list matches all form factors.
     57   if (filter.form_factor_size() == 0)
     58     return true;
     59 
     60   for (int i = 0; i < filter.form_factor_size(); ++i) {
     61     if (filter.form_factor(i) == form_factor)
     62       return true;
     63   }
     64   return false;
     65 }
     66 
     67 bool CheckStudyHardwareClass(const Study_Filter& filter,
     68                              const std::string& hardware_class) {
     69   // Empty hardware_class and exclude_hardware_class matches all.
     70   if (filter.hardware_class_size() == 0 &&
     71       filter.exclude_hardware_class_size() == 0) {
     72     return true;
     73   }
     74 
     75   // Checks if we are supposed to filter for a specified set of
     76   // hardware_classes. Note that this means this overrides the
     77   // exclude_hardware_class in case that ever occurs (which it shouldn't).
     78   if (filter.hardware_class_size() > 0) {
     79     for (int i = 0; i < filter.hardware_class_size(); ++i) {
     80       // Check if the entry is a substring of |hardware_class|.
     81       size_t position = hardware_class.find(filter.hardware_class(i));
     82       if (position != std::string::npos)
     83         return true;
     84     }
     85     // None of the requested hardware_classes match.
     86     return false;
     87   }
     88 
     89   // Omit if matches any of the exclude entries.
     90   for (int i = 0; i < filter.exclude_hardware_class_size(); ++i) {
     91     // Check if the entry is a substring of |hardware_class|.
     92     size_t position = hardware_class.find(
     93         filter.exclude_hardware_class(i));
     94     if (position != std::string::npos)
     95       return false;
     96   }
     97 
     98   // None of the exclusions match, so this accepts.
     99   return true;
    100 }
    101 
    102 bool CheckStudyLocale(const Study_Filter& filter, const std::string& locale) {
    103   // An empty locale list matches all locales.
    104   if (filter.locale_size() == 0)
    105     return true;
    106 
    107   for (int i = 0; i < filter.locale_size(); ++i) {
    108     if (filter.locale(i) == locale)
    109       return true;
    110   }
    111   return false;
    112 }
    113 
    114 bool CheckStudyPlatform(const Study_Filter& filter, Study_Platform platform) {
    115   // An empty platform list matches all platforms.
    116   if (filter.platform_size() == 0)
    117     return true;
    118 
    119   for (int i = 0; i < filter.platform_size(); ++i) {
    120     if (filter.platform(i) == platform)
    121       return true;
    122   }
    123   return false;
    124 }
    125 
    126 bool CheckStudyStartDate(const Study_Filter& filter,
    127                          const base::Time& date_time) {
    128   if (filter.has_start_date()) {
    129     const base::Time start_date =
    130         ConvertStudyDateToBaseTime(filter.start_date());
    131     return date_time >= start_date;
    132   }
    133 
    134   return true;
    135 }
    136 
    137 bool CheckStudyVersion(const Study_Filter& filter,
    138                        const base::Version& version) {
    139   if (filter.has_min_version()) {
    140     if (version.CompareToWildcardString(filter.min_version()) < 0)
    141       return false;
    142   }
    143 
    144   if (filter.has_max_version()) {
    145     if (version.CompareToWildcardString(filter.max_version()) > 0)
    146       return false;
    147   }
    148 
    149   return true;
    150 }
    151 
    152 bool IsStudyExpired(const Study& study, const base::Time& date_time) {
    153   if (study.has_expiry_date()) {
    154     const base::Time expiry_date =
    155         ConvertStudyDateToBaseTime(study.expiry_date());
    156     return date_time >= expiry_date;
    157   }
    158 
    159   return false;
    160 }
    161 
    162 bool ShouldAddStudy(
    163     const Study& study,
    164     const std::string& locale,
    165     const base::Time& reference_date,
    166     const base::Version& version,
    167     Study_Channel channel,
    168     Study_FormFactor form_factor,
    169     const std::string& hardware_class) {
    170   if (study.has_filter()) {
    171     if (!CheckStudyChannel(study.filter(), channel)) {
    172       DVLOG(1) << "Filtered out study " << study.name() << " due to channel.";
    173       return false;
    174     }
    175 
    176     if (!CheckStudyFormFactor(study.filter(), form_factor)) {
    177       DVLOG(1) << "Filtered out study " << study.name() <<
    178                   " due to form factor.";
    179       return false;
    180     }
    181 
    182     if (!CheckStudyLocale(study.filter(), locale)) {
    183       DVLOG(1) << "Filtered out study " << study.name() << " due to locale.";
    184       return false;
    185     }
    186 
    187     if (!CheckStudyPlatform(study.filter(), GetCurrentPlatform())) {
    188       DVLOG(1) << "Filtered out study " << study.name() << " due to platform.";
    189       return false;
    190     }
    191 
    192     if (!CheckStudyVersion(study.filter(), version)) {
    193       DVLOG(1) << "Filtered out study " << study.name() << " due to version.";
    194       return false;
    195     }
    196 
    197     if (!CheckStudyStartDate(study.filter(), reference_date)) {
    198       DVLOG(1) << "Filtered out study " << study.name() <<
    199                   " due to start date.";
    200       return false;
    201     }
    202 
    203     if (!CheckStudyHardwareClass(study.filter(), hardware_class)) {
    204       DVLOG(1) << "Filtered out study " << study.name() <<
    205                   " due to hardware_class.";
    206       return false;
    207     }
    208   }
    209 
    210   DVLOG(1) << "Kept study " << study.name() << ".";
    211   return true;
    212 }
    213 
    214 }  // namespace internal
    215 
    216 void FilterAndValidateStudies(
    217     const VariationsSeed& seed,
    218     const std::string& locale,
    219     const base::Time& reference_date,
    220     const base::Version& version,
    221     Study_Channel channel,
    222     Study_FormFactor form_factor,
    223     const std::string& hardware_class,
    224     std::vector<ProcessedStudy>* filtered_studies) {
    225   DCHECK(version.IsValid());
    226 
    227   // Add expired studies (in a disabled state) only after all the non-expired
    228   // studies have been added (and do not add an expired study if a corresponding
    229   // non-expired study got added). This way, if there's both an expired and a
    230   // non-expired study that applies, the non-expired study takes priority.
    231   std::set<std::string> created_studies;
    232   std::vector<const Study*> expired_studies;
    233 
    234   for (int i = 0; i < seed.study_size(); ++i) {
    235     const Study& study = seed.study(i);
    236     if (!internal::ShouldAddStudy(study, locale, reference_date, version,
    237                                   channel, form_factor, hardware_class)) {
    238       continue;
    239     }
    240 
    241     if (internal::IsStudyExpired(study, reference_date)) {
    242       expired_studies.push_back(&study);
    243     } else if (!ContainsKey(created_studies, study.name())) {
    244       ProcessedStudy::ValidateAndAppendStudy(&study, false, filtered_studies);
    245       created_studies.insert(study.name());
    246     }
    247   }
    248 
    249   for (size_t i = 0; i < expired_studies.size(); ++i) {
    250     if (!ContainsKey(created_studies, expired_studies[i]->name())) {
    251       ProcessedStudy::ValidateAndAppendStudy(expired_studies[i], true,
    252                                              filtered_studies);
    253     }
    254   }
    255 }
    256 
    257 }  // namespace variations
    258