|  | /* | 
|  | *  Copyright 2019 The WebRTC project authors. All Rights Reserved. | 
|  | * | 
|  | *  Use of this source code is governed by a BSD-style license | 
|  | *  that can be found in the LICENSE file in the root of the source | 
|  | *  tree. An additional intellectual property rights grant can be found | 
|  | *  in the file PATENTS.  All contributing project authors may | 
|  | *  be found in the AUTHORS file in the root of the source tree. | 
|  | */ | 
|  | #include "rtc_base/experiments/field_trial_parser.h" | 
|  |  | 
|  | #include <inttypes.h> | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <map> | 
|  | #include <type_traits> | 
|  | #include <utility> | 
|  |  | 
|  | #include "absl/strings/string_view.h" | 
|  | #include "rtc_base/checks.h" | 
|  | #include "rtc_base/logging.h" | 
|  | #include "rtc_base/numerics/safe_conversions.h" | 
|  |  | 
|  | namespace webrtc { | 
|  |  | 
|  | FieldTrialParameterInterface::FieldTrialParameterInterface( | 
|  | absl::string_view key) | 
|  | : key_(key) {} | 
|  | FieldTrialParameterInterface::~FieldTrialParameterInterface() { | 
|  | RTC_DCHECK(used_) << "Field trial parameter with key: '" << key_ | 
|  | << "' never used."; | 
|  | } | 
|  |  | 
|  | void ParseFieldTrial( | 
|  | std::initializer_list<FieldTrialParameterInterface*> fields, | 
|  | absl::string_view trial_string) { | 
|  | std::map<absl::string_view, FieldTrialParameterInterface*> field_map; | 
|  | FieldTrialParameterInterface* keyless_field = nullptr; | 
|  | for (FieldTrialParameterInterface* field : fields) { | 
|  | field->MarkAsUsed(); | 
|  | if (!field->sub_parameters_.empty()) { | 
|  | for (FieldTrialParameterInterface* sub_field : field->sub_parameters_) { | 
|  | RTC_DCHECK(!sub_field->key_.empty()); | 
|  | sub_field->MarkAsUsed(); | 
|  | field_map[sub_field->key_] = sub_field; | 
|  | } | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (field->key_.empty()) { | 
|  | RTC_DCHECK(!keyless_field); | 
|  | keyless_field = field; | 
|  | } else { | 
|  | field_map[field->key_] = field; | 
|  | } | 
|  | } | 
|  | bool logged_unknown_key = false; | 
|  |  | 
|  | absl::string_view tail = trial_string; | 
|  | while (!tail.empty()) { | 
|  | size_t key_end = tail.find_first_of(",:"); | 
|  | absl::string_view key = tail.substr(0, key_end); | 
|  | std::optional<std::string> opt_value; | 
|  | if (key_end == absl::string_view::npos) { | 
|  | tail = ""; | 
|  | } else if (tail[key_end] == ':') { | 
|  | tail = tail.substr(key_end + 1); | 
|  | size_t value_end = tail.find(','); | 
|  | opt_value.emplace(tail.substr(0, value_end)); | 
|  | if (value_end == absl::string_view::npos) { | 
|  | tail = ""; | 
|  | } else { | 
|  | tail = tail.substr(value_end + 1); | 
|  | } | 
|  | } else { | 
|  | RTC_DCHECK_EQ(tail[key_end], ','); | 
|  | tail = tail.substr(key_end + 1); | 
|  | } | 
|  |  | 
|  | auto field = field_map.find(key); | 
|  | if (field != field_map.end()) { | 
|  | if (!field->second->Parse(std::move(opt_value))) { | 
|  | RTC_LOG(LS_WARNING) << "Failed to read field with key: '" << key | 
|  | << "' in trial: \"" << trial_string << "\""; | 
|  | } | 
|  | } else if (!opt_value && keyless_field && !key.empty()) { | 
|  | if (!keyless_field->Parse(std::string(key))) { | 
|  | RTC_LOG(LS_WARNING) << "Failed to read empty key field with value '" | 
|  | << key << "' in trial: \"" << trial_string << "\""; | 
|  | } | 
|  | } else if (key.empty() || key[0] != '_') { | 
|  | // "_" is be used to prefix keys that are part of the string for | 
|  | // debugging purposes but not neccessarily used. | 
|  | // e.g. WebRTC-Experiment/param: value, _DebuggingString | 
|  | if (!logged_unknown_key) { | 
|  | RTC_LOG(LS_INFO) << "No field with key: '" << key | 
|  | << "' (found in trial: \"" << trial_string << "\")"; | 
|  | std::string valid_keys; | 
|  | for (const auto& f : field_map) { | 
|  | valid_keys.append(f.first.data(), f.first.size()); | 
|  | valid_keys += ", "; | 
|  | } | 
|  | RTC_LOG(LS_INFO) << "Valid keys are: " << valid_keys; | 
|  | logged_unknown_key = true; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | for (FieldTrialParameterInterface* field : fields) { | 
|  | field->ParseDone(); | 
|  | } | 
|  | } | 
|  |  | 
|  | template <> | 
|  | std::optional<bool> ParseTypedParameter<bool>(absl::string_view str) { | 
|  | if (str == "true" || str == "1") { | 
|  | return true; | 
|  | } else if (str == "false" || str == "0") { | 
|  | return false; | 
|  | } | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | template <> | 
|  | std::optional<double> ParseTypedParameter<double>(absl::string_view str) { | 
|  | double value; | 
|  | char unit[2]{0, 0}; | 
|  | if (sscanf(std::string(str).c_str(), "%lf%1s", &value, unit) >= 1) { | 
|  | if (unit[0] == '%') | 
|  | return value / 100; | 
|  | return value; | 
|  | } else { | 
|  | return std::nullopt; | 
|  | } | 
|  | } | 
|  |  | 
|  | template <> | 
|  | std::optional<int> ParseTypedParameter<int>(absl::string_view str) { | 
|  | int64_t value; | 
|  | if (sscanf(std::string(str).c_str(), "%" SCNd64, &value) == 1) { | 
|  | if (rtc::IsValueInRangeForNumericType<int, int64_t>(value)) { | 
|  | return static_cast<int>(value); | 
|  | } | 
|  | } | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | template <> | 
|  | std::optional<unsigned> ParseTypedParameter<unsigned>(absl::string_view str) { | 
|  | int64_t value; | 
|  | if (sscanf(std::string(str).c_str(), "%" SCNd64, &value) == 1) { | 
|  | if (rtc::IsValueInRangeForNumericType<unsigned, int64_t>(value)) { | 
|  | return static_cast<unsigned>(value); | 
|  | } | 
|  | } | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | template <> | 
|  | std::optional<std::string> ParseTypedParameter<std::string>( | 
|  | absl::string_view str) { | 
|  | return std::string(str); | 
|  | } | 
|  |  | 
|  | template <> | 
|  | std::optional<std::optional<bool>> ParseTypedParameter<std::optional<bool>>( | 
|  | absl::string_view str) { | 
|  | return ParseOptionalParameter<bool>(str); | 
|  | } | 
|  | template <> | 
|  | std::optional<std::optional<int>> ParseTypedParameter<std::optional<int>>( | 
|  | absl::string_view str) { | 
|  | return ParseOptionalParameter<int>(str); | 
|  | } | 
|  | template <> | 
|  | std::optional<std::optional<unsigned>> | 
|  | ParseTypedParameter<std::optional<unsigned>>(absl::string_view str) { | 
|  | return ParseOptionalParameter<unsigned>(str); | 
|  | } | 
|  | template <> | 
|  | std::optional<std::optional<double>> ParseTypedParameter<std::optional<double>>( | 
|  | absl::string_view str) { | 
|  | return ParseOptionalParameter<double>(str); | 
|  | } | 
|  |  | 
|  | FieldTrialFlag::FieldTrialFlag(absl::string_view key) | 
|  | : FieldTrialFlag(key, false) {} | 
|  |  | 
|  | FieldTrialFlag::FieldTrialFlag(absl::string_view key, bool default_value) | 
|  | : FieldTrialParameterInterface(key), value_(default_value) {} | 
|  |  | 
|  | bool FieldTrialFlag::Get() const { | 
|  | return value_; | 
|  | } | 
|  |  | 
|  | webrtc::FieldTrialFlag::operator bool() const { | 
|  | return value_; | 
|  | } | 
|  |  | 
|  | bool FieldTrialFlag::Parse(std::optional<std::string> str_value) { | 
|  | // Only set the flag if there is no argument provided. | 
|  | if (str_value) { | 
|  | std::optional<bool> opt_value = ParseTypedParameter<bool>(*str_value); | 
|  | if (!opt_value) | 
|  | return false; | 
|  | value_ = *opt_value; | 
|  | } else { | 
|  | value_ = true; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | AbstractFieldTrialEnum::AbstractFieldTrialEnum( | 
|  | absl::string_view key, | 
|  | int default_value, | 
|  | std::map<std::string, int> mapping) | 
|  | : FieldTrialParameterInterface(key), | 
|  | value_(default_value), | 
|  | enum_mapping_(mapping) { | 
|  | for (auto& key_val : enum_mapping_) | 
|  | valid_values_.insert(key_val.second); | 
|  | } | 
|  | AbstractFieldTrialEnum::AbstractFieldTrialEnum(const AbstractFieldTrialEnum&) = | 
|  | default; | 
|  | AbstractFieldTrialEnum::~AbstractFieldTrialEnum() = default; | 
|  |  | 
|  | bool AbstractFieldTrialEnum::Parse(std::optional<std::string> str_value) { | 
|  | if (str_value) { | 
|  | auto it = enum_mapping_.find(*str_value); | 
|  | if (it != enum_mapping_.end()) { | 
|  | value_ = it->second; | 
|  | return true; | 
|  | } | 
|  | std::optional<int> value = ParseTypedParameter<int>(*str_value); | 
|  | if (value.has_value() && | 
|  | (valid_values_.find(*value) != valid_values_.end())) { | 
|  | value_ = *value; | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | template class FieldTrialParameter<bool>; | 
|  | template class FieldTrialParameter<double>; | 
|  | template class FieldTrialParameter<int>; | 
|  | template class FieldTrialParameter<unsigned>; | 
|  | template class FieldTrialParameter<std::string>; | 
|  |  | 
|  | template class FieldTrialConstrained<double>; | 
|  | template class FieldTrialConstrained<int>; | 
|  | template class FieldTrialConstrained<unsigned>; | 
|  |  | 
|  | template class FieldTrialOptional<double>; | 
|  | template class FieldTrialOptional<int>; | 
|  | template class FieldTrialOptional<unsigned>; | 
|  | template class FieldTrialOptional<bool>; | 
|  | template class FieldTrialOptional<std::string>; | 
|  |  | 
|  | }  // namespace webrtc |