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 chrome_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 
    211   DVLOG(1) << "Kept study " << study.name() << ".";
    212   return true;
    213 }
    214 
    215 }  // namespace internal
    216 
    217 void FilterAndValidateStudies(
    218     const VariationsSeed& seed,
    219     const std::string& locale,
    220     const base::Time& reference_date,
    221     const base::Version& version,
    222     Study_Channel channel,
    223     Study_FormFactor form_factor,
    224     const std::string& hardware_class,
    225     std::vector<ProcessedStudy>* filtered_studies) {
    226   DCHECK(version.IsValid());
    227 
    228   // Add expired studies (in a disabled state) only after all the non-expired
    229   // studies have been added (and do not add an expired study if a corresponding
    230   // non-expired study got added). This way, if there's both an expired and a
    231   // non-expired study that applies, the non-expired study takes priority.
    232   std::set<std::string> created_studies;
    233   std::vector<const Study*> expired_studies;
    234 
    235   for (int i = 0; i < seed.study_size(); ++i) {
    236     const Study& study = seed.study(i);
    237     if (!internal::ShouldAddStudy(study, locale, reference_date, version,
    238                                   channel, form_factor, hardware_class)) {
    239       continue;
    240     }
    241 
    242     if (internal::IsStudyExpired(study, reference_date)) {
    243       expired_studies.push_back(&study);
    244     } else if (!ContainsKey(created_studies, study.name())) {
    245       ProcessedStudy::ValidateAndAppendStudy(&study, false, filtered_studies);
    246       created_studies.insert(study.name());
    247     }
    248   }
    249 
    250   for (size_t i = 0; i < expired_studies.size(); ++i) {
    251     if (!ContainsKey(created_studies, expired_studies[i]->name())) {
    252       ProcessedStudy::ValidateAndAppendStudy(expired_studies[i], true,
    253                                              filtered_studies);
    254     }
    255   }
    256 }
    257 
    258 }  // namespace chrome_variations
    259