Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,56 @@
#
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
# template version 3
#
# module setup:
# - ${MODULE_NAME}: module name
ev_setup_cpp_module()
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
# insert your custom targets and additional config variables here
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"charger/ISO15118_chargerImpl.cpp"
"extensions/iso15118_extensionsImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# Add pkg-config functionality
find_package(PkgConfig REQUIRED)
target_include_directories(${MODULE_NAME} PRIVATE
crypto
connection
)
target_link_libraries(${MODULE_NAME} PUBLIC -lpthread)
target_link_libraries(${MODULE_NAME}
PRIVATE
cbv2g::din
cbv2g::iso2
cbv2g::tp
)
target_sources(${MODULE_NAME}
PRIVATE
"connection/connection.cpp"
"log.cpp"
"sdp.cpp"
"tools.cpp"
"v2g_ctx.cpp"
"v2g_server.cpp"
)
target_link_libraries(${MODULE_NAME}
PRIVATE
everest::tls
)
target_sources(${MODULE_NAME}
PRIVATE
"connection/tls_connection.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,126 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2022-2023 chargebyte GmbH
// Copyright (C) 2022-2023 Contributors to EVerest
#include "IsoMux.hpp"
#include "connection.hpp"
#include "connection/tls_connection.hpp"
#include "log.hpp"
#include "sdp.hpp"
#include <everest/tls/openssl_util.hpp>
namespace {
void log_handler(openssl::log_level_t level, const std::string& str) {
switch (level) {
case openssl::log_level_t::debug:
// ignore debug logs
break;
case openssl::log_level_t::info:
EVLOG_info << str;
break;
case openssl::log_level_t::warning:
EVLOG_warning << str;
break;
case openssl::log_level_t::error:
default:
EVLOG_error << str;
break;
}
}
} // namespace
struct v2g_context* v2g_ctx = nullptr;
namespace module {
void IsoMux::init() {
/* create v2g context */
v2g_ctx = v2g_ctx_create(&(*r_security));
if (v2g_ctx == nullptr)
return;
v2g_ctx->proxy_port_iso2 = config.proxy_port_iso2;
v2g_ctx->proxy_port_iso20 = config.proxy_port_iso20;
v2g_ctx->selected_iso20 = false;
v2g_ctx->iso20_proxy_enabled = true;
v2g_ctx->tls_key_logging = config.tls_key_logging;
(void)openssl::set_log_handler(log_handler);
v2g_ctx->tls_server = &tls_server;
this->r_security->subscribe_certificate_store_update(
[this](const types::evse_security::CertificateStoreUpdate& update) {
if (!update.leaf_certificate_type.has_value()) {
return;
}
if (update.leaf_certificate_type.value() != types::evse_security::LeafCertificateType::V2G) {
return;
}
dlog(DLOG_LEVEL_INFO, "Certificate store update received, reconfiguring TLS server");
auto config = std::make_unique<tls::Server::config_t>();
if (build_config(*config, v2g_ctx)) {
dlog(DLOG_LEVEL_INFO, "Configuration of TLS server successful, updating it");
v2g_ctx->tls_server->update(*config);
} else {
dlog(DLOG_LEVEL_INFO, "Configuration of TLS server failed, suspending it");
v2g_ctx->tls_server->suspend();
}
});
invoke_init(*p_charger);
invoke_init(*p_extensions);
}
void IsoMux::ready() {
int rv = 0;
dlog(DLOG_LEVEL_DEBUG, "Starting SDP responder");
rv = connection_init(v2g_ctx);
if (rv == -1) {
dlog(DLOG_LEVEL_ERROR, "Failed to initialize connection");
goto err_out;
}
rv = sdp_init(v2g_ctx);
if (rv == -1) {
dlog(DLOG_LEVEL_ERROR, "Failed to start SDP responder");
goto err_out;
}
dlog(DLOG_LEVEL_DEBUG, "starting socket server(s)");
if (connection_start_servers(v2g_ctx)) {
dlog(DLOG_LEVEL_ERROR, "start_connection_servers() failed");
goto err_out;
}
invoke_ready(*p_charger);
invoke_ready(*p_extensions);
rv = sdp_listen(v2g_ctx);
if (rv == -1) {
dlog(DLOG_LEVEL_ERROR, "sdp_listen() failed");
goto err_out;
}
return;
err_out:
v2g_ctx_free(v2g_ctx);
}
IsoMux::~IsoMux() {
v2g_ctx_free(v2g_ctx);
}
bool IsoMux::selected_iso20() {
return v2g_ctx->selected_iso20;
}
} // namespace module

View File

@@ -0,0 +1,95 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef ISO_MUX_HPP
#define ISO_MUX_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/ISO15118_charger/Implementation.hpp>
#include <generated/interfaces/iso15118_extensions/Implementation.hpp>
// headers for required interface implementations
#include <generated/interfaces/ISO15118_charger/Interface.hpp>
#include <generated/interfaces/evse_security/Interface.hpp>
#include <generated/interfaces/iso15118_extensions/Interface.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include "v2g_ctx.hpp"
#include <everest/tls/tls.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
std::string device;
std::string tls_security;
bool tls_key_logging;
int tls_timeout;
int proxy_port_iso2;
int proxy_port_iso20;
std::string proxy_device;
};
class IsoMux : public Everest::ModuleBase {
public:
IsoMux() = delete;
IsoMux(const ModuleInfo& info, std::unique_ptr<ISO15118_chargerImplBase> p_charger,
std::unique_ptr<iso15118_extensionsImplBase> p_extensions, std::unique_ptr<evse_securityIntf> r_security,
std::unique_ptr<ISO15118_chargerIntf> r_iso2, std::unique_ptr<ISO15118_chargerIntf> r_iso20,
std::unique_ptr<iso15118_extensionsIntf> r_ext2, std::unique_ptr<iso15118_extensionsIntf> r_ext20,
Conf& config) :
ModuleBase(info),
p_charger(std::move(p_charger)),
p_extensions(std::move(p_extensions)),
r_security(std::move(r_security)),
r_iso2(std::move(r_iso2)),
r_iso20(std::move(r_iso20)),
r_ext2(std::move(r_ext2)),
r_ext20(std::move(r_ext20)),
config(config){};
const std::unique_ptr<ISO15118_chargerImplBase> p_charger;
const std::unique_ptr<iso15118_extensionsImplBase> p_extensions;
const std::unique_ptr<evse_securityIntf> r_security;
const std::unique_ptr<ISO15118_chargerIntf> r_iso2;
const std::unique_ptr<ISO15118_chargerIntf> r_iso20;
const std::unique_ptr<iso15118_extensionsIntf> r_ext2;
const std::unique_ptr<iso15118_extensionsIntf> r_ext20;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
~IsoMux();
bool selected_iso20();
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
protected:
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
// insert your protected definitions here
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
private:
friend class LdEverest;
void init();
void ready();
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
// insert your private definitions here
tls::Server tls_server;
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
};
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
// insert other definitions here
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
} // namespace module
#endif // ISO_MUX_HPP

View File

