• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetCurrentPlatform()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.
ConvertStudyDateToBaseTime(int64 date_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 
CheckStudyChannel(const Study_Filter & filter,Study_Channel channel)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 
CheckStudyFormFactor(const Study_Filter & filter,Study_FormFactor form_factor)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 
CheckStudyHardwareClass(const Study_Filter & filter,const std::string & hardware_class)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 
CheckStudyLocale(const Study_Filter & filter,const std::string & locale)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 
CheckStudyPlatform(const Study_Filter & filter,Study_Platform platform)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 
CheckStudyStartDate(const Study_Filter & filter,const base::Time & date_time)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 
CheckStudyVersion(const Study_Filter & filter,const base::Version & version)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 
IsStudyExpired(const Study & study,const base::Time & date_time)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 
ShouldAddStudy(const Study & study,const std::string & locale,const base::Time & reference_date,const base::Version & version,Study_Channel channel,Study_FormFactor form_factor,const std::string & hardware_class)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 
FilterAndValidateStudies(const VariationsSeed & seed,const std::string & locale,const base::Time & reference_date,const base::Version & version,Study_Channel channel,Study_FormFactor form_factor,const std::string & hardware_class,std::vector<ProcessedStudy> * filtered_studies)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