blob: 889cefac81a21b5497130bbdd5f6af615c6c051d [file] [log] [blame]
// Copyright 2018 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 <inttypes.h>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <base/check.h>
#include <base/strings/stringprintf.h>
#include <base/strings/string_number_conversions.h>
#include "runtime_probe/probe_result_checker.h"
#include "runtime_probe/utils/type_utils.h"
namespace {
using ValidatorOperator = runtime_probe::ValidatorOperator;
using ReturnCode = runtime_probe::FieldConverter::ReturnCode;
constexpr const char* GetPrefix(ValidatorOperator op) {
switch (op) {
case ValidatorOperator::NOP:
return "!nop ";
case ValidatorOperator::RE:
return "!re ";
case ValidatorOperator::EQ:
return "!eq ";
case ValidatorOperator::NE:
return "!ne ";
case ValidatorOperator::GT:
return "!gt ";
case ValidatorOperator::GE:
return "!ge ";
case ValidatorOperator::LT:
return "!lt ";
case ValidatorOperator::LE:
return "!le ";
default:
DCHECK(false) << "should never reach here";
}
return nullptr;
}
constexpr const char* ToString(ValidatorOperator op) {
switch (op) {
case ValidatorOperator::NOP:
return "NOP";
case ValidatorOperator::RE:
return "RE";
case ValidatorOperator::EQ:
return "EQ";
case ValidatorOperator::NE:
return "NE";
case ValidatorOperator::GT:
return "GT";
case ValidatorOperator::GE:
return "GE";
case ValidatorOperator::LT:
return "LT";
case ValidatorOperator::LE:
return "LE";
default:
DCHECK(false) << "should never reach here";
}
return nullptr;
}
bool SplitValidateRuleString(const base::StringPiece& validate_rule,
ValidatorOperator* operator_,
base::StringPiece* operand) {
if (validate_rule.empty()) {
*operator_ = ValidatorOperator::NOP;
*operand = "";
return true;
}
auto first_space_idx = validate_rule.find_first_of(' ');
auto prefix = validate_rule.substr(0, first_space_idx + 1);
auto rest = validate_rule.substr(first_space_idx + 1);
for (int i = 0; i < static_cast<int>(ValidatorOperator::NUM_OP); i++) {
auto op = static_cast<ValidatorOperator>(i);
if (prefix == GetPrefix(op)) {
*operator_ = op;
if (op != ValidatorOperator::NOP) // NOP shouldn't have operand.
*operand = rest;
return true;
}
}
return false;
}
template <typename ConverterType>
std::unique_ptr<ConverterType> BuildNumericConverter(
const base::StringPiece& validate_rule) {
ValidatorOperator op;
base::StringPiece rest;
if (SplitValidateRuleString(validate_rule, &op, &rest)) {
if (op == ValidatorOperator::NOP)
return std::make_unique<ConverterType>(op, 0);
if (op == ValidatorOperator::EQ || op == ValidatorOperator::NE ||
op == ValidatorOperator::GT || op == ValidatorOperator::GE ||
op == ValidatorOperator::LT || op == ValidatorOperator::LE) {
typename ConverterType::OperandType operand;
if (ConverterType::StringToOperand(rest.as_string(), &operand)) {
return std::make_unique<ConverterType>(op, operand);
} else {
LOG(ERROR) << "Can't convert to operand: " << rest.as_string();
}
}
}
LOG(ERROR) << "Invalid validate rule: " << validate_rule;
return nullptr;
}
template <typename ValueType>
ReturnCode CheckNumber(ValidatorOperator op,
const ValueType lhs,
const ValueType rhs) {
bool is_valid = true;
switch (op) {
case ValidatorOperator::NOP:
break;
case ValidatorOperator::EQ:
is_valid = lhs == rhs;
break;
case ValidatorOperator::GE:
is_valid = lhs >= rhs;
break;
case ValidatorOperator::GT:
is_valid = lhs > rhs;
break;
case ValidatorOperator::LE:
is_valid = lhs <= rhs;
break;
case ValidatorOperator::LT:
is_valid = lhs < rhs;
break;
case ValidatorOperator::NE:
is_valid = lhs != rhs;
break;
default:
return ReturnCode::UNSUPPORTED_OPERATOR;
}
return is_valid ? ReturnCode::OK : ReturnCode::INVALID_VALUE;
}
} // namespace
namespace runtime_probe {
using ReturnCode = FieldConverter::ReturnCode;
std::string StringFieldConverter::ToString() const {
return base::StringPrintf("StringFieldConverter(%s, %s)",
::ToString(operator_), operand_.c_str());
}
std::string IntegerFieldConverter::ToString() const {
return base::StringPrintf("IntegerFieldConverter(%s, %d)",
::ToString(operator_), operand_);
}
std::string HexFieldConverter::ToString() const {
return base::StringPrintf("HexFieldConverter(%s, 0x%" PRIx64 ")",
::ToString(operator_), operand_);
}
std::string DoubleFieldConverter::ToString() const {
return base::StringPrintf("DoubleFieldConverter(%s, %f)",
::ToString(operator_), operand_);
}
std::unique_ptr<StringFieldConverter> StringFieldConverter::Build(
const base::StringPiece& validate_rule) {
ValidatorOperator op;
base::StringPiece pattern;
if (SplitValidateRuleString(validate_rule, &op, &pattern)) {
if (op == ValidatorOperator::NOP)
return std::make_unique<StringFieldConverter>(op, "");
if (op == ValidatorOperator::EQ || op == ValidatorOperator::NE) {
return std::make_unique<StringFieldConverter>(op, pattern);
}
if (op == ValidatorOperator::RE) {
auto instance = std::make_unique<StringFieldConverter>(op, pattern);
if (instance->regex_->error().empty()) {
// No error, the pattern is valid.
return instance;
}
// Error string is set to non-empty if there are errors.
LOG(ERROR) << "Invalid pattern: " << pattern;
LOG(ERROR) << instance->regex_->error();
}
}
LOG(ERROR) << "Invalid validate rule: " << validate_rule;
return nullptr;
}
std::unique_ptr<IntegerFieldConverter> IntegerFieldConverter::Build(
const base::StringPiece& validate_rule) {
return BuildNumericConverter<IntegerFieldConverter>(validate_rule);
}
std::unique_ptr<HexFieldConverter> HexFieldConverter::Build(
const base::StringPiece& validate_rule) {
return BuildNumericConverter<HexFieldConverter>(validate_rule);
}
std::unique_ptr<DoubleFieldConverter> DoubleFieldConverter::Build(
const base::StringPiece& validate_rule) {
return BuildNumericConverter<DoubleFieldConverter>(validate_rule);
}
ReturnCode StringFieldConverter::Convert(const std::string& field_name,
base::Value* dict_value) const {
CHECK(dict_value);
auto* value = dict_value->FindKey(field_name);
if (!value)
return ReturnCode::FIELD_NOT_FOUND;
switch (value->type()) {
case base::Value::Type::DOUBLE:
dict_value->SetStringKey(field_name, std::to_string(value->GetDouble()));
return ReturnCode::OK;
case base::Value::Type::INTEGER:
dict_value->SetStringKey(field_name, std::to_string(value->GetInt()));
return ReturnCode::OK;
case base::Value::Type::NONE:
dict_value->SetStringKey(field_name, "null");
return ReturnCode::OK;
case base::Value::Type::STRING:
return ReturnCode::OK;
default:
return ReturnCode::INCOMPATIBLE_VALUE;
}
}
ReturnCode IntegerFieldConverter::Convert(const std::string& field_name,
base::Value* dict_value) const {
CHECK(dict_value);
auto* value = dict_value->FindKey(field_name);
if (!value)
return ReturnCode::FIELD_NOT_FOUND;
switch (value->type()) {
case base::Value::Type::DOUBLE:
dict_value->SetIntKey(field_name, static_cast<int>(value->GetDouble()));
return ReturnCode::OK;
case base::Value::Type::INTEGER:
return ReturnCode::OK;
case base::Value::Type::STRING: {
const auto& string_value = value->GetString();
OperandType int_value;
if (StringToOperand(string_value, &int_value)) {
dict_value->SetIntKey(field_name, int_value);
return ReturnCode::OK;
} else {
LOG(ERROR) << "Failed to convert '" << string_value << "' to integer.";
return ReturnCode::INCOMPATIBLE_VALUE;
}
}
default:
return ReturnCode::INCOMPATIBLE_VALUE;
}
}
ReturnCode HexFieldConverter::Convert(const std::string& field_name,
base::Value* dict_value) const {
CHECK(dict_value);
auto* value = dict_value->FindKey(field_name);
if (!value)
return ReturnCode::FIELD_NOT_FOUND;
OperandType int_value;
switch (value->type()) {
case base::Value::Type::DOUBLE:
dict_value->SetIntKey(field_name, static_cast<int>(value->GetDouble()));
return ReturnCode::OK;
case base::Value::Type::INTEGER: {
int_value = value->GetInt();
break;
}
case base::Value::Type::STRING: {
const auto& string_value_ = value->GetString();
if (!StringToOperand(string_value_, &int_value)) {
LOG(ERROR) << "Failed to convert '" << string_value_ << "' to integer.";
return ReturnCode::INCOMPATIBLE_VALUE;
}
break;
}
default:
return ReturnCode::INCOMPATIBLE_VALUE;
}
// Since base::Value only supports 32-bit integer, we use string field to
// store large integers. We convert values to decimal strings to make
// google::protobuf::util::JsonStringToMessage parse strings to integers
// correctly.
const auto string_value = base::NumberToString(int_value);
dict_value->SetStringKey(field_name, string_value);
return ReturnCode::OK;
}
ReturnCode DoubleFieldConverter::Convert(const std::string& field_name,
base::Value* dict_value) const {
CHECK(dict_value);
auto* value = dict_value->FindKey(field_name);
if (!value)
return ReturnCode::FIELD_NOT_FOUND;
switch (value->type()) {
case base::Value::Type::DOUBLE:
return ReturnCode::OK;
case base::Value::Type::INTEGER:
dict_value->SetDoubleKey(field_name, value->GetDouble());
return ReturnCode::OK;
case base::Value::Type::STRING: {
const auto& string_value = value->GetString();
double double_value;
if (StringToDouble(string_value, &double_value)) {
dict_value->SetDoubleKey(field_name, double_value);
return ReturnCode::OK;
} else {
LOG(ERROR) << "Failed to convert '" << string_value << "' to double.";
return ReturnCode::INCOMPATIBLE_VALUE;
}
}
default:
return ReturnCode::INCOMPATIBLE_VALUE;
}
}
ReturnCode StringFieldConverter::Validate(const std::string& field_name,
base::Value* dict_value) const {
CHECK(dict_value);
auto* value_ = dict_value->FindKey(field_name);
if (!value_)
return ReturnCode::FIELD_NOT_FOUND;
if (!value_->is_string())
return ReturnCode::INCOMPATIBLE_VALUE;
const auto& value = value_->GetString();
bool is_valid = true;
switch (operator_) {
case ValidatorOperator::NOP:
break;
case ValidatorOperator::EQ:
is_valid = value == operand_;
break;
case ValidatorOperator::RE:
is_valid = regex_->FullMatch(value);
break;
case ValidatorOperator::NE:
is_valid = value != operand_;
break;
default:
return ReturnCode::UNSUPPORTED_OPERATOR;
}
return is_valid ? ReturnCode::OK : ReturnCode::INVALID_VALUE;
}
ReturnCode IntegerFieldConverter::Validate(const std::string& field_name,
base::Value* dict_value) const {
CHECK(dict_value);
auto* value_ = dict_value->FindKey(field_name);
if (!value_)
return ReturnCode::FIELD_NOT_FOUND;
if (!value_->is_int())
return ReturnCode::INCOMPATIBLE_VALUE;
const auto value = value_->GetInt();
return CheckNumber(operator_, value, operand_);
}
ReturnCode HexFieldConverter::Validate(const std::string& field_name,
base::Value* dict_value) const {
CHECK(dict_value);
auto* value_ = dict_value->FindKey(field_name);
if (!value_)
return ReturnCode::FIELD_NOT_FOUND;
OperandType value;
switch (value_->type()) {
case base::Value::Type::INTEGER: {
value = value_->GetInt();
break;
}
case base::Value::Type::STRING: {
const auto& string_value = value_->GetString();
if (!StringToInt64(string_value, &value))
return ReturnCode::INCOMPATIBLE_VALUE;
break;
}
default:
return ReturnCode::INCOMPATIBLE_VALUE;
}
return CheckNumber(operator_, value, operand_);
}
ReturnCode DoubleFieldConverter::Validate(const std::string& field_name,
base::Value* dict_value) const {
CHECK(dict_value);
auto* value_ = dict_value->FindKey(field_name);
if (!value_)
return ReturnCode::FIELD_NOT_FOUND;
if (!value_->is_double() && !value_->is_int())
return ReturnCode::INCOMPATIBLE_VALUE;
const auto value = value_->GetDouble();
return CheckNumber(operator_, value, operand_);
}
std::unique_ptr<ProbeResultChecker> ProbeResultChecker::FromValue(
const base::Value& dict_value) {
std::unique_ptr<ProbeResultChecker> instance{new ProbeResultChecker()};
for (const auto& entry : dict_value.DictItems()) {
const auto& key = entry.first;
const auto& val = entry.second;
auto print_error_and_return = [&val]() {
LOG(ERROR) << "'expect' attribute should be a list whose values are"
<< "[<required:bool>, <expected_type:string>, "
<< "<optional_validate_rule:string>], got: " << val;
return nullptr;
};
const auto& list_value = val.GetList();
if (list_value.size() < 2 || list_value.size() > 3)
return print_error_and_return();
if (!list_value[0].is_bool())
return print_error_and_return();
bool required = list_value[0].GetBool();
auto* target =
required ? &instance->required_fields_ : &instance->optional_fields_;
if (!list_value[1].is_string())
return print_error_and_return();
const auto& expect_type = list_value[1].GetString();
std::string validate_rule;
if (list_value.size() == 3) {
if (!list_value[2].is_string())
return print_error_and_return();
validate_rule = list_value[2].GetString();
}
std::unique_ptr<FieldConverter> converter = nullptr;
if (expect_type == "str") {
converter = StringFieldConverter::Build(validate_rule);
} else if (expect_type == "int") {
converter = IntegerFieldConverter::Build(validate_rule);
} else if (expect_type == "double") {
converter = DoubleFieldConverter::Build(validate_rule);
} else if (expect_type == "hex") {
converter = HexFieldConverter::Build(validate_rule);
}
if (converter == nullptr) {
LOG(ERROR) << "Cannot build converter, 'expect_type': " << expect_type
<< ", 'validate_rule': " << validate_rule;
return nullptr;
} else {
(*target)[key] = std::move(converter);
}
}
return instance;
}
bool ProbeResultChecker::Apply(base::Value* probe_result) const {
bool success = true;
CHECK(probe_result != nullptr);
// Try to convert and validate each required fields.
// Any failures will cause the final result be |false|.
for (const auto& entry : required_fields_) {
const auto& key = entry.first;
const auto& converter = entry.second;
if (!probe_result->FindKey(key)) {
DVLOG(2) << "Missing key: " << key;
success = false;
break;
}
auto return_code = converter->Convert(key, probe_result);
if (return_code != ReturnCode::OK) {
auto* value = probe_result->FindKey(key);
LOG(ERROR) << "Failed to apply " << converter->ToString() << " on "
<< *value << "(ReturnCode = " << static_cast<int>(return_code)
<< ")";
success = false;
break;
}
}
// |ProbeStatement| will remove this element from final results, there is no
// need to continue.
if (!success) {
VLOG(3) << "probe_result = " << *probe_result;
return false;
}
// Try to convert and validate each optional fields.
// For failures, just remove them from probe_result and continue.
for (const auto& entry : optional_fields_) {
const auto& key = entry.first;
const auto& converter = entry.second;
if (!probe_result->FindKey(key))
continue;
auto return_code = converter->Convert(key, probe_result);
if (return_code != ReturnCode::OK) {
VLOG(1) << "Optional field '" << key << "' has unexpected value, "
<< "remove it from probe result.";
probe_result->RemoveKey(key);
}
}
// Now all fields should have the correct type, let's validate them.
for (const auto& entry : required_fields_) {
auto return_code = entry.second->Validate(entry.first, probe_result);
if (return_code != ReturnCode::OK) {
success = false;
break;
}
}
// Optional fields shouldn't have expect value.
return success;
}
} // namespace runtime_probe