@@ -0,0 +1,787 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2022-2023 chargebyte GmbH
// Copyright (C) 2022-2023 Contributors to EVerest
#include "ISO15118_chargerImpl.hpp"
#include "log.hpp"
#include "v2g_ctx.hpp"
#include <algorithm>
const std::string CERTS_SUB_DIR = "certs"; // relativ path of the certs
using namespace std::chrono_literals;
using BidiMode = types::iso15118::SaeJ2847BidiMode;
namespace module {
namespace charger {
void ISO15118_chargerImpl::init() {
if (!v2g_ctx) {
dlog(DLOG_LEVEL_ERROR, "v2g_ctx not created");
return;
}
/* Configure if_name and auth_mode */
v2g_ctx->if_name = mod->config.device.data();
dlog(DLOG_LEVEL_DEBUG, "if_name %s", v2g_ctx->if_name);
v2g_ctx->proxy_if_name = mod->config.proxy_device.empty() ? nullptr : mod->config.proxy_device.data();
/* Configure tls_security */
if (mod->config.tls_security == "force") {
v2g_ctx->tls_security = TLS_SECURITY_FORCE;
dlog(DLOG_LEVEL_DEBUG, "tls_security force");
} else if (mod->config.tls_security == "allow") {
v2g_ctx->tls_security = TLS_SECURITY_ALLOW;
dlog(DLOG_LEVEL_DEBUG, "tls_security allow");
} else {
v2g_ctx->tls_security = TLS_SECURITY_PROHIBIT;
dlog(DLOG_LEVEL_DEBUG, "tls_security prohibit");
}
v2g_ctx->network_read_timeout_tls = mod->config.tls_timeout;
v2g_ctx->certs_path = mod->info.paths.etc / CERTS_SUB_DIR;
// Subscribe all vars
mod->r_iso2->subscribe_supported_app_protocols_secc([this](const auto value) {
if (auto merged = merge_supported_app_protocols(value, ProtocolSource::Iso2)) {
publish_supported_app_protocols_secc(*merged);
}
});
mod->r_iso2->subscribe_require_auth_eim([this]() {
if (not mod->selected_iso20()) {
publish_require_auth_eim(nullptr);
}
});
mod->r_iso20->subscribe_require_auth_eim([this]() {
if (mod->selected_iso20()) {
publish_require_auth_eim(nullptr);
}
});
mod->r_iso2->subscribe_require_auth_pnc([this](const auto value) {
if (not mod->selected_iso20()) {
publish_require_auth_pnc(value);
}
});
mod->r_iso20->subscribe_require_auth_pnc([this](const auto value) {
if (mod->selected_iso20()) {
publish_require_auth_pnc(value);
}
});
mod->r_iso2->subscribe_ac_close_contactor([this]() {
if (not mod->selected_iso20()) {
publish_ac_close_contactor(nullptr);
}
});
mod->r_iso20->subscribe_ac_close_contactor([this]() {
if (mod->selected_iso20()) {
publish_ac_close_contactor(nullptr);
}
});
mod->r_iso2->subscribe_ac_open_contactor([this]() {
if (not mod->selected_iso20()) {
publish_ac_open_contactor(nullptr);
}
});
mod->r_iso20->subscribe_supported_app_protocols_secc([this](const auto value) {
if (auto merged = merge_supported_app_protocols(value, ProtocolSource::Iso20)) {
publish_supported_app_protocols_secc(*merged);
}
});
mod->r_iso20->subscribe_ac_open_contactor([this]() {
if (mod->selected_iso20()) {
publish_ac_open_contactor(nullptr);
}
});
mod->r_iso2->subscribe_start_cable_check([this]() {
if (not mod->selected_iso20()) {
publish_start_cable_check(nullptr);
}
});
mod->r_iso20->subscribe_start_cable_check([this]() {
if (mod->selected_iso20()) {
publish_start_cable_check(nullptr);
}
});
mod->r_iso2->subscribe_start_pre_charge([this]() {
if (not mod->selected_iso20()) {
publish_start_pre_charge(nullptr);
}
});
mod->r_iso20->subscribe_start_pre_charge([this]() {
if (mod->selected_iso20()) {
publish_start_pre_charge(nullptr);
}
});
mod->r_iso2->subscribe_dc_open_contactor([this]() {
if (not mod->selected_iso20()) {
publish_dc_open_contactor(nullptr);
}
});
mod->r_iso20->subscribe_dc_open_contactor([this]() {
if (mod->selected_iso20()) {
publish_dc_open_contactor(nullptr);
}
});
mod->r_iso2->subscribe_v2g_setup_finished([this]() {
if (not mod->selected_iso20()) {
publish_v2g_setup_finished(nullptr);
}
});
mod->r_iso20->subscribe_v2g_setup_finished([this]() {
if (mod->selected_iso20()) {
publish_v2g_setup_finished(nullptr);
}
});
mod->r_iso2->subscribe_current_demand_started([this]() {
if (not mod->selected_iso20()) {
publish_current_demand_started(nullptr);
}
});
mod->r_iso20->subscribe_current_demand_started([this]() {
if (mod->selected_iso20()) {
publish_current_demand_started(nullptr);
}
});
mod->r_iso2->subscribe_current_demand_finished([this]() {
if (not mod->selected_iso20()) {
publish_current_demand_finished(nullptr);
}
});
mod->r_iso20->subscribe_current_demand_finished([this]() {
if (mod->selected_iso20()) {
publish_current_demand_finished(nullptr);
}
});
mod->r_iso2->subscribe_sae_bidi_mode_active([this]() {
if (not mod->selected_iso20()) {
publish_sae_bidi_mode_active(nullptr);
}
});
mod->r_iso20->subscribe_sae_bidi_mode_active([this]() {
if (mod->selected_iso20()) {
publish_sae_bidi_mode_active(nullptr);
}
});
mod->r_iso2->subscribe_evcc_id([this](const auto id) {
if (not mod->selected_iso20()) {
publish_evcc_id(id);
}
});
mod->r_iso20->subscribe_evcc_id([this](const auto id) {
if (mod->selected_iso20()) {
publish_evcc_id(id);
}
});
mod->r_iso2->subscribe_selected_payment_option([this](const auto o) {
if (not mod->selected_iso20()) {
publish_selected_payment_option(o);
}
});
mod->r_iso20->subscribe_selected_payment_option([this](const auto o) {
if (mod->selected_iso20()) {
publish_selected_payment_option(o);
}
});
mod->r_iso2->subscribe_requested_energy_transfer_mode([this](const auto o) {
if (not mod->selected_iso20()) {
publish_requested_energy_transfer_mode(o);
}
});
mod->r_iso20->subscribe_requested_energy_transfer_mode([this](const auto o) {
if (mod->selected_iso20()) {
publish_requested_energy_transfer_mode(o);
}
});
mod->r_iso2->subscribe_departure_time([this](const auto o) {
if (not mod->selected_iso20()) {
publish_departure_time(o);
}
});
mod->r_iso20->subscribe_departure_time([this](const auto o) {
if (mod->selected_iso20()) {
publish_departure_time(o);
}
});
mod->r_iso2->subscribe_ac_eamount([this](const auto o) {
if (not mod->selected_iso20()) {
publish_ac_eamount(o);
}
});
mod->r_iso20->subscribe_ac_eamount([this](const auto o) {
if (mod->selected_iso20()) {
publish_ac_eamount(o);
}
});
mod->r_iso2->subscribe_ac_ev_max_voltage([this](const auto o) {
if (not mod->selected_iso20()) {
publish_ac_ev_max_voltage(o);
}
});
mod->r_iso20->subscribe_ac_ev_max_voltage([this](const auto o) {
if (mod->selected_iso20()) {
publish_ac_ev_max_voltage(o);
}
});
mod->r_iso2->subscribe_ac_ev_max_current([this](const auto o) {
if (not mod->selected_iso20()) {
publish_ac_ev_max_current(o);
}
});
mod->r_iso20->subscribe_ac_ev_max_current([this](const auto o) {
if (mod->selected_iso20()) {
publish_ac_ev_max_current(o);
}
});
mod->r_iso2->subscribe_ac_ev_min_current([this](const auto o) {
if (not mod->selected_iso20()) {
publish_ac_ev_min_current(o);
}
});
mod->r_iso20->subscribe_ac_ev_min_current([this](const auto o) {
if (mod->selected_iso20()) {
publish_ac_ev_min_current(o);
}
});
mod->r_iso20->subscribe_ac_ev_power_limits([this](const auto o) {
if (mod->selected_iso20()) {
publish_ac_ev_power_limits(o);
}
});
mod->r_iso20->subscribe_ac_ev_present_powers([this](const auto o) {
if (mod->selected_iso20()) {
publish_ac_ev_present_powers(o);
}
});
mod->r_iso20->subscribe_ac_ev_dynamic_control_mode([this](const auto o) {
if (mod->selected_iso20()) {
publish_ac_ev_dynamic_control_mode(o);
}
});
mod->r_iso2->subscribe_dc_ev_energy_capacity([this](const auto o) {
if (not mod->selected_iso20()) {
publish_dc_ev_energy_capacity(o);
}
});
mod->r_iso20->subscribe_dc_ev_energy_capacity([this](const auto o) {
if (mod->selected_iso20()) {
publish_dc_ev_energy_capacity(o);
}
});
mod->r_iso2->subscribe_dc_ev_energy_request([this](const auto o) {
if (not mod->selected_iso20()) {
publish_dc_ev_energy_request(o);
}
});
mod->r_iso20->subscribe_dc_ev_energy_request([this](const auto o) {
if (mod->selected_iso20()) {
publish_dc_ev_energy_request(o);
}
});
mod->r_iso2->subscribe_dc_full_soc([this](const auto o) {
if (not mod->selected_iso20()) {
publish_dc_full_soc(o);
}
});
mod->r_iso20->subscribe_dc_full_soc([this](const auto o) {
if (mod->selected_iso20()) {
publish_dc_full_soc(o);
}
});
mod->r_iso2->subscribe_dc_bulk_soc([this](const auto o) {
if (not mod->selected_iso20()) {
publish_dc_bulk_soc(o);
}
});
mod->r_iso20->subscribe_dc_bulk_soc([this](const auto o) {
if (mod->selected_iso20()) {
publish_dc_bulk_soc(o);
}
});
mod->r_iso2->subscribe_dc_ev_status([this](const auto o) {
if (not mod->selected_iso20()) {
publish_dc_ev_status(o);
}
});
mod->r_iso20->subscribe_dc_ev_status([this](const auto o) {
if (mod->selected_iso20()) {
publish_dc_ev_status(o);
}
});
mod->r_iso2->subscribe_dc_bulk_charging_complete([this](const auto o) {
if (not mod->selected_iso20()) {
publish_dc_bulk_charging_complete(o);
}
});
mod->r_iso20->subscribe_dc_bulk_charging_complete([this](const auto o) {
if (mod->selected_iso20()) {
publish_dc_bulk_charging_complete(o);
}
});
mod->r_iso2->subscribe_dc_charging_complete([this](const auto o) {
if (not mod->selected_iso20()) {
publish_dc_charging_complete(o);
}
});
mod->r_iso20->subscribe_dc_charging_complete([this](const auto o) {
if (mod->selected_iso20()) {
publish_dc_charging_complete(o);
}
});
mod->r_iso2->subscribe_dc_ev_target_voltage_current([this](const auto o) {
if (not mod->selected_iso20()) {
publish_dc_ev_target_voltage_current(o);
}
});
mod->r_iso20->subscribe_dc_ev_target_voltage_current([this](const auto o) {
if (mod->selected_iso20()) {
publish_dc_ev_target_voltage_current(o);
}
});
mod->r_iso2->subscribe_dc_ev_maximum_limits([this](const auto o) {
if (not mod->selected_iso20()) {
publish_dc_ev_maximum_limits(o);
}
});
mod->r_iso20->subscribe_dc_ev_maximum_limits([this](const auto o) {
if (mod->selected_iso20()) {
publish_dc_ev_maximum_limits(o);
}
});
mod->r_iso2->subscribe_dc_ev_remaining_time([this](const auto o) {
if (not mod->selected_iso20()) {
publish_dc_ev_remaining_time(o);
}
});
mod->r_iso20->subscribe_dc_ev_remaining_time([this](const auto o) {
if (mod->selected_iso20()) {
publish_dc_ev_remaining_time(o);
}
});
mod->r_iso2->subscribe_hlc_session_failed([this](const auto reason) {
if (not mod->selected_iso20()) {
publish_hlc_session_failed(reason);
}
});
mod->r_iso20->subscribe_hlc_session_failed([this](const auto reason) {
if (mod->selected_iso20()) {
publish_hlc_session_failed(reason);
}
});
mod->r_iso2->subscribe_dlink_terminate([this]() {
if (not mod->selected_iso20()) {
publish_dlink_terminate(nullptr);
}
});
mod->r_iso20->subscribe_dlink_terminate([this]() {
if (mod->selected_iso20()) {
publish_dlink_terminate(nullptr);
}
});
mod->r_iso2->subscribe_dlink_error([this]() {
if (not mod->selected_iso20()) {
publish_dlink_error(nullptr);
}
});
mod->r_iso20->subscribe_dlink_error([this]() {
if (mod->selected_iso20()) {
publish_dlink_error(nullptr);
}
});
mod->r_iso2->subscribe_dlink_pause([this]() {
if (not mod->selected_iso20()) {
publish_dlink_pause(nullptr);
}
});
mod->r_iso20->subscribe_dlink_pause([this]() {
if (mod->selected_iso20()) {
publish_dlink_pause(nullptr);
}
});
mod->r_iso2->subscribe_ev_app_protocol([this](const auto o) {
if (not mod->selected_iso20()) {
publish_ev_app_protocol(o);
}
});
mod->r_iso20->subscribe_ev_app_protocol([this](const auto o) {
if (mod->selected_iso20()) {
publish_ev_app_protocol(o);
}
});
mod->r_iso2->subscribe_v2g_messages([this](const auto o) {
if (not mod->selected_iso20()) {
publish_v2g_messages(o);
}
});
mod->r_iso20->subscribe_v2g_messages([this](const auto o) {
if (mod->selected_iso20()) {
publish_v2g_messages(o);
}
});
mod->r_iso2->subscribe_selected_protocol([this](const auto o) {
if (not mod->selected_iso20()) {
publish_selected_protocol(o);
}
});
mod->r_iso20->subscribe_selected_protocol([this](const auto o) {
if (mod->selected_iso20()) {
publish_selected_protocol(o);
}
});
mod->r_iso2->subscribe_selected_service_parameters([this](const auto& param) {
if (not mod->selected_iso20()) {
publish_selected_service_parameters(param);
}
});
mod->r_iso20->subscribe_selected_service_parameters([this](const auto& param) {
if (mod->selected_iso20()) {
publish_selected_service_parameters(param);
}
});
mod->r_iso2->subscribe_display_parameters([this](const auto o) {
if (not mod->selected_iso20()) {
publish_display_parameters(o);
}
});
mod->r_iso20->subscribe_display_parameters([this](const auto o) {
if (mod->selected_iso20()) {
publish_display_parameters(o);
}
});
mod->r_iso20->subscribe_d20_dc_dynamic_charge_mode([this](const auto o) {
if (mod->selected_iso20()) {
publish_d20_dc_dynamic_charge_mode(o);
}
});
mod->r_iso2->subscribe_dc_ev_present_voltage([this](const auto o) {
if (not mod->selected_iso20()) {
publish_dc_ev_present_voltage(o);
}
});
mod->r_iso20->subscribe_dc_ev_present_voltage([this](const auto o) {
if (mod->selected_iso20()) {
publish_dc_ev_present_voltage(o);
}
});
mod->r_iso2->subscribe_meter_info_requested([this]() {
if (not mod->selected_iso20()) {
publish_meter_info_requested(nullptr);
}
});
mod->r_iso20->subscribe_meter_info_requested([this]() {
if (mod->selected_iso20()) {
publish_meter_info_requested(nullptr);
}
});
}
void ISO15118_chargerImpl::ready() {
}
std::optional<types::iso15118::SupportedAppProtocols>
ISO15118_chargerImpl::merge_supported_app_protocols(const types::iso15118::SupportedAppProtocols& value,
ProtocolSource source) {
std::lock_guard<std::mutex> lock(supported_app_protocols_mutex);
bool changed = false;
for (const auto protocol : value.app_protocols) {
if (std::find(supported_app_protocols.app_protocols.begin(), supported_app_protocols.app_protocols.end(),
protocol) == supported_app_protocols.app_protocols.end()) {
supported_app_protocols.app_protocols.push_back(protocol);
changed = true;
}
protocol_sources[protocol] = source;
}
return changed ? std::make_optional(supported_app_protocols) : std::nullopt;
}
void ISO15118_chargerImpl::handle_setup(types::iso15118::EVSEID& evse_id,
types::iso15118::SaeJ2847BidiMode& sae_j2847_mode, bool& debug_mode) {
mod->r_iso20->call_setup(evse_id, sae_j2847_mode, debug_mode);
mod->r_iso2->call_setup(evse_id, sae_j2847_mode, debug_mode);
}
void ISO15118_chargerImpl::handle_set_charging_parameters(types::iso15118::SetupPhysicalValues& physical_values) {
mod->r_iso20->call_set_charging_parameters(physical_values);
mod->r_iso2->call_set_charging_parameters(physical_values);
}
void ISO15118_chargerImpl::handle_session_setup(std::vector<types::iso15118::PaymentOption>& payment_options,
bool& supported_certificate_service,
bool& central_contract_validation_allowed) {
mod->r_iso20->call_session_setup(payment_options, supported_certificate_service,
central_contract_validation_allowed);
mod->r_iso2->call_session_setup(payment_options, supported_certificate_service,
central_contract_validation_allowed);
}
void ISO15118_chargerImpl::handle_bpt_setup(types::iso15118::BptSetup& bpt_config) {
if (mod->selected_iso20()) {
mod->r_iso20->call_bpt_setup(bpt_config);
}
}
void ISO15118_chargerImpl::handle_set_powersupply_capabilities(types::power_supply_DC::Capabilities& capabilities) {
mod->r_iso20->call_set_powersupply_capabilities(capabilities);
mod->r_iso2->call_set_powersupply_capabilities(capabilities);
}
void ISO15118_chargerImpl::handle_authorization_response(
types::authorization::AuthorizationStatus& authorization_status,
types::authorization::CertificateStatus& certificate_status) {
if (mod->selected_iso20()) {
mod->r_iso20->call_authorization_response(authorization_status, certificate_status);
} else {
mod->r_iso2->call_authorization_response(authorization_status, certificate_status);
}
}
void ISO15118_chargerImpl::handle_ac_contactor_closed(bool& status) {
if (mod->selected_iso20()) {
mod->r_iso20->call_ac_contactor_closed(status);
} else {
mod->r_iso2->call_ac_contactor_closed(status);
}
}
void ISO15118_chargerImpl::handle_dlink_ready(bool& value) {
if (mod->selected_iso20()) {
mod->r_iso20->call_dlink_ready(value);
} else {
mod->r_iso2->call_dlink_ready(value);
}
}
void ISO15118_chargerImpl::handle_cable_check_finished(bool& status) {
if (mod->selected_iso20()) {
mod->r_iso20->call_cable_check_finished(status);
} else {
mod->r_iso2->call_cable_check_finished(status);
}
}
void ISO15118_chargerImpl::handle_receipt_is_required(bool& receipt_required) {
mod->r_iso20->call_receipt_is_required(receipt_required);
mod->r_iso2->call_receipt_is_required(receipt_required);
}
void ISO15118_chargerImpl::handle_stop_charging(bool& stop) {
if (mod->selected_iso20()) {
mod->r_iso20->call_stop_charging(stop);
} else {
mod->r_iso2->call_stop_charging(stop);
}
}
void ISO15118_chargerImpl::handle_pause_charging(bool& pause) {
if (mod->selected_iso20()) {
mod->r_iso20->call_pause_charging(pause);
} else {
mod->r_iso2->call_pause_charging(pause);
}
}
void ISO15118_chargerImpl::handle_no_energy_pause_charging(types::iso15118::NoEnergyPauseMode& mode) {
if (mod->selected_iso20()) {
mod->r_iso20->call_no_energy_pause_charging(mode);
} else {
mod->r_iso2->call_no_energy_pause_charging(mode);
}
}
bool ISO15118_chargerImpl::handle_update_supported_app_protocols(
types::iso15118::SupportedAppProtocols& updated_supported_app_protocols) {
bool iso20_configured = false;
if (updated_supported_app_protocols.app_protocols.empty()) {
v2g_ctx->iso20_proxy_enabled = false;
EVLOG_info << "No protocols configured, disabling ISO-20 proxy routing";
// Forward empty updates to both implementations
// TODO(FH): Currently only forwarded to the ISO-2 module, because ISO-20 module is not able to
// disable supported protocols. Currently, this implementation gap has been resolved by deactivating
// the ISO-20 proxy if the ISO-20 protocol has not been enabled. In that case, the ISO-2
// proxy takes over the responsibility for responding to the SupportedAppReq.
const bool ok_iso2 = mod->r_iso2->call_update_supported_app_protocols(updated_supported_app_protocols);
mod->r_iso20->call_update_supported_app_protocols(updated_supported_app_protocols);
return ok_iso2;
}
types::iso15118::SupportedAppProtocols iso2_payload;
types::iso15118::SupportedAppProtocols iso20_payload;
{
/* Iterate though the list of supported_app_protocols and check whether the received protocol is intended for
the r_iso2 or the r_iso20 instance */
std::lock_guard<std::mutex> lock(supported_app_protocols_mutex);
for (const auto protocol : updated_supported_app_protocols.app_protocols) {
const auto source_it = protocol_sources.find(protocol);
const bool known_protocol = std::find(this->supported_app_protocols.app_protocols.begin(),
this->supported_app_protocols.app_protocols.end(),
protocol) != this->supported_app_protocols.app_protocols.end();
if (source_it == protocol_sources.end() || !known_protocol) {
EVLOG_warning << "Received update for unsupported app protocol: "
<< types::iso15118::supported_app_protocol_to_string(protocol);
return false;
}
if (source_it->second == ProtocolSource::Iso20) {
iso20_configured = true;
iso20_payload.app_protocols.push_back(protocol);
} else {
iso2_payload.app_protocols.push_back(protocol);
}
}
}
/* update supported protocols */
bool ok = true;
if (!iso2_payload.app_protocols.empty()) {
ok = mod->r_iso2->call_update_supported_app_protocols(iso2_payload) && ok;
}
if (!iso20_payload.app_protocols.empty()) {
ok = mod->r_iso20->call_update_supported_app_protocols(iso20_payload) && ok;
}
/* route to ISO-20 proxy only when ISO15118-20 is explicitly configured. */
v2g_ctx->iso20_proxy_enabled = iso20_configured;
if (!iso20_configured) {
EVLOG_debug << "Disabling ISO-20 proxy routing since no ISO15118-20 protocol is configured";
}
return ok;
}
void ISO15118_chargerImpl::handle_update_energy_transfer_modes(
std::vector<types::iso15118::EnergyTransferMode>& supported_energy_transfer_modes) {
mod->r_iso20->call_update_energy_transfer_modes(supported_energy_transfer_modes);
mod->r_iso2->call_update_energy_transfer_modes(supported_energy_transfer_modes);
}
void ISO15118_chargerImpl::handle_update_ac_max_current(double& max_current) {
mod->r_iso20->call_update_ac_max_current(max_current);
mod->r_iso2->call_update_ac_max_current(max_current);
}
void ISO15118_chargerImpl::handle_update_ac_parameters(types::iso15118::AcParameters& ac_parameters) {
if (mod->selected_iso20()) {
mod->r_iso20->call_update_ac_parameters(ac_parameters);
}
}
void ISO15118_chargerImpl::handle_update_ac_maximum_limits(types::iso15118::AcEvseMaximumPower& maximum_limits) {
if (mod->selected_iso20()) {
mod->r_iso20->call_update_ac_maximum_limits(maximum_limits);
}
}
void ISO15118_chargerImpl::handle_update_ac_minimum_limits(types::iso15118::AcEvseMinimumPower& minimum_limits) {
if (mod->selected_iso20()) {
mod->r_iso20->call_update_ac_minimum_limits(minimum_limits);
}
}
void ISO15118_chargerImpl::handle_update_ac_target_values(types::iso15118::AcTargetValues& target_values) {
if (mod->selected_iso20()) {
mod->r_iso20->call_update_ac_target_values(target_values);
}
}
void ISO15118_chargerImpl::handle_update_ac_present_power(types::units::Power& present_power) {
if (mod->selected_iso20()) {
mod->r_iso20->call_update_ac_present_power(present_power);
}
}
void ISO15118_chargerImpl::handle_update_dc_maximum_limits(types::iso15118::DcEvseMaximumLimits& maximum_limits) {
mod->r_iso20->call_update_dc_maximum_limits(maximum_limits);
mod->r_iso2->call_update_dc_maximum_limits(maximum_limits);
}
void ISO15118_chargerImpl::handle_update_dc_minimum_limits(types::iso15118::DcEvseMinimumLimits& minimum_limits) {
mod->r_iso20->call_update_dc_minimum_limits(minimum_limits);
mod->r_iso2->call_update_dc_minimum_limits(minimum_limits);
}
void ISO15118_chargerImpl::handle_update_isolation_status(types::iso15118::IsolationStatus& isolation_status) {
if (mod->selected_iso20()) {
mod->r_iso20->call_update_isolation_status(isolation_status);
} else {
mod->r_iso2->call_update_isolation_status(isolation_status);
}
}
void ISO15118_chargerImpl::handle_update_dc_present_values(
types::iso15118::DcEvsePresentVoltageCurrent& present_voltage_current) {
mod->r_iso20->call_update_dc_present_values(present_voltage_current);
mod->r_iso2->call_update_dc_present_values(present_voltage_current);
}
void ISO15118_chargerImpl::handle_update_meter_info(types::powermeter::Powermeter& powermeter) {
mod->r_iso20->call_update_meter_info(powermeter);
mod->r_iso2->call_update_meter_info(powermeter);
}
void ISO15118_chargerImpl::handle_send_error(types::iso15118::EvseError& error) {
if (mod->selected_iso20()) {
mod->r_iso20->call_send_error(error);
} else {
mod->r_iso2->call_send_error(error);
}
}
void ISO15118_chargerImpl::handle_reset_error() {
if (mod->selected_iso20()) {
mod->r_iso20->call_reset_error();
} else {
mod->r_iso2->call_reset_error();
}
}
} // namespace charger
} // namespace module

