From 4d10b1234e6298f8f0b9d2606fedaa3d46ad6aa6 Mon Sep 17 00:00:00 2001 From: pietfried Date: Mon, 17 Jul 2023 15:49:07 +0200 Subject: [PATCH] - Added handling for ClearCache.req - Improved updating and deleting of cache entries - implemented validate_token with linear flow Signed-off-by: pietfried --- include/ocpp/v201/charge_point.hpp | 6 +- include/ocpp/v201/database_handler.hpp | 9 ++ lib/ocpp/v201/charge_point.cpp | 141 +++++++++++++++++-------- lib/ocpp/v201/database_handler.cpp | 34 ++++++ 4 files changed, 143 insertions(+), 47 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index a7e0713b0..ff9a9168f 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -180,9 +181,12 @@ class ChargePoint : ocpp::ChargingStationBase { void handle_get_report_req(Call call); void handle_reset_req(Call call); + // Functional Block C: Authorization + void handle_clear_cache_req(Call call); + // Functional Block E: Transaction void handle_start_transaction_event_response(CallResult call_result, - const int32_t evse_id); + const int32_t evse_id, const std::string &id_token); // Function Block F: Remote transaction control void handle_unlock_connector(Call call); diff --git a/include/ocpp/v201/database_handler.hpp b/include/ocpp/v201/database_handler.hpp index c97376aa1..3494ba42e 100644 --- a/include/ocpp/v201/database_handler.hpp +++ b/include/ocpp/v201/database_handler.hpp @@ -25,6 +25,7 @@ class DatabaseHandler { fs::path sql_init_path; void sql_init(); + bool clear_table(const std::string& table_name); public: DatabaseHandler(const fs::path& database_path, const fs::path& sql_init_path); @@ -46,6 +47,14 @@ class DatabaseHandler { /// \return std::optional get_auth_cache_entry(const std::string& id_token_hash); + /// \brief Deletes the cache entry for the given \p id_token_hash + /// \param id_token_hash + void delete_auth_cache_entry(const std::string& id_token_hash); + + /// \brief Deletes all entries of the AUTH_CACHE table. Returns true if the operation was successful, else false + /// \return + bool clear_authorization_cache(); + void insert_availability(const int32_t evse_id, std::optional connector_id, const OperationalStatusEnum& operational_status, const bool replace); diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 9847fb29e..c66278ac2 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -181,47 +181,53 @@ AuthorizeResponse ChargePoint::validate_token(const IdToken id_token, const std: // TODO(piet): C01.FR.16 // TODO(piet): C01.FR.17 - // TODO(piet): C10.FR.05 // TODO(piet): C10.FR.06 // TODO(piet): C10.FR.07 - // TODO(piet): C10.FR.09 AuthorizeResponse response; IdTokenInfo id_token_info; - // C01.FR.01 - if (this->device_model->get_optional_value(ControllerComponentVariables::AuthCtrlrEnabled).value_or(true)) { - if (this->device_model->get_optional_value(ControllerComponentVariables::AuthCacheCtrlrEnabled) - .value_or(true)) { - const auto cache_entry = - this->database_handler->get_auth_cache_entry(utils::sha256(id_token.idToken.get())); - if (cache_entry.has_value() and - this->device_model->get_value(ControllerComponentVariables::LocalPreAuthorize) and - cache_entry.value().status == AuthorizationStatusEnum::Accepted and - (!cache_entry.value().cacheExpiryDateTime.has_value() or - cache_entry.value().cacheExpiryDateTime.value().to_time_point() > DateTime().to_time_point())) { - response.idTokenInfo = cache_entry.value(); - return response; - } - } - // C01.FR.02 + if (!this->device_model->get_optional_value(ControllerComponentVariables::AuthCtrlrEnabled).value_or(true)) { + id_token_info.status = AuthorizationStatusEnum::Accepted; + response.idTokenInfo = id_token_info; + return response; + } + + if (!this->device_model->get_optional_value(ControllerComponentVariables::AuthCacheCtrlrEnabled) + .value_or(true)) { + EVLOG_info << "AuthCache not enabled: Sending Authorize.req"; + return this->authorize_req(id_token, certificate, ocsp_request_data); + } + + const auto hashed_id_token = utils::sha256(id_token.idToken.get()); + const auto cache_entry = this->database_handler->get_auth_cache_entry(hashed_id_token); + + if (!cache_entry.has_value()) { + EVLOG_info << "AuthCache enabled but not entry found: Sending Authorize.req"; response = this->authorize_req(id_token, certificate, ocsp_request_data); - // C10.FR.08 - if (!response.idTokenInfo.cacheExpiryDateTime.has_value() and - this->device_model->get_optional_value(ControllerComponentVariables::AuthCacheLifeTime).has_value()) { - // when CSMS does not set cacheExpiryDateTime and config variable for AuthCacheLifeTime is present - response.idTokenInfo.cacheExpiryDateTime = - DateTime(date::utc_clock::now() + std::chrono::seconds(this->device_model->get_value( - ControllerComponentVariables::AuthCacheLifeTime))); - } this->database_handler->insert_auth_cache_entry(utils::sha256(id_token.idToken.get()), response.idTokenInfo); return response; + } - } else { - id_token_info.status = AuthorizationStatusEnum::Accepted; - response.idTokenInfo = id_token_info; + if ((cache_entry.value().cacheExpiryDateTime.has_value() and + cache_entry.value().cacheExpiryDateTime.value().to_time_point() < DateTime().to_time_point())) { + EVLOG_info << "Entry found in AuthCache but cacheExpiryDate exceeded: Sending Authorize.req"; + this->database_handler->delete_auth_cache_entry(hashed_id_token); + response = this->authorize_req(id_token, certificate, ocsp_request_data); + this->database_handler->insert_auth_cache_entry(utils::sha256(id_token.idToken.get()), response.idTokenInfo); return response; } + + if (this->device_model->get_value(ControllerComponentVariables::LocalPreAuthorize) and + cache_entry.value().status == AuthorizationStatusEnum::Accepted) { + EVLOG_info << "Found valid entry in AuthCache"; + response.idTokenInfo = cache_entry.value(); + return response; + } + + response = this->authorize_req(id_token, certificate, ocsp_request_data); + this->database_handler->insert_auth_cache_entry(utils::sha256(id_token.idToken.get()), response.idTokenInfo); + return response; } void ChargePoint::on_event(const std::vector& events) { @@ -326,6 +332,9 @@ void ChargePoint::handle_message(const json& json_message, const MessageType& me case MessageType::GetLog: this->handle_get_log_req(json_message); break; + case MessageType::ClearCache: + this->handle_clear_cache_req(json_message); + break; } } @@ -546,14 +555,27 @@ AuthorizeResponse ChargePoint::authorize_req(const IdToken id_token, const std:: auto future = this->send_async(call); const auto enhanced_message = future.get(); - if (enhanced_message.messageType == MessageType::AuthorizeResponse) { - ocpp::CallResult call_result = enhanced_message.message; - return call_result.msg; - } else { - AuthorizeResponse response; + AuthorizeResponse response; + + if (enhanced_message.messageType != MessageType::AuthorizeResponse) { response.idTokenInfo.status = AuthorizationStatusEnum::Unknown; return response; } + + ocpp::CallResult call_result = enhanced_message.message; + if (response.idTokenInfo.cacheExpiryDateTime.has_value() or + !this->device_model->get_optional_value(ControllerComponentVariables::AuthCacheLifeTime).has_value()) { + return call_result.msg; + } + + // C10.FR.08 + // when CSMS does not set cacheExpiryDateTime and config variable for AuthCacheLifeTime is present use the + // configured AuthCacheLifeTime + response = call_result.msg; + response.idTokenInfo.cacheExpiryDateTime = DateTime( + date::utc_clock::now() + + std::chrono::seconds(this->device_model->get_value(ControllerComponentVariables::AuthCacheLifeTime))); + return response; } void ChargePoint::status_notification_req(const int32_t evse_id, const int32_t connector_id, @@ -602,10 +624,16 @@ void ChargePoint::transaction_event_req(const TransactionEventEnum& event_type, ocpp::Call call(req, this->message_queue->createMessageId()); if (event_type == TransactionEventEnum::Started) { + if (!evse.has_value() or !id_token.has_value()) { + EVLOG_error << "Request to send TransactionEvent(Started) without given evse or id_token. These properties " + "are required for this eventType \"Started\"!"; + return; + } auto future = this->send_async(call); const auto enhanced_message = future.get(); if (enhanced_message.messageType == MessageType::TransactionEventResponse) { - this->handle_start_transaction_event_response(enhanced_message.message, evse.value().id); + this->handle_start_transaction_event_response(enhanced_message.message, evse.value().id, + id_token.value().idToken.get()); } else if (enhanced_message.offline) { // TODO(piet): offline handling } @@ -819,20 +847,41 @@ void ChargePoint::handle_reset_req(Call call) { } } +void ChargePoint::handle_clear_cache_req(Call call) { + ClearCacheResponse response; + response.status = ClearCacheStatusEnum::Rejected; + + if (this->device_model->get_optional_value(ControllerComponentVariables::AuthCacheCtrlrEnabled) + .value_or(true) and + this->database_handler->clear_authorization_cache()) { + response.status = ClearCacheStatusEnum::Accepted; + } + + ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); +} + void ChargePoint::handle_start_transaction_event_response(CallResult call_result, - const int32_t evse_id) { + const int32_t evse_id, const std::string& id_token) { const auto msg = call_result.msg; - if (msg.idTokenInfo.has_value() and msg.idTokenInfo.value().status != AuthorizationStatusEnum::Accepted) { - if (this->device_model->get_value(ControllerComponentVariables::StopTxOnInvalidId)) { - this->callbacks.stop_transaction_callback(evse_id, ReasonEnum::DeAuthorized); - } else { - if (this->device_model->get_optional_value(ControllerComponentVariables::MaxEnergyOnInvalidId) - .has_value()) { - // TODO(piet): E05.FR.03 - // Energy delivery to the EV SHALL be allowed until the amount of energy specified in - // MaxEnergyOnInvalidId has been reached. + if (msg.idTokenInfo.has_value()) { + // C10.FR.05 + if (this->device_model->get_optional_value(ControllerComponentVariables::AuthCacheCtrlrEnabled) + .value_or(true)) { + this->database_handler->insert_auth_cache_entry(utils::sha256(id_token), msg.idTokenInfo.value()); + } + if (msg.idTokenInfo.value().status != AuthorizationStatusEnum::Accepted) { + if (this->device_model->get_value(ControllerComponentVariables::StopTxOnInvalidId)) { + this->callbacks.stop_transaction_callback(evse_id, ReasonEnum::DeAuthorized); } else { - this->callbacks.pause_charging_callback(evse_id); + if (this->device_model->get_optional_value(ControllerComponentVariables::MaxEnergyOnInvalidId) + .has_value()) { + // TODO(piet): E05.FR.03 + // Energy delivery to the EV SHALL be allowed until the amount of energy specified in + // MaxEnergyOnInvalidId has been reached. + } else { + this->callbacks.pause_charging_callback(evse_id); + } } } } diff --git a/lib/ocpp/v201/database_handler.cpp b/lib/ocpp/v201/database_handler.cpp index 2e846530e..c6401e806 100644 --- a/lib/ocpp/v201/database_handler.cpp +++ b/lib/ocpp/v201/database_handler.cpp @@ -39,6 +39,15 @@ void DatabaseHandler::sql_init() { } } +bool DatabaseHandler::clear_table(const std::string& table_name) { + char* err_msg = 0; + std::string sql = "DELETE FROM " + table_name + ";"; + if (sqlite3_exec(this->db, sql.c_str(), NULL, NULL, &err_msg) != SQLITE_OK) { + return false; + } + return true; +} + void DatabaseHandler::open_connection() { if (sqlite3_open(this->database_file_path.c_str(), &this->db) != SQLITE_OK) { EVLOG_error << "Error opening database at " << this->database_file_path.c_str() << ": " << sqlite3_errmsg(db); @@ -105,6 +114,31 @@ std::optional DatabaseHandler::get_auth_cache_entry(const std::stri } } +void DatabaseHandler::delete_auth_cache_entry(const std::string& id_token_hash) { + try { + std::string sql = "DELETE FROM AUTH_CACHE WHERE ID_TOKEN_HASH = @id_token_hash"; + sqlite3_stmt* stmt; + if (sqlite3_prepare_v2(this->db, sql.c_str(), sql.size(), &stmt, NULL) != SQLITE_OK) { + EVLOG_error << "Could not prepare insert statement: " << sqlite3_errmsg(this->db); + return; + } + + sqlite3_bind_text(stmt, 1, id_token_hash.c_str(), id_token_hash.length(), NULL); + if (sqlite3_step(stmt) != SQLITE_DONE) { + EVLOG_error << "Could not delete from table: " << sqlite3_errmsg(this->db); + } + if (sqlite3_finalize(stmt) != SQLITE_OK) { + EVLOG_error << "Error deleting from table: " << sqlite3_errmsg(this->db); + } + } catch (const std::exception& e) { + EVLOG_error << "Exception while deleting from auth cache table: " << e.what(); + } +} + +bool DatabaseHandler::clear_authorization_cache() { + return this->clear_table("AUTH_CACHE"); +} + void DatabaseHandler::insert_availability(const int32_t evse_id, std::optional connector_id, const OperationalStatusEnum& operational_status, const bool replace) { std::string sql = "INSERT OR REPLACE INTO AVAILABILITY (EVSE_ID, CONNECTOR_ID, OPERATIONAL_STATUS) VALUES "