Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions include/iso15118/ev/d20/context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <iso15118/message/variant.hpp>

#include <iso15118/ev/d20/session.hpp>
#include <iso15118/io/sha_hash.hpp>

namespace iso15118::ev::d20 {

Expand Down Expand Up @@ -76,6 +77,26 @@ class Context {
session_stopped = stop;
}

bool is_session_stopped() {
return session_stopped;
}

void set_charger_cert_hash(std::optional<io::sha512_hash_t> hash) {
charger_cert_hash = hash;
}

auto get_charger_cert_hash() const {
return charger_cert_hash;
}

void set_charger_cert_session_hash(std::optional<io::sha512_hash_t> hash) {
charger_cert_session_hash = hash;
}

auto get_charger_cert_session_hash() const {
return charger_cert_session_hash;
}

message_20::datatypes::Identifier get_evcc_id() {
return evcc_id;
}
Expand All @@ -93,6 +114,10 @@ class Context {
Session session{std::array<uint8_t, Session::ID_LENGTH>{}};

bool session_stopped{false};

std::optional<io::sha512_hash_t> charger_cert_hash{std::nullopt};

std::optional<io::sha512_hash_t> charger_cert_session_hash{std::nullopt};
};

} // namespace iso15118::ev::d20
7 changes: 6 additions & 1 deletion include/iso15118/ev/d20/session.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ class Session {

public:
static constexpr auto ID_LENGTH = 8;
Session(std::array<uint8_t, ID_LENGTH> id_) : id(id_) {};
Session(std::array<uint8_t, ID_LENGTH> id_) : id(id_){};
~Session() = default;

std::array<uint8_t, ID_LENGTH> get_id() const {
return id;
}

// Sets the session ID, mostly for testing purposes
void set_id(const std::array<uint8_t, ID_LENGTH>& new_id) {
id = new_id;
}

private:
std::array<uint8_t, ID_LENGTH> id{};
};
Expand Down
5 changes: 3 additions & 2 deletions include/iso15118/ev/d20/state/authorization_setup.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@

namespace iso15118::ev::d20::state {

struct AuthorizationSetup : public StateBase{
struct AuthorizationSetup : public StateBase {
public:
AuthorizationSetup(Context& ctx) : StateBase(ctx, StateID::AuthorizationSetup) {}
AuthorizationSetup(Context& ctx) : StateBase(ctx, StateID::AuthorizationSetup) {
}

void enter() final;

Expand Down
19 changes: 19 additions & 0 deletions include/iso15118/ev/d20/state/dc_charge_parameter_discovery.hpp
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
5 changes: 3 additions & 2 deletions include/iso15118/ev/d20/state/session_setup.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@

namespace iso15118::ev::d20::state {

struct SessionSetup : public StateBase{
struct SessionSetup : public StateBase {
public:
SessionSetup(Context& ctx) : StateBase(ctx, StateID::SessionSetup) {}
SessionSetup(Context& ctx) : StateBase(ctx, StateID::SessionSetup) {
}

void enter() final;

Expand Down
6 changes: 3 additions & 3 deletions include/iso15118/ev/d20/states.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,17 @@ struct StateBase {
using ContainerType = BasePointerType;
using EventType = Event;

StateBase(Context& ctx, StateID id) : m_ctx(ctx), m_id(id) {};
StateBase(Context& ctx, StateID id) : m_ctx(ctx), m_id(id){};

virtual ~StateBase() = default;

StateID get_id() const {
return m_id;
}

virtual void enter() {};
virtual void enter(){};
virtual Result feed(Event) = 0;
virtual void leave() {};
virtual void leave(){};

protected:
Context& m_ctx;
Expand Down
1 change: 1 addition & 0 deletions src/iso15118/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ target_sources(iso15118
ev/d20/context_helper.cpp
ev/d20/state/session_setup.cpp
ev/d20/state/authorization_setup.cpp
ev/d20/state/dc_charge_parameter_discovery.cpp

io/connection_plain.cpp
io/logging.cpp
Expand Down
22 changes: 22 additions & 0 deletions src/iso15118/ev/d20/state/dc_charge_parameter_discovery.cpp
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
149 changes: 116 additions & 33 deletions src/iso15118/ev/d20/state/session_setup.cpp
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) {
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() {
Expand All @@ -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
Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn´t clear to me in the standard.
image
From this it appears that the session should be terminated.

Copy link
Member

Choose a reason for hiding this comment

The 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.
I think in this case the ev should start a new session. The charger should handle this.

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 {};
}
Expand Down
2 changes: 1 addition & 1 deletion test/iso15118/ev/fsm/helper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ using namespace iso15118;

class FsmStateHelper {
public:
FsmStateHelper() : ctx(msg_exch) {};
FsmStateHelper() : ctx(msg_exch){};

ev::d20::Context& get_context();

Expand Down
Loading