View File

@@ -0,0 +1,111 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef CHARGER_ISO15118_CHARGER_IMPL_HPP
#define CHARGER_ISO15118_CHARGER_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/ISO15118_charger/Implementation.hpp>
#include "../IsoMux.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
#include <mutex>
#include <optional>
#include <unordered_map>
#include "v2g.hpp"
extern struct v2g_context* v2g_ctx;
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace charger {
struct Conf {};
class ISO15118_chargerImpl : public ISO15118_chargerImplBase {
public:
ISO15118_chargerImpl() = delete;
ISO15118_chargerImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<IsoMux>& mod, Conf& config) :
ISO15118_chargerImplBase(ev, "charger"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// command handler functions (virtual)
virtual void handle_setup(types::iso15118::EVSEID& evse_id, types::iso15118::SaeJ2847BidiMode& sae_j2847_mode,
bool& debug_mode) override;
virtual void handle_set_charging_parameters(types::iso15118::SetupPhysicalValues& physical_values) override;
virtual void handle_session_setup(std::vector<types::iso15118::PaymentOption>& payment_options,
bool& supported_certificate_service,
bool& central_contract_validation_allowed) override;
virtual void handle_bpt_setup(types::iso15118::BptSetup& bpt_config) override;
virtual void handle_set_powersupply_capabilities(types::power_supply_DC::Capabilities& capabilities) override;
virtual void handle_authorization_response(types::authorization::AuthorizationStatus& authorization_status,
types::authorization::CertificateStatus& certificate_status) override;
virtual void handle_ac_contactor_closed(bool& status) override;
virtual void handle_dlink_ready(bool& value) override;
virtual void handle_cable_check_finished(bool& status) override;
virtual void handle_receipt_is_required(bool& receipt_required) override;
virtual void handle_stop_charging(bool& stop) override;
virtual void handle_pause_charging(bool& pause) override;
virtual void handle_no_energy_pause_charging(types::iso15118::NoEnergyPauseMode& mode) override;
virtual bool
handle_update_supported_app_protocols(types::iso15118::SupportedAppProtocols& supported_app_protocols) override;
virtual void handle_update_energy_transfer_modes(
std::vector<types::iso15118::EnergyTransferMode>& supported_energy_transfer_modes) override;
virtual void handle_update_ac_max_current(double& max_current) override;
virtual void handle_update_ac_parameters(types::iso15118::AcParameters& ac_parameters) override;
virtual void handle_update_ac_maximum_limits(types::iso15118::AcEvseMaximumPower& maximum_limits) override;
virtual void handle_update_ac_minimum_limits(types::iso15118::AcEvseMinimumPower& minimum_limits) override;
virtual void handle_update_ac_target_values(types::iso15118::AcTargetValues& target_values) override;
virtual void handle_update_ac_present_power(types::units::Power& present_power) override;
virtual void handle_update_dc_maximum_limits(types::iso15118::DcEvseMaximumLimits& maximum_limits) override;
virtual void handle_update_dc_minimum_limits(types::iso15118::DcEvseMinimumLimits& minimum_limits) override;
virtual void handle_update_isolation_status(types::iso15118::IsolationStatus& isolation_status) override;
virtual void
handle_update_dc_present_values(types::iso15118::DcEvsePresentVoltageCurrent& present_voltage_current) override;
virtual void handle_update_meter_info(types::powermeter::Powermeter& powermeter) override;
virtual void handle_send_error(types::iso15118::EvseError& error) override;
virtual void handle_reset_error() override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<IsoMux>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
types::iso15118::SupportedAppProtocols supported_app_protocols; // aggregated set of protocols from iso2/iso20
std::mutex supported_app_protocols_mutex; // guards shared protocol state
enum class ProtocolSource {
Iso2,
Iso20
};
std::unordered_map<types::iso15118::SupportedAppProtocol, ProtocolSource>
protocol_sources; // tracks which instance advertised each protocol
std::optional<types::iso15118::SupportedAppProtocols>
merge_supported_app_protocols(const types::iso15118::SupportedAppProtocols& value, ProtocolSource source);
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace charger
} // namespace module
#endif // CHARGER_ISO15118_CHARGER_IMPL_HPP

View File

@@ -0,0 +1,687 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2022-2023 chargebyte GmbH
// Copyright (C) 2022-2023 Contributors to EVerest
#include "connection.hpp"
#include "log.hpp"
#include "tls_connection.hpp"
#include "tools.hpp"
#include "v2g_server.hpp"
#include <arpa/inet.h>
#include <cstring>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fstream>
#include <inttypes.h>
#include <iostream>
#include <net/if.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "proxy.hpp"
#define DEFAULT_SOCKET_BACKLOG 3
#define DEFAULT_TCP_PORT 61342
#define DEFAULT_TLS_PORT 64110
#define ERROR_SESSION_ALREADY_STARTED 2
#define CLIENT_FIN_TIMEOUT 3000
/*!
* \brief connection_create_socket This function creates a tcp/tls socket
* \param sockaddr to bind the socket to an interface
* \return Returns \c 0 on success, otherwise \c -1
*/
static int connection_create_socket(struct sockaddr_in6* sockaddr) {
socklen_t addrlen = sizeof(*sockaddr);
int s, enable = 1;
static bool error_once = false;
/* create socket */
s = socket(AF_INET6, SOCK_STREAM, 0);
if (s == -1) {
if (!error_once) {
dlog(DLOG_LEVEL_ERROR, "socket() failed: %s", strerror(errno));
error_once = true;
}
return -1;
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)) == -1) {
if (!error_once) {
dlog(DLOG_LEVEL_ERROR, "setsockopt(SO_REUSEPORT) failed: %s", strerror(errno));
error_once = true;
}
close(s);
return -1;
}
/* bind it to interface */
if (bind(s, reinterpret_cast<struct sockaddr*>(sockaddr), addrlen) == -1) {
if (!error_once) {
dlog(DLOG_LEVEL_WARNING, "bind() failed: %s", strerror(errno));
error_once = true;
}
close(s);
return -1;
}
/* listen on this socket */
if (listen(s, DEFAULT_SOCKET_BACKLOG) == -1) {
if (!error_once) {
dlog(DLOG_LEVEL_ERROR, "listen() failed: %s", strerror(errno));
error_once = true;
}
close(s);
return -1;
}
/* retrieve the actual port number we are listening on */
if (getsockname(s, reinterpret_cast<struct sockaddr*>(sockaddr), &addrlen) == -1) {
if (!error_once) {
dlog(DLOG_LEVEL_ERROR, "getsockname() failed: %s", strerror(errno));
error_once = true;
}
close(s);
return -1;
}
return s;
}
/*!
* \brief check_interface This function checks the interface name. The interface name is
* configured automatically in case it is pre-initialized to “auto.
* \param sockaddr to bind the socket to an interface
* \return Returns \c 0 on success, otherwise \c -1
*/
int check_interface(struct v2g_context* v2g_ctx) {
if (v2g_ctx == nullptr || v2g_ctx->if_name == nullptr) {
return -1;
}
struct ipv6_mreq mreq = {};
std::memset(&mreq, 0, sizeof(mreq));
if (strcmp(v2g_ctx->if_name, "auto") == 0) {
v2g_ctx->if_name = choose_first_ipv6_interface();
}
if (v2g_ctx->if_name == nullptr) {
return -1;
}
mreq.ipv6mr_interface = if_nametoindex(v2g_ctx->if_name);
if (!mreq.ipv6mr_interface) {
dlog(DLOG_LEVEL_ERROR, "No such interface: %s", v2g_ctx->if_name);
return -1;
}
return (v2g_ctx->if_name == nullptr) ? -1 : 0;
}
/*!
* \brief connection_init This function initilizes the tcp and tls interface.
* \param v2g_context is the V2G context.
* \return Returns \c 0 on success, otherwise \c -1
*/
int connection_init(struct v2g_context* v2g_ctx) {
if (check_interface(v2g_ctx) == -1) {
return -1;
}
if (v2g_ctx->tls_security != TLS_SECURITY_FORCE) {
v2g_ctx->local_tcp_addr = static_cast<sockaddr_in6*>(calloc(1, sizeof(*v2g_ctx->local_tcp_addr)));
if (v2g_ctx->local_tcp_addr == nullptr) {
dlog(DLOG_LEVEL_ERROR, "Failed to allocate memory for TCP address");
return -1;
}
}
if (v2g_ctx->tls_security != TLS_SECURITY_PROHIBIT) {
v2g_ctx->local_tls_addr = static_cast<sockaddr_in6*>(calloc(1, sizeof(*v2g_ctx->local_tls_addr)));
if (!v2g_ctx->local_tls_addr) {
dlog(DLOG_LEVEL_ERROR, "Failed to allocate memory for TLS address");
return -1;
}
}
while (1) {
if (v2g_ctx->local_tcp_addr) {
get_interface_ipv6_address(v2g_ctx->if_name, ADDR6_TYPE_LINKLOCAL, v2g_ctx->local_tcp_addr);
if (v2g_ctx->local_tls_addr) {
// Handle allowing TCP with TLS (TLS_SECURITY_ALLOW)
memcpy(v2g_ctx->local_tls_addr, v2g_ctx->local_tcp_addr, sizeof(*v2g_ctx->local_tls_addr));
}
} else {
// Handle forcing TLS security (TLS_SECURITY_FORCE)
get_interface_ipv6_address(v2g_ctx->if_name, ADDR6_TYPE_LINKLOCAL, v2g_ctx->local_tls_addr);
}
if (v2g_ctx->local_tcp_addr) {
char buffer[INET6_ADDRSTRLEN];
/*
* When we bind with port = 0, the kernel assigns a dynamic port from the range configured
* in /proc/sys/net/ipv4/ip_local_port_range. This is on a recent Ubuntu Linux e.g.
* $ cat /proc/sys/net/ipv4/ip_local_port_range
* 32768 60999
* However, in ISO15118 spec the IANA range with 49152 to 65535 is referenced. So we have the
* problem that the kernel (without further configuration - and we want to avoid this) could
* hand out a port which is not "range compatible".
* To fulfill the ISO15118 standard, we simply try to bind to static port numbers.
*/
v2g_ctx->local_tcp_addr->sin6_port = htons(DEFAULT_TCP_PORT);
v2g_ctx->tcp_socket = connection_create_socket(v2g_ctx->local_tcp_addr);
if (v2g_ctx->tcp_socket < 0) {
/* retry until interface is ready */
sleep(1);
continue;
}
if (inet_ntop(AF_INET6, &v2g_ctx->local_tcp_addr->sin6_addr, buffer, sizeof(buffer)) != nullptr) {
dlog(DLOG_LEVEL_INFO, "TCP server on %s is listening on port [%s%%%" PRIu32 "]:%" PRIu16,
v2g_ctx->if_name, buffer, v2g_ctx->local_tcp_addr->sin6_scope_id,
ntohs(v2g_ctx->local_tcp_addr->sin6_port));
} else {
dlog(DLOG_LEVEL_ERROR, "TCP server on %s is listening, but inet_ntop failed: %s", v2g_ctx->if_name,
strerror(errno));
return -1;
}
}
if (v2g_ctx->local_tls_addr) {
char buffer[INET6_ADDRSTRLEN];
/* see comment above for reason */
v2g_ctx->local_tls_addr->sin6_port = htons(DEFAULT_TLS_PORT);
v2g_ctx->tls_socket.fd = connection_create_socket(v2g_ctx->local_tls_addr);
if (v2g_ctx->tls_socket.fd < 0) {
if (v2g_ctx->tcp_socket != -1) {
/* free the TCP socket */
close(v2g_ctx->tcp_socket);
}
/* retry until interface is ready */
sleep(1);
continue;
}
if (inet_ntop(AF_INET6, &v2g_ctx->local_tls_addr->sin6_addr, buffer, sizeof(buffer)) != nullptr) {
dlog(DLOG_LEVEL_INFO, "TLS server on %s is listening on port [%s%%%" PRIu32 "]:%" PRIu16,
v2g_ctx->if_name, buffer, v2g_ctx->local_tls_addr->sin6_scope_id,
ntohs(v2g_ctx->local_tls_addr->sin6_port));
} else {
dlog(DLOG_LEVEL_INFO, "TLS server on %s is listening, but inet_ntop failed: %s", v2g_ctx->if_name,
strerror(errno));
return -1;
}
}
/* Sockets should be ready, leave the loop */
break;
}
if (v2g_ctx->local_tls_addr) {
return tls::connection_init(v2g_ctx);
}
return 0;
}
/*!
* \brief is_sequence_timeout This function checks if a sequence timeout has occurred.
* \param ts_start Is the time after waiting of the next request message.
* \param ctx is the V2G context.
* \return Returns \c true if a timeout has occurred, otherwise \c false
*/
bool is_sequence_timeout(struct timespec ts_start, struct v2g_context* ctx) {
struct timespec ts_current;
int sequence_timeout = V2G_SEQUENCE_TIMEOUT_60S;
if (((clock_gettime(CLOCK_MONOTONIC, &ts_current)) != 0) ||
(timespec_to_ms(timespec_sub(ts_current, ts_start)) > sequence_timeout)) {
dlog(DLOG_LEVEL_ERROR, "Sequence timeout has occurred (message: %s)", v2g_msg_type[ctx->current_v2g_msg]);
return true;
}
return false;
}
/*!
* \brief connection_read This function reads from socket until requested bytes are received or sequence
* timeout is reached
* \param conn is the v2g connection context
* \param buf is the buffer to store the v2g message
* \param count is the number of bytes to read
* \return Returns \c true if a timeout has occurred, otherwise \c false
*/
ssize_t connection_read(struct v2g_connection* conn, unsigned char* buf, size_t count, bool read_complete) {
struct timespec ts_start;
int bytes_read = 0;
if (clock_gettime(CLOCK_MONOTONIC, &ts_start) == -1) {
dlog(DLOG_LEVEL_ERROR, "clock_gettime(ts_start) failed: %s", strerror(errno));
return -1;
}
/* loop until we got all requested bytes or sequence timeout DIN [V2G-DC-432]*/
if (read_complete) {
while ((bytes_read < count) && (is_sequence_timeout(ts_start, conn->ctx) == false) &&
(conn->ctx->is_connection_terminated == false)) { // [V2G2-536]
int num_of_bytes;
/* use select for timeout handling */
struct timeval tv;
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(conn->conn.socket_fd, &read_fds);
tv.tv_sec = conn->ctx->network_read_timeout / 1000;
tv.tv_usec = (conn->ctx->network_read_timeout % 1000) * 1000;
num_of_bytes = select(conn->conn.socket_fd + 1, &read_fds, nullptr, nullptr, &tv);
if (num_of_bytes == -1) {
if (errno == EINTR)
continue;
return -1;
}
/* Zero fds ready means we timed out, so let upper loop check our sequence timeout */
if (num_of_bytes == 0) {
continue;
}
num_of_bytes = (int)read(conn->conn.socket_fd, &buf[bytes_read], count - bytes_read);
if (num_of_bytes == -1) {
if (errno == EINTR)
continue;
return -1;
}
/* return when peer closed connection */
if (num_of_bytes == 0)
return bytes_read;
bytes_read += num_of_bytes;
}
} else {
bytes_read = (int)read(conn->conn.socket_fd, buf, count);
}
if (conn->ctx->is_connection_terminated == true) {
dlog(DLOG_LEVEL_ERROR, "Reading from tcp-socket aborted");
return -2;
}
return (ssize_t)bytes_read; // [V2G2-537] read bytes are currupted if reading from socket was interrupted
// (V2G_SECC_Sequence_Timeout)
}
/*!
* \brief connection_read This function writes to socket until bytes are written to the socket
* \param conn is the v2g connection context
* \param buf is the buffer where the v2g message is stored
* \param count is the number of bytes to write
* \return Returns \c true if a timeout has occurred, otherwise \c false
*/
ssize_t connection_write(struct v2g_connection* conn, unsigned char* buf, size_t count) {
int bytes_written = 0;
/* loop until we got all requested bytes out */
while (bytes_written < count) {
int num_of_bytes;
num_of_bytes = (int)write(conn->conn.socket_fd, &buf[bytes_written], count - bytes_written);
if (num_of_bytes == -1) {
if (errno == EINTR)
continue;
return -1;
}
/* return when peer closed connection */
if (num_of_bytes == 0)
return bytes_written;
bytes_written += num_of_bytes;
}
return (ssize_t)bytes_written;
}
static void wait_for_peer_close(int fd, int timeout_ms) {
struct pollfd pfd = {};
pfd.fd = fd;
pfd.events = POLLIN | POLLHUP;
int rc = poll(&pfd, 1, timeout_ms);
if (rc <= 0) {
return;
}
if (pfd.revents & (POLLIN | POLLHUP)) {
char buf[64];
while (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) > 0) {
}
}
}
/**
* This is the 'main' function of a thread, which handles a TCP connection.
*/
void* connection_handle_tcp(void* data) {
struct v2g_connection* conn = static_cast<struct v2g_connection*>(data);
bool error_occurred{false};
connection_handle(data);
/* tear down connection gracefully */
dlog(DLOG_LEVEL_INFO, "Multiplexer: Closing TCP connection");
/* some EV's did not like the immediate shutdown. Therefore we sleep for 2 seconds */
std::this_thread::sleep_for(std::chrono::seconds(2));
if (shutdown(conn->conn.socket_fd, SHUT_WR) == -1) {
dlog(DLOG_LEVEL_ERROR, "shutdown() failed: %s", strerror(errno));
error_occurred = true;
}
/* wait briefly for peer FIN or timeout */
wait_for_peer_close(conn->conn.socket_fd, CLIENT_FIN_TIMEOUT);
if (close(conn->conn.socket_fd) == -1) {
dlog(DLOG_LEVEL_ERROR, "close() failed: %s", strerror(errno));
error_occurred = true;
}
if (not error_occurred) {
dlog(DLOG_LEVEL_INFO, "Multiplexer: TCP connection closed gracefully");
}
free(conn);
return nullptr;
}
/**
* This is the 'main' function of a thread, which handles a TCP connection.
*/
void* connection_handle(void* data) {
struct v2g_connection* conn = static_cast<struct v2g_connection*>(data);
int rv = 0;
bool iso20{false};
conn->buffer = static_cast<uint8_t*>(malloc(DEFAULT_BUFFER_SIZE));
if (not conn->buffer) {
return nullptr;
}
/* check if the v2g-session is already running in another thread, if not, handle v2g-connection */
if (conn->ctx->state == 0) {
iso20 = v2g_detect_iso20_support(conn);
} else {
rv = ERROR_SESSION_ALREADY_STARTED;
dlog(DLOG_LEVEL_WARNING, "%s", "Closing tcp-connection. v2g-session is already running");
}
uint16_t port = conn->ctx->proxy_port_iso2;
conn->ctx->selected_iso20 = false;
const bool iso20_proxy_enabled = conn->ctx->iso20_proxy_enabled;
// Open TCP connection to the proxied module
if (iso20 && iso20_proxy_enabled) {
// Notify the proxy layer about the protocol decision
conn->ctx->selected_iso20 = true;
port = conn->ctx->proxy_port_iso20;
} else if (iso20 && !iso20_proxy_enabled) {
dlog(DLOG_LEVEL_INFO, "ISO-20 requested by EV, but ISO-20 is disabled in supported app protocols. "
"Routing to ISO-2/DIN proxy.");
}
int proxy_fd = proxy_connect(port, conn->ctx->proxy_if_name);
if (proxy_fd > 0) {
EVLOG_info << "Connected to proxy module for " << (conn->ctx->selected_iso20 ? "ISO-20" : "ISO-2/DIN");
conn->proxy(conn, proxy_fd);
}
return nullptr;
}
int connection_proxy(struct v2g_connection* conn, int proxy_fd) {
dlog(DLOG_LEVEL_INFO, "Multiplexer: Proxy TCP->TCP");
int ev_fd = conn->conn.socket_fd;
// SupportedAppProtocolReq message is still in buffer, we need to forward it to the external stack
write(proxy_fd, conn->buffer, conn->payload_len + 8);
struct pollfd poll_list[2];
poll_list[0].fd = proxy_fd;
poll_list[1].fd = ev_fd;
poll_list[0].events = POLLIN;
poll_list[1].events = POLLIN;
unsigned char buf[2048];
while (true) {
int ret = poll(poll_list, 2, -1);
if (ret == -1) {
return -1; // poll error
}
// Timed out, but we blocked forever. This could be a spurious wakeup, so just try again.
if (ret == 0) {
continue;
}
if (poll_list[0].revents & POLLIN) {
// we can read from proxy (connection to local ISO module)
int nrbytes = read(proxy_fd, buf, sizeof(buf));
if (nrbytes == 0) {
break;
}
// write data to EV
nrbytes = conn->write(conn, buf, nrbytes);
}
if (poll_list[0].revents & POLLERR or poll_list[0].revents & POLLHUP or poll_list[0].revents & POLLNVAL) {
// something is wrong with the TCP connection to the ISO module
return -1;
}
if (poll_list[1].revents & POLLIN) {
// we can read from EV
int nrbytes = conn->read(conn, buf, sizeof(buf), false);
if (nrbytes == 0) {
break;
}
// write data to proxy
nrbytes = write(proxy_fd, buf, nrbytes);
}
if (poll_list[1].revents & POLLERR or poll_list[1].revents & POLLHUP or poll_list[1].revents & POLLNVAL) {
// something is wrong with the TCP connection to the EV
return -1;
}
}
close(proxy_fd);
return 0;
}
static void* connection_server(void* data) {
struct v2g_context* ctx = static_cast<v2g_context*>(data);
struct v2g_connection* conn = NULL;
pthread_attr_t attr;
/* create the thread in detached state so we don't need to join every single one */
if (pthread_attr_init(&attr) != 0) {
dlog(DLOG_LEVEL_ERROR, "pthread_attr_init failed: %s", strerror(errno));
goto thread_exit;
}
if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0) {
dlog(DLOG_LEVEL_ERROR, "pthread_attr_setdetachstate failed: %s", strerror(errno));
goto thread_exit;
}
while (1) {
char client_addr[INET6_ADDRSTRLEN];
struct sockaddr_in6 addr;
socklen_t addrlen = sizeof(addr);
/* cleanup old one and create new connection context */
free(conn);
conn = static_cast<v2g_connection*>(calloc(1, sizeof(*conn)));
if (!conn) {
dlog(DLOG_LEVEL_ERROR, "Calloc failed: %s", strerror(errno));
break;
}
/* setup common stuff */
conn->ctx = ctx;
conn->read = &connection_read;
conn->write = &connection_write;
conn->proxy = &connection_proxy;
/* if this thread is the TLS thread, then connections are TLS secured;
* return code is non-zero if equal so align it
*/
conn->is_tls_connection = false;
/* wait for an incoming connection */
conn->conn.socket_fd = accept(ctx->tcp_socket, (struct sockaddr*)&addr, &addrlen);
if (conn->conn.socket_fd == -1) {
dlog(DLOG_LEVEL_ERROR, "Accept(tcp) failed: %s", strerror(errno));
continue;
}
if (inet_ntop(AF_INET6, &addr, client_addr, sizeof(client_addr)) != NULL) {
dlog(DLOG_LEVEL_INFO, "Incoming connection on %s from [%s]:%" PRIu16, ctx->if_name, client_addr,
ntohs(addr.sin6_port));
} else {
dlog(DLOG_LEVEL_ERROR, "Incoming connection on %s, but inet_ntop failed: %s", ctx->if_name,
strerror(errno));
}
// store the port to create a udp socket
conn->ctx->udp_port = ntohs(addr.sin6_port);
if (pthread_create(&conn->thread_id, &attr, connection_handle_tcp, conn) != 0) {
dlog(DLOG_LEVEL_ERROR, "pthread_create() failed: %s", strerror(errno));
continue;
}
/* is up to the thread to cleanup conn */
conn = NULL;
}
thread_exit:
if (pthread_attr_destroy(&attr) != 0) {
dlog(DLOG_LEVEL_ERROR, "pthread_attr_destroy failed: %s", strerror(errno));
}
/* clean up if dangling */
free(conn);
return NULL;
}
int connection_start_servers(struct v2g_context* ctx) {
int rv, tcp_started = 0;
if (ctx->tcp_socket != -1) {
rv = pthread_create(&ctx->tcp_thread, NULL, connection_server, ctx);
if (rv != 0) {
dlog(DLOG_LEVEL_ERROR, "pthread_create(tcp) failed: %s", strerror(errno));
return -1;
}
tcp_started = 1;
}
if (ctx->tls_socket.fd != -1) {
rv = tls::connection_start_server(ctx);
if (rv != 0) {
if (tcp_started) {
pthread_cancel(ctx->tcp_thread);
pthread_join(ctx->tcp_thread, NULL);
}
dlog(DLOG_LEVEL_ERROR, "pthread_create(tls) failed: %s", strerror(errno));
return -1;
}
}
return 0;
}
int create_udp_socket(const uint16_t udp_port, const char* interface_name) {
constexpr auto LINK_LOCAL_MULTICAST = "ff02::1";
int udp_socket = socket(AF_INET6, SOCK_DGRAM, 0);
if (udp_socket < 0) {
EVLOG_error << "Could not create socket: " << strerror(errno);
return udp_socket;
}
// source setup
// find port between 49152-65535
auto could_bind = false;
auto source_port = 49152;
for (; source_port < 65535; source_port++) {
sockaddr_in6 source_address = {AF_INET6, htons(source_port)};
if (bind(udp_socket, reinterpret_cast<sockaddr*>(&source_address), sizeof(sockaddr_in6)) == 0) {
could_bind = true;
break;
}
}
if (!could_bind) {
EVLOG_error << "Could not bind: " << strerror(errno);
return -1;
}
EVLOG_info << "UDP socket bound to source port: " << source_port;
const auto index = if_nametoindex(interface_name);
auto mreq = ipv6_mreq{};
mreq.ipv6mr_interface = index;
if (inet_pton(AF_INET6, LINK_LOCAL_MULTICAST, &mreq.ipv6mr_multiaddr) <= 0) {
EVLOG_error << "Failed to setup multicast address" << strerror(errno);
return -1;
}
if (setsockopt(udp_socket, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
EVLOG_error << "Could not add multicast group membership: " << strerror(errno);
return -1;
}
if (setsockopt(udp_socket, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index, sizeof(index)) < 0) {
EVLOG_error << "Could not set interface name: " << interface_name << "with error: " << strerror(errno);
}
// destination setup
sockaddr_in6 destination_address = {AF_INET6, htons(udp_port)};
if (inet_pton(AF_INET6, LINK_LOCAL_MULTICAST, &destination_address.sin6_addr) <= 0) {
EVLOG_error << "Failed to setup server address" << strerror(errno);
}
const auto connected =
connect(udp_socket, reinterpret_cast<sockaddr*>(&destination_address), sizeof(sockaddr_in6)) == 0;
if (!connected) {
EVLOG_error << "Could not connect: " << strerror(errno);
return -1;
}
return udp_socket;
}

View File

@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2022 chargebyte GmbH
// Copyright (C) 2022 Contributors to EVerest
#ifndef CONNECTION_H
#define CONNECTION_H
#include <cstddef>
#include <netinet/in.h>
#include "v2g_ctx.hpp"
/*!
* \brief initialise TCP/TLS connections
* \param ctx the V2G context
* \return 0 on success
*/
int connection_init(struct v2g_context* ctx);
/*!
* \brief start TCP/TLS servers
* \param ctx the V2G context
* \return 0 on success
*/
int connection_start_servers(struct v2g_context* ctx);
int create_udp_socket(const uint16_t udp_port, const char* interface_name);
/*!
* \brief check for V2G message sequence timeout
* \param ts_start start time
* \param ctx the V2G context
* \return true on timeout
*/
bool is_sequence_timeout(struct timespec ts_start, struct v2g_context* ctx);
/*!
* \brief connection_read This abstracts a read from the connection socket, so that higher level functions
* are not required to distinguish between TCP and TLS connections.
* \param conn v2g connection context
* \param buf buffer to store received message sequence.
* \param count number of read bytes.
* \return Returns the number of read bytes if successful, otherwise returns -1 for reading errors and
* -2 for closed connection */
ssize_t connection_read(struct v2g_connection* conn, unsigned char* buf, std::size_t count, bool read_complete);
/*!
* \brief connection_write This abstracts a write to the connection socket, so that higher level functions
* are not required to distinguish between TCP and TLS connections.
* \param conn v2g connection context
* \param buf buffer to store received message sequence.
* \param count size of the buffer
* \return Returns the number of read bytes if successful, otherwise returns -1 for reading errors and
* -2 for closed connection */
ssize_t connection_write(struct v2g_connection* conn, unsigned char* buf, std::size_t count);
void* connection_handle_tcp(void* data);
void* connection_handle(void* data);
int connection_proxy(struct v2g_connection* conn, int proxy_fd);
#endif /* CONNECTION_H */

View File

@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2022 chargebyte GmbH
// Copyright (C) 2022 Contributors to EVerest
#ifndef ISOMUX_PROXY_H
#define ISOMUX_PROXY_H
#include <arpa/inet.h>
#include <cstddef>
#include <netinet/in.h>
#include <unistd.h>
#include "../tools.hpp"
/*!
* \brief connect to a local V2G server
* \param port port to connect to
* \param proxy_if_name network interface whose IPv6 link-local address is used
* as the connection target. Pass nullptr (default) to fall back to ::1.
* \return 0 on failure, otherwise the socket
*/
inline int proxy_connect(uint16_t port, const char* proxy_if_name = nullptr) {
int sock_fd = -1;
struct sockaddr_in6 server_addr {};
/* Create socket for communication with server */
sock_fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
if (sock_fd == -1) {
perror("socket()");
return -1;
}
server_addr.sin6_family = AF_INET6;
if (proxy_if_name != nullptr && proxy_if_name[0] != '\0') {
/* Connect to the link-local address of the dedicated proxy interface */
if (get_interface_ipv6_address(proxy_if_name, ADDR6_TYPE_LINKLOCAL, &server_addr) != 0) {
perror("proxy_connect: get_interface_ipv6_address()");
close(sock_fd);
return -1;
}
} else {
/* Fall back to loopback */
inet_pton(AF_INET6, "::1", &server_addr.sin6_addr);
}
server_addr.sin6_port = htons(port);
/* Try to do TCP handshake with server */
int ret = connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret == -1) {
perror("connect()");
close(sock_fd);
return -1;
}
return sock_fd;
}
#endif /* ISOMUX_PROXY_H */

