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:
56
tools/EVerest-main/modules/EVSE/IsoMux/CMakeLists.txt
Normal file
56
tools/EVerest-main/modules/EVSE/IsoMux/CMakeLists.txt
Normal 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
|
||||
126
tools/EVerest-main/modules/EVSE/IsoMux/IsoMux.cpp
Normal file
126
tools/EVerest-main/modules/EVSE/IsoMux/IsoMux.cpp
Normal 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
|
||||
95
tools/EVerest-main/modules/EVSE/IsoMux/IsoMux.hpp
Normal file
95
tools/EVerest-main/modules/EVSE/IsoMux/IsoMux.hpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
687
tools/EVerest-main/modules/EVSE/IsoMux/connection/connection.cpp
Normal file
687
tools/EVerest-main/modules/EVSE/IsoMux/connection/connection.cpp
Normal 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;
|
||||
}
|
||||
@@ -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 */
|
||||
61
tools/EVerest-main/modules/EVSE/IsoMux/connection/proxy.hpp
Normal file
61
tools/EVerest-main/modules/EVSE/IsoMux/connection/proxy.hpp
Normal 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 */
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
|
||||
131
tools/EVerest-main/modules/EVSE/IsoMux/log.cpp
Normal file
131
tools/EVerest-main/modules/EVSE/IsoMux/log.cpp
Normal 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];
|
||||
}
|
||||
73
tools/EVerest-main/modules/EVSE/IsoMux/log.hpp
Normal file
73
tools/EVerest-main/modules/EVSE/IsoMux/log.hpp
Normal 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 */
|
||||
74
tools/EVerest-main/modules/EVSE/IsoMux/manifest.yaml
Normal file
74
tools/EVerest-main/modules/EVSE/IsoMux/manifest.yaml
Normal 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
|
||||
337
tools/EVerest-main/modules/EVSE/IsoMux/sdp.cpp
Normal file
337
tools/EVerest-main/modules/EVSE/IsoMux/sdp.cpp
Normal 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;
|
||||
}
|
||||
12
tools/EVerest-main/modules/EVSE/IsoMux/sdp.hpp
Normal file
12
tools/EVerest-main/modules/EVSE/IsoMux/sdp.hpp
Normal 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 */
|
||||
174
tools/EVerest-main/modules/EVSE/IsoMux/tools.cpp
Normal file
174
tools/EVerest-main/modules/EVSE/IsoMux/tools.cpp
Normal 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");
|
||||
}
|
||||
}
|
||||
48
tools/EVerest-main/modules/EVSE/IsoMux/tools.hpp
Normal file
48
tools/EVerest-main/modules/EVSE/IsoMux/tools.hpp
Normal 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 */
|
||||
189
tools/EVerest-main/modules/EVSE/IsoMux/v2g.hpp
Normal file
189
tools/EVerest-main/modules/EVSE/IsoMux/v2g.hpp
Normal 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 */
|
||||
60
tools/EVerest-main/modules/EVSE/IsoMux/v2g_ctx.cpp
Normal file
60
tools/EVerest-main/modules/EVSE/IsoMux/v2g_ctx.cpp
Normal 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);
|
||||
}
|
||||
24
tools/EVerest-main/modules/EVSE/IsoMux/v2g_ctx.hpp
Normal file
24
tools/EVerest-main/modules/EVSE/IsoMux/v2g_ctx.hpp
Normal 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 */
|
||||
180
tools/EVerest-main/modules/EVSE/IsoMux/v2g_server.cpp
Normal file
180
tools/EVerest-main/modules/EVSE/IsoMux/v2g_server.cpp
Normal 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;
|
||||
}
|
||||
33
tools/EVerest-main/modules/EVSE/IsoMux/v2g_server.hpp
Normal file
33
tools/EVerest-main/modules/EVSE/IsoMux/v2g_server.hpp
Normal 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 */
|
||||
Reference in New Issue
Block a user