1 // Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
2 //
3 // Use of this source code is governed by a BSD-style license
4 // that can be found in the LICENSE file in the root of the source
5 // tree. An additional intellectual property rights grant can be found
6 // in the file PATENTS. All contributing project authors may
7 // be found in the AUTHORS file in the root of the source tree.
8 //
9
10 #include "system_wrappers/include/field_trial.h"
11
12 #include <stddef.h>
13
14 #include <map>
15 #include <string>
16 #include <utility>
17
18 #include "absl/algorithm/container.h"
19 #include "absl/strings/string_view.h"
20 #include "experiments/registered_field_trials.h"
21 #include "rtc_base/checks.h"
22 #include "rtc_base/containers/flat_set.h"
23 #include "rtc_base/logging.h"
24 #include "rtc_base/string_encode.h"
25
26 // Simple field trial implementation, which allows client to
27 // specify desired flags in InitFieldTrialsFromString.
28 namespace webrtc {
29 namespace field_trial {
30
31 static const char* trials_init_string = NULL;
32
33 namespace {
34
35 constexpr char kPersistentStringSeparator = '/';
36
TestKeys()37 flat_set<std::string>& TestKeys() {
38 static auto* test_keys = new flat_set<std::string>();
39 return *test_keys;
40 }
41
42 // Validates the given field trial string.
43 // E.g.:
44 // "WebRTC-experimentFoo/Enabled/WebRTC-experimentBar/Enabled100kbps/"
45 // Assigns the process to group "Enabled" on WebRTCExperimentFoo trial
46 // and to group "Enabled100kbps" on WebRTCExperimentBar.
47 //
48 // E.g. invalid config:
49 // "WebRTC-experiment1/Enabled" (note missing / separator at the end).
FieldTrialsStringIsValidInternal(const absl::string_view trials)50 bool FieldTrialsStringIsValidInternal(const absl::string_view trials) {
51 if (trials.empty())
52 return true;
53
54 size_t next_item = 0;
55 std::map<absl::string_view, absl::string_view> field_trials;
56 while (next_item < trials.length()) {
57 size_t name_end = trials.find(kPersistentStringSeparator, next_item);
58 if (name_end == trials.npos || next_item == name_end)
59 return false;
60 size_t group_name_end =
61 trials.find(kPersistentStringSeparator, name_end + 1);
62 if (group_name_end == trials.npos || name_end + 1 == group_name_end)
63 return false;
64 absl::string_view name = trials.substr(next_item, name_end - next_item);
65 absl::string_view group_name =
66 trials.substr(name_end + 1, group_name_end - name_end - 1);
67
68 next_item = group_name_end + 1;
69
70 // Fail if duplicate with different group name.
71 if (field_trials.find(name) != field_trials.end() &&
72 field_trials.find(name)->second != group_name) {
73 return false;
74 }
75
76 field_trials[name] = group_name;
77 }
78
79 return true;
80 }
81
82 } // namespace
83
FieldTrialsStringIsValid(absl::string_view trials_string)84 bool FieldTrialsStringIsValid(absl::string_view trials_string) {
85 return FieldTrialsStringIsValidInternal(trials_string);
86 }
87
InsertOrReplaceFieldTrialStringsInMap(std::map<std::string,std::string> * fieldtrial_map,const absl::string_view trials_string)88 void InsertOrReplaceFieldTrialStringsInMap(
89 std::map<std::string, std::string>* fieldtrial_map,
90 const absl::string_view trials_string) {
91 if (FieldTrialsStringIsValidInternal(trials_string)) {
92 std::vector<absl::string_view> tokens = rtc::split(trials_string, '/');
93 // Skip last token which is empty due to trailing '/'.
94 for (size_t idx = 0; idx < tokens.size() - 1; idx += 2) {
95 (*fieldtrial_map)[std::string(tokens[idx])] =
96 std::string(tokens[idx + 1]);
97 }
98 } else {
99 RTC_DCHECK_NOTREACHED() << "Invalid field trials string:" << trials_string;
100 }
101 }
102
MergeFieldTrialsStrings(absl::string_view first,absl::string_view second)103 std::string MergeFieldTrialsStrings(absl::string_view first,
104 absl::string_view second) {
105 std::map<std::string, std::string> fieldtrial_map;
106 InsertOrReplaceFieldTrialStringsInMap(&fieldtrial_map, first);
107 InsertOrReplaceFieldTrialStringsInMap(&fieldtrial_map, second);
108
109 // Merge into fieldtrial string.
110 std::string merged = "";
111 for (auto const& fieldtrial : fieldtrial_map) {
112 merged += fieldtrial.first + '/' + fieldtrial.second + '/';
113 }
114 return merged;
115 }
116
117 #ifndef WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT
FindFullName(absl::string_view name)118 std::string FindFullName(absl::string_view name) {
119 #if WEBRTC_STRICT_FIELD_TRIALS
120 RTC_DCHECK(absl::c_linear_search(kRegisteredFieldTrials, name) ||
121 TestKeys().contains(name))
122 << name << " is not registered.";
123 #endif
124
125 if (trials_init_string == NULL)
126 return std::string();
127
128 absl::string_view trials_string(trials_init_string);
129 if (trials_string.empty())
130 return std::string();
131
132 size_t next_item = 0;
133 while (next_item < trials_string.length()) {
134 // Find next name/value pair in field trial configuration string.
135 size_t field_name_end =
136 trials_string.find(kPersistentStringSeparator, next_item);
137 if (field_name_end == trials_string.npos || field_name_end == next_item)
138 break;
139 size_t field_value_end =
140 trials_string.find(kPersistentStringSeparator, field_name_end + 1);
141 if (field_value_end == trials_string.npos ||
142 field_value_end == field_name_end + 1)
143 break;
144 absl::string_view field_name =
145 trials_string.substr(next_item, field_name_end - next_item);
146 absl::string_view field_value = trials_string.substr(
147 field_name_end + 1, field_value_end - field_name_end - 1);
148 next_item = field_value_end + 1;
149
150 if (name == field_name)
151 return std::string(field_value);
152 }
153 return std::string();
154 }
155 #endif // WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT
156
157 // Optionally initialize field trial from a string.
InitFieldTrialsFromString(const char * trials_string)158 void InitFieldTrialsFromString(const char* trials_string) {
159 RTC_LOG(LS_INFO) << "Setting field trial string:" << trials_string;
160 if (trials_string) {
161 RTC_DCHECK(FieldTrialsStringIsValidInternal(trials_string))
162 << "Invalid field trials string:" << trials_string;
163 };
164 trials_init_string = trials_string;
165 }
166
GetFieldTrialString()167 const char* GetFieldTrialString() {
168 return trials_init_string;
169 }
170
FieldTrialsAllowedInScopeForTesting(flat_set<std::string> keys)171 FieldTrialsAllowedInScopeForTesting::FieldTrialsAllowedInScopeForTesting(
172 flat_set<std::string> keys) {
173 TestKeys() = std::move(keys);
174 }
175
~FieldTrialsAllowedInScopeForTesting()176 FieldTrialsAllowedInScopeForTesting::~FieldTrialsAllowedInScopeForTesting() {
177 TestKeys().clear();
178 }
179
180 } // namespace field_trial
181 } // namespace webrtc
182