diff --git a/dependencies.yaml b/dependencies.yaml index d15dbb77e0..a5a1dc4ff2 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -31,7 +31,7 @@ websocketpp: cmake_condition: "LIBOCPP_ENABLE_DEPRECATED_WEBSOCKETPP" libevse-security: git: https://github.com/EVerest/libevse-security.git - git_tag: v0.7.0 + git_tag: 7624055c9a23c75e310ea7b3ebb7010063cf4c6c libwebsockets: git: https://github.com/warmcat/libwebsockets.git git_tag: v4.3.3 diff --git a/include/ocpp/common/websocket/websocket_libwebsockets.hpp b/include/ocpp/common/websocket/websocket_libwebsockets.hpp index 08ecbe9849..53a3f0bf04 100644 --- a/include/ocpp/common/websocket/websocket_libwebsockets.hpp +++ b/include/ocpp/common/websocket/websocket_libwebsockets.hpp @@ -54,7 +54,7 @@ class WebsocketTlsTPM final : public WebsocketBase { int process_callback(void* wsi_ptr, int callback_reason, void* user, void* in, size_t len); private: - void tls_init(struct ssl_ctx_st* ctx, const std::string& path_chain, const std::string& path_key, bool tpm_key, + bool tls_init(struct ssl_ctx_st* ctx, const std::string& path_chain, const std::string& path_key, bool custom_key, std::optional& password); void client_loop(); void recv_loop(); diff --git a/include/ocpp/v201/ctrlr_component_variables.hpp b/include/ocpp/v201/ctrlr_component_variables.hpp index 39f757319c..24a95027d8 100644 --- a/include/ocpp/v201/ctrlr_component_variables.hpp +++ b/include/ocpp/v201/ctrlr_component_variables.hpp @@ -16,11 +16,9 @@ extern const Component& AuthCacheCtrlr; extern const Component& AuthCtrlr; extern const Component& ChargingStation; extern const Component& ClockCtrlr; -extern const Component& Connector; extern const Component& CustomizationCtrlr; extern const Component& DeviceDataCtrlr; extern const Component& DisplayMessageCtrlr; -extern const Component& EVSE; extern const Component& ISO15118Ctrlr; extern const Component& LocalAuthListCtrlr; extern const Component& MonitoringCtrlr; @@ -33,6 +31,13 @@ extern const Component& TariffCostCtrlr; extern const Component& TxCtrlr; } // namespace ControllerComponents +namespace StandardizedVariables { +extern const Variable& Problem; +extern const Variable& Tripped; +extern const Variable& Overload; +extern const Variable& Fallback; +}; // namespace StandardizedVariables + // Provides access to standardized variables of OCPP2.0.1 spec namespace ControllerComponentVariables { extern const ComponentVariable& InternalCtrlrEnabled; diff --git a/lib/ocpp/common/websocket/websocket_libwebsockets.cpp b/lib/ocpp/common/websocket/websocket_libwebsockets.cpp index 31398562c2..f21d14cb61 100644 --- a/lib/ocpp/common/websocket/websocket_libwebsockets.cpp +++ b/lib/ocpp/common/websocket/websocket_libwebsockets.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest -#include +#include #include #include @@ -40,7 +40,7 @@ template <> class std::default_delete { namespace ocpp { -using evse_security::is_tpm_key_file; +using evse_security::is_custom_private_key_file; using evse_security::OpenSSLProvider; enum class EConnectionState { @@ -276,16 +276,30 @@ static int callback_minimal(struct lws* wsi, enum lws_callback_reasons reason, v return 0; } +static int private_key_callback(char* buf, int size, int rwflag, void* userdata) { + const auto* password = static_cast(userdata); + const std::size_t max_pass_len = (size - 1); // we exclude the endline + const std::size_t max_copy_chars = + std::min(max_pass_len, password->length()); // truncate if pass is too large and buffer too small + + std::memset(buf, 0, size); + std::memcpy(buf, password->c_str(), max_copy_chars); + + return max_copy_chars; +} + constexpr auto local_protocol_name = "lws-everest-client"; static const struct lws_protocols protocols[] = {{local_protocol_name, callback_minimal, 0, 0, 0, NULL, 0}, LWS_PROTOCOL_LIST_TERM}; -void WebsocketTlsTPM::tls_init(SSL_CTX* ctx, const std::string& path_chain, const std::string& path_key, bool tpm_key, - std::optional& password) { +bool WebsocketTlsTPM::tls_init(SSL_CTX* ctx, const std::string& path_chain, const std::string& path_key, + bool custom_key, std::optional& password) { auto rc = SSL_CTX_set_cipher_list(ctx, this->connection_options.supported_ciphers_12.c_str()); if (rc != 1) { EVLOG_debug << "SSL_CTX_set_cipher_list return value: " << rc; - EVLOG_AND_THROW(std::runtime_error("Could not set TLSv1.2 cipher list")); + EVLOG_error << "Could not set TLSv1.2 cipher list"; + + return false; } rc = SSL_CTX_set_ciphersuites(ctx, this->connection_options.supported_ciphers_13.c_str()); @@ -298,24 +312,35 @@ void WebsocketTlsTPM::tls_init(SSL_CTX* ctx, const std::string& path_chain, cons if (this->connection_options.security_profile == 3) { if ((path_chain.empty()) || (path_key.empty())) { EVLOG_error << "Cert chain: [" << path_chain << "] key: " << path_key << "]"; - EVLOG_AND_THROW(std::runtime_error("No certificate and key for SSL")); + EVLOG_error << "No certificate or key found for SSL"; + + return false; } if (1 != SSL_CTX_use_certificate_chain_file(ctx, path_chain.c_str())) { ERR_print_errors_fp(stderr); - EVLOG_AND_THROW(std::runtime_error("Could not use client certificate file within SSL context")); + EVLOG_error << "Could not use client certificate file within SSL context"; + + return false; } - SSL_CTX_set_default_passwd_cb_userdata(ctx, reinterpret_cast(password.value_or("").data())); + if (password.has_value()) { + SSL_CTX_set_default_passwd_cb_userdata(ctx, &password.value()); + SSL_CTX_set_default_passwd_cb(ctx, private_key_callback); + } if (1 != SSL_CTX_use_PrivateKey_file(ctx, path_key.c_str(), SSL_FILETYPE_PEM)) { ERR_print_errors_fp(stderr); - EVLOG_AND_THROW(std::runtime_error("Could not set private key file within SSL context")); + EVLOG_error << "Could not set private key file within SSL context"; + + return false; } if (false == SSL_CTX_check_private_key(ctx)) { ERR_print_errors_fp(stderr); - EVLOG_AND_THROW(std::runtime_error("Could not check private key within SSL context")); + EVLOG_error << "Could not check private key within SSL context"; + + return false; } } @@ -328,7 +353,7 @@ void WebsocketTlsTPM::tls_init(SSL_CTX* ctx, const std::string& path_chain, cons if (rc != 1) { EVLOG_error << "Could not load CA verify locations, error: " << ERR_error_string(ERR_get_error(), NULL); - EVLOG_AND_THROW(std::runtime_error("Could not load CA verify locations")); + return false; } } @@ -336,7 +361,7 @@ void WebsocketTlsTPM::tls_init(SSL_CTX* ctx, const std::string& path_chain, cons rc = SSL_CTX_set_default_verify_paths(ctx); if (rc != 1) { EVLOG_error << "Could not load default CA verify path, error: " << ERR_error_string(ERR_get_error(), NULL); - EVLOG_AND_THROW(std::runtime_error("Could not load CA verify locations")); + return false; } } @@ -374,6 +399,8 @@ void WebsocketTlsTPM::tls_init(SSL_CTX* ctx, const std::string& path_chain, cons // Extra info SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); // to verify server certificate + + return true; } void WebsocketTlsTPM::recv_loop() { @@ -450,11 +477,13 @@ void WebsocketTlsTPM::client_loop() { info.fd_limit_per_thread = 1 + 1 + 1; + // Lifetime of this is important since we use the data from this in private_key_callback() + std::optional private_key_password; + if (this->connection_options.security_profile == 2 || this->connection_options.security_profile == 3) { // Setup context - need to know the key type first std::string path_key; std::string path_chain; - std::optional password; if (this->connection_options.security_profile == 3) { const auto certificate_response = @@ -462,8 +491,14 @@ void WebsocketTlsTPM::client_loop() { if (certificate_response.status != ocpp::GetCertificateInfoStatus::Accepted or !certificate_response.info.has_value()) { - EVLOG_AND_THROW(std::runtime_error( - "Connecting with security profile 3 but no client side certificate is present or valid")); + EVLOG_error << "Connecting with security profile 3 but no client side certificate is present or valid"; + + local_data->update_state(EConnectionState::ERROR); + on_conn_fail(); + + // Notify conn waiter + conn_cv.notify_one(); + return; } const auto& certificate_info = certificate_response.info.value(); @@ -473,25 +508,31 @@ void WebsocketTlsTPM::client_loop() { } else if (certificate_info.certificate_single_path.has_value()) { path_chain = certificate_info.certificate_single_path.value(); } else { - EVLOG_AND_THROW(std::runtime_error( - "Connecting with security profile 3 but no client side certificate is present or valid")); + EVLOG_error << "Connecting with security profile 3 but no client side certificate is present or valid"; + + local_data->update_state(EConnectionState::ERROR); + on_conn_fail(); + + // Notify conn waiter + conn_cv.notify_one(); + return; } path_key = certificate_info.key_path; - password = certificate_info.password; + private_key_password = certificate_info.password; } SSL_CTX* ssl_ctx = nullptr; - bool tpm_key = false; + bool custom_key = false; if (!path_key.empty()) { - tpm_key = is_tpm_key_file(path_key); + custom_key = is_custom_private_key_file(path_key); } OpenSSLProvider provider; - if (tpm_key) { - provider.set_tls_mode(OpenSSLProvider::mode_t::tpm2_provider); + if (custom_key) { + provider.set_tls_mode(OpenSSLProvider::mode_t::custom_provider); } else { provider.set_tls_mode(OpenSSLProvider::mode_t::default_provider); } @@ -501,11 +542,27 @@ void WebsocketTlsTPM::client_loop() { if (ssl_ctx == nullptr) { ERR_print_errors_fp(stderr); - EVLOG_AND_THROW(std::runtime_error("Unable to create ssl ctx.")); + EVLOG_error << "Unable to create ssl ctx"; + + local_data->update_state(EConnectionState::ERROR); + on_conn_fail(); + + // Notify conn waiter + conn_cv.notify_one(); + return; } // Init TLS data - tls_init(ssl_ctx, path_chain, path_key, tpm_key, password); + if (tls_init(ssl_ctx, path_chain, path_key, custom_key, private_key_password) == false) { + EVLOG_error << "Unable to init tls"; + + local_data->update_state(EConnectionState::ERROR); + on_conn_fail(); + + // Notify conn waiter + conn_cv.notify_one(); + return; + } // Setup our context info.provided_client_ssl_ctx = ssl_ctx; @@ -518,6 +575,7 @@ void WebsocketTlsTPM::client_loop() { if (nullptr == lws_ctx) { EVLOG_error << "lws init failed!"; local_data->update_state(EConnectionState::FINALIZED); + return; } // Conn acquire the lws context diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 8f7804e5e5..4625291d08 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -2412,10 +2412,6 @@ void ChargePoint::handle_get_base_report_req(Call call) { GetBaseReportResponse response; response.status = GenericDeviceModelStatusEnum::Accepted; - if (msg.reportBase == ReportBaseEnum::SummaryInventory) { - response.status = GenericDeviceModelStatusEnum::NotSupported; - } - ocpp::CallResult call_result(response, call.uniqueId); this->send(call_result); diff --git a/lib/ocpp/v201/ctrlr_component_variables.cpp b/lib/ocpp/v201/ctrlr_component_variables.cpp index 577e1465ce..9070ec80ff 100644 --- a/lib/ocpp/v201/ctrlr_component_variables.cpp +++ b/lib/ocpp/v201/ctrlr_component_variables.cpp @@ -28,6 +28,13 @@ const Component& TariffCostCtrlr = {"TariffCostCtrlr"}; const Component& TxCtrlr = {"TxCtrlr"}; } // namespace ControllerComponents +namespace StandardizedVariables { +const Variable& Problem = {"Problem"}; +const Variable& Tripped = {"Tripped"}; +const Variable& Overload = {"Overload"}; +const Variable& Fallback = {"Fallback"}; +}; // namespace StandardizedVariables + namespace ControllerComponentVariables { const ComponentVariable& InternalCtrlrEnabled = { diff --git a/lib/ocpp/v201/device_model.cpp b/lib/ocpp/v201/device_model.cpp index 3fec38bd9b..86d43b527c 100644 --- a/lib/ocpp/v201/device_model.cpp +++ b/lib/ocpp/v201/device_model.cpp @@ -198,6 +198,24 @@ bool validate_value(const VariableCharacteristics& characteristics, const std::s } } +bool include_in_summary_inventory(const ComponentVariable& cv, const VariableAttribute& attribute) { + if (cv == ControllerComponentVariables::ChargingStationAvailabilityState) { + return true; + } + if (cv.component.name == "EVSE" and cv.variable == EvseComponentVariables::AvailabilityState) { + return true; + } + if (cv.component.name == "Connector" and cv.variable == ConnectorComponentVariables::AvailabilityState) { + return true; + } + if ((cv.variable == StandardizedVariables::Fallback or cv.variable == StandardizedVariables::Overload or + cv.variable == StandardizedVariables::Problem or cv.variable == StandardizedVariables::Tripped) and + attribute.value.value_or("") == "true") { + return true; + } + return false; +} + GetVariableStatusEnum DeviceModel::request_value_internal(const Component& component_id, const Variable& variable_id, const AttributeEnum& attribute_enum, std::string& value, bool allow_write_only) { @@ -327,14 +345,13 @@ std::vector DeviceModel::get_base_report_data(const ReportBaseEnum& report_data.component = component; report_data.variable = variable; + ComponentVariable cv = {component, std::nullopt, variable}; + // request the variable attribute from the device model storage const auto variable_attributes = this->storage->get_variable_attributes(component, variable); // iterate over possibly (Actual, Target, MinSet, MaxSet) for (const auto& variable_attribute : variable_attributes) { - // FIXME(piet): Right now this reports only FullInventory (ReadOnly, - // ReadWrite or WriteOnly) and ConfigurationInventory (ReadWrite or WriteOnly) correctly - // TODO(piet): SummaryInventory if (report_base == ReportBaseEnum::FullInventory or (report_base == ReportBaseEnum::ConfigurationInventory and (variable_attribute.mutability == MutabilityEnum::ReadWrite or @@ -345,6 +362,10 @@ std::vector DeviceModel::get_base_report_data(const ReportBaseEnum& report_data.variableAttribute.back().value.reset(); } report_data.variableCharacteristics = variable_map.at(variable).characteristics; + } else if (report_base == ReportBaseEnum::SummaryInventory) { + if (include_in_summary_inventory(cv, variable_attribute)) { + report_data.variableAttribute.push_back(variable_attribute); + } } } if (!report_data.variableAttribute.empty()) {