View File

@@ -0,0 +1,415 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#include "tls_connection.hpp"
#include "connection.hpp"
#include "log.hpp"
#include "v2g.hpp"
#include "v2g_server.hpp"
#include <everest/tls/tls.hpp>
#include <openssl/ssl.h>
#include <cassert>
#include <cerrno>
#include <cstring>
#include <ctime>
#include <iostream>
#include <memory>
#include <poll.h>
#include <sys/types.h>
#include <thread>
namespace {
// used when ctx->network_read_timeout_tls is 0
constexpr int default_timeout_ms = 1000;
void process_connection_thread(std::shared_ptr<tls::ServerConnection> con, struct v2g_context* ctx) {
assert(con != nullptr);
assert(ctx != nullptr);
openssl::pkey_ptr contract_public_key{nullptr, nullptr};
auto connection = std::make_unique<v2g_connection>();
connection->ctx = ctx;
connection->is_tls_connection = true;
connection->read = &tls::connection_read;
connection->write = &tls::connection_write;
connection->proxy = &tls::connection_proxy;
connection->tls_connection = con.get();
connection->pubkey = &contract_public_key;
dlog(DLOG_LEVEL_INFO, "Incoming TLS connection");
bool loop{true};
while (loop) {
loop = false;
const auto result = con->accept();
switch (result) {
case tls::Connection::result_t::success:
// TODO(james-ctc) v2g_ctx->tls_key_logging
if (ctx->state == 0) {
const auto rv = ::connection_handle(connection.get());
dlog(DLOG_LEVEL_INFO, "connection_handle exited with %d", rv);
} else {
dlog(DLOG_LEVEL_INFO, "%s", "Closing tls-connection. v2g-session is already running");
}
con->shutdown();
break;
case tls::Connection::result_t::want_read:
case tls::Connection::result_t::want_write:
loop = con->wait_for(result, default_timeout_ms) == tls::Connection::result_t::success;
break;
case tls::Connection::result_t::closed:
case tls::Connection::result_t::timeout:
default:
break;
}
}
}
void handle_new_connection_cb(tls::Server::ConnectionPtr&& con, struct v2g_context* ctx) {
assert(con != nullptr);
assert(ctx != nullptr);
// create a thread to process this connection
try {
// passing unique pointers through thread parameters is problematic
std::shared_ptr<tls::ServerConnection> connection(con.release());
std::thread connection_loop(process_connection_thread, connection, ctx);
connection_loop.detach();
} catch (const std::system_error&) {
// unable to start thread
dlog(DLOG_LEVEL_ERROR, "pthread_create() failed: %s", strerror(errno));
con->shutdown();
}
}
void server_loop_thread(struct v2g_context* ctx) {
assert(ctx != nullptr);
assert(ctx->tls_server != nullptr);
const auto res = ctx->tls_server->serve([ctx](auto con) { handle_new_connection_cb(std::move(con), ctx); });
if (res != tls::Server::state_t::stopped) {
dlog(DLOG_LEVEL_ERROR, "tls::Server failed to serve");
}
}
tls::Server::OptionalConfig configure_ssl(struct v2g_context* ctx) {
try {
dlog(DLOG_LEVEL_WARNING, "configure_ssl");
auto config = std::make_unique<tls::Server::config_t>();
// The config of interest is from Evse Security, no point in updating
// config when there is a problem
if (build_config(*config, ctx)) {
return {{std::move(config)}};
}
} catch (const std::bad_alloc&) {
dlog(DLOG_LEVEL_ERROR, "unable to create TLS config");
}
return std::nullopt;
}
} // namespace
namespace tls {
int connection_init(struct v2g_context* ctx) {
using state_t = tls::Server::state_t;
assert(ctx != nullptr);
assert(ctx->tls_server != nullptr);
assert(ctx->r_security != nullptr);
int res{-1};
tls::Server::config_t config;
// build_config can fail due to issues with Evse Security,
// this can be retried later. Not treated as an error.
(void)build_config(config, ctx);
// apply config
ctx->tls_server->stop();
ctx->tls_server->wait_stopped();
const auto result = ctx->tls_server->init(config, [ctx]() { return configure_ssl(ctx); });
if ((result == state_t::init_complete) || (result == state_t::init_socket)) {
res = 0;
}
return res;
}
int connection_start_server(struct v2g_context* ctx) {
assert(ctx != nullptr);
assert(ctx->tls_server != nullptr);
// only starts the TLS server
int res = 0;
try {
ctx->tls_server->stop();
ctx->tls_server->wait_stopped();
if (ctx->tls_server->state() == tls::Server::state_t::stopped) {
// need to re-initialise
tls::connection_init(ctx);
}
std::thread serve_loop(server_loop_thread, ctx);
serve_loop.detach();
ctx->tls_server->wait_running();
} catch (const std::system_error&) {
// unable to start thread (caller logs failure)
res = -1;
}
return res;
}
ssize_t connection_read(struct v2g_connection* conn, unsigned char* buf, const std::size_t count, bool read_complete) {
assert(conn != nullptr);
assert(conn->tls_connection != nullptr);
ssize_t result{0};
std::size_t bytes_read{0};
timespec ts_start{};
if (clock_gettime(CLOCK_MONOTONIC, &ts_start) == -1) {
dlog(DLOG_LEVEL_ERROR, "clock_gettime(ts_start) failed: %s", strerror(errno));
result = -1;
}
while ((bytes_read < count) && (result >= 0)) {
const std::size_t remaining = count - bytes_read;
std::size_t bytes_in{0};
auto* ptr = reinterpret_cast<std::byte*>(&buf[bytes_read]);
const auto read_res = conn->tls_connection->read(ptr, remaining, bytes_in);
switch (read_res) {
case tls::Connection::result_t::success:
bytes_read += bytes_in;
break;
case tls::Connection::result_t::want_read:
case tls::Connection::result_t::want_write:
conn->tls_connection->wait_for(read_res, default_timeout_ms);
break;
case tls::Connection::result_t::timeout:
// the MBedTLS code loops on timeout, is_sequence_timeout() is used instead
break;
case tls::Connection::result_t::closed:
default:
result = -1;
break;
}
if (conn->ctx->is_connection_terminated) {
dlog(DLOG_LEVEL_ERROR, "Reading from tcp-socket aborted");
conn->tls_connection->shutdown();
result = -2;
}
if (::is_sequence_timeout(ts_start, conn->ctx)) {
break;
}
if (not read_complete) {
break;
}
}
return (result < 0) ? result : static_cast<ssize_t>(bytes_read);
}
ssize_t connection_write(struct v2g_connection* conn, unsigned char* buf, std::size_t count) {
assert(conn != nullptr);
assert(conn->tls_connection != nullptr);
ssize_t result{0};
std::size_t bytes_written{0};
while ((bytes_written < count) && (result >= 0)) {
const std::size_t remaining = count - bytes_written;
std::size_t bytes_out{0};
const auto* ptr = reinterpret_cast<std::byte*>(&buf[bytes_written]);
const auto write_res = conn->tls_connection->write(ptr, remaining, bytes_out);
switch (write_res) {
case tls::Connection::result_t::success:
bytes_written += bytes_out;
break;
case tls::Connection::result_t::want_read:
case tls::Connection::result_t::want_write:
conn->tls_connection->wait_for(write_res, default_timeout_ms);
break;
case tls::Connection::result_t::timeout:
// the MBedTLS code loops on timeout
break;
case tls::Connection::result_t::closed:
default:
result = -1;
break;
}
}
if ((result == -1) && (conn->tls_connection->state() == tls::Connection::state_t::closed)) {
// if the connection has closed - return the number of bytes sent
result = 0;
}
return (result < 0) ? result : static_cast<ssize_t>(bytes_written);
}
bool build_config(tls::Server::config_t& config, struct v2g_context* ctx) {
assert(ctx != nullptr);
assert(ctx->r_security != nullptr);
using types::evse_security::CaCertificateType;
using types::evse_security::EncodingFormat;
using types::evse_security::GetCertificateInfoStatus;
using types::evse_security::LeafCertificateType;
/*
* libevse-security checks for an optional password and when one
* isn't set is uses an empty string as the password rather than nullptr.
* hence private keys are always encrypted.
*/
bool bResult{false};
config.cipher_list = "ECDHE-ECDSA-AES128-SHA256";
config.ciphersuites = ""; // disable TLS 1.3
config.verify_client = false; // contract certificate managed in-band in 15118-2
// use the existing configured socket
// TODO(james-ctc): switch to server socket init code otherwise there
// may be issues with reinitialisation
config.socket = ctx->tls_socket.fd;
config.io_timeout_ms = static_cast<std::int32_t>(ctx->network_read_timeout_tls);
config.tls_key_logging = ctx->tls_key_logging;
// information from libevse-security
const auto cert_info =
ctx->r_security->call_get_leaf_certificate_info(LeafCertificateType::V2G, EncodingFormat::PEM, false);
if (cert_info.status != GetCertificateInfoStatus::Accepted) {
dlog(DLOG_LEVEL_ERROR, "Failed to read cert_info! Not Accepted");
} else {
if (cert_info.info) {
const auto& info = cert_info.info.value();
const auto cert_path = info.certificate.value_or("");
const auto key_path = info.key;
// workaround (see above libevse-security comment)
const auto key_password = info.password.value_or("");
auto& ref = config.chains.emplace_back();
ref.certificate_chain_file = cert_path.c_str();
ref.private_key_file = key_path.c_str();
ref.private_key_password = key_password.c_str();
if (info.ocsp) {
for (const auto& ocsp : info.ocsp.value()) {
const char* file{nullptr};
if (ocsp.ocsp_path) {
file = ocsp.ocsp_path.value().c_str();
}
ref.ocsp_response_files.push_back(file);
}
}
bResult = true;
} else {
dlog(DLOG_LEVEL_ERROR, "Failed to read cert_info! Empty response");
}
}
return bResult;
}
int connection_proxy(struct v2g_connection* conn, int proxy_fd) {
dlog(DLOG_LEVEL_INFO, "Multiplexer: Proxy TLS->TCP");
int ev_fd = conn->tls_connection->socket(); // underlying socket of TLS connection
// SupportedAppProtocolReq message is still in buffer, we need to forward it to the external stack
write(proxy_fd, conn->buffer, conn->payload_len + 8);
struct pollfd poll_list[2];
poll_list[0].fd = proxy_fd;
poll_list[1].fd = ev_fd;
poll_list[0].events = POLLIN;
poll_list[1].events = POLLIN;
unsigned char buf[2048];
// Set reading to (more or less) non-blocking
conn->tls_connection->set_read_timeout(10);
while (true) {
// Note we cannot simply poll on the underlying system socket for TLS connection
// as it does not guarantee that SSL_read/write will not block after the poll
// (an SSL_read my trigger an actual write or multiple reads on the system socket)
// So we have to try a non-blocking SSL_read first, openssl will then tell us
// what to wait for on the socket before we try again (read, write or both)
auto r = conn->read(conn, buf, sizeof(buf), false);
if (r < 0) {
// something is wrong with the connection, exiting...
break;
} else if (r > 0) {
// successfully read bytes, forward to proxy module
write(proxy_fd, buf, r);
}
// check if SSL was actually waiting on write
int e = SSL_get_error(conn->tls_connection->ssl_context(), r);
if (e == SSL_ERROR_WANT_WRITE) {
poll_list[1].events = POLLIN | POLLOUT;
} else {
poll_list[1].events = POLLIN;
}
int ret = poll(poll_list, 2, -1);
if (ret == -1) {
return -1; // poll error
}
// Timed out, but we blocked forever. This could be a spurious wakeup, so just try again.
if (ret == 0) {
continue;
}
if (poll_list[0].revents & POLLIN) {
// we can read from proxy (connection to local ISO module)
int nrbytes = read(proxy_fd, buf, sizeof(buf));
if (nrbytes == 0) {
break;
}
// write data to EV
nrbytes = conn->write(conn, buf, nrbytes);
}
if (poll_list[0].revents & POLLERR or poll_list[0].revents & POLLHUP or poll_list[0].revents & POLLNVAL) {
// something is wrong with the TCP connection to the ISO module
return -1;
}
if (poll_list[1].revents & POLLIN or poll_list[1].revents & POLLOUT) {
// we can read from / write to the EV raw socket, just continue here.
// The actual SSL_read() will happen at the beginning of the loop
continue;
}
if (poll_list[1].revents & POLLERR or poll_list[1].revents & POLLHUP or poll_list[1].revents & POLLNVAL) {
// something is wrong with the TCP connection to the EV
return -1;
}
}
close(proxy_fd);
return 0;
}
} // namespace tls

