blob: d39ca2877820cdc9d7d48b5d0718f06dd8a01ec4 [file] [log] [blame]
/* Copyright 2019 The Chromium OS 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 "hardware_verifier/verifier_impl.h"
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <base/check.h>
#include <base/logging.h>
#include <base/optional.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/system/sys_info.h>
#include <chromeos-config/libcros_config/cros_config.h>
namespace hardware_verifier {
namespace {
constexpr auto kGenericComponentName = "generic";
constexpr auto kNoMatchComponentName = "NO_MATCH";
void AddFoundComponentInfo(
HwVerificationReport* hw_verification_report,
const runtime_probe::ProbeRequest_SupportCategory& component_category,
const std::string& comp_name,
const QualificationStatus status) {
auto* found_comp_info = hw_verification_report->add_found_component_infos();
found_comp_info->set_component_category(component_category);
found_comp_info->set_component_uuid(comp_name);
found_comp_info->set_qualification_status(status);
if (status != QualificationStatus::QUALIFIED) {
hw_verification_report->set_is_compliant(false);
}
}
bool IsModelComponent(const ComponentInfo& comp_info,
const std::string& model_name) {
if (model_name.empty())
return true;
const auto& parts = base::SplitString(model_name, "_", base::KEEP_WHITESPACE,
base::SPLIT_WANT_ALL);
return base::StartsWith(comp_info.component_uuid(), parts[0] + "_");
}
} // namespace
VerifierImpl::VerifierImpl() {
auto config = std::make_unique<brillo::CrosConfig>();
CHECK(config->Init());
cros_config_ = std::move(config);
using CppType = google::protobuf::FieldDescriptor::CppType;
constexpr int kCppTypeMsg = CppType::CPPTYPE_MESSAGE;
constexpr int kCppTypeStr = CppType::CPPTYPE_STRING;
// Resolve |comp_category_infos_| in the constructor.
const auto* category_enum_desc =
runtime_probe::ProbeRequest_SupportCategory_descriptor();
comp_category_infos_.resize(category_enum_desc->value_count());
const auto* probe_result_desc = runtime_probe::ProbeResult::descriptor();
const auto* generic_device_info_desc =
HwVerificationReport_GenericDeviceInfo::descriptor();
for (int i = 0; i < category_enum_desc->value_count(); ++i) {
auto* comp_category_info = &comp_category_infos_[i];
const auto& comp_category_name = category_enum_desc->value(i)->name();
comp_category_info->enum_value = category_enum_desc->value(i)->number();
comp_category_info->enum_name = comp_category_name;
if (comp_category_info->enum_value ==
runtime_probe::ProbeRequest_SupportCategory_UNKNOWN)
continue;
const auto* field_desc =
probe_result_desc->FindFieldByName(comp_category_name);
DCHECK(field_desc && field_desc->cpp_type() == kCppTypeMsg &&
field_desc->is_repeated())
<< "Field (" << comp_category_name << ") must be a repeated field for "
<< "the HW components in |runtime_probe::ProbeResult|.";
comp_category_info->probe_result_comp_field = field_desc;
const auto* probe_result_comp_desc = field_desc->message_type();
field_desc = probe_result_comp_desc->FindFieldByName("name");
DCHECK(field_desc && field_desc->cpp_type() == kCppTypeStr &&
field_desc->is_optional())
<< "Field (" << comp_category_name
<< ") should contain a string of the name of the component.";
comp_category_info->probe_result_comp_name_field = field_desc;
field_desc = probe_result_comp_desc->FindFieldByName("values");
DCHECK(field_desc && field_desc->cpp_type() == kCppTypeMsg &&
field_desc->is_optional())
<< "Field (" << comp_category_name
<< ") should contain a message field for the component values.";
comp_category_info->probe_result_comp_values_field = field_desc;
field_desc = generic_device_info_desc->FindFieldByName(comp_category_name);
if (field_desc) {
DCHECK(field_desc->cpp_type() == kCppTypeMsg && field_desc->is_repeated())
<< "|hardware_verifier::HwVerificationReport_GenericDeviceInfo| "
<< "should contain a repeated field for the generic ("
<< comp_category_name << ") components.";
} else {
VLOG(1) << "(" << comp_category_name << ") field is not found in "
<< "|hardware_verifier::HwVerificationReport_GenericDeviceInfo|, "
<< "will ignore the generic component of that category.";
}
comp_category_info->report_comp_values_field = field_desc;
}
}
base::Optional<HwVerificationReport> VerifierImpl::Verify(
const runtime_probe::ProbeResult& probe_result,
const HwVerificationSpec& hw_verification_spec) const {
// A dictionary of 'expected_component_category => seen'.
std::map<int, bool> seen_comp;
// Collect the categories of generic components we found.
std::set<int> seen_generic_comp;
// A dictionary which maps (component_category, component_uuid) to its
// qualification status.
std::map<int, std::map<std::string, QualificationStatus>> qual_status_dict;
const auto model_name = GetModelName();
for (const auto& comp_info : hw_verification_spec.component_infos()) {
if (!IsModelComponent(comp_info, model_name))
continue;
const auto& category = comp_info.component_category();
const auto& uuid = comp_info.component_uuid();
const auto& qualification_status = comp_info.qualification_status();
const auto& insert_result =
qual_status_dict[static_cast<int>(category)].emplace(
uuid, qualification_status);
if (!insert_result.second) {
LOG(ERROR)
<< "The verification spec contains duplicated component infos.";
return base::nullopt;
}
// We expect to see this component in probe result.
seen_comp[category] = false;
}
// A dictionary which maps component_category to the field names in the
// allowlist.
std::map<int, std::set<std::string>> generic_comp_value_allowlists;
for (const auto& spec_info :
hw_verification_spec.generic_component_value_allowlists()) {
const auto& insert_result = generic_comp_value_allowlists.emplace(
spec_info.component_category(),
std::set<std::string>(spec_info.field_names().cbegin(),
spec_info.field_names().cend()));
if (!insert_result.second) {
LOG(ERROR) << "Duplicated allowlist tables for category (num="
<< spec_info.component_category() << ") are detected in the "
<< "verification spec.";
return base::nullopt;
}
}
HwVerificationReport hw_verification_report;
hw_verification_report.set_is_compliant(true);
auto* generic_device_info =
hw_verification_report.mutable_generic_device_info();
const auto* generic_device_info_refl = generic_device_info->GetReflection();
const auto* probe_result_refl = probe_result.GetReflection();
for (const auto& comp_category_info : comp_category_infos_) {
if (comp_category_info.enum_value ==
runtime_probe::ProbeRequest_SupportCategory_UNKNOWN)
continue;
const auto& comp_name_to_qual_status =
qual_status_dict[comp_category_info.enum_value];
// the default allowlist is empty.
const auto& generic_comp_value_allowlist =
generic_comp_value_allowlists[comp_category_info.enum_value];
const auto& num_comps = probe_result_refl->FieldSize(
probe_result, comp_category_info.probe_result_comp_field);
for (int i = 0; i < num_comps; ++i) {
const auto& comp = probe_result_refl->GetRepeatedMessage(
probe_result, comp_category_info.probe_result_comp_field, i);
const auto* comp_refl = comp.GetReflection();
const auto& comp_name = comp_refl->GetString(
comp, comp_category_info.probe_result_comp_name_field);
// If the component name is "generic", add it to |generic_device_info|
// in the report.
if (comp_name == kGenericComponentName) {
seen_generic_comp.insert(comp_category_info.enum_value);
if (!comp_category_info.report_comp_values_field) {
VLOG(1) << "Ignore the generic component of ("
<< comp_category_info.enum_name << ") category.";
} else {
// Duplicate the original values and filter the fields by the
// allowlist.
auto* dup_comp_values = generic_device_info_refl->AddMessage(
generic_device_info, comp_category_info.report_comp_values_field);
dup_comp_values->CopyFrom(comp_refl->GetMessage(
comp, comp_category_info.probe_result_comp_values_field));
const auto* dup_comp_values_refl = dup_comp_values->GetReflection();
const auto* dup_comp_values_desc = dup_comp_values->GetDescriptor();
for (int j = 0; j < dup_comp_values_desc->field_count(); ++j) {
const auto* field = dup_comp_values_desc->field(j);
if (!generic_comp_value_allowlist.count(field->name())) {
dup_comp_values_refl->ClearField(dup_comp_values, field);
}
}
}
continue;
}
// If the component name is not "generic", do the regular qualification
// status check.
const auto& qual_status_it = comp_name_to_qual_status.find(comp_name);
if (qual_status_it == comp_name_to_qual_status.end()) {
LOG(ERROR) << "The probe result contains unregonizable components "
<< "(category=" << comp_category_info.enum_name
<< ", uuid=" << comp_name << ").";
return base::nullopt;
}
// TODO(b147654337): How about components that are "missing", that is:
// - It is expected on the system (according to SKU or MODEL).
// - We cannot find this in generic nor non-generic components.
AddFoundComponentInfo(
&hw_verification_report,
static_cast<runtime_probe::ProbeRequest_SupportCategory>(
comp_category_info.enum_value),
comp_name, qual_status_it->second);
seen_comp[comp_category_info.enum_value] = true;
}
}
for (const auto& it : seen_comp) {
// We have found a generic component in this category, but this doesn't have
// any qualification status.
if (!it.second && seen_generic_comp.count(it.first)) {
AddFoundComponentInfo(
&hw_verification_report,
static_cast<runtime_probe::ProbeRequest_SupportCategory>(it.first),
kNoMatchComponentName, QualificationStatus::NO_MATCH);
}
}
// TODO(yhong): Implement the SKU specific checks.
return hw_verification_report;
}
void VerifierImpl::SetCrosConfigForTesting(
std::unique_ptr<brillo::CrosConfigInterface> cros_config) {
cros_config_ = std::move(cros_config);
}
std::string VerifierImpl::GetModelName() const {
std::string model_name;
if (cros_config_ &&
cros_config_->GetString(kCrosConfigModelNamePath, kCrosConfigModelNameKey,
&model_name))
return model_name;
// Fallback to sys_info.
return base::SysInfo::GetLsbReleaseBoard();
}
} // namespace hardware_verifier