| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/cobalt/bin/app/configuration_data.h" |
| |
| #include <lib/inspect/cpp/inspect.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include "src/lib/files/file.h" |
| #include "src/lib/files/path.h" |
| #include "src/lib/fxl/strings/concatenate.h" |
| #include "src/lib/fxl/strings/trim.h" |
| #include "src/lib/json_parser/json_parser.h" |
| #include "third_party/cobalt/src/lib/util/file_util.h" |
| #include "third_party/cobalt/src/public/cobalt_service_interface.h" |
| #include "third_party/cobalt/src/public/lib/statusor/status_macros.h" |
| |
| namespace cobalt { |
| |
| using cobalt::lib::statusor::StatusOr; |
| |
| const char FuchsiaConfigurationData::kDefaultEnvironmentDir[] = "/pkg/data"; |
| const char FuchsiaConfigurationData::kDefaultConfigDir[] = "/config/data"; |
| const char FuchsiaConfigurationData::kDefaultBuildDir[] = "/config/data/build"; |
| |
| namespace { |
| |
| constexpr char kCobaltEnvironmentFile[] = "cobalt_environment"; |
| const config::Environment kDefaultEnvironment = config::Environment::PROD; |
| |
| constexpr char kBuildTypeFile[] = "type"; |
| |
| constexpr char kConfigFile[] = "config.json"; |
| |
| constexpr char kReleaseStageKey[] = "release_stage"; |
| const cobalt::ReleaseStage kDefaultReleaseStage = cobalt::ReleaseStage::GA; |
| |
| constexpr char kDefaultDataCollectionPolicyKey[] = "default_data_collection_policy"; |
| // When we start Cobalt, we have no idea what the current state of user consent is. Starting with |
| // DO_NOT_UPLOAD will allow us to collect metrics while the system is booting, before we get an |
| // updated policy from the UserConsentWatcher. |
| // |
| // If we started with DO_NOT_COLLECT, we could possibly miss early boot metrics entirely, and if |
| // we started with COLLECT_AND_UPLOAD, we could possibly violate the user's chosen |
| // DataCollectionPolicy by uploading metrics when they have opted out. |
| const cobalt::CobaltServiceInterface::DataCollectionPolicy kDefaultDataCollectionPolicy = |
| cobalt::CobaltServiceInterface::DataCollectionPolicy::DO_NOT_UPLOAD; |
| |
| constexpr char kWatchForUserConsentKey[] = "watch_for_user_consent"; |
| const bool kDefaultWatchForUserConsent = true; |
| |
| constexpr char kEnableReplacementMetricsKey[] = "enable_replacement_metrics"; |
| const bool kDefaultEnableReplacementMetrics = false; |
| |
| // This will be found under the config directory. |
| constexpr char kApiKeyFile[] = "api_key.hex"; |
| constexpr char kDefaultApiKey[] = "cobalt-default-api-key"; |
| |
| constexpr char kAnalyzerDevelTinkPublicKeyPath[] = "/pkg/data/keys/analyzer_devel_public"; |
| constexpr char kShufflerDevelTinkPublicKeyPath[] = "/pkg/data/keys/shuffler_devel_public"; |
| constexpr char kAnalyzerProdTinkPublicKeyPath[] = "/pkg/data/keys/analyzer_prod_public"; |
| constexpr char kShufflerProdTinkPublicKeyPath[] = "/pkg/data/keys/shuffler_prod_public"; |
| |
| } // namespace |
| |
| JSONHelper::JSONHelper(const std::string& path) |
| : config_file_contents_(json_parser_.ParseFromFile(path)) {} |
| |
| template <typename T> |
| StatusOr<T> MakeBadTypeError(const std::string& key, const std::string& expected, |
| rapidjson::Type actual) { |
| static const char* kTypeNames[] = {"Null", "False", "True", "Object", |
| "Array", "String", "Number"}; |
| |
| return Status(StatusCode::INVALID_ARGUMENT, |
| fxl::Concatenate({"Key ", key, " is not of type ", expected, "."}), |
| fxl::Concatenate({"Key ", key, " is expected to be a ", expected, |
| ", but was instead a ", std::string(kTypeNames[actual])})); |
| } |
| |
| StatusOr<std::string> JSONHelper::GetString(const std::string& key) const { |
| CB_RETURN_IF_ERROR(EnsureKey(key)); |
| |
| if (!config_file_contents_[key].IsString()) { |
| return MakeBadTypeError<std::string>(key, "string", config_file_contents_[key].GetType()); |
| } |
| |
| return StatusOr(config_file_contents_[key].GetString()); |
| } |
| |
| StatusOr<bool> JSONHelper::GetBool(const std::string& key) const { |
| CB_RETURN_IF_ERROR(EnsureKey(key)); |
| |
| if (!config_file_contents_[key].IsBool()) { |
| return MakeBadTypeError<bool>(key, "bool", config_file_contents_[key].GetType()); |
| } |
| |
| return config_file_contents_[key].GetBool(); |
| } |
| |
| Status JSONHelper::EnsureKey(const std::string& key) const { |
| if (json_parser_.HasError()) { |
| return Status(StatusCode::INTERNAL, "Failed to parse json file.", json_parser_.error_str()); |
| } |
| |
| if (!config_file_contents_.HasMember(key)) { |
| return Status(StatusCode::NOT_FOUND, |
| fxl::Concatenate({"Key ", key, " not present in the config."})); |
| } |
| |
| return Status::OK; |
| } |
| |
| namespace { |
| // Parse the cobalt environment value from the config data. |
| config::Environment LookupCobaltEnvironment(const std::string& environment_dir) { |
| auto environment_path = files::JoinPath(environment_dir, kCobaltEnvironmentFile); |
| std::string cobalt_environment; |
| if (files::ReadFileToString(environment_path, &cobalt_environment)) { |
| FX_LOGS(INFO) << "Loaded Cobalt environment from config file " << environment_path << ": " |
| << cobalt_environment; |
| if (cobalt_environment == "LOCAL") |
| return config::Environment::LOCAL; |
| if (cobalt_environment == "PROD") |
| return config::Environment::PROD; |
| if (cobalt_environment == "DEVEL") |
| return config::Environment::DEVEL; |
| FX_LOGS(ERROR) << "Failed to parse the contents of config file " << environment_path << ": " |
| << cobalt_environment |
| << ". Falling back to default environment: " << kDefaultEnvironment; |
| } else { |
| FX_LOGS(ERROR) << "Failed to read config file " << environment_path |
| << ". Falling back to default environment: " << kDefaultEnvironment; |
| } |
| return kDefaultEnvironment; |
| } |
| |
| std::string LookupApiKeyOrDefault(const std::string& config_dir) { |
| auto api_key_path = files::JoinPath(config_dir, kApiKeyFile); |
| std::string api_key = util::ReadHexFileOrDefault(api_key_path, kDefaultApiKey); |
| if (api_key == kDefaultApiKey) { |
| FX_LOGS(INFO) << "LookupApiKeyOrDefault: Using default Cobalt API key."; |
| } else { |
| FX_LOGS(INFO) << "LookupApiKeyOrDefault: Using secret Cobalt API key."; |
| } |
| |
| return api_key; |
| } |
| |
| #define ASSIGN_OR_RETURN_DEFAULT(lhs, def, rexpr) \ |
| ASSIGN_OR_RETURN_DEFAULT_IMPL(_status_or_value##__COUNTER__, lhs, def, rexpr) |
| |
| #define ASSIGN_OR_RETURN_DEFAULT_IMPL(statusor, lhs, def, rexpr) \ |
| auto statusor = (rexpr); \ |
| if (!statusor.ok()) { \ |
| auto status = statusor.status(); \ |
| if (status.error_details().empty()) { \ |
| FX_LOGS(ERROR) << "Failed to read from config. " << status.error_message() \ |
| << ". Using default."; \ |
| } else { \ |
| FX_LOGS(ERROR) << "Failed to read from config. " << status.error_message() << " (" \ |
| << status.error_details() << "). Using default."; \ |
| } \ |
| return def; \ |
| } \ |
| lhs = std::move(statusor.ValueOrDie()) |
| |
| cobalt::ReleaseStage LookupReleaseStage(const JSONHelper& json_helper) { |
| ASSIGN_OR_RETURN_DEFAULT(auto release_stage, kDefaultReleaseStage, |
| json_helper.GetString(kReleaseStageKey)); |
| |
| FX_LOGS(INFO) << "Loaded Cobalt release stage from config file: " << release_stage; |
| if (release_stage == "DEBUG") { |
| return cobalt::ReleaseStage::DEBUG; |
| } else if (release_stage == "FISHFOOD") { |
| return cobalt::ReleaseStage::FISHFOOD; |
| } else if (release_stage == "DOGFOOD") { |
| return cobalt::ReleaseStage::DOGFOOD; |
| } else if (release_stage == "GA") { |
| return cobalt::ReleaseStage::GA; |
| } |
| |
| FX_LOGS(ERROR) << "Failed to parse the release stage: `" << release_stage |
| << "`. Falling back to default of " << kDefaultReleaseStage << "."; |
| return kDefaultReleaseStage; |
| } |
| |
| cobalt::CobaltServiceInterface::DataCollectionPolicy LookupDataCollectionPolicy( |
| const JSONHelper& json_helper) { |
| ASSIGN_OR_RETURN_DEFAULT(auto data_collection_policy, kDefaultDataCollectionPolicy, |
| json_helper.GetString(kDefaultDataCollectionPolicyKey)); |
| |
| FX_LOGS(INFO) << "Loaded Cobalt data collection policy from config file: " |
| << data_collection_policy; |
| if (data_collection_policy == "DO_NOT_COLLECT") { |
| return cobalt::CobaltServiceInterface::DataCollectionPolicy::DO_NOT_COLLECT; |
| } else if (data_collection_policy == "DO_NOT_UPLOAD") { |
| return cobalt::CobaltServiceInterface::DataCollectionPolicy::DO_NOT_UPLOAD; |
| } else if (data_collection_policy == "COLLECT_AND_UPLOAD") { |
| return cobalt::CobaltServiceInterface::DataCollectionPolicy::COLLECT_AND_UPLOAD; |
| } |
| |
| FX_LOGS(ERROR) << "Failed to parse the data collection policy: `" << data_collection_policy |
| << "`. Falling back to default."; |
| return kDefaultDataCollectionPolicy; |
| } |
| |
| bool LookupWatchForUserConsent(const JSONHelper& json_helper) { |
| ASSIGN_OR_RETURN_DEFAULT(auto watch_for_user_consent, kDefaultWatchForUserConsent, |
| json_helper.GetBool(kWatchForUserConsentKey)); |
| |
| return watch_for_user_consent; |
| } |
| |
| bool LookupEnableReplacementMetrics(const JSONHelper& json_helper) { |
| ASSIGN_OR_RETURN_DEFAULT(auto enable_replacement_metrics, kDefaultEnableReplacementMetrics, |
| json_helper.GetBool(kEnableReplacementMetricsKey)); |
| |
| return enable_replacement_metrics; |
| } |
| |
| SystemProfile::BuildType LookupBuildType(const std::string& build_type_dir) { |
| auto build_type_path = files::JoinPath(build_type_dir, kBuildTypeFile); |
| std::string build_type; |
| if (!files::ReadFileToString(build_type_path, &build_type)) { |
| // The build type file is not populated for all devices. |
| FX_LOGS(WARNING) << "No build type found at " << build_type_path |
| << ". Falling back to default type: " << SystemProfile::UNKNOWN_TYPE; |
| return SystemProfile::UNKNOWN_TYPE; |
| } |
| // Trim trailing whitespace. |
| size_t end = build_type.find_last_not_of(" \n\r\t\f\v"); |
| build_type = (end == std::string::npos) ? "" : build_type.substr(0, end + 1); |
| |
| if (build_type == "eng") { |
| return SystemProfile::ENG; |
| } |
| if (build_type == "user") { |
| return SystemProfile::USER; |
| } |
| if (build_type == "userdebug") { |
| return SystemProfile::USER_DEBUG; |
| } |
| FX_LOGS(ERROR) << "Unexpected contents of build type file " << build_type_path << ": " |
| << build_type << ". Falling back to default type: " << SystemProfile::OTHER_TYPE; |
| return SystemProfile::OTHER_TYPE; |
| } |
| |
| } // namespace |
| |
| FuchsiaConfigurationData::FuchsiaConfigurationData(const std::string& config_dir, |
| const std::string& environment_dir, |
| const std::string& build_type_dir) |
| : backend_environment_(LookupCobaltEnvironment(environment_dir)), |
| backend_configuration_(config::ConfigurationData(backend_environment_)), |
| api_key_(LookupApiKeyOrDefault(config_dir)), |
| json_helper_(files::JoinPath(config_dir, kConfigFile)), |
| release_stage_(LookupReleaseStage(json_helper_)), |
| data_collection_policy_(LookupDataCollectionPolicy(json_helper_)), |
| watch_for_user_consent_(LookupWatchForUserConsent(json_helper_)), |
| enable_replacement_metrics_(LookupEnableReplacementMetrics(json_helper_)), |
| build_type_(LookupBuildType(build_type_dir)) {} |
| |
| config::Environment FuchsiaConfigurationData::GetBackendEnvironment() const { |
| return backend_environment_; |
| } |
| |
| const char* FuchsiaConfigurationData::AnalyzerPublicKeyPath() const { |
| if (backend_environment_ == config::DEVEL) |
| return kAnalyzerDevelTinkPublicKeyPath; |
| if (backend_environment_ == config::PROD) |
| return kAnalyzerProdTinkPublicKeyPath; |
| FX_LOGS(ERROR) << "Failed to handle any environments. Falling back to using analyzer key for " |
| "DEVEL environment."; |
| return kAnalyzerDevelTinkPublicKeyPath; |
| } |
| |
| const char* FuchsiaConfigurationData::ShufflerPublicKeyPath() const { |
| switch (backend_environment_) { |
| case config::PROD: |
| return kShufflerProdTinkPublicKeyPath; |
| case config::DEVEL: |
| return kShufflerDevelTinkPublicKeyPath; |
| default: { |
| FX_LOGS(ERROR) << "Failed to handle environment enum: " << backend_environment_ |
| << ". Falling back to using shuffler key for DEVEL environment."; |
| return kShufflerDevelTinkPublicKeyPath; |
| } |
| } |
| } |
| |
| int32_t FuchsiaConfigurationData::GetLogSourceId() const { |
| return backend_configuration_.GetLogSourceId(); |
| } |
| |
| SystemProfile_BuildType FuchsiaConfigurationData::GetBuildType() const { return build_type_; } |
| |
| cobalt::ReleaseStage FuchsiaConfigurationData::GetReleaseStage() const { return release_stage_; } |
| |
| cobalt::CobaltServiceInterface::DataCollectionPolicy |
| FuchsiaConfigurationData::GetDataCollectionPolicy() const { |
| return data_collection_policy_; |
| } |
| |
| bool FuchsiaConfigurationData::GetWatchForUserConsent() const { return watch_for_user_consent_; } |
| |
| bool FuchsiaConfigurationData::GetEnableReplacementMetrics() const { |
| return enable_replacement_metrics_; |
| } |
| |
| std::string FuchsiaConfigurationData::GetApiKey() const { return api_key_; } |
| |
| void FuchsiaConfigurationData::PopulateInspect(inspect::Node& inspect_node) const { |
| inspect_node.RecordInt("backend_environment", backend_environment_); |
| inspect_node.RecordInt("release_stage", release_stage_); |
| inspect_node.RecordInt("data_collection_policy", static_cast<int>(data_collection_policy_)); |
| inspect_node.RecordBool("watch_for_user_consent", watch_for_user_consent_); |
| inspect_node.RecordBool("enable_replacement_metrics", enable_replacement_metrics_); |
| inspect_node.RecordInt("build_type", build_type_); |
| } |
| |
| } // namespace cobalt |