blob: 04f791c1b26cd30fc2beae45f3787b3714fcba9f [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <cstdint>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/hash/hash.h>
#include <base/logging.h>
#include <base/values.h>
#include <base/system/sys_info.h>
#include <brillo/data_encoding.h>
#include <libcrossystem/crossystem.h>
#include "libsegmentation/device_info.pb.h"
#include "libsegmentation/feature_management.h"
#include "libsegmentation/feature_management_impl.h"
#include "libsegmentation/feature_management_interface.h"
#include "libsegmentation/feature_management_util.h"
#include "proto/feature_management.pb.h"
// Autogenerated by feature-management-data,
// Contains protobuf definitions.
#include <libsegmentation/libsegmentation_pb.h>
using chromiumos::feature_management::api::software::Feature;
namespace segmentation {
// Sysfs file corresponding to VPD state. This will be used to persist device
// info state and read cache device info state.
constexpr const char kVpdSysfsFilePath[] =
"/sys/firmware/vpd/rw/feature_device_info";
constexpr const char kTempDeviceInfoPath[] =
"/run/libsegmentation/feature_device_info";
#if USE_FEATURE_MANAGEMENT
// Sysfs file corresponding to VPD state. This will be used to persist device
// info state and read cache device info state.
const char* kDeviceInfoFilePath = kVpdSysfsFilePath;
#else
constexpr char kDeviceInfoFilePath[] = "";
FeatureManagementInterface::FeatureLevel
FeatureManagementImpl::GetFeatureLevel() {
return FeatureLevel::FEATURE_LEVEL_0;
}
FeatureManagementInterface::ScopeLevel FeatureManagementImpl::GetScopeLevel() {
return ScopeLevel::SCOPE_LEVEL_0;
}
#endif
FeatureManagementInterface::FeatureLevel
FeatureManagementImpl::GetMaxFeatureLevel() {
return FeatureLevel::kMaxValue;
}
FeatureManagementImpl::FeatureManagementImpl()
: FeatureManagementImpl(nullptr,
base::FilePath(kDeviceInfoFilePath),
protobuf_features,
protobuf_devices,
"") {}
FeatureManagementImpl::FeatureManagementImpl(
crossystem::Crossystem* crossystem,
const base::FilePath& device_info_file_path,
const std::string& feature_db,
const std::string& selection_db,
const std::string& os_version)
: device_info_file_path_(device_info_file_path),
temp_device_info_file_path_(kTempDeviceInfoPath) {
persist_via_vpd_ =
device_info_file_path_ == base::FilePath(kVpdSysfsFilePath);
std::string decoded_pb;
brillo::data_encoding::Base64Decode(feature_db, &decoded_pb);
feature_bundle_.ParseFromString(decoded_pb);
brillo::data_encoding::Base64Decode(selection_db, &decoded_pb);
selection_bundle_.ParseFromString(decoded_pb);
#if USE_FEATURE_MANAGEMENT
std::string version(os_version);
if (version.empty()) {
base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_VERSION", &version);
}
if (!version.empty()) {
current_version_hash_ = base::PersistentHash(version);
} else {
current_version_hash_ = 0;
LOG(ERROR) << "Failed to retrieve CHROMEOS_RELEASE_VERSION";
}
if (crossystem) {
crossystem_ = crossystem;
} else {
crossystem_backend_ = std::make_unique<crossystem::Crossystem>();
crossystem_ = crossystem_backend_.get();
}
#endif
}
bool FeatureManagementImpl::IsFeatureEnabled(const std::string& name) {
int prefix_len = strlen(FeatureManagement::kPrefix);
if (name.compare(0, prefix_len, FeatureManagement::kPrefix))
return false;
enum FeatureLevel feature_level = GetFeatureLevel();
if (feature_level == FEATURE_LEVEL_UNKNOWN)
feature_level = FEATURE_LEVEL_0;
enum ScopeLevel scope_level = GetScopeLevel();
if (scope_level == SCOPE_LEVEL_UNKNOWN)
scope_level = SCOPE_LEVEL_0;
// Now we have the feature_level and the scope_level, after we
// found that the feature exist, check if its level is equal or
// greater than the DUT feature_level and if its scope array contains
// the DUT scope_level.
std::string in_feature = name.substr(prefix_len);
for (const auto& feature : feature_bundle_.features()) {
if (!feature.name().compare(in_feature)) {
auto it = std::find(feature.scopes().begin(), feature.scopes().end(),
scope_level);
return feature_level - FEATURE_LEVEL_VALID_OFFSET >=
feature.feature_level() &&
it != feature.scopes().end();
}
}
return false;
}
const std::set<std::string> FeatureManagementImpl::ListFeatures(
const FeatureUsage usage) {
enum FeatureLevel feature_level = GetFeatureLevel();
if (feature_level == FEATURE_LEVEL_UNKNOWN)
feature_level = FEATURE_LEVEL_0;
enum ScopeLevel scope_level = GetScopeLevel();
if (scope_level == SCOPE_LEVEL_UNKNOWN)
scope_level = SCOPE_LEVEL_0;
// Now we have the feature_level and the scope_level, return all features
// that:
// 1. matches the usage and
// 2. its feature level is equal or greater than the DUT feature_level and
// 3. its scope array contains the DUT scope_level.
std::set<std::string> features;
for (const auto& feature : feature_bundle_.features()) {
auto it = std::find(feature.scopes().begin(), feature.scopes().end(),
scope_level);
if (std::find(feature.usages().begin(), feature.usages().end(), usage) !=
feature.usages().end() &&
feature_level - FEATURE_LEVEL_VALID_OFFSET >= feature.feature_level() &&
it != feature.scopes().end())
features.emplace(FeatureManagement::kPrefix + feature.name());
}
return features;
}
} // namespace segmentation