View File

@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#ifndef TLS_CONNECTION_HPP_
#define TLS_CONNECTION_HPP_
#include <cstddef>
#include <everest/tls/tls.hpp>
#include <unistd.h>
struct v2g_context;
struct v2g_connection;
namespace tls {
/*!
* \param ctx v2g connection context
* \return returns 0 on succss and -1 on error
*/
int connection_init(struct v2g_context* ctx);
/*!
* \param ctx v2g connection context
* \return returns 0 on succss and -1 on error
*/
int connection_start_server(struct v2g_context* ctx);
/*!
* \brief connection_read This abstracts a read from the connection socket, so that higher level functions
* are not required to distinguish between TCP and TLS connections.
* \param conn v2g connection context
* \param buf buffer to store received message sequence.
* \param count number of read bytes.
* \return Returns the number of read bytes if successful, otherwise returns -1 for reading errors and
* -2 for closed connection */
ssize_t connection_read(struct v2g_connection* conn, unsigned char* buf, std::size_t count, bool read_complete);
/*!
* \brief connection_write This abstracts a write to the connection socket, so that higher level functions
* are not required to distinguish between TCP and TLS connections.
* \param conn v2g connection context
* \param buf buffer to store received message sequence.
* \param count size of the buffer
* \return Returns the number of read bytes if successful, otherwise returns -1 for reading errors and
* -2 for closed connection */
ssize_t connection_write(struct v2g_connection* conn, unsigned char* buf, std::size_t count);
/*!
* \brief build_config This builds the TLS server configuration based on the v2g context.
* \param config TLS server configuration to be filled
* \param ctx v2g connection context
* \return Returns true if the configuration was built successfully, otherwise false.
*/
bool build_config(tls::Server::config_t& config, struct v2g_context* ctx);
int connection_proxy(struct v2g_connection* conn, int proxy_fd);
} // namespace tls
#endif // TLS_CONNECTION_HPP_

View File

