Files
cariflex/tools/EVerest-main/lib/everest/framework/lib/config_service.cpp
Eric F d398a6ced2 Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
2026-06-08 00:38:27 -04:00

646 lines
26 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <exception>
#include <everest/logging.hpp>
#include <utils/config/storage.hpp>
#include <utils/config/types.hpp>
#include <utils/config_service.hpp>
#include <utils/conversions.hpp>
namespace Everest {
namespace config {
bool ModuleIdType::operator<(const ModuleIdType& rhs) const {
return (this->module_id < rhs.module_id ||
(this->module_id == rhs.module_id && this->module_type < rhs.module_type));
}
enum class AccessMethod {
Read,
Write
};
namespace {
bool access_allowed(const everest::config::Access& access, const std::string& origin, const std::string& target,
AccessMethod method) {
if (origin == target) {
// a module can always read and write its own config
return true;
}
if (not access.config.has_value()) {
return false;
}
const auto& config_access = access.config.value();
const auto& config_access_modules_it = config_access.modules.find(target);
switch (method) {
case AccessMethod::Read:
if (config_access.allow_global_read) {
return true;
}
if (config_access_modules_it != config_access.modules.end()) {
if (config_access_modules_it->second.allow_read) {
return true;
}
}
break;
case AccessMethod::Write:
if (config_access.allow_global_write) {
return true;
}
if (config_access_modules_it != config_access.modules.end()) {
if (config_access_modules_it->second.allow_write) {
return true;
}
}
break;
}
return false;
}
nlohmann::json get_module_config(const std::string& module_id, const ManagerConfig& config) {
const auto& module_configurations = config.get_module_configurations();
return get_serialized_module_config(module_id, module_configurations);
}
everest::config::ModuleConfigurationParameters
update_mutability(const everest::config::ModuleConfigurationParameters& configuration_parameters,
bool allow_set_read_only = false) {
if (not allow_set_read_only) {
return configuration_parameters;
}
everest::config::ModuleConfigurationParameters updated_configuration_parameters = configuration_parameters;
for (auto& [impl_id, config_params] : updated_configuration_parameters) {
for (auto& config_param : config_params) {
auto& mutability = config_param.characteristics.mutability;
if (mutability == everest::config::Mutability::ReadOnly) {
config_param.characteristics.mutability = everest::config::Mutability::ReadWrite;
}
}
}
return updated_configuration_parameters;
}
GetResponse handle_get_module_config(const std::string& origin, const ManagerConfig& config) {
GetResponse get_response;
get_response.type = GetType::Module;
get_response.data = get_module_config(origin, config);
return get_response;
}
Response handle_get_config_value(const GetRequest& get_request, const std::string& origin, ManagerConfig& config) {
Response response;
response.type = Type::Get;
GetResponse get_response;
get_response.type = GetType::Value;
if (not get_request.identifier.has_value()) {
response.status = ResponseStatus::Error; // no identifier is always an error in this case
response.status_info = "No identifier provided";
} else {
const auto identifier = get_request.identifier.value();
const auto& module_configs = config.get_module_configurations();
const everest::config::Access access = module_configs.at(origin).access;
if (not access_allowed(access, origin, identifier.module_id, AccessMethod::Read)) {
response.status = ResponseStatus::AccessDenied;
response.status_info =
fmt::format("Access to config item denied: {} cannot access {}", origin, identifier.module_id);
} else {
const auto get_config_value_response = config.get_config_value(identifier);
if (get_config_value_response.status == everest::config::GetSetResponseStatus::OK) {
if (get_config_value_response.configuration_parameter.has_value()) {
get_response.data = get_config_value_response.configuration_parameter.value();
response.status = ResponseStatus::Ok;
}
}
}
}
response.response = get_response;
return response;
}
GetResponse handle_get_all_configs(const std::string& origin, const ManagerConfig& config) {
// FIXME: we might run into size limits when the config is really large, split this up in the
// future!
GetResponse get_response;
get_response.type = GetType::All;
json all_configs = json::object();
const auto& module_configs = config.get_module_configurations();
everest::config::Access access = module_configs.at(origin).access;
for (const auto& [module_id, module_name] : config.get_module_names()) {
if (not access_allowed(access, origin, module_id, AccessMethod::Read)) {
// request.origin has no access to module_id.config
continue;
}
auto allow_set_read_only = false;
if (access.config.has_value()) {
const auto config_access = access.config.value();
allow_set_read_only = config_access.allow_set_read_only;
if (not allow_set_read_only) {
// check if allow_set_read_only is set for specific modules
const auto module_config_access_it = config_access.modules.find(module_id);
if (module_config_access_it != config_access.modules.end()) {
allow_set_read_only = module_config_access_it->second.allow_set_read_only;
}
}
}
all_configs[module_id] =
update_mutability(module_configs.at(module_id).configuration_parameters, allow_set_read_only);
}
get_response.data = all_configs;
return get_response;
}
GetResponse handle_get_all_mappings(const std::string& origin, const ManagerConfig& config) {
GetResponse get_response;
get_response.type = GetType::AllMappings;
json all_mappings = json::object();
const auto& module_configs = config.get_module_configurations();
const everest::config::Access access = module_configs.at(origin).access;
for (const auto& [module_id, module_name] : config.get_module_names()) {
if (not access_allowed(access, origin, module_id, AccessMethod::Read)) {
// request.origin has no access to module_id.mappings
continue;
}
all_mappings[module_id] = module_configs.at(module_id).mapping;
}
get_response.data = all_mappings;
return get_response;
}
Response handle_set_request(const SetRequest& set_request, const std::string& origin, ManagerConfig& config) {
Response response;
response.type = Type::Set;
SetResponse set_response;
set_response.status = SetResponseStatus::Rejected;
const auto& module_configs = config.get_module_configurations();
const everest::config::Access access = module_configs.at(origin).access;
if (not access_allowed(access, origin, set_request.identifier.module_id, AccessMethod::Write)) {
set_response.status = SetResponseStatus::Rejected;
response.status = ResponseStatus::AccessDenied;
response.status_info =
fmt::format("Access to config item denied: {} cannot access {}", origin, set_request.identifier.module_id);
} else {
// TODO: explicit input validation
const auto& target_module_config = module_configs.at(set_request.identifier.module_id);
everest::config::SetConfigStatus status = everest::config::SetConfigStatus::Rejected;
const auto impl_id = set_request.identifier.module_implementation_id.value_or(MODULE_IMPLEMENTATION_ID);
for (const auto& config_entry : target_module_config.configuration_parameters.at(impl_id)) {
if (config_entry.name == set_request.identifier.configuration_parameter_name) {
try {
const auto value = parse_config_value(config_entry.characteristics.datatype, set_request.value);
status = config.set_config_value(set_request.identifier, value);
response.status = ResponseStatus::Ok;
} catch (const std::exception& e) {
response.status = ResponseStatus::Error;
response.status_info = fmt::format("Could not set config entry {} of module {}: {}",
set_request.identifier.configuration_parameter_name,
set_request.identifier.module_id, e.what());
}
break;
}
}
set_response.status = conversions::set_config_status_to_set_response_status(status);
}
response.response = set_response;
return response;
}
} // namespace
ConfigServiceClient::ConfigServiceClient(std::shared_ptr<MQTTAbstraction> mqtt_abstraction,
const std::string& module_id,
const std::map<std::string, std::string, std::less<>>& module_names) :
mqtt_abstraction(mqtt_abstraction), origin(module_id), module_names(module_names) {
}
std::map<ModuleIdType, everest::config::ModuleConfigurationParameters> ConfigServiceClient::get_module_configs() {
Request get_request;
get_request.type = Type::Get;
get_request.request = GetRequest{GetType::All};
get_request.origin = this->origin;
MQTTRequest mqtt_request;
mqtt_request.response_topic =
fmt::format("{}modules/{}/response", mqtt_abstraction->get_everest_prefix(), get_request.origin);
mqtt_request.request_topic = fmt::format("{}config/request", mqtt_abstraction->get_everest_prefix());
mqtt_request.request_data = json(get_request).dump();
try {
Response response = mqtt_abstraction->get(mqtt_request, mqtt_get_config_retries);
if (response.status != ResponseStatus::Ok) {
EVLOG_error << "Could not get module configs via MQTT";
return {};
}
GetResponse get_response = std::get<GetResponse>(response.response);
std::map<ModuleIdType, everest::config::ModuleConfigurationParameters> module_configs;
for (const auto& [module_id, config_maps] : get_response.data.items()) {
ModuleIdType module_id_type;
module_id_type.module_id = module_id;
module_id_type.module_type = module_names.at(module_id);
module_configs[module_id_type] = config_maps;
}
return module_configs;
} catch (const std::exception& e) {
EVLOG_error << "Could not get module configs via MQTT: " << e.what();
return {};
}
}
std::map<std::string, ModuleTierMappings> ConfigServiceClient::get_mappings() {
Request get_request;
get_request.type = Type::Get;
get_request.request = GetRequest{GetType::AllMappings};
get_request.origin = this->origin;
MQTTRequest mqtt_request;
mqtt_request.response_topic =
fmt::format("{}modules/{}/response", mqtt_abstraction->get_everest_prefix(), get_request.origin);
mqtt_request.request_topic = fmt::format("{}config/request", mqtt_abstraction->get_everest_prefix());
mqtt_request.request_data = json(get_request).dump();
try {
Response response = mqtt_abstraction->get(mqtt_request, mqtt_get_config_retries);
if (response.status != ResponseStatus::Ok) {
EVLOG_error << "Could not get mappings configs via MQTT";
return {};
}
GetResponse get_response = std::get<GetResponse>(response.response);
std::map<std::string, ModuleTierMappings> mappings;
for (const auto& [module_id, mapping] : get_response.data.items()) {
mappings[module_id] = everest::config::parse_mapping(mapping);
}
return mappings;
} catch (const std::exception& e) {
EVLOG_error << "Could not get mappings configs via MQTT: " << e.what();
return {};
}
}
SetConfigResult
ConfigServiceClient::set_config_value(const everest::config::ConfigurationParameterIdentifier& identifier,
const std::string& value) {
SetConfigResult result;
Request request;
request.type = Type::Set;
request.origin = this->origin;
SetRequest set_request;
set_request.identifier = identifier;
set_request.value = value;
request.request = set_request;
try {
MQTTRequest mqtt_request;
mqtt_request.response_topic =
fmt::format("{}modules/{}/response", mqtt_abstraction->get_everest_prefix(), request.origin);
mqtt_request.request_topic = fmt::format("{}config/request", mqtt_abstraction->get_everest_prefix());
mqtt_request.request_data = json(request).dump();
const Response response = mqtt_abstraction->get(mqtt_request, mqtt_get_config_retries);
result.status = response.status;
result.status_info = response.status_info;
if (response.status == ResponseStatus::Ok) {
if (response.type.has_value() and response.type.value() == Type::Set) {
const SetResponse set_response = std::get<SetResponse>(response.response);
result.set_status = conversions::set_response_status_to_set_config_status(set_response.status);
}
}
return result;
} catch (const std::exception& e) {
EVLOG_info << "Could not set config value: " << identifier.module_id << ": "
<< identifier.module_implementation_id.value_or(MODULE_IMPLEMENTATION_ID) << ": "
<< identifier.configuration_parameter_name << ": " << e.what();
result.status = ResponseStatus::Error;
}
return result;
}
GetConfigResult
ConfigServiceClient::get_config_value(const everest::config::ConfigurationParameterIdentifier& identifier) {
GetConfigResult result;
Request request;
request.type = Type::Get;
request.origin = this->origin;
GetRequest get_request;
get_request.type = GetType::Value;
get_request.identifier = identifier;
request.request = get_request;
try {
MQTTRequest mqtt_request;
mqtt_request.response_topic =
fmt::format("{}modules/{}/response", mqtt_abstraction->get_everest_prefix(), request.origin);
mqtt_request.request_topic = fmt::format("{}config/request", mqtt_abstraction->get_everest_prefix());
mqtt_request.request_data = json(request).dump();
const Response response = mqtt_abstraction->get(mqtt_request, mqtt_get_config_retries);
result.status = response.status;
result.status_info = response.status_info;
if (response.status == ResponseStatus::Ok) {
if (response.type.has_value() and response.type.value() == Type::Get) {
const GetResponse get_response = std::get<GetResponse>(response.response);
result.configuration_parameter = get_response.data;
}
}
} catch (const std::exception& e) {
EVLOG_info << "Could not get config value: " << identifier.module_id << ": "
<< identifier.module_implementation_id.value_or(MODULE_IMPLEMENTATION_ID) << ": "
<< identifier.configuration_parameter_name << ": " << e.what();
}
return result;
}
ConfigService::ConfigService(MQTTAbstraction& mqtt_abstraction, std::shared_ptr<ManagerConfig> config) :
mqtt_abstraction(mqtt_abstraction), config(config) {
// TODO: thread-safe?
const Handler global_config_request_handler = [&mqtt_abstraction, config](const std::string& /*topic*/,
const nlohmann::json& data) {
Response response;
response.status = ResponseStatus::Error;
try {
Request request = data;
response.type = request.type;
const auto response_topic =
fmt::format("{}modules/{}/response", mqtt_abstraction.get_everest_prefix(), request.origin);
if (request.type == Type::Get) {
const GetRequest get_request = std::get<GetRequest>(request.request);
if (get_request.type == GetType::Module) {
response.response = handle_get_module_config(request.origin, *config);
response.status = ResponseStatus::Ok;
} else if (get_request.type == GetType::Value) {
response = handle_get_config_value(get_request, request.origin, *config);
} else if (get_request.type == GetType::All) {
response.response = handle_get_all_configs(request.origin, *config);
response.status = ResponseStatus::Ok;
} else if (get_request.type == GetType::AllMappings) {
response.response = handle_get_all_mappings(request.origin, *config);
response.status = ResponseStatus::Ok;
}
} else if (request.type == Type::Set) {
response = handle_set_request(std::get<SetRequest>(request.request), request.origin, *config);
}
MqttMessagePayload payload{MqttMessageType::GetConfigResponse, response};
mqtt_abstraction.publish(response_topic, payload, QOS::QOS2);
} catch (const std::exception& e) {
EVLOG_error << "Exception during handling of request: " << e.what();
} catch (...) {
EVLOG_error << "Could not parse request: " << data.dump();
}
};
const std::string global_config_request_topic =
fmt::format("{}config/request", mqtt_abstraction.get_everest_prefix());
this->get_config_token = std::make_shared<TypedHandler>(HandlerType::GetConfig,
std::make_shared<Handler>(global_config_request_handler));
mqtt_abstraction.register_handler(global_config_request_topic, this->get_config_token, QOS::QOS2);
}
namespace conversions {
std::string type_to_string(Type type) {
switch (type) {
case Type::Get:
return "Get";
case Type::Set:
return "Set";
case Type::Unknown:
return "Unknown";
}
throw std::out_of_range("Could not convert Type to string");
}
Type string_to_type(const std::string& type) {
if (type == "Get") {
return Type::Get;
} else if (type == "Set") {
return Type::Set;
} else if (type == "Unknown") {
return Type::Unknown;
}
throw std::out_of_range("Could not convert " + type + " to Type");
}
std::string get_type_to_string(GetType type) {
switch (type) {
case GetType::All:
return "All";
case GetType::Module:
return "Module";
case GetType::Value:
return "Value";
case GetType::AllMappings:
return "AllMappings";
case GetType::Unknown:
return "Unknown";
}
throw std::out_of_range("Could not convert GetType to string");
}
GetType string_to_get_type(const std::string& type) {
if (type == "All") {
return GetType::All;
} else if (type == "Module") {
return GetType::Module;
} else if (type == "Value") {
return GetType::Value;
} else if (type == "AllMappings") {
return GetType::AllMappings;
} else if (type == "Unknown") {
return GetType::Unknown;
}
throw std::out_of_range("Could not convert " + type + " to GetType");
}
std::string response_status_to_string(ResponseStatus status) {
switch (status) {
case ResponseStatus::Ok:
return "Ok";
case ResponseStatus::Error:
return "Error";
case ResponseStatus::AccessDenied:
return "AccessDenied";
}
throw std::out_of_range("Could not convert ResponseStatus to string");
}
ResponseStatus string_to_response_status(const std::string& status) {
if (status == "Ok") {
return ResponseStatus::Ok;
} else if (status == "Error") {
return ResponseStatus::Error;
} else if (status == "AccessDenied") {
return ResponseStatus::AccessDenied;
}
throw std::out_of_range("Could not convert " + status + " to ResponseStatus");
}
std::string set_response_status_to_string(SetResponseStatus status) {
switch (status) {
case SetResponseStatus::Accepted:
return "Accepted";
case SetResponseStatus::Rejected:
return "Rejected";
case SetResponseStatus::RebootRequired:
return "RebootRequired";
}
throw std::out_of_range("Could not convert SetResponseStatus to string");
}
SetResponseStatus string_to_set_response_status(const std::string& status) {
if (status == "Accepted") {
return SetResponseStatus::Accepted;
} else if (status == "Rejected") {
return SetResponseStatus::Rejected;
} else if (status == "RebootRequired") {
return SetResponseStatus::RebootRequired;
}
throw std::out_of_range("Could not convert " + status + " to SetResponseStatus");
}
everest::config::SetConfigStatus set_response_status_to_set_config_status(SetResponseStatus status) {
switch (status) {
case SetResponseStatus::Accepted:
return everest::config::SetConfigStatus::Accepted;
case SetResponseStatus::Rejected:
return everest::config::SetConfigStatus::Rejected;
case SetResponseStatus::RebootRequired:
return everest::config::SetConfigStatus::RebootRequired;
}
throw std::out_of_range("Could not convert SetResponseStatus to SetConfigStatus");
}
SetResponseStatus set_config_status_to_set_response_status(everest::config::SetConfigStatus status) {
switch (status) {
case everest::config::SetConfigStatus::Accepted:
return SetResponseStatus::Accepted;
case everest::config::SetConfigStatus::Rejected:
return SetResponseStatus::Rejected;
case everest::config::SetConfigStatus::RebootRequired:
return SetResponseStatus::RebootRequired;
}
throw std::out_of_range("Could not convert SetConfigStatus to SetResponseStatus");
}
} // namespace conversions
std::ostream& operator<<(std::ostream& os, const GetType& t) {
os << conversions::get_type_to_string(t);
return os;
}
void to_json(nlohmann::json& j, const GetRequest& r) {
j = {{"type", conversions::get_type_to_string(r.type)}};
if (r.identifier.has_value()) {
j["identifier"] = r.identifier.value();
}
}
void from_json(const nlohmann::json& j, GetRequest& r) {
r.type = conversions::string_to_get_type(j.at("type"));
if (j.contains("identifier")) {
r.identifier = j.at("identifier");
}
}
void to_json(nlohmann::json& j, const SetRequest& r) {
j = {{"value", r.value}};
j["identifier"] = nlohmann::json(r.identifier);
}
void from_json(const nlohmann::json& j, SetRequest& r) {
r.identifier = j.at("identifier");
r.value = j.at("value");
}
void to_json(nlohmann::json& j, const GetResponse& r) {
j = {{"type", conversions::get_type_to_string(r.type)}, {"data", r.data}};
}
void from_json(const nlohmann::json& j, GetResponse& r) {
r.type = conversions::string_to_get_type(j.at("type"));
r.data = j.at("data");
}
void to_json(nlohmann::json& j, const SetResponse& r) {
j = {{"status", conversions::set_response_status_to_string(r.status)}};
}
void from_json(const nlohmann::json& j, SetResponse& r) {
r.status = conversions::string_to_set_response_status(j.at("status"));
}
void to_json(nlohmann::json& j, const Request& r) {
j = {{"type", conversions::type_to_string(r.type)}, {"origin", r.origin}};
j["request"] = Everest::variant_to_json(r.request);
}
void from_json(const nlohmann::json& j, Request& r) {
r.type = conversions::string_to_type(j.at("type"));
if (r.type == Type::Get) {
r.request = j.at("request").get<GetRequest>();
} else if (r.type == Type::Set) {
r.request = j.at("request").get<SetRequest>();
}
r.origin = j.at("origin");
}
void to_json(nlohmann::json& j, const Response& r) {
j = {{"status", conversions::response_status_to_string(r.status)}, {"status_info", r.status_info}};
if (r.type.has_value()) {
j["type"] = conversions::type_to_string(r.type.value());
}
j["response"] = Everest::variant_to_json(r.response);
}
void from_json(const nlohmann::json& j, Response& r) {
r.status = conversions::string_to_response_status(j.at("status"));
r.status_info = j.at("status_info");
if (j.contains("type")) {
Type type = conversions::string_to_type(j.at("type"));
r.type = type;
if (type == Type::Get) {
r.response = j.at("response").get<GetResponse>();
} else if (type == Type::Set) {
r.response = j.at("response").get<SetResponse>();
}
}
}
} // namespace config
} // namespace Everest
NLOHMANN_JSON_NAMESPACE_BEGIN
void adl_serializer<everest::config::ConfigurationParameterIdentifier>::to_json(
nlohmann::json& j, const everest::config::ConfigurationParameterIdentifier& c) {
j = {{"module_id", c.module_id}, {"configuration_parameter_name", c.configuration_parameter_name}};
if (c.module_implementation_id.has_value()) {
j["module_implementation_id"] = c.module_implementation_id.value();
}
}
void adl_serializer<everest::config::ConfigurationParameterIdentifier>::from_json(
const nlohmann::json& j, everest::config::ConfigurationParameterIdentifier& c) {
c.module_id = j.at("module_id");
c.configuration_parameter_name = j.at("configuration_parameter_name");
if (j.contains("module_implementation_id")) {
c.module_implementation_id = j.at("module_implementation_id");
}
}
NLOHMANN_JSON_NAMESPACE_END