diff --git a/config/v16/profile_schemas/Security.json b/config/v16/profile_schemas/Security.json index 1316f309d..0ba9ba090 100644 --- a/config/v16/profile_schemas/Security.json +++ b/config/v16/profile_schemas/Security.json @@ -41,6 +41,12 @@ "maximum": 3, "description": "This configuration key is used to set the security profile used by the Charge Point", "readOnly": false + }, + "DisableSecurityEventNotifications": { + "type": "boolean", + "description": "When set to true, no SecurityEventNotification.req messages will be sent by the Charge Point", + "readOnly": false, + "default": false } } } diff --git a/include/ocpp/common/message_queue.hpp b/include/ocpp/common/message_queue.hpp index 13b7431b1..ea2232a3c 100644 --- a/include/ocpp/common/message_queue.hpp +++ b/include/ocpp/common/message_queue.hpp @@ -430,18 +430,25 @@ template class MessageQueue { MessageQueue(send_callback, config, {}, databaseHandler) { } - void get_transaction_messages_from_db() { + void get_transaction_messages_from_db(bool ignore_security_event_notifications = false) { std::vector transaction_messages = database_handler->get_transaction_messages(); if (!transaction_messages.empty()) { for (auto& transaction_message : transaction_messages) { - std::shared_ptr> message = - std::make_shared>(transaction_message.json_message); - message->messageType = string_to_messagetype(transaction_message.message_type); - message->timestamp = transaction_message.timestamp; - message->message_attempts = transaction_message.message_attempts; - transaction_message_queue.push_back(message); + + if (ignore_security_event_notifications && + transaction_message.message_type == "SecurityEventNotification") { + // remove from database in case SecurityEventNotification.req should not be sent + this->database_handler->remove_transaction_message(transaction_message.unique_id); + } else { + std::shared_ptr> message = + std::make_shared>(transaction_message.json_message); + message->messageType = string_to_messagetype(transaction_message.message_type); + message->timestamp = transaction_message.timestamp; + message->message_attempts = transaction_message.message_attempts; + transaction_message_queue.push_back(message); + } } this->new_message = true; diff --git a/include/ocpp/common/types.hpp b/include/ocpp/common/types.hpp index c085458b7..11568dfc2 100644 --- a/include/ocpp/common/types.hpp +++ b/include/ocpp/common/types.hpp @@ -593,6 +593,7 @@ inline const std::string INVALIDFIRMWARESIGNATURE = "InvalidFirmwareSignature"; inline const std::string INVALIDFIRMWARESIGNINGCERTIFICATE = "InvalidFirmwareSigningCertificate"; inline const std::string INVALIDCSMSCERTIFICATE = "InvalidCsmsCertificate"; inline const std::string INVALIDCHARGINGSTATIONCERTIFICATE = "InvalidChargingStationCertificate"; +inline const std::string INVALIDCHARGEPOINTCERTIFICATE = "InvalidChargePointCertificate"; // for OCPP1.6 inline const std::string INVALIDTLSVERSION = "InvalidTLSVersion"; inline const std::string INVALIDTLSCIPHERSUITE = "InvalidTLSCipherSuite"; } // namespace security_events diff --git a/include/ocpp/v16/charge_point.hpp b/include/ocpp/v16/charge_point.hpp index 8dac65e64..d5cbad4cd 100644 --- a/include/ocpp/v16/charge_point.hpp +++ b/include/ocpp/v16/charge_point.hpp @@ -73,15 +73,19 @@ class ChargePoint { /// \param connector_status_map initial state of connectors including connector 0 with reduced set of states /// (Available, Unavailable, Faulted). connector_status_map is empty, last availability states from the persistant /// storage will be used + /// \param bootreason reason for calling the start function /// \return - bool start(const std::map& connector_status_map = {}); + bool start(const std::map& connector_status_map = {}, + BootReasonEnum bootreason = BootReasonEnum::PowerUp); /// \brief Restarts the ChargePoint if it has been stopped before. The ChargePoint is reinitialized, connects to the /// websocket and starts to communicate OCPP messages again /// \param connector_status_map initial state of connectors including connector 0 with reduced set of states /// (Available, Unavailable, Faulted). connector_status_map is empty, last availability states from the persistant /// storage will be used - bool restart(const std::map& connector_status_map = {}); + /// \param bootreason reason for calling the start function + bool restart(const std::map& connector_status_map = {}, + BootReasonEnum bootreason = BootReasonEnum::ApplicationReset); // \brief Resets the internal state machine for the connectors using the given \p connector_status_map /// \param connector_status_map state of connectors including connector 0 with reduced set of states (Available, diff --git a/include/ocpp/v16/charge_point_configuration.hpp b/include/ocpp/v16/charge_point_configuration.hpp index d0bad8058..af2a13cfa 100644 --- a/include/ocpp/v16/charge_point_configuration.hpp +++ b/include/ocpp/v16/charge_point_configuration.hpp @@ -330,6 +330,11 @@ class ChargePointConfiguration { void setSecurityProfile(int32_t security_profile); KeyValue getSecurityProfileKeyValue(); + // // Security profile - optional with default + bool getDisableSecurityEventNotifications(); + void setDisableSecurityEventNotifications(bool disable_security_event_notifications); + KeyValue getDisableSecurityEventNotificationsKeyValue(); + // Local Auth List Management Profile bool getLocalAuthListEnabled(); void setLocalAuthListEnabled(bool local_auth_list_enabled); diff --git a/include/ocpp/v16/charge_point_impl.hpp b/include/ocpp/v16/charge_point_impl.hpp index 64273d848..f2dd6ca0d 100644 --- a/include/ocpp/v16/charge_point_impl.hpp +++ b/include/ocpp/v16/charge_point_impl.hpp @@ -86,6 +86,7 @@ namespace v16 { class ChargePointImpl : ocpp::ChargingStationBase { private: bool initialized; + BootReasonEnum bootreason; ChargePointConnectionState connection_state; bool boot_notification_callerror; RegistrationStatus registration_status; @@ -373,15 +374,18 @@ class ChargePointImpl : ocpp::ChargingStationBase { /// \brief Starts the ChargePoint, initializes and connects to the Websocket endpoint and initializes a /// BootNotification.req /// \param connector_status_map initial state of connectors including connector 0 with reduced set of states - /// (Available, Unavailable, Faulted) \return - bool start(const std::map& connector_status_map); + /// (Available, Unavailable, Faulted) + /// \param bootreason reason for calling the start function + /// \return + bool start(const std::map& connector_status_map, BootReasonEnum bootreason); /// \brief Restarts the ChargePoint if it has been stopped before. The ChargePoint is reinitialized, connects to the /// websocket and starts to communicate OCPP messages again /// \param connector_status_map initial state of connectors including connector 0 with reduced set of states /// (Available, Unavailable, Faulted). connector_status_map is empty, last availability states from the persistant /// storage will be used - bool restart(const std::map& connector_status_map); + /// \param bootreason reason for calling the restart function + bool restart(const std::map& connector_status_map, BootReasonEnum bootreason); /// \brief Resets the internal state machine for the connectors using the given \p connector_status_map /// \param connector_status_map state of connectors including connector 0 with reduced set of states (Available, diff --git a/include/ocpp/v16/types.hpp b/include/ocpp/v16/types.hpp index 18d5d8b45..0aebd2e7a 100644 --- a/include/ocpp/v16/types.hpp +++ b/include/ocpp/v16/types.hpp @@ -186,6 +186,19 @@ struct AvailabilityChange { bool persist; }; +/// \brief BootReasonEnum contains the different boot reasons of the charge point (copied from OCPP2.0.1 definition) +enum BootReasonEnum { + ApplicationReset, + FirmwareUpdate, + LocalReset, + PowerUp, + RemoteReset, + ScheduledReset, + Triggered, + Unknown, + Watchdog +}; + } // namespace v16 } // namespace ocpp diff --git a/lib/ocpp/v16/charge_point.cpp b/lib/ocpp/v16/charge_point.cpp index 7cf7303ef..363fd99ae 100644 --- a/lib/ocpp/v16/charge_point.cpp +++ b/lib/ocpp/v16/charge_point.cpp @@ -21,12 +21,12 @@ ChargePoint::ChargePoint(const std::string& config, const fs::path& share_path, ChargePoint::~ChargePoint() = default; -bool ChargePoint::start(const std::map& connector_status_map) { - return this->charge_point->start(connector_status_map); +bool ChargePoint::start(const std::map& connector_status_map, BootReasonEnum bootreason) { + return this->charge_point->start(connector_status_map, bootreason); } -bool ChargePoint::restart(const std::map& connector_status_map) { - return this->charge_point->restart(connector_status_map); +bool ChargePoint::restart(const std::map& connector_status_map, BootReasonEnum bootreason) { + return this->charge_point->restart(connector_status_map, bootreason); } bool ChargePoint::stop() { diff --git a/lib/ocpp/v16/charge_point_configuration.cpp b/lib/ocpp/v16/charge_point_configuration.cpp index a4f57b272..424508cc1 100644 --- a/lib/ocpp/v16/charge_point_configuration.cpp +++ b/lib/ocpp/v16/charge_point_configuration.cpp @@ -1719,6 +1719,23 @@ KeyValue ChargePointConfiguration::getSecurityProfileKeyValue() { return kv; } +bool ChargePointConfiguration::getDisableSecurityEventNotifications() { + return this->config["Security"]["DisableSecurityEventNotifications"]; +} + +void ChargePointConfiguration::setDisableSecurityEventNotifications(bool disable_security_event_notifications) { + this->config["Security"]["DisableSecurityEventNotifications"] = disable_security_event_notifications; + this->setInUserConfig("Security", "DisableSecurityEventNotifications", disable_security_event_notifications); +} + +KeyValue ChargePointConfiguration::getDisableSecurityEventNotificationsKeyValue() { + KeyValue kv; + kv.key = "DisableSecurityEventNotifications"; + kv.readonly = false; + kv.value.emplace(ocpp::conversions::bool_to_string(this->getDisableSecurityEventNotifications())); + return kv; +} + // Local Auth List Management Profile bool ChargePointConfiguration::getLocalAuthListEnabled() { if (this->config.contains("LocalAuthListManagement")) { @@ -2287,6 +2304,9 @@ std::optional ChargePointConfiguration::get(CiString<50> key) { if (key == "SecurityProfile") { return this->getSecurityProfileKeyValue(); } + if (key == "DisableSecurityEventNotifications") { + return this->getDisableSecurityEventNotificationsKeyValue(); + } if (key == "StopTransactionOnEVSideDisconnect") { return this->getStopTransactionOnEVSideDisconnectKeyValue(); } @@ -2530,6 +2550,9 @@ ConfigurationStatus ChargePointConfiguration::set(CiString<50> key, CiString<500 if (key == "CpoName") { this->setCpoName(value.get()); } + if (key == "DisableSecurityEventNotifications") { + this->setDisableSecurityEventNotifications(ocpp::conversions::string_to_bool(value.get())); + } if (key == "HeartbeatInterval") { try { auto [valid, interval] = is_positive_integer(value.get()); diff --git a/lib/ocpp/v16/charge_point_impl.cpp b/lib/ocpp/v16/charge_point_impl.cpp index f4794003d..cf78527a1 100644 --- a/lib/ocpp/v16/charge_point_impl.cpp +++ b/lib/ocpp/v16/charge_point_impl.cpp @@ -28,6 +28,7 @@ ChargePointImpl::ChargePointImpl(const std::string& config, const fs::path& shar ocpp::ChargingStationBase(evse_security, security_configuration), boot_notification_callerror(false), initialized(false), + bootreason(BootReasonEnum::PowerUp), connection_state(ChargePointConnectionState::Disconnected), registration_status(RegistrationStatus::Pending), diagnostics_status(DiagnosticsStatus::Idle), @@ -789,7 +790,8 @@ void ChargePointImpl::send_meter_value(int32_t connector, MeterValue meter_value this->send(call, initiated_by_trigger_message); } -bool ChargePointImpl::start(const std::map& connector_status_map) { +bool ChargePointImpl::start(const std::map& connector_status_map, BootReasonEnum bootreason) { + this->bootreason = bootreason; this->init_state_machine(connector_status_map); this->init_websocket(); this->websocket->connect(); @@ -800,7 +802,7 @@ bool ChargePointImpl::start(const std::map& connector_st return true; } -bool ChargePointImpl::restart(const std::map& connector_status_map) { +bool ChargePointImpl::restart(const std::map& connector_status_map, BootReasonEnum bootreason) { if (this->stopped) { EVLOG_info << "Restarting OCPP Chargepoint"; this->database_handler->open_db_connection(this->configuration->getNumberOfConnectors()); @@ -808,7 +810,7 @@ bool ChargePointImpl::restart(const std::map& connector_ this->message_queue = this->create_message_queue(); this->initialized = true; - return this->start(connector_status_map); + return this->start(connector_status_map, bootreason); } else { EVLOG_warning << "Attempting to restart Chargepoint while it has not been stopped before"; return false; @@ -1013,6 +1015,7 @@ void ChargePointImpl::message_callback(const std::string& message) { auto call_error = CallError(MessageId(json_message.at(MESSAGE_ID).get()), "FormationViolation", e.what(), json({}, true)); this->send(call_error); + this->securityEventNotification(ocpp::security_events::INVALIDMESSAGES, message, true); } } } @@ -1182,7 +1185,9 @@ void ChargePointImpl::handleBootNotificationResponse(ocpp::CallResultmessage_queue->get_transaction_messages_from_db(); + // push transaction messages including SecurityEventNotification.req onto the message queue + this->message_queue->get_transaction_messages_from_db( + this->configuration->getDisableSecurityEventNotifications()); if (this->is_pnc_enabled()) { this->ocsp_request_timer->timeout(INITIAL_CERTIFICATE_REQUESTS_DELAY); @@ -1198,6 +1203,17 @@ void ChargePointImpl::handleBootNotificationResponse(ocpp::CallResultocsp_request_timer->timeout(INITIAL_CERTIFICATE_REQUESTS_DELAY); } + if (this->bootreason == BootReasonEnum::RemoteReset) { + this->securityEventNotification(CiString<50>(ocpp::security_events::RESET_OR_REBOOT), + "Charging Station rebooted due to requested remote reset!", true); + } else if (this->bootreason == BootReasonEnum::ScheduledReset) { + this->securityEventNotification(CiString<50>(ocpp::security_events::RESET_OR_REBOOT), + "Charging Station rebooted due to a scheduled reset!", true); + } else if (this->bootreason == BootReasonEnum::PowerUp) { + this->securityEventNotification(CiString<50>(ocpp::security_events::STARTUP_OF_THE_DEVICE), + "The Charge Point has booted", true); + } + this->stop_pending_transactions(); break; @@ -2229,7 +2245,7 @@ void ChargePointImpl::handleCertificateSignedRequest(ocpp::Callsend(call_result); if (response.status == CertificateSignedStatusEnumType::Rejected) { - this->securityEventNotification("InvalidChargePointCertificate", + this->securityEventNotification(ocpp::security_events::INVALIDCHARGEPOINTCERTIFICATE, ocpp::conversions::install_certificate_result_to_string(result), true); } @@ -2312,7 +2328,7 @@ void ChargePointImpl::handleInstallCertificateRequest(ocpp::Callsend(call_result); if (response.status == InstallCertificateStatusEnumType::Rejected) { - this->securityEventNotification("InvalidCentralSystemCertificate", + this->securityEventNotification(ocpp::security_events::INVALIDCSMSCERTIFICATE, ocpp::conversions::install_certificate_result_to_string(result), true); } } @@ -2348,7 +2364,8 @@ void ChargePointImpl::handleSignedUpdateFirmware(ocpp::CallsecurityEventNotification("InvalidFirmwareSigningCertificate", "Certificate is invalid.", true); + this->securityEventNotification(ocpp::security_events::INVALIDFIRMWARESIGNINGCERTIFICATE, + "Certificate is invalid.", true); } } @@ -2362,8 +2379,10 @@ void ChargePointImpl::securityEventNotification(const std::string& type, const s this->logging->security(json(req).dump()); - ocpp::Call call(req, this->message_queue->createMessageId()); - this->send(call); + if (!this->configuration->getDisableSecurityEventNotifications()) { + ocpp::Call call(req, this->message_queue->createMessageId()); + this->send(call); + } if (triggered_internally and this->security_event_callback != nullptr) { this->security_event_callback(type, tech_info); @@ -2413,7 +2432,7 @@ void ChargePointImpl::signed_firmware_update_status_notification(FirmwareStatusE this->send(call, initiated_by_trigger_message); if (status == FirmwareStatusEnumType::InvalidSignature) { - this->securityEventNotification("InvalidFirmwareSignature", "", true); + this->securityEventNotification(ocpp::security_events::INVALIDFIRMWARESIGNATURE, "", true); } if (this->firmware_update_is_pending) { @@ -2957,7 +2976,7 @@ void ChargePointImpl::handle_data_transfer_pnc_certificate_signed(Callsend(call_result); if (certificate_response.status == CertificateSignedStatusEnumType::Rejected) { - this->securityEventNotification("InvalidChargePointCertificate", tech_info, true); + this->securityEventNotification(ocpp::security_events::INVALIDCHARGEPOINTCERTIFICATE, tech_info, true); } } catch (const json::exception& e) { EVLOG_warning << "Could not parse data of DataTransfer message CertificateSigned.req: " << e.what(); @@ -3424,6 +3443,10 @@ void ChargePointImpl::on_firmware_update_status_notification(int32_t request_id, } catch (const std::out_of_range& e) { EVLOG_debug << "Could not convert incoming FirmwareStatusNotification to OCPP type"; } + + if (firmware_update_status == FirmwareStatusNotification::Installed) { + this->securityEventNotification(ocpp::security_events::FIRMWARE_UPDATED, "Firmware update was installed", true); + } } void ChargePointImpl::diagnostic_status_notification(DiagnosticsStatus status) {