@@ -0,0 +1,42 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "iso15118_extensionsImpl.hpp"
#include "log.hpp"
namespace module {
namespace extensions {
void iso15118_extensionsImpl::init() {
if (!v2g_ctx) {
dlog(DLOG_LEVEL_ERROR, "v2g_ctx not created");
return;
}
mod->r_ext2->subscribe_iso15118_certificate_request([this](const auto o) {
if (not mod->selected_iso20()) {
publish_iso15118_certificate_request(o);
}
});
mod->r_ext20->subscribe_iso15118_certificate_request([this](const auto o) {
if (mod->selected_iso20()) {
publish_iso15118_certificate_request(o);
}
});
}
void iso15118_extensionsImpl::ready() {
}
void iso15118_extensionsImpl::handle_set_get_certificate_response(
types::iso15118::ResponseExiStreamStatus& certificate_response) {
if (mod->selected_iso20()) {
mod->r_ext20->call_set_get_certificate_response(certificate_response);
} else {
mod->r_ext20->call_set_get_certificate_response(certificate_response);
}
}
} // namespace extensions
} // namespace module

View File

@@ -0,0 +1,62 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef EXTENSIONS_ISO15118_EXTENSIONS_IMPL_HPP
#define EXTENSIONS_ISO15118_EXTENSIONS_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/iso15118_extensions/Implementation.hpp>
#include "../IsoMux.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
extern struct v2g_context* v2g_ctx;
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace extensions {
struct Conf {};
class iso15118_extensionsImpl : public iso15118_extensionsImplBase {
public:
iso15118_extensionsImpl() = delete;
iso15118_extensionsImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<IsoMux>& mod, Conf& config) :
iso15118_extensionsImplBase(ev, "extensions"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// command handler functions (virtual)
virtual void
handle_set_get_certificate_response(types::iso15118::ResponseExiStreamStatus& certificate_response) override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<IsoMux>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace extensions
} // namespace module
#endif // EXTENSIONS_ISO15118_EXTENSIONS_IMPL_HPP

View File

@@ -0,0 +1,131 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2022-2023 chargebyte GmbH
// Copyright (C) 2022-2023 Contributors to EVerest
#include "log.hpp"
#include <everest/logging.hpp> // for logging
#include <stdarg.h> // for va_list, va_{start,end}()
#include <stdio.h> // for v*printf()
#include <stdlib.h> // for atoi()
#include <string.h> // for strlen()
#include <sys/time.h> // for gettimeofday()
#include <time.h> // for strftime()
dloglevel_t minloglevel_current = DLOG_LEVEL_INFO;
static const char* debug_level_logstring_map[DLOG_LEVEL_NUMLEVELS] = {
// tailing space, no need to add it later when printing
// try to keep the strings almost same length, looks better
"[(LOG)] ", "[ERROR] ", "[WARN] ", "[INFO] ", "[DEBUG] ", "[TRACE] "};
const char* debug_level_mqtt_string_map[DLOG_LEVEL_NUMLEVELS] = {"always", "error", "warning",
"info", "debug", "trace"};
// FIXME: inline?
void dlog_func(const dloglevel_t loglevel, const char* filename, const int linenumber, const char* functionname,
const char* format, ...) {
// fast exit
if (loglevel > minloglevel_current) {
return;
}
char* format_copy = NULL;
FILE* outstream = stderr; // change output target here, if desired
struct timeval debug_tval;
struct tm tm;
char log_datetimestamp[16]; // length due to format [00:00:00.000], rounded up to fit 32-bit alignment
gettimeofday(&debug_tval, NULL); // ignore return value
size_t offset =
strftime(log_datetimestamp, sizeof(log_datetimestamp), "[%H:%M:%S", gmtime_r(&debug_tval.tv_sec, &tm));
if (offset < 1) {
// in our use of strftime(), this is an error
return;
}
snprintf(log_datetimestamp + offset, sizeof(log_datetimestamp) - offset, ".%03ld] ", debug_tval.tv_usec / 1000);
va_list args;
va_start(args, format);
// print the user given part
// strip possible newline character from user-given string
// FIXME: could be skipped
if (format) {
size_t formatlen = std::string(format).size();
format_copy = static_cast<char*>(calloc(1, formatlen + 1)); // additional byte for terminating \0
memcpy(format_copy, format, formatlen);
if ((formatlen >= 1) && (format_copy[formatlen - 1] == '\n')) {
format_copy[formatlen - 1] = '\0';
}
}
char output[256];
if (format_copy != NULL) {
vsnprintf(output, sizeof(output), format_copy, args);
}
// force EOL
fputs("\n", outstream);
fflush(outstream);
va_end(args);
if (format_copy) {
free(format_copy);
}
switch (loglevel) {
case DLOG_LEVEL_ERROR:
EVLOG_error << output;
break;
case DLOG_LEVEL_WARNING:
EVLOG_warning << output;
break;
case DLOG_LEVEL_INFO:
EVLOG_info << output;
break;
case DLOG_LEVEL_DEBUG:
EVLOG_debug << output;
break;
case DLOG_LEVEL_TRACE:
EVLOG_verbose << output;
break;
default:
EVLOG_critical << "Unknown log level";
break;
}
}
void dlog_level_inc(void) {
dloglevel_t minloglevel_new = (dloglevel_t)((int)minloglevel_current + 1);
if (minloglevel_new == DLOG_LEVEL_NUMLEVELS) {
// wrap to bottom, but not DLOG_LEVEL_ALWAYS
minloglevel_new = DLOG_LEVEL_ERROR;
}
dlog_level_set(minloglevel_new);
}
void dlog_level_set(const dloglevel_t loglevel) {
// no sanity checks currently
const dloglevel_t minloglevel_old = minloglevel_current;
dloglevel_t newloglevel = loglevel;
if (newloglevel >= DLOG_LEVEL_NUMLEVELS) {
// set something illegally high
newloglevel = (dloglevel_t)(int)(DLOG_LEVEL_NUMLEVELS - 1);
}
if (newloglevel <= DLOG_LEVEL_ALWAYS) {
// set something illegally low
newloglevel = (dloglevel_t)(int)(DLOG_LEVEL_ALWAYS + 1);
}
if (newloglevel != minloglevel_current) {
minloglevel_current = newloglevel;
dlog(DLOG_LEVEL_ALWAYS, "switched log level from %d (\"%s\") to %d (\"%s\")", minloglevel_old,
debug_level_logstring_map[minloglevel_old], newloglevel, debug_level_logstring_map[newloglevel]);
}
}
dloglevel_t dlog_level_get(void) {
return minloglevel_current;
}
static const char* dlog_level_get_string(const dloglevel_t loglevel) {
if ((loglevel < 1) || loglevel >= DLOG_LEVEL_NUMLEVELS) {
return "invalid_level";
}
return debug_level_mqtt_string_map[loglevel];
}

View File

@@ -0,0 +1,73 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2022-2023 chargebyte GmbH
// Copyright (C) 2022-2023 Contributors to EVerest
#ifndef LOG_H
#define LOG_H
/**
* @brief Describe the intended log level of a message, or the maximum level a message must have to be displayed.
*/
typedef enum dloglevel_t {
DLOG_LEVEL_ALWAYS = 0, ///< internal use only, for notification of log level change
DLOG_LEVEL_ERROR, ///< error
DLOG_LEVEL_WARNING, ///< warning, not leading to unexpected behavior such as termination
DLOG_LEVEL_INFO, ///< informational message
DLOG_LEVEL_DEBUG, ///< message to help debug daemon activity
DLOG_LEVEL_TRACE, ///< message to provide extra internal information
DLOG_LEVEL_NUMLEVELS, ///< don't use, only for internal detection of upper range
} dloglevel_t;
/**
* @brief Internal: Issue a log message. Please use the dlog() macro instead.
*
* @return void
*/
void dlog_func(const dloglevel_t loglevel, const char* filename, const int linenumber, const char* functionname,
const char* format, ...);
/**
* @brief Increase the log level to the next higher step (more messages). At the highest step, the level rolls over to
* the lowest.
*
* @return void
*/
void dlog_level_inc(void);
/**
* @brief Set the log level.
* @param[in] loglevel the log level the logger shall use, of type enum dloglevel
*
* @return void
*/
void dlog_level_set(const dloglevel_t loglevel);
/**
* @brief Get the log level.
*
* @return dloglevel_t the currently valid log level
*/
dloglevel_t dlog_level_get(void);
/**
* @brief Set the log level from an MQTT topic string.
* @param[in] loglevel the log level the logger shall use, as an MQTT string
*
* @return void
*/
// dloglevel_t dlog_level_set_from_mqtt_string(const char *level_string);
/**
* @brief Issue a log message.
*
* @param[in] level the log level this message belongs to (type enum dloglevel)
* @param[in] printf()-like format string and parameters, without tailing '\n'
*
* @return void
*/
// this is a macro, so that when dlog() is used, it gets expanded at the caller's location
#define dlog(level, ...) \
do { \
dlog_func((level), __FILE__, __LINE__, __func__, ##__VA_ARGS__); \
} while (0)
#endif /* LOG_H */

View File

@@ -0,0 +1,74 @@
description: >-
This module is a multiplexer to support switching over between different ISO module implementations
config:
device:
description: >-
Ethernet device used for HLC. Any local interface that has an ipv6
link-local and a MAC addr will work
type: string
default: eth0
tls_security:
description: >-
Controls how to handle encrypted communication
type: string
enum:
- prohibit
- allow
- force
default: allow
tls_key_logging:
description: >-
Enable/Disable the export of TLS session keys (pre-master-secret)
during a TLS handshake. This log file can be used to decrypt TLS
sessions. Note that this option is for testing and simulation
purpose only
type: boolean
default: false
tls_timeout:
description: >-
Set the TLS timeout in ms when establishing a tls connection
type: integer
default: 15000
proxy_port_iso2:
description: >-
TCP port of the local ISO2 instance
type: integer
default: 61341
proxy_port_iso20:
description: >-
TCP port of the local ISO20 instance
type: integer
default: 50000
proxy_device:
description: >-
Network interface used to reach the local ISO2/ISO20 module instances.
When set, the proxy connects to the IPv6 link-local address of this
interface. Leave empty to use the default loopback (::1).
type: string
default: ""
provides:
charger:
interface: ISO15118_charger
description: >-
This module implements the ISO15118-2 implementation of
an AC or DC charger
extensions:
interface: iso15118_extensions
description: >-
This interface is used to share data between ISO15118 and OCPP modules
to support the requirements of the OCPP protocol
requires:
security:
interface: evse_security
iso2:
interface: ISO15118_charger
iso20:
interface: ISO15118_charger
ext2:
interface: iso15118_extensions
ext20:
interface: iso15118_extensions
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Cornelius Claussen

View File

@@ -0,0 +1,337 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2022-2023 chargebyte GmbH
// Copyright (C) 2022-2023 Contributors to EVerest
#include "sdp.hpp"
#include "log.hpp"
#include <arpa/inet.h>
#include <errno.h>
#include <ifaddrs.h>
#include <inttypes.h>
#include <net/if.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define DEBUG 1
/* defines for V2G SDP implementation */
#define SDP_SRV_PORT 15118
#define SDP_VERSION 0x01
#define SDP_INVERSE_VERSION 0xfe
#define SDP_HEADER_LEN 8
#define SDP_REQUEST_PAYLOAD_LEN 2
#define SDP_RESPONSE_PAYLOAD_LEN 20
#define SDP_REQUEST_TYPE 0x9000
#define SDP_RESPONSE_TYPE 0x9001
#define POLL_TIMEOUT 20
enum sdp_security {
SDP_SECURITY_TLS = 0x00,
SDP_SECURITY_NONE = 0x10,
};
enum sdp_transport_protocol {
SDP_TRANSPORT_PROTOCOL_TCP = 0x00,
SDP_TRANSPORT_PROTOCOL_UDP = 0x10,
};
/* link-local multicast address ff02::1 aka ip6-allnodes */
#define IN6ADDR_ALLNODES \
{ 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 }
/* bundles various aspects of a SDP query */
struct sdp_query {
struct v2g_context* v2g_ctx;
struct sockaddr_in6 remote_addr;
enum sdp_security security_requested;
enum sdp_transport_protocol proto_requested;
};
/*
* Fills the SDP header into a given buffer
*/
static int sdp_write_header(uint8_t* buffer, uint16_t payload_type, uint32_t payload_len) {
int offset = 0;
buffer[offset++] = SDP_VERSION;
buffer[offset++] = SDP_INVERSE_VERSION;
/* payload is network byte order */
buffer[offset++] = (payload_type >> 8) & 0xff;
buffer[offset++] = payload_type & 0xff;
/* payload_length is network byte order */
buffer[offset++] = (payload_len >> 24) & 0xff;
buffer[offset++] = (payload_len >> 16) & 0xff;
buffer[offset++] = (payload_len >> 8) & 0xff;
buffer[offset++] = payload_len & 0xff;
return offset;
}
static int sdp_validate_header(uint8_t* buffer, uint16_t expected_payload_type, uint32_t expected_payload_len) {
uint16_t payload_type;
uint32_t payload_len;
if (buffer[0] != SDP_VERSION) {
dlog(DLOG_LEVEL_ERROR, "Invalid SDP version");
return -1;
}
if (buffer[1] != SDP_INVERSE_VERSION) {
dlog(DLOG_LEVEL_ERROR, "Invalid SDP inverse version");
return -1;
}
payload_type = (buffer[2] << 8) + buffer[3];
if (payload_type != expected_payload_type) {
dlog(DLOG_LEVEL_ERROR, "Invalid payload type: expected %" PRIu16 ", received %" PRIu16, expected_payload_type,
payload_type);
return -1;
}
payload_len = (buffer[4] << 24) + (buffer[5] << 16) + (buffer[6] << 8) + buffer[7];
if (payload_len != expected_payload_len) {
dlog(DLOG_LEVEL_ERROR, "Invalid payload length: expected %" PRIu32 ", received %" PRIu32, expected_payload_len,
payload_len);
return -1;
}
return 0;
}
int sdp_create_response(uint8_t* buffer, struct sockaddr_in6* addr, enum sdp_security security,
enum sdp_transport_protocol proto) {
int offset = SDP_HEADER_LEN;
/* fill in first the payload */
/* address is already network byte order */
memcpy(&buffer[offset], &addr->sin6_addr, sizeof(addr->sin6_addr));
offset += sizeof(addr->sin6_addr);
memcpy(&buffer[offset], &addr->sin6_port, sizeof(addr->sin6_port));
offset += sizeof(addr->sin6_port);
buffer[offset++] = security;
buffer[offset++] = proto;
/* now fill in the header with payload length */
sdp_write_header(buffer, SDP_RESPONSE_TYPE, offset - SDP_HEADER_LEN);
return offset;
}
/*
* Sends a SDP response packet
*/
int sdp_send_response(int sdp_socket, struct sdp_query* sdp_query) {
uint8_t buffer[SDP_HEADER_LEN + SDP_RESPONSE_PAYLOAD_LEN];
int rv = 0;
/* at the moment we only understand TCP protocol */
if (sdp_query->proto_requested != SDP_TRANSPORT_PROTOCOL_TCP) {
dlog(DLOG_LEVEL_ERROR, "SDP requested unsupported protocol 0x%02x, announcing nothing",
sdp_query->proto_requested);
return 1;
}
using state_t = tls::Server::state_t;
const auto tls_server_state = sdp_query->v2g_ctx->tls_server->state();
const auto tls_server_available =
(tls_server_state == state_t::init_complete or tls_server_state == state_t::running);
switch (sdp_query->security_requested) {
case SDP_SECURITY_TLS:
if (sdp_query->v2g_ctx->local_tls_addr and tls_server_available) {
dlog(DLOG_LEVEL_INFO, "SDP requested TLS, announcing TLS");
sdp_create_response(buffer, sdp_query->v2g_ctx->local_tls_addr, SDP_SECURITY_TLS,
SDP_TRANSPORT_PROTOCOL_TCP);
break;
}
if (sdp_query->v2g_ctx->local_tcp_addr) {
dlog(DLOG_LEVEL_INFO, "SDP requested TLS, announcing NO-TLS");
sdp_create_response(buffer, sdp_query->v2g_ctx->local_tcp_addr, SDP_SECURITY_NONE,
SDP_TRANSPORT_PROTOCOL_TCP);
break;
}
dlog(DLOG_LEVEL_ERROR, "SDP requested TLS, announcing nothing");
return 1;
case SDP_SECURITY_NONE:
if (sdp_query->v2g_ctx->local_tcp_addr) {
dlog(DLOG_LEVEL_INFO, "SDP requested NO-TLS, announcing NO-TLS");
sdp_create_response(buffer, sdp_query->v2g_ctx->local_tcp_addr, SDP_SECURITY_NONE,
SDP_TRANSPORT_PROTOCOL_TCP);
break;
}
if (sdp_query->v2g_ctx->local_tls_addr and tls_server_available) {
dlog(DLOG_LEVEL_INFO, "SDP requested NO-TLS, announcing TLS");
sdp_create_response(buffer, sdp_query->v2g_ctx->local_tls_addr, SDP_SECURITY_TLS,
SDP_TRANSPORT_PROTOCOL_TCP);
break;
}
dlog(DLOG_LEVEL_ERROR, "SDP requested NO-TLS, announcing nothing");
return 1;
default:
dlog(DLOG_LEVEL_ERROR, "SDP requested unsupported security 0x%02x, announcing nothing",
sdp_query->security_requested);
return 1;
}
if (sendto(sdp_socket, buffer, sizeof(buffer), 0, (struct sockaddr*)&sdp_query->remote_addr,
sizeof(struct sockaddr_in6)) != sizeof(buffer)) {
rv = -1;
}
if (DEBUG) {
char addrbuf[INET6_ADDRSTRLEN] = {0};
const char* addr;
int saved_errno = errno;
addr = inet_ntop(AF_INET6, &sdp_query->remote_addr.sin6_addr, addrbuf, sizeof(addrbuf));
if (rv == 0) {
dlog(DLOG_LEVEL_INFO, "sendto([%s]:%" PRIu16 ") succeeded", addr, ntohs(sdp_query->remote_addr.sin6_port));
} else {
dlog(DLOG_LEVEL_ERROR, "sendto([%s]:%" PRIu16 ") failed: %s", addr, ntohs(sdp_query->remote_addr.sin6_port),
strerror(saved_errno));
}
}
return rv;
}
int sdp_init(struct v2g_context* v2g_ctx) {
struct sockaddr_in6 sdp_addr = {AF_INET6, htons(SDP_SRV_PORT)};
struct ipv6_mreq mreq = {{IN6ADDR_ALLNODES}, 0};
int enable = 1;
mreq.ipv6mr_interface = if_nametoindex(v2g_ctx->if_name);
if (!mreq.ipv6mr_interface) {
dlog(DLOG_LEVEL_ERROR, "No such interface: %s", v2g_ctx->if_name);
return -1;
}
/* create receiving socket */
v2g_ctx->sdp_socket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
if (v2g_ctx->sdp_socket == -1) {
dlog(DLOG_LEVEL_ERROR, "socket() failed: %s", strerror(errno));
return -1;
}
if (setsockopt(v2g_ctx->sdp_socket, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(enable)) == -1) {
dlog(DLOG_LEVEL_ERROR, "setsockopt(SO_REUSEPORT) failed: %s", strerror(errno));
close(v2g_ctx->sdp_socket);
return -1;
}
sdp_addr.sin6_addr = in6addr_any;
if (bind(v2g_ctx->sdp_socket, (struct sockaddr*)&sdp_addr, sizeof(sdp_addr)) == -1) {
dlog(DLOG_LEVEL_ERROR, "bind() failed: %s", strerror(errno));
close(v2g_ctx->sdp_socket);
return -1;
}
dlog(DLOG_LEVEL_INFO, "SDP socket setup succeeded");
/* bind only to specified device */
if (setsockopt(v2g_ctx->sdp_socket, SOL_SOCKET, SO_BINDTODEVICE, v2g_ctx->if_name, strlen(v2g_ctx->if_name)) ==
-1) {
dlog(DLOG_LEVEL_ERROR, "setsockopt(SO_BINDTODEVICE) failed: %s", strerror(errno));
close(v2g_ctx->sdp_socket);
return -1;
}
dlog(DLOG_LEVEL_TRACE, "bind only to specified device");
/* join multicast group */
if (setsockopt(v2g_ctx->sdp_socket, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) {
dlog(DLOG_LEVEL_ERROR, "setsockopt(IPV6_JOIN_GROUP) failed: %s", strerror(errno));
close(v2g_ctx->sdp_socket);
return -1;
}
dlog(DLOG_LEVEL_TRACE, "joined multicast group");
return 0;
}
int sdp_listen(struct v2g_context* v2g_ctx) {
/* Init pollfd struct */
struct pollfd pollfd = {v2g_ctx->sdp_socket, POLLIN, 0};
while (!v2g_ctx->shutdown) {
uint8_t buffer[SDP_HEADER_LEN + SDP_REQUEST_PAYLOAD_LEN];
char addrbuf[INET6_ADDRSTRLEN] = {0};
const char* addr = addrbuf;
struct sdp_query sdp_query = {
.v2g_ctx = v2g_ctx,
};
socklen_t addrlen = sizeof(sdp_query.remote_addr);
/* Check if data was received on socket */
signed status = poll(&pollfd, 1, POLL_TIMEOUT);
if (status == -1) {
if (errno == EINTR) { // If the call did not succeed because it was interrupted
continue;
} else {
dlog(DLOG_LEVEL_ERROR, "poll() failed: %s", strerror(errno));
continue;
}
}
/* If new data was received, handle sdp request */
if (status > 0) {
ssize_t len = recvfrom(v2g_ctx->sdp_socket, buffer, sizeof(buffer), 0,
(struct sockaddr*)&sdp_query.remote_addr, &addrlen);
if (len == -1) {
if (errno != EINTR)
dlog(DLOG_LEVEL_ERROR, "recvfrom() failed: %s", strerror(errno));
continue;
}
addr = inet_ntop(AF_INET6, &sdp_query.remote_addr.sin6_addr, addrbuf, sizeof(addrbuf));
if (len != sizeof(buffer)) {
dlog(DLOG_LEVEL_WARNING, "Discarded packet from [%s]:%" PRIu16 " due to unexpected length %zd", addr,
ntohs(sdp_query.remote_addr.sin6_port), len);
continue;
}
if (sdp_validate_header(buffer, SDP_REQUEST_TYPE, SDP_REQUEST_PAYLOAD_LEN)) {
dlog(DLOG_LEVEL_WARNING, "Packet with invalid SDP header received from [%s]:%" PRIu16, addr,
ntohs(sdp_query.remote_addr.sin6_port));
continue;
}
sdp_query.security_requested = (sdp_security)buffer[SDP_HEADER_LEN + 0];
sdp_query.proto_requested = (sdp_transport_protocol)buffer[SDP_HEADER_LEN + 1];
dlog(DLOG_LEVEL_INFO, "Received packet from [%s]:%" PRIu16 " with security 0x%02x and protocol 0x%02x",
addr, ntohs(sdp_query.remote_addr.sin6_port), sdp_query.security_requested, sdp_query.proto_requested);
sdp_send_response(v2g_ctx->sdp_socket, &sdp_query);
}
}
if (close(v2g_ctx->sdp_socket) == -1) {
dlog(DLOG_LEVEL_ERROR, "close() failed: %s", strerror(errno));
}
return 0;
}

View File

@@ -0,0 +1,12 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2022-2023 chargebyte GmbH
// Copyright (C) 2022-2023 Contributors to EVerest
#ifndef SDP_H
#define SDP_H
#include "v2g.hpp"
int sdp_init(struct v2g_context* v2g_ctx);
int sdp_listen(struct v2g_context* v2g_ctx);
#endif /* SDP_H */

View File

@@ -0,0 +1,174 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2022-2023 chargebyte GmbH
// Copyright (C) 2022-2023 Contributors to EVerest
#include "tools.hpp"
#include "log.hpp"
#include <arpa/inet.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <iomanip>
#include <math.h>
#include <sstream>
#include <stdio.h>
#include <string.h>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
ssize_t safe_read(int fd, void* buf, size_t count) {
for (;;) {
ssize_t result = read(fd, buf, count);
if (result >= 0)
return result;
else if (errno == EINTR)
continue;
else
return result;
}
}
const char* choose_first_ipv6_interface() {
struct ifaddrs *ifaddr, *ifa;
char buffer[INET6_ADDRSTRLEN];
if (getifaddrs(&ifaddr) == -1)
return NULL;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (!ifa->ifa_addr)
continue;
if (ifa->ifa_addr->sa_family == AF_INET6) {
inet_ntop(AF_INET6, &ifa->ifa_addr->sa_data, buffer, sizeof(buffer));
if (strstr(buffer, "fe80") != NULL) {
return ifa->ifa_name;
}
}
}
dlog(DLOG_LEVEL_ERROR, "No necessary IPv6 link-local address was found!");
return NULL;
}
int get_interface_ipv6_address(const char* if_name, enum Addr6Type type, struct sockaddr_in6* addr) {
struct ifaddrs *ifaddr, *ifa;
int rv = -1;
if (getifaddrs(&ifaddr) == -1)
return -1;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (!ifa->ifa_addr)
continue;
if (ifa->ifa_addr->sa_family != AF_INET6)
continue;
if (strcmp(ifa->ifa_name, if_name) != 0)
continue;
/* on Linux the scope_id is interface index for link-local addresses */
switch (type) {
case ADDR6_TYPE_GLOBAL: /* no link-local address requested */
if ((reinterpret_cast<struct sockaddr_in6*>(ifa->ifa_addr))->sin6_scope_id != 0)
continue;
break;
case ADDR6_TYPE_LINKLOCAL: /* link-local address requested */
if ((reinterpret_cast<struct sockaddr_in6*>(ifa->ifa_addr))->sin6_scope_id == 0)
continue;
break;
default: /* any address of the interface requested */
/* use first found */
break;
}
memcpy(addr, ifa->ifa_addr, sizeof(*addr));
rv = 0;
goto out;
}
out:
freeifaddrs(ifaddr);
return rv;
}
#define NSEC_PER_SEC 1000000000L
void set_normalized_timespec(struct timespec* ts, time_t sec, int64_t nsec) {
while (nsec >= NSEC_PER_SEC) {
nsec -= NSEC_PER_SEC;
++sec;
}
while (nsec < 0) {
nsec += NSEC_PER_SEC;
--sec;
}
ts->tv_sec = sec;
ts->tv_nsec = nsec;
}
struct timespec timespec_sub(struct timespec lhs, struct timespec rhs) {
struct timespec ts_delta;
set_normalized_timespec(&ts_delta, lhs.tv_sec - rhs.tv_sec, lhs.tv_nsec - rhs.tv_nsec);
return ts_delta;
}
long long timespec_to_ms(struct timespec ts) {
return ((long long)ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);
}
bool get_dir_filename(char* file_name, uint8_t file_name_len, const char* path, const char* file_name_identifier) {
file_name[0] = '\0';
if (path == NULL) {
dlog(DLOG_LEVEL_ERROR, "Invalid file path");
return false;
}
DIR* d = opendir(path); // open the path
if (d == NULL) {
dlog(DLOG_LEVEL_ERROR, "Unable to open file path %s", path);
return false;
}
struct dirent* dir; // for the directory entries
uint8_t file_name_identifier_len = std::string(file_name_identifier).size();
while ((dir = readdir(d)) != NULL) {
if (dir->d_type != DT_DIR) {
/* if the type is not directory*/
if ((std::string(dir->d_name).size() > (file_name_identifier_len)) && /* Plus one for the numbering */
(strncmp(file_name_identifier, dir->d_name, file_name_identifier_len) == 0) &&
(file_name_len > std::string(dir->d_name).size())) {
strncpy(file_name, dir->d_name, std::string(dir->d_name).size() + 1);
break;
}
}
}
closedir(d);
return (file_name[0] != '\0');
}
types::iso15118::HashAlgorithm convert_to_hash_algorithm(const types::evse_security::HashAlgorithm hash_algorithm) {
switch (hash_algorithm) {
case types::evse_security::HashAlgorithm::SHA256:
return types::iso15118::HashAlgorithm::SHA256;
case types::evse_security::HashAlgorithm::SHA384:
return types::iso15118::HashAlgorithm::SHA384;
case types::evse_security::HashAlgorithm::SHA512:
return types::iso15118::HashAlgorithm::SHA512;
default:
throw std::runtime_error(
"Could not convert types::evse_security::HashAlgorithm to types::iso15118::HashAlgorithm");
}
}

View File

@@ -0,0 +1,48 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2022-2023 chargebyte GmbH
// Copyright (C) 2022-2023 Contributors to EVerest
#ifndef TOOLS_H
#define TOOLS_H
#include <generated/types/evse_security.hpp>
#include <generated/types/iso15118.hpp>
#include <netinet/in.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
enum Addr6Type {
ADDR6_TYPE_UNPSEC = -1,
ADDR6_TYPE_GLOBAL = 0,
ADDR6_TYPE_LINKLOCAL = 1,
};
const char* choose_first_ipv6_interface();
int get_interface_ipv6_address(const char* if_name, enum Addr6Type type, struct sockaddr_in6* addr);
void set_normalized_timespec(struct timespec* ts, time_t sec, int64_t nsec);
struct timespec timespec_sub(struct timespec lhs, struct timespec rhs);
long long timespec_to_ms(struct timespec ts);
/*!
* \brief get_dir_filename This function searches for a specific name (AFileNameIdentifier) in a file path and stores
* the complete name with file ending in \c AFileName
* \param file_name is the buffer to write the file name.
* \param file_name_len is the length of the buffer.
* \param path is the file path which will be used to search for the specific file.
* \param file_name_identifier is the identifier of the file (file without file ending).
* \return Returns \c true if the file could be found, otherwise \c false.
*/
bool get_dir_filename(char* file_name, uint8_t file_name_len, const char* path, const char* file_name_identifier);
/**
* \brief convert the given \p hash_algorithm to type types::iso15118::HashAlgorithm
* \param hash_algorithm
* \return types::iso15118::HashAlgorithm
*/
types::iso15118::HashAlgorithm convert_to_hash_algorithm(const types::evse_security::HashAlgorithm hash_algorithm);
#endif /* TOOLS_H */

View File

@@ -0,0 +1,189 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2022-2023 chargebyte GmbH
// Copyright (C) 2022-2023 Contributors to EVerest
#ifndef V2G_H
#define V2G_H
#include <generated/interfaces/ISO15118_charger/Implementation.hpp>
#include <generated/interfaces/evse_security/Interface.hpp>
#include <atomic>
#include <cstdint>
#include <netinet/in.h>
#include <pthread.h>
#include <everest/tls/openssl_util.hpp>
#include <everest/tls/tls.hpp>
#include <cbv2g/app_handshake/appHand_Datatypes.h>
#include <cbv2g/common/exi_basetypes.h>
#include <cbv2g/common/exi_bitstream.h>
#include <cbv2g/din/din_msgDefDatatypes.h>
#include <cbv2g/iso_2/iso2_msgDefDatatypes.h>
/* timeouts in milliseconds */
#define V2G_SEQUENCE_TIMEOUT_60S 60000 /* [V2G2-443] et.al. */
#define V2G_SEQUENCE_TIMEOUT_10S 10000
#define ISO_15118_2013_MSG_DEF "urn:iso:15118:2:2013:MsgDef"
#define ISO_15118_2013_MAJOR 2
#define ISO_15118_2010_MSG_DEF "urn:iso:15118:2:2010:MsgDef"
#define ISO_15118_2010_MAJOR 1
#define DIN_70121_MSG_DEF "urn:din:70121:2012:MsgDef"
#define DIN_70121_MAJOR 2
#define EVSE_LEAF_KEY_FILE_NAME "CPO_EVSE_LEAF.key"
#define EVSE_PROV_KEY_FILE_NAME "PROV_LEAF.key"
#define MO_ROOT_CRT_NAME "MO_ROOT_CRT"
#define V2G_ROOT_CRT_NAME "V2G_ROOT_CRT"
#define MAX_V2G_ROOT_CERTS 10
#define MAX_KEY_PW_LEN 32
#define FORCE_PUB_MSG 25 // max msg cycles when topics values must be udpated
#define MAX_PCID_LEN 17
#define DEFAULT_BUFFER_SIZE 8192
#define DEBUG 1
enum tls_security_level {
TLS_SECURITY_ALLOW = 0,
TLS_SECURITY_PROHIBIT,
TLS_SECURITY_FORCE
};
enum v2g_event {
V2G_EVENT_NO_EVENT = 0,
V2G_EVENT_TERMINATE_CONNECTION, // Terminate the connection immediately
V2G_EVENT_SEND_AND_TERMINATE, // Send next msg and terminate the connection
V2G_EVENT_SEND_RECV_EXI_MSG, // If msg must not be exi-encoded and can be sent directly
V2G_EVENT_IGNORE_MSG // Received message can't be handled
};
enum v2g_protocol {
V2G_PROTO_DIN70121 = 0,
V2G_PROTO_ISO15118_2010,
V2G_PROTO_ISO15118_2013,
V2G_PROTO_ISO15118_2015,
V2G_UNKNOWN_PROTOCOL
};
/*!
* \brief The res_msg_ids enum is a list of response msg ids
*/
enum V2gMsgTypeId {
V2G_SUPPORTED_APP_PROTOCOL_MSG = 0,
V2G_SESSION_SETUP_MSG,
V2G_SERVICE_DISCOVERY_MSG,
V2G_SERVICE_DETAIL_MSG,
V2G_PAYMENT_SERVICE_SELECTION_MSG,
V2G_PAYMENT_DETAILS_MSG,
V2G_AUTHORIZATION_MSG,
V2G_CHARGE_PARAMETER_DISCOVERY_MSG,
V2G_METERING_RECEIPT_MSG,
V2G_CERTIFICATE_UPDATE_MSG,
V2G_CERTIFICATE_INSTALLATION_MSG,
V2G_CHARGING_STATUS_MSG,
V2G_CABLE_CHECK_MSG,
V2G_PRE_CHARGE_MSG,
V2G_POWER_DELIVERY_MSG,
V2G_CURRENT_DEMAND_MSG,
V2G_WELDING_DETECTION_MSG,
V2G_SESSION_STOP_MSG,
V2G_UNKNOWN_MSG
};
/* Struct for tls-session-log-key tracing */
typedef struct keylogDebugCtx {
FILE* file;
bool inClientRandom;
bool inMasterSecret;
uint8_t hexdumpLinesToProcess;
int udp_socket;
std::string udp_buffer;
} keylogDebugCtx;
/**
* Abstracts a charging port, i.e. a power outlet in this daemon.
*
* **** NOTE ****
* Be very careful about adding C++ objects since constructors and
* destructors are not called. (see v2g_ctx_create() and calloc)
*/
struct v2g_context {
std::atomic_bool shutdown;
evse_securityIntf* r_security;
struct event* com_setup_timeout;
uint16_t proxy_port_iso2;
uint16_t proxy_port_iso20;
const char* if_name;
const char* proxy_if_name;
struct sockaddr_in6* local_tcp_addr;
struct sockaddr_in6* local_tls_addr;
std::string certs_path;
uint32_t network_read_timeout; /* in milli seconds */
uint32_t network_read_timeout_tls; /* in milli seconds */
bool selected_iso20{false};
std::atomic_bool iso20_proxy_enabled{true};
enum tls_security_level tls_security;
int sdp_socket;
int tcp_socket;
int udp_port;
int udp_socket;
pthread_t tcp_thread;
struct {
int fd;
} tls_socket;
tls::Server* tls_server;
bool tls_key_logging;
enum V2gMsgTypeId current_v2g_msg; /* holds the last v2g msg type */
int state; /* holds the current state id */
std::atomic_bool is_connection_terminated; /* Is set to true if the connection is terminated (CP State A/F, shutdown
immediately without response message) */
};
/**
* High-level abstraction of an incoming TCP/TLS connection on a certain charging port.
*/
struct v2g_connection {
pthread_t thread_id;
struct v2g_context* ctx;
bool is_tls_connection;
// used for non-TLS connections
struct {
int socket_fd;
} conn;
tls::Connection* tls_connection;
openssl::pkey_ptr* pubkey;
ssize_t (*read)(struct v2g_connection* conn, unsigned char* buf, std::size_t count, bool read_complete);
ssize_t (*write)(struct v2g_connection* conn, unsigned char* buf, std::size_t count);
int (*proxy)(struct v2g_connection* conn, int proxy_fd);
/* V2GTP EXI encoding/decoding stuff */
uint8_t* buffer;
uint32_t payload_len;
exi_bitstream_t stream;
struct appHand_exiDocument handshake_req;
struct appHand_exiDocument handshake_resp;
};
#endif /* V2G_H */

View File

@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2022-2023 chargebyte GmbH
// Copyright (C) 2022-2023 Contributors to EVerest
#include <cstdlib>
#include <cstring>
#include <dirent.h>
#include <errno.h>
#include <math.h>
#include <unistd.h> // sleep
#include "log.hpp"
#include "v2g_ctx.hpp"
#include <cbv2g/iso_2/iso2_msgDefDatatypes.h>
struct v2g_context* v2g_ctx_create(evse_securityIntf* r_security) {
struct v2g_context* ctx;
// TODO There are c++ objects within v2g_context and calloc doesn't call initialisers.
// free() will not call destructors
ctx = static_cast<v2g_context*>(calloc(1, sizeof(*ctx)));
if (!ctx)
return NULL;
ctx->r_security = r_security;
ctx->tls_security = TLS_SECURITY_PROHIBIT; // default
ctx->local_tcp_addr = NULL;
ctx->local_tls_addr = NULL;
/* interface from config file or options */
ctx->if_name = "eth1";
ctx->proxy_if_name = nullptr;
ctx->network_read_timeout = 1000;
ctx->network_read_timeout_tls = 5000;
ctx->sdp_socket = -1;
ctx->tcp_socket = -1;
ctx->tls_socket.fd = -1;
ctx->com_setup_timeout = NULL;
return ctx;
free_out:
free(ctx->local_tls_addr);
free(ctx->local_tcp_addr);
free(ctx);
return NULL;
}
void v2g_ctx_free(struct v2g_context* ctx) {
free(ctx->local_tls_addr);
ctx->local_tls_addr = NULL;
free(ctx->local_tcp_addr);
ctx->local_tcp_addr = NULL;
free(ctx);
}

View File

@@ -0,0 +1,24 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2022-2023 chargebyte GmbH
// Copyright (C) 2022-2023 Contributors to EVerest
#ifndef V2G_CTX_H
#define V2G_CTX_H
#include "v2g.hpp"
#include <stdbool.h>
#define PHY_VALUE_MULT_MIN -3
#define PHY_VALUE_MULT_MAX 3
#define PHY_VALUE_VALUE_MIN SHRT_MIN
#define PHY_VALUE_VALUE_MAX SHRT_MAX
struct v2g_context* v2g_ctx_create(evse_securityIntf* r_security);
/*!
* \brief v2g_ctx_free
* \param ctx
*/
void v2g_ctx_free(struct v2g_context* ctx);
#endif /* V2G_CTX_H */

View File

@@ -0,0 +1,180 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2023 chargebyte GmbH
// Copyright (C) 2023 Contributors to EVerest
#include "v2g_server.hpp"
#include <cstdint>
#include <cstdlib>
#include <inttypes.h>
#include <string.h>
#include <unistd.h>
#include <cbv2g/app_handshake/appHand_Decoder.h>
#include <cbv2g/app_handshake/appHand_Encoder.h>
#include <cbv2g/common/exi_basetypes.h>
#include <cbv2g/din/din_msgDefDecoder.h>
#include <cbv2g/din/din_msgDefEncoder.h>
#include <cbv2g/exi_v2gtp.h>
#include <cbv2g/iso_2/iso2_msgDefDecoder.h>
#include <cbv2g/iso_2/iso2_msgDefEncoder.h>
#include "connection.hpp"
#include "log.hpp"
#include "tools.hpp"
#define MAX_RES_TIME 98
/*!
* \brief v2g_incoming_v2gtp This function reads the V2G transport header
* \param conn hold the context of the V2G-connection.
* \return Returns 0 if the V2G-session was successfully stopped, otherwise -1.
*/
static int v2g_incoming_v2gtp(struct v2g_connection* conn) {
assert(conn != nullptr);
assert(conn->read != nullptr);
int rv;
/* read and process header */
rv = conn->read(conn, conn->buffer, V2GTP_HEADER_LENGTH, true);
if (rv < 0) {
dlog(DLOG_LEVEL_ERROR, "connection_read(header) failed: %s",
(rv == -1) ? strerror(errno) : "connection terminated");
return -1;
}
/* peer closed connection */
if (rv == 0)
return 1;
if (rv != V2GTP_HEADER_LENGTH) {
dlog(DLOG_LEVEL_ERROR, "connection_read(header) too short: expected %d, got %d", V2GTP_HEADER_LENGTH, rv);
return -1;
}
rv = V2GTP_ReadHeader(conn->buffer, &conn->payload_len);
if (rv == -1) {
dlog(DLOG_LEVEL_ERROR, "Invalid v2gtp header");
return -1;
}
if (conn->payload_len >= UINT32_MAX - V2GTP_HEADER_LENGTH) {
dlog(DLOG_LEVEL_ERROR, "Prevent integer overflow - payload too long: have %d, would need %u",
DEFAULT_BUFFER_SIZE, conn->payload_len);
return -1;
}
if (conn->payload_len + V2GTP_HEADER_LENGTH > DEFAULT_BUFFER_SIZE) {
dlog(DLOG_LEVEL_ERROR, "payload too long: have %d, would need %u", DEFAULT_BUFFER_SIZE,
conn->payload_len + V2GTP_HEADER_LENGTH);
/* we have no way to flush/discard remaining unread data from the socket without reading it in chunks,
* but this opens the chance to bind us in a "endless" read loop; so to protect us, simply close the connection
*/
return -1;
}
/* read request */
rv = conn->read(conn, &conn->buffer[V2GTP_HEADER_LENGTH], conn->payload_len, true);
if (rv < 0) {
dlog(DLOG_LEVEL_ERROR, "connection_read(payload) failed: %s",
(rv == -1) ? strerror(errno) : "connection terminated");
return -1;
}
if (rv != conn->payload_len) {
dlog(DLOG_LEVEL_ERROR, "connection_read(payload) too short: expected %d, got %d", conn->payload_len, rv);
return -1;
}
/* adjust buffer pos to decode request */
conn->stream.byte_pos = V2GTP_HEADER_LENGTH;
conn->stream.data_size = conn->payload_len + V2GTP_HEADER_LENGTH;
return 0;
}
/*!
* \brief v2g_handle_apphandshake After receiving a supportedAppProtocolReq message,
* the SECC shall process the received information. DIN [V2G-DC-436] ISO [V2G2-540]
* \param conn hold the context of the v2g-connection.
* \return Returns a v2g-event of type enum v2g_event.
*/
static bool v2g_sniff_apphandshake(struct v2g_connection* conn, bool& iso20) {
int i;
iso20 = false;
/* validate handshake request and create response */
init_appHand_exiDocument(&conn->handshake_resp);
conn->handshake_resp.supportedAppProtocolRes_isUsed = 1;
conn->handshake_resp.supportedAppProtocolRes.ResponseCode =
appHand_responseCodeType_Failed_NoNegotiation; // [V2G2-172]
dlog(DLOG_LEVEL_INFO, "Handling SupportedAppProtocolReq");
conn->ctx->current_v2g_msg = V2G_SUPPORTED_APP_PROTOCOL_MSG;
if (decode_appHand_exiDocument(&conn->stream, &conn->handshake_req) != 0) {
dlog(DLOG_LEVEL_ERROR, "decode_appHandExiDocument() failed");
return false; // If the mesage can't be decoded we have to terminate the tcp-connection
// (e.g. after an unexpected message)
}
for (i = 0; i < conn->handshake_req.supportedAppProtocolReq.AppProtocol.arrayLen; i++) {
struct appHand_AppProtocolType* app_proto = &conn->handshake_req.supportedAppProtocolReq.AppProtocol.array[i];
char* proto_ns = strndup(static_cast<const char*>(app_proto->ProtocolNamespace.characters),
app_proto->ProtocolNamespace.charactersLen);
if (!proto_ns) {
dlog(DLOG_LEVEL_ERROR, "out-of-memory condition");
return V2G_EVENT_TERMINATE_CONNECTION;
}
dlog(DLOG_LEVEL_INFO,
"handshake_req: Namespace: %s, Version: %" PRIu32 ".%" PRIu32 ", SchemaID: %" PRIu8 ", Priority: %" PRIu8,
proto_ns, app_proto->VersionNumberMajor, app_proto->VersionNumberMinor, app_proto->SchemaID,
app_proto->Priority);
// Check if it supports ISO-20
const char* iso20_urn = "urn:iso:std:iso:15118:-20";
if (strncmp(iso20_urn, proto_ns, strlen(iso20_urn)) == 0) {
iso20 = true;
free(proto_ns);
return true;
}
free(proto_ns);
}
return true;
}
bool v2g_detect_iso20_support(struct v2g_connection* conn) {
int rv = -1;
enum v2g_event rvAppHandshake = V2G_EVENT_NO_EVENT;
enum v2g_protocol selected_protocol = V2G_UNKNOWN_PROTOCOL;
/* static setup */
conn->stream.data = conn->buffer;
bool app_protocol_received = false;
do {
/* setup for receive */
conn->stream.data[0] = 0;
conn->payload_len = 0;
exi_bitstream_init(&conn->stream, conn->buffer, 0, 0, nullptr);
/* next call return -1 on error, 1 when peer closed connection, 0 on success */
rv = v2g_incoming_v2gtp(conn);
if (rv != 0) {
dlog(DLOG_LEVEL_ERROR, "v2g_incoming_v2gtp() failed");
}
if (conn->ctx->is_connection_terminated == true) {
rv = -1;
}
bool iso20 = false;
app_protocol_received = v2g_sniff_apphandshake(conn, iso20);
if (iso20) {
return true;
}
} while ((rv == 1) && not app_protocol_received);
return false;
}

View File

@@ -0,0 +1,33 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2023 chargebyte GmbH
// Copyright (C) 2023 Contributors to EVerest
#ifndef V2G_SERVER_H
#define V2G_SERVER_H
#include "v2g.hpp"
static const char* v2g_msg_type[] = {
"Supported App Protocol",
"Session Setup",
"Service Discovery",
"Service Detail",
"Payment Service Selection",
"Payment Details",
"Authorization",
"Charge Parameter Discovery",
"Metering Receipt",
"Certificate Update",
"Certificate Installation",
"Charging Status",
"Cable Check",
"Pre Charge",
"Power Delivery",
"Current Demand",
"Welding Detection",
"Session Stop",
"Unknown",
};
bool v2g_detect_iso20_support(struct v2g_connection* conn);
#endif /* V2G_SERVER_H */