-
Notifications
You must be signed in to change notification settings - Fork 16
Add SessionSetup to EV side state machine #155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/adding-ev-d20
Are you sure you want to change the base?
Changes from 3 commits
6232f3c
c9f5d89
ec5c657
5374730
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // Copyright 2025 Pionix GmbH and Contributors to EVerest | ||
| #pragma once | ||
|
|
||
| #include "../states.hpp" | ||
|
|
||
| namespace iso15118::ev::d20::state { | ||
|
|
||
| struct DC_ChargeParameterDiscovery : public StateBase { | ||
| public: | ||
| DC_ChargeParameterDiscovery(Context& ctx) : StateBase(ctx, StateID::DC_ChargeParameterDiscovery) { | ||
| } | ||
|
|
||
| void enter() final; | ||
|
|
||
| Result feed(Event) final; | ||
| }; | ||
|
|
||
| } // namespace iso15118::ev::d20::state |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // Copyright 2025 Pionix GmbH and Contributors to EVerest | ||
| #include <iso15118/detail/helper.hpp> | ||
| #include <iso15118/ev/d20/state/dc_charge_parameter_discovery.hpp> | ||
| #include <iso15118/ev/detail/d20/context_helper.hpp> | ||
| #include <iso15118/message/dc_charge_parameter_discovery.hpp> | ||
|
|
||
| namespace iso15118::ev::d20::state { | ||
|
|
||
| void DC_ChargeParameterDiscovery::enter() { | ||
| // TODO(SL): Adding logging | ||
| } | ||
|
|
||
| Result DC_ChargeParameterDiscovery::feed(Event ev) { | ||
| if (ev != Event::V2GTP_MESSAGE) { | ||
| return {}; | ||
| } else { | ||
| return {}; | ||
| } | ||
| } | ||
|
|
||
| } // namespace iso15118::ev::d20::state | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,36 +1,62 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // Copyright 2025 Pionix GmbH and Contributors to EVerest | ||
| #include <iso15118/ev/d20/state/session_setup.hpp> | ||
|
|
||
| #include <optional> | ||
|
|
||
| #include <iso15118/ev/d20/state/authorization_setup.hpp> | ||
| #include <iso15118/message/session_setup.hpp> | ||
| #include <iso15118/message/supported_app_protocol.hpp> | ||
|
|
||
| // Copyright 2025 Pionix GmbH, Roger Bedell, and Contributors to EVerest | ||
| #include <algorithm> | ||
| #include <iomanip> | ||
| #include <iso15118/detail/helper.hpp> | ||
| #include <iso15118/ev/d20/context.hpp> | ||
| #include <iso15118/ev/d20/state/authorization_setup.hpp> | ||
| #include <iso15118/ev/d20/state/dc_charge_parameter_discovery.hpp> | ||
| #include <iso15118/ev/d20/state/session_setup.hpp> | ||
| #include <iso15118/ev/detail/d20/context_helper.hpp> | ||
| #include <iso15118/io/sha_hash.hpp> | ||
| #include <iso15118/message/authorization_setup.hpp> | ||
| #include <iso15118/message/dc_charge_parameter_discovery.hpp> | ||
| #include <iso15118/message/session_setup.hpp> | ||
| #include <openssl/evp.h> | ||
| #include <optional> | ||
| #include <sstream> | ||
|
|
||
| namespace iso15118::ev::d20::state { | ||
|
|
||
| namespace { | ||
| using ResponseCode = message_20::SupportedAppProtocolResponse::ResponseCode; | ||
| using ResponseCode = message_20::datatypes::ResponseCode; | ||
| bool check_response_code(ResponseCode response_code) { | ||
| switch (response_code) { | ||
| case ResponseCode::OK_SuccessfulNegotiation: | ||
| case ResponseCode::OK_NewSessionEstablished: | ||
| return true; | ||
| case ResponseCode::OK_OldSessionJoined: | ||
| return true; | ||
| case ResponseCode::OK_SuccessfulNegotiationWithMinorDeviation: | ||
| logf_error("Evse has sent SupportedAppProtocolResponse with a ResponseCode: " | ||
| "OK_SuccessfulNegotiationWithMinorDeviation. In 15118-20 this is not allowed!"); | ||
| return false; | ||
| case ResponseCode::Failed_NoNegotiation: | ||
| logf_error("Evse has sent SupportedAppProtocolResponse with a ResponseCode: " | ||
| "Failed_NoNegotiation"); | ||
| [[fallthrough]]; | ||
| default: | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| bool session_is_zero(const message_20::datatypes::SessionId& session_id) { | ||
SebaLukas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return std::all_of(session_id.begin(), session_id.end(), [](int i) { return i == 0; }); | ||
| } | ||
|
|
||
| io::sha512_hash_t calculate_new_cert_session_id_hash(const io::sha512_hash_t& charger_cert_hash, | ||
| const message_20::datatypes::SessionId& session_id) { | ||
| io::sha512_hash_t session_id_charger_hash{}; | ||
| std::array<std::uint8_t, 64 + 8> concatenated_session_id_charger{}; | ||
|
|
||
| std::copy(session_id.begin(), session_id.end(), concatenated_session_id_charger.begin()); | ||
| std::copy(charger_cert_hash.begin(), charger_cert_hash.end(), | ||
| concatenated_session_id_charger.begin() + session_id.size()); | ||
|
|
||
| unsigned int digestlen{0}; | ||
|
|
||
| const auto result = EVP_Digest(concatenated_session_id_charger.data(), concatenated_session_id_charger.size(), | ||
| session_id_charger_hash.data(), &digestlen, EVP_sha512(), nullptr); | ||
| if (not result) { | ||
| logf_error("X509_digest failed"); | ||
| return std::array<std::uint8_t, 64>{}; | ||
| } | ||
|
|
||
| return session_id_charger_hash; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| void SessionSetup::enter() { | ||
|
|
@@ -43,34 +69,91 @@ Result SessionSetup::feed(Event ev) { | |
| } | ||
|
|
||
| const auto variant = m_ctx.pull_response(); | ||
| bool session_resumed = false; | ||
|
|
||
| if (const auto res = variant->get_if<message_20::SupportedAppProtocolResponse>()) { | ||
| if (const auto res = variant->get_if<message_20::SessionSetupResponse>()) { | ||
|
|
||
| if (not check_response_code(res->response_code)) { | ||
| m_ctx.stop_session(true); // Tell stack to close the tcp/tls connection | ||
| return {}; | ||
| } | ||
|
|
||
| if (not res->schema_id.has_value()) { | ||
| logf_error( | ||
| "SupportedAppProtocolRes should have a SchemaId. This is here not the case! Abort the session."); | ||
| if (res->evseid.size() <= 0) { | ||
| logf_error("EVSEID is empty. Abort the session."); | ||
| m_ctx.stop_session(true); // Tell stack to close the tcp/tls connection | ||
| return {}; | ||
| } else { | ||
| // m_ctx.evse_info.evse_id = res->evseid; | ||
| } | ||
|
|
||
| // TODO(SL): Check schema id and select the correct schema | ||
|
|
||
| // 2. Create SessionSetupReq | ||
| message_20::SessionSetupRequest req; | ||
|
|
||
| setup_header(req.header, m_ctx.get_session()); | ||
| req.evccid = m_ctx.get_evcc_id(); | ||
|
|
||
| m_ctx.respond(req); | ||
| // TODO(RB): Check if the returned sessionid is ok by checking against the sent one. | ||
| // If the sent one was 0, the new one should be non zero. | ||
| const auto charger_cert_hash = m_ctx.get_charger_cert_hash(); | ||
|
|
||
| if (session_is_zero(m_ctx.get_session().get_id())) { | ||
| if (session_is_zero(res->header.session_id)) { | ||
| logf_error("Returned SessionID is zero although a new session was requested. Abort the session."); | ||
| m_ctx.stop_session(true); // Tell stack to close the tcp/tls connection | ||
| return {}; | ||
| } else { | ||
| // New session established successfully | ||
| m_ctx.get_session().set_id(res->header.session_id); | ||
|
|
||
| // Save the charger cert hash and the calculated hash of charger cert and session id for later use | ||
| if (charger_cert_hash.has_value()) { | ||
| m_ctx.set_charger_cert_hash(charger_cert_hash.value()); | ||
| const auto new_charger_cert_session_hash = | ||
| calculate_new_cert_session_id_hash(charger_cert_hash.value(), res->header.session_id); | ||
| m_ctx.set_charger_cert_session_hash(new_charger_cert_session_hash); | ||
| } else { | ||
| logf_warning("No charger certificate hash available although a new session was established."); | ||
| } | ||
| } | ||
| } else { | ||
| // If the sent one was non 0, the returned one should be the same, so we are doing a resume. In order to | ||
| // proceed it needs to be verified that the pairing of charger and ev is the same as before by checking a | ||
| // hash of the charger cert and the session id that was saved when the session was paused with the new hash | ||
| // calculated from the returned session id and the charger cert hash. If they are not the same, this is an | ||
| // error, and the session must be shut down. | ||
| // Make sure we have a charger cert hash to check against | ||
| if (not charger_cert_hash.has_value()) { | ||
| logf_error( | ||
| "No charger certificate hash available although an old session was resumed. Abort the session."); | ||
| m_ctx.stop_session(true); // Tell stack to close the tcp/tls connection | ||
| return {}; | ||
| } | ||
|
Comment on lines
+122
to
+127
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case the ev can also go to AuthSetup and treat this as a new session
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess the standard mean terminating the old session. And starting a new one. |
||
| const auto new_charger_cert_session_hash = | ||
| calculate_new_cert_session_id_hash(charger_cert_hash.value(), res->header.session_id); | ||
| if (m_ctx.get_charger_cert_session_hash() != new_charger_cert_session_hash) { | ||
| logf_error("Charger certificate/session hash does not match the saved one although an old session was " | ||
| "resumed. Abort the session."); | ||
| m_ctx.stop_session(true); // Tell stack to close the tcp/tls connection | ||
| return {}; | ||
| } | ||
| // If we reach this point, the session has been successfully resumed | ||
| logf_info("Session resumed successfully."); | ||
|
|
||
| session_resumed = true; | ||
| } | ||
|
|
||
| return {m_ctx.create_state<AuthorizationSetup>()}; | ||
| // if the session is resumed, then no authorization setup or authorization needs to be done, we continue with | ||
| // what was already in progress in the context. | ||
| if (session_resumed) { | ||
| logf_info("Session is resumed, continuing with the existing context."); | ||
| // RDB TODO Go directly to either AC or DC ChargeParameterDiscovery | ||
| message_20::DC_ChargeParameterDiscoveryRequest req; | ||
| setup_header(req.header, m_ctx.get_session()); | ||
| m_ctx.respond(req); | ||
| // The DC_ChargeParameterDiscovery state handles the DC_ChargeParameterDiscoveryResponse | ||
| return {m_ctx.create_state<DC_ChargeParameterDiscovery>()}; | ||
| } else { | ||
| message_20::AuthorizationSetupRequest req; | ||
| setup_header(req.header, m_ctx.get_session()); | ||
| m_ctx.respond(req); | ||
| return {m_ctx.create_state<AuthorizationSetup>()}; | ||
| } | ||
| } else { | ||
| logf_error("expected SupportedAppProtocol! But code type id: %d", variant->get_type()); | ||
| logf_error("expected SessionSetupResponse! But code type id: %d", variant->get_type()); | ||
| m_ctx.stop_session(true); // Tell stack to close the tcp/tls connection | ||
| return {}; | ||
| } | ||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.