Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

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

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

View File

@@ -0,0 +1,212 @@
get_target_property(GENERATED_INCLUDE_DIR generate_cpp_files EVEREST_GENERATED_INCLUDE_DIR)
find_package(libevent)
find_package(OpenSSL 3)
set(LIB_EVEREST_TLS_TESTS_DIR "${PROJECT_SOURCE_DIR}/lib/everest/tls/tests")
set(TESTS_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/tests/include")
set(TLS_TEST_FILES
alt_openssl-pki.conf
iso_pkey.asn1
openssl-pki.conf
ocsp_response.der
pki.sh
)
add_custom_command(
OUTPUT ${TLS_TEST_FILES}
COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/pki
COMMAND cd pki && cp ${TLS_TEST_FILES} ${CMAKE_CURRENT_BINARY_DIR}/
WORKING_DIRECTORY ${LIB_EVEREST_TLS_TESTS_DIR}
)
add_custom_target(v2g_test_files_target
DEPENDS ${TLS_TEST_FILES}
)
set(TLS_GTEST_NAME v2g_openssl_test)
add_executable(${TLS_GTEST_NAME})
add_dependencies(${TLS_GTEST_NAME} v2g_test_files_target)
add_dependencies(${TLS_GTEST_NAME} generate_cpp_files)
target_include_directories(${TLS_GTEST_NAME} PRIVATE
.. ../crypto
${GENERATED_INCLUDE_DIR}
${CMAKE_BINARY_DIR}/generated/modules/${MODULE_NAME}
)
target_compile_definitions(${TLS_GTEST_NAME} PRIVATE
-DUNIT_TEST
)
target_sources(${TLS_GTEST_NAME} PRIVATE
${LIB_EVEREST_TLS_TESTS_DIR}/gtest_main.cpp
log.cpp
openssl_test.cpp
../crypto/crypto_openssl.cpp
)
target_link_libraries(${TLS_GTEST_NAME} PRIVATE
GTest::gtest
cbv2g::din
cbv2g::iso2
cbv2g::tp
everest::framework
everest::evse_security
everest::tls
)
set(V2G_MAIN_NAME v2g_server)
add_executable(${V2G_MAIN_NAME})
add_dependencies(${V2G_MAIN_NAME} generate_cpp_files)
target_include_directories(${V2G_MAIN_NAME} PRIVATE
.. ../connection ${TESTS_INCLUDE_DIR}
${GENERATED_INCLUDE_DIR}
${CMAKE_BINARY_DIR}/generated/modules/${MODULE_NAME}
${CMAKE_BINARY_DIR}/generated/include
)
target_compile_definitions(${V2G_MAIN_NAME} PRIVATE
-DUNIT_TEST
)
target_sources(${V2G_MAIN_NAME} PRIVATE
../connection/connection.cpp
../connection/tls_connection.cpp
../tools.cpp
../v2g_ctx.cpp
log.cpp
requirement.cpp
v2g_main.cpp
)
target_link_libraries(${V2G_MAIN_NAME} PRIVATE
cbv2g::din
cbv2g::iso2
cbv2g::tp
everest::log
everest::framework
everest::evse_security
everest::tls
-levent -lpthread -levent_pthreads
)
# runs fine locally, fails in CI
add_test(${TLS_GTEST_NAME} ${TLS_GTEST_NAME})
ev_register_test_target(${TLS_GTEST_NAME})
set(DIN_SERVER_NAME din_server_test)
add_executable(${DIN_SERVER_NAME})
target_include_directories(${DIN_SERVER_NAME} PRIVATE
.. ../connection ${TESTS_INCLUDE_DIR}
${GENERATED_INCLUDE_DIR}
${CMAKE_BINARY_DIR}/generated/modules/${MODULE_NAME}
${CMAKE_BINARY_DIR}/generated/include
)
add_dependencies(${DIN_SERVER_NAME} generate_cpp_files)
target_compile_definitions(${DIN_SERVER_NAME} PRIVATE
-DUNIT_TEST
-DLIBEVSE_CRYPTO_SUPPLIER_OPENSSL
)
target_sources(${DIN_SERVER_NAME} PRIVATE
din_server_test.cpp
log.cpp
../din_server.cpp
../tools.cpp # TODO: Maybe mock this one
)
target_link_libraries(${DIN_SERVER_NAME}
PRIVATE
GTest::gtest_main
OpenSSL::SSL
OpenSSL::Crypto
cbv2g::din
cbv2g::iso2
cbv2g::tp
everest::framework
everest::evse_security
everest::tls
)
add_test(${DIN_SERVER_NAME} ${DIN_SERVER_NAME})
ev_register_test_target(${DIN_SERVER_NAME})
set(SDP_NAME sdp_test)
add_executable(${SDP_NAME})
target_include_directories(${SDP_NAME} PRIVATE
.. ../connection ${TESTS_INCLUDE_DIR}
${GENERATED_INCLUDE_DIR}
${CMAKE_BINARY_DIR}/generated/modules/${MODULE_NAME}
${CMAKE_BINARY_DIR}/generated/include
)
add_dependencies(${SDP_NAME} generate_cpp_files)
target_compile_definitions(${SDP_NAME} PRIVATE
-DUNIT_TEST
)
target_sources(${SDP_NAME} PRIVATE
sdp_test.cpp
log.cpp
../sdp.cpp
../tools.cpp
)
target_link_libraries(${SDP_NAME}
PRIVATE
GTest::gtest_main
cbv2g::tp
everest::framework
everest::tls
)
add_test(${SDP_NAME} ${SDP_NAME})
ev_register_test_target(${SDP_NAME})
set(V2GCTX_NAME v2g_ctx_test)
add_executable(${V2GCTX_NAME})
target_include_directories(${V2GCTX_NAME} PRIVATE
.. ../connection ${TESTS_INCLUDE_DIR}
${GENERATED_INCLUDE_DIR}
${CMAKE_BINARY_DIR}/generated/modules/${MODULE_NAME}
${CMAKE_BINARY_DIR}/generated/include
)
add_dependencies(${V2GCTX_NAME} generate_cpp_files)
target_compile_definitions(${V2GCTX_NAME} PRIVATE
-DUNIT_TEST
-DLIBEVSE_CRYPTO_SUPPLIER_OPENSSL
)
target_sources(${V2GCTX_NAME} PRIVATE
v2g_ctx_test.cpp
log.cpp
tools_test.cpp
../v2g_ctx.cpp
../tools.cpp
)
target_link_libraries(${V2GCTX_NAME}
PRIVATE
GTest::gtest_main
OpenSSL::SSL
OpenSSL::Crypto
cbv2g::din
cbv2g::iso2
cbv2g::tp
everest::framework
everest::evse_security
everest::tls
-levent -lpthread -levent_pthreads
)
add_test(${V2GCTX_NAME} ${V2GCTX_NAME})
ev_register_test_target(${V2GCTX_NAME})

View File

@@ -0,0 +1,121 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef ISO15118_CHARGERIMPLSTUB_H_
#define ISO15118_CHARGERIMPLSTUB_H_
#include <iostream>
#include <memory>
#include "ModuleAdapterStub.hpp"
#include <generated/interfaces/ISO15118_charger/Implementation.hpp>
//-----------------------------------------------------------------------------
namespace module::stub {
struct ISO15118_chargerImplStub : public ISO15118_chargerImplBase {
ISO15118_chargerImplStub(ModuleAdapterStub& adapter) : ISO15118_chargerImplBase(&adapter, "EvseV2G"){};
ISO15118_chargerImplStub(ModuleAdapterStub* adapter) : ISO15118_chargerImplBase(adapter, "EvseV2G"){};
virtual void init() {
}
virtual void ready() {
}
virtual void handle_setup(types::iso15118::EVSEID& evse_id, types::iso15118::SaeJ2847BidiMode& sae_j2847_mode,
bool& debug_mode) {
std::cout << "ISO15118_chargerImplBase::handle_setup called" << std::endl;
}
virtual void handle_update_energy_transfer_modes(
std::vector<types::iso15118::EnergyTransferMode>& supported_energy_transfer_modes) {
std::cout << "ISO15118_chargerImplBase::handle_update_energy_transfer_modes called" << std::endl;
}
virtual void handle_set_charging_parameters(types::iso15118::SetupPhysicalValues& physical_values) {
std::cout << "ISO15118_chargerImplBase::handle_set_charging_parameters called" << std::endl;
}
virtual void handle_session_setup(std::vector<types::iso15118::PaymentOption>& payment_options,
bool& supported_certificate_service, bool& central_contract_validation_allowed) {
std::cout << "ISO15118_chargerImplBase::handle_session_setup called" << std::endl;
}
virtual void handle_authorization_response(types::authorization::AuthorizationStatus& authorization_status,
types::authorization::CertificateStatus& certificate_status) {
std::cout << "ISO15118_chargerImplBase::handle_authorization_response called" << std::endl;
}
virtual void handle_bpt_setup(types::iso15118::BptSetup& config) {
std::cout << "ISO15118_chargerImplBase::handle_bpt_setup called" << std::endl;
}
virtual void handle_set_powersupply_capabilities(types::power_supply_DC::Capabilities& capabilities) {
std::cout << "ISO15118_chargerImplBase::handle_set_powersupply_capabilities called" << std::endl;
}
virtual void handle_ac_contactor_closed(bool& status) {
std::cout << "ISO15118_chargerImplBase::handle_ac_contactor_closed called" << std::endl;
}
virtual void handle_dlink_ready(bool& value) {
std::cout << "ISO15118_chargerImplBase::handle_dlink_ready called" << std::endl;
}
virtual void handle_cable_check_finished(bool& status) {
std::cout << "ISO15118_chargerImplBase::handle_cable_check_finished called" << std::endl;
}
virtual void handle_receipt_is_required(bool& receipt_required) {
std::cout << "ISO15118_chargerImplBase::handle_receipt_is_required called" << std::endl;
}
virtual void handle_stop_charging(bool& stop) {
std::cout << "ISO15118_chargerImplBase::handle_stop_charging called" << std::endl;
}
virtual void handle_pause_charging(bool& pause) {
std::cout << "ISO15118_chargerImplBase::handle_pause_charging called" << std::endl;
}
virtual void handle_no_energy_pause_charging(types::iso15118::NoEnergyPauseMode& mode) {
std::cout << "ISO15118_chargerImplBase::handle_no_energy_pause_charging called" << std::endl;
}
virtual bool
handle_update_supported_app_protocols(types::iso15118::SupportedAppProtocols& supported_app_protocols) {
std::cout << "ISO15118_chargerImplBase::handle_update_supported_app_protocols called" << std::endl;
return true;
}
virtual void handle_update_ac_max_current(double& max_current) {
std::cout << "ISO15118_chargerImplBase::handle_update_ac_max_current called" << std::endl;
}
virtual void handle_update_ac_parameters(types::iso15118::AcParameters& ac_parameters) {
std::cout << "ISO15118_chargerImplBase::handle_update_ac_parameters called" << std::endl;
}
virtual void handle_update_ac_maximum_limits(types::iso15118::AcEvseMaximumPower& maximum_limits) {
std::cout << "ISO15118_chargerImplBase::handle_update_ac_maximum_limits called" << std::endl;
}
virtual void handle_update_ac_minimum_limits(types::iso15118::AcEvseMinimumPower& minimum_limits) {
std::cout << "ISO15118_chargerImplBase::handle_update_ac_minimum_limits called" << std::endl;
}
virtual void handle_update_ac_target_values(types::iso15118::AcTargetValues& target_values) {
std::cout << "ISO15118_chargerImplBase::handle_update_ac_target_values called" << std::endl;
}
virtual void handle_update_ac_present_power(types::units::Power& present_power) {
std::cout << "ISO15118_chargerImplBase::handle_update_ac_present_power called" << std::endl;
}
virtual void handle_update_dc_maximum_limits(types::iso15118::DcEvseMaximumLimits& maximum_limits) {
std::cout << "ISO15118_chargerImplBase::handle_update_dc_maximum_limits called" << std::endl;
}
virtual void handle_update_dc_minimum_limits(types::iso15118::DcEvseMinimumLimits& minimum_limits) {
std::cout << "ISO15118_chargerImplBase::handle_update_dc_minimum_limits called" << std::endl;
}
virtual void handle_update_isolation_status(types::iso15118::IsolationStatus& isolation_status) {
std::cout << "ISO15118_chargerImplBase::handle_update_isolation_status called" << std::endl;
}
virtual void
handle_update_dc_present_values(types::iso15118::DcEvsePresentVoltageCurrent& present_voltage_current) {
std::cout << "ISO15118_chargerImplBase::handle_update_dc_present_values called" << std::endl;
}
virtual void handle_update_meter_info(types::powermeter::Powermeter& powermeter) {
std::cout << "ISO15118_chargerImplBase::handle_update_meter_info called" << std::endl;
}
virtual void handle_send_error(types::iso15118::EvseError& error) {
std::cout << "ISO15118_chargerImplBase::handle_send_error called" << std::endl;
}
virtual void handle_reset_error() {
std::cout << "ISO15118_chargerImplBase::handle_reset_error called" << std::endl;
}
};
} // namespace module::stub
#endif // ISO15118_CHARGERIMPLSTUB_H_

View File

@@ -0,0 +1,51 @@
# Tests
Building tests:
```sh
$ cd EVerest
$ mkdir build
$ cd build
$ cmake -GNinja -DEVEREST_CORE_BUILD_TESTING=ON ..
$ ninja install
```
`touch release.json` may be needed if it hasn't been created
(then re-run `ninja install`).
## Run EVerest in SIL
1. start MQTT broker
2. from `build/run-scripts` run `./run-sil-dc-tls.sh`
3. from `build/run-scripts` run `./nodered-sil-dc.sh`
4. open web browser [EVerest Node-RED dashboard](http://localhost:1880/ui/)
## Unit tests
- `./v2g_openssl_test`
- automatically runs `pki.sh`
- run from the directory containing the executable
### Standalone V2G TLS server
Tests the Server class via the functions in connection.cpp and
tls_connection.cpp.
- `./v2g_server -i <interface name>`
- connects to IPv6 only with a link local address
- requires `boost` library so LD_LIBRARY_PATH may need to be set
- displays the address it is listening on. e.g.
`[fe80::ae91:a1ff:fec9:a947%3]:64109`
- supports multiple connections
- gracefully terminates after 80 seconds
- `valgrind` can be used to check memory allocations
(has some leaks - possibly in v2g_ctx_start_events thread)
- requires client certificate
- s_client echos back what is typed with a delay since V2G has a long timeout
The connect argument must match what was displayed by `v2g_server`
```sh
openssl s_client -connect [fe80::ae91:a1ff:fec9:a947%3]:64109 -verify 2 -CAfile server_root_cert.pem -cert client_cert.pem -cert_chain client_chain.pem -key client_priv.pem -verify_return_error -verify_hostname evse.pionix.de -status
```

View File

@@ -0,0 +1,563 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <din_server.hpp>
#include <cstring>
#include <gtest/gtest.h>
#include "ISO15118_chargerImplStub.hpp"
#include "cbv2g/din/din_msgDefDatatypes.h"
#include "utest_log.hpp"
#include "v2g.hpp"
#include <memory>
void publish_dc_ev_maximum_limits(struct v2g_context* ctx, const float& v2g_dc_ev_max_current_limit,
const unsigned int& v2g_dc_ev_max_current_limit_is_used,
const float& v2g_dc_ev_max_power_limit,
const unsigned int& v2g_dc_ev_max_power_limit_is_used,
const float& v2g_dc_ev_max_voltage_limit,
const unsigned int& v2g_dc_ev_max_voltage_limit_is_used) {
}
void stop_timer(struct event** event_timer, char const* const timer_name, struct v2g_context* ctx) {
}
void log_selected_energy_transfer_type(int selected_energy_transfer_mode) {
}
uint64_t v2g_session_id_from_exi(bool is_iso, void* exi_in) {
return 0;
}
void publish_dc_ev_target_voltage_current(struct v2g_context* ctx, const float& v2g_dc_ev_target_voltage,
const float& v2g_dc_ev_target_current) {
}
void publish_dc_ev_remaining_time(struct v2g_context* ctx, const float& v2g_dc_ev_remaining_time_to_full_soc,
const unsigned int& v2g_dc_ev_remaining_time_to_full_soc_is_used,
const float& v2g_dc_ev_remaining_time_to_bulk_soc,
const unsigned int& v2g_dc_ev_remaining_time_to_bulk_soc_is_used) {
}
namespace {
class DinServerTest : public testing::Test {
protected:
std::unique_ptr<v2g_connection> conn;
std::unique_ptr<v2g_context> ctx;
std::unique_ptr<din_exiDocument> exi_in;
std::unique_ptr<din_exiDocument> exi_out;
module::stub::ModuleAdapterStub adapter;
module::stub::ISO15118_chargerImplStub charger;
DinServerTest() : charger(adapter) {
}
void SetUp() override {
conn = std::make_unique<v2g_connection>();
ctx = std::make_unique<v2g_context>();
exi_in = std::make_unique<din_exiDocument>();
exi_out = std::make_unique<din_exiDocument>();
module::stub::clear_logs();
conn->ctx = ctx.get();
conn->ctx->p_charger = &charger;
conn->exi_in.dinEXIDocument = exi_in.get();
conn->exi_out.dinEXIDocument = exi_out.get();
}
void TearDown() override {
}
};
class DinServerTestValidateResponseCode
: public DinServerTest,
public testing::WithParamInterface<
std::tuple<int /*din_responseCodeType*/, bool, bool, bool, int /*V2gMsgTypeId*/, uint64_t, uint64_t, bool>> {
};
// For all test cases:
// TODO: Define helper functions to set the conn and ctx variables
// ----------------------------------------------------------------
// Potential test for SessionSetup:
// Bad Case:
// Setting no EvseID -> A check should be added -> But a default value is in ctx provided.
TEST_F(DinServerTest, session_setup_generating_new_session_id) {
// Setting up session_setup_req
auto& session_setup_req = exi_in->V2G_Message.Body.SessionSetupReq;
exi_in->V2G_Message.Body.SessionSetupReq_isUsed = true;
init_din_SessionSetupReqType(&session_setup_req);
const uint8_t evcc_id[8] = {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11};
memcpy(session_setup_req.EVCCID.bytes, evcc_id, sizeof(evcc_id));
session_setup_req.EVCCID.bytesLen = sizeof(evcc_id);
// Setting up conn
ctx->current_v2g_msg = V2G_SESSION_SETUP_MSG;
ctx->evse_v2g_data.session_id = 0;
ctx->evse_v2g_data.date_time_now_is_used = 0;
ctx->ev_v2g_data.received_session_id = 0;
std::string evse_id = std::string("DE*PNX*TET1*234");
strcpy(reinterpret_cast<char*>(ctx->evse_v2g_data.evse_id.bytes), evse_id.data());
ctx->evse_v2g_data.evse_id.bytesLen = evse_id.size();
// Setting up session_setup_res
auto& session_setup_res = exi_out->V2G_Message.Body.SessionSetupRes;
exi_out->V2G_Message.Body.SessionSetupRes_isUsed = 1u;
init_din_SessionSetupResType(&session_setup_res);
EXPECT_EQ(states::handle_din_session_setup(conn.get()), V2G_EVENT_NO_EVENT);
EXPECT_EQ(module::stub::get_logs(dloglevel_t::DLOG_LEVEL_ERROR).size(), 0);
EXPECT_EQ(module::stub::get_logs(dloglevel_t::DLOG_LEVEL_INFO).size(), 3);
EXPECT_EQ(session_setup_res.DateTimeNow_isUsed, false);
// Checking if session id is generated
EXPECT_GT(ctx->evse_v2g_data.session_id, 0);
// Checking if evse id was set correctly
EXPECT_EQ(evse_id, std::string(reinterpret_cast<char*>(session_setup_res.EVSEID.bytes)));
}
TEST_F(DinServerTest, session_setup_old_session_id) {
// Setting up session_setup_req
auto& session_setup_req = exi_in->V2G_Message.Body.SessionSetupReq;
exi_in->V2G_Message.Body.SessionSetupReq_isUsed = true;
init_din_SessionSetupReqType(&session_setup_req);
const uint8_t evcc_id[8] = {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11};
memcpy(session_setup_req.EVCCID.bytes, evcc_id, sizeof(evcc_id));
session_setup_req.EVCCID.bytesLen = sizeof(evcc_id);
// Setting up conn
ctx->current_v2g_msg = V2G_SESSION_SETUP_MSG;
ctx->evse_v2g_data.session_id = 4158610156;
ctx->evse_v2g_data.date_time_now_is_used = 0;
ctx->ev_v2g_data.received_session_id = 0;
std::string evse_id = std::string("DE*PNX*TET1*234");
strcpy(reinterpret_cast<char*>(ctx->evse_v2g_data.evse_id.bytes), evse_id.data());
ctx->evse_v2g_data.evse_id.bytesLen = evse_id.size();
// Setting up session_setup_res
auto& session_setup_res = exi_out->V2G_Message.Body.SessionSetupRes;
exi_out->V2G_Message.Body.SessionSetupRes_isUsed = 1u;
init_din_SessionSetupResType(&session_setup_res);
EXPECT_EQ(states::handle_din_session_setup(conn.get()), V2G_EVENT_NO_EVENT);
EXPECT_EQ(module::stub::get_logs(dloglevel_t::DLOG_LEVEL_ERROR).size(), 0);
EXPECT_EQ(module::stub::get_logs(dloglevel_t::DLOG_LEVEL_INFO).size(), 2);
EXPECT_EQ(session_setup_res.DateTimeNow_isUsed, false);
// Checking if session id is generated
EXPECT_EQ(ctx->evse_v2g_data.session_id, 4158610156);
// Checking if evse id was set correctly
EXPECT_EQ(evse_id, std::string(reinterpret_cast<char*>(session_setup_res.EVSEID.bytes)));
}
TEST_F(DinServerTest, session_setup_datetime_is_used) {
// Setting up session_setup_req
auto& session_setup_req = exi_in->V2G_Message.Body.SessionSetupReq;
exi_in->V2G_Message.Body.SessionSetupReq_isUsed = true;
init_din_SessionSetupReqType(&session_setup_req);
const uint8_t evcc_id[8] = {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11};
memcpy(session_setup_req.EVCCID.bytes, evcc_id, sizeof(evcc_id));
session_setup_req.EVCCID.bytesLen = sizeof(evcc_id);
// Setting up conn
ctx->current_v2g_msg = V2G_SESSION_SETUP_MSG;
ctx->evse_v2g_data.session_id = 0;
ctx->evse_v2g_data.date_time_now_is_used = true;
ctx->ev_v2g_data.received_session_id = 0;
std::string evse_id = std::string("DE*PNX*TET1*234");
strcpy(reinterpret_cast<char*>(ctx->evse_v2g_data.evse_id.bytes), evse_id.data());
ctx->evse_v2g_data.evse_id.bytesLen = evse_id.size();
// Setting up session_setup_res
auto& session_setup_res = exi_out->V2G_Message.Body.SessionSetupRes;
exi_out->V2G_Message.Body.SessionSetupRes_isUsed = 1u;
init_din_SessionSetupResType(&session_setup_res);
EXPECT_EQ(states::handle_din_session_setup(conn.get()), V2G_EVENT_NO_EVENT);
EXPECT_EQ(module::stub::get_logs(dloglevel_t::DLOG_LEVEL_ERROR).size(), 0);
EXPECT_EQ(module::stub::get_logs(dloglevel_t::DLOG_LEVEL_INFO).size(), 3);
EXPECT_EQ(session_setup_res.DateTimeNow_isUsed, true);
EXPECT_GT(session_setup_res.DateTimeNow, 0);
// Checking if session id is generated
EXPECT_GT(ctx->evse_v2g_data.session_id, 0);
// Checking if evse id was set correctly
EXPECT_EQ(evse_id, std::string(reinterpret_cast<char*>(session_setup_res.EVSEID.bytes)));
}
TEST_F(DinServerTest, din_service_discovery_good_case) {
// TODO(sl): Maybe add this to check exi_out proberly
exi_out->V2G_Message.Body.ServiceDiscoveryRes_isUsed = true;
init_din_ServiceDiscoveryResType(&exi_out->V2G_Message.Body.ServiceDiscoveryRes);
// TODO: Setting the correct session_id + received_session_id via functions
EXPECT_EQ(states::handle_din_service_discovery(conn.get()), V2G_EVENT_NO_EVENT);
EXPECT_EQ(module::stub::get_logs(dloglevel_t::DLOG_LEVEL_ERROR).size(), 1);
EXPECT_EQ(module::stub::get_logs(dloglevel_t::DLOG_LEVEL_INFO).size(), 0);
}
TEST_F(DinServerTest, handle_din_contract_authentication_check_evse_processing_finished) {
// TODO: set a prober session id
ctx->evse_v2g_data.session_id = 0;
ctx->evse_v2g_data.date_time_now_is_used = 0;
ctx->current_v2g_msg = V2G_AUTHORIZATION_MSG;
ctx->ev_v2g_data.received_session_id = 0;
ctx->evse_v2g_data.evse_processing[PHASE_AUTH] = 0;
EXPECT_EQ(states::handle_din_contract_authentication(conn.get()), V2G_EVENT_NO_EVENT); // TODO
auto& res = exi_out->V2G_Message.Body.ContractAuthenticationRes;
// EXPECT_EQ(res.ResponseCode, din_responseCodeType_OK);
EXPECT_EQ(res.EVSEProcessing, din_EVSEProcessingType_Finished);
EXPECT_EQ(ctx->state, WAIT_FOR_CHARGEPARAMETERDISCOVERY);
EXPECT_EQ(module::stub::get_logs(dloglevel_t::DLOG_LEVEL_ERROR).size(), 1);
EXPECT_EQ(module::stub::get_logs(dloglevel_t::DLOG_LEVEL_INFO).size(), 0);
}
TEST_F(DinServerTest, handle_din_contract_authentication_check_evse_processing_ongoing) {
// TODO: set a prober session id
ctx->evse_v2g_data.session_id = 0;
ctx->evse_v2g_data.date_time_now_is_used = 0;
ctx->current_v2g_msg = V2G_AUTHORIZATION_MSG;
ctx->ev_v2g_data.received_session_id = 0;
ctx->evse_v2g_data.evse_processing[PHASE_AUTH] = 1;
EXPECT_EQ(states::handle_din_contract_authentication(conn.get()), V2G_EVENT_NO_EVENT); // TODO
auto& res = exi_out->V2G_Message.Body.ContractAuthenticationRes;
// EXPECT_EQ(res.ResponseCode, din_responseCodeType_OK);
EXPECT_EQ(res.EVSEProcessing, din_EVSEProcessingType_Ongoing);
EXPECT_EQ(ctx->state, WAIT_FOR_AUTHORIZATION);
EXPECT_EQ(module::stub::get_logs(dloglevel_t::DLOG_LEVEL_ERROR).size(), 1);
EXPECT_EQ(module::stub::get_logs(dloglevel_t::DLOG_LEVEL_INFO).size(), 0);
}
// if not otherwise specified, the following testcases are happy paths
TEST_F(DinServerTest, din_validate_response_code_TERMINATE_CONNECTION) {
// which response code is actually irrelevant here and was picked at random
auto tmp = din_responseCodeType_FAILED_TariffSelectionInvalid;
// only this bool determines the outcome
ctx->is_connection_terminated = true;
EXPECT_EQ(utils::din_validate_response_code(&tmp, conn.get()), V2G_EVENT_TERMINATE_CONNECTION);
}
TEST_F(DinServerTest, din_validate_response_code_EVENT_NO_EVENT_failed_response_FAILED) {
// which response code is actually irrelevant here and was picked at random
// FAILED code
auto tmp = din_responseCodeType_FAILED_ChallengeInvalid;
ctx->is_connection_terminated = false;
ctx->terminate_connection_on_failed_response = false;
EXPECT_EQ(utils::din_validate_response_code(&tmp, conn.get()), V2G_EVENT_NO_EVENT);
}
TEST_F(DinServerTest, din_validate_response_code_EVENT_NO_EVENT_failed_response_OK) {
// which response code is actually irrelevant here and was picked at random
// OK code
auto tmp = din_responseCodeType_OK;
ctx->is_connection_terminated = false;
ctx->terminate_connection_on_failed_response = false;
EXPECT_EQ(utils::din_validate_response_code(&tmp, conn.get()), V2G_EVENT_NO_EVENT);
}
TEST_F(DinServerTest, din_validate_response_code_EVENT_NO_EVENT_OK) {
// which response code is actually irrelevant here and was picked at random
// OK code
auto tmp = din_responseCodeType_OK;
ctx->is_connection_terminated = false;
ctx->stop_hlc = false; //||
ctx->intl_emergency_shutdown = false;
ctx->current_v2g_msg = V2G_SESSION_SETUP_MSG; // &&
ctx->evse_v2g_data.session_id = 1;
ctx->ev_v2g_data.received_session_id = 2;
ctx->terminate_connection_on_failed_response = true;
EXPECT_EQ(utils::din_validate_response_code(&tmp, conn.get()), V2G_EVENT_NO_EVENT);
}
TEST_F(DinServerTest, din_validate_response_code_EVENT_NO_EVENT_OK_bad_path_1) {
// OK code
auto tmp = din_responseCodeType_OK;
ctx->is_connection_terminated = false;
ctx->stop_hlc = true; //||
ctx->intl_emergency_shutdown = false;
ctx->current_v2g_msg = V2G_SESSION_SETUP_MSG; // &&
ctx->evse_v2g_data.session_id = 1;
ctx->ev_v2g_data.received_session_id = 2;
ctx->terminate_connection_on_failed_response = true;
EXPECT_NE(utils::din_validate_response_code(&tmp, conn.get()), V2G_EVENT_NO_EVENT);
}
TEST_F(DinServerTest, din_validate_response_code_EVENT_NO_EVENT_OK_bad_path_2) {
// OK code
auto tmp = din_responseCodeType_OK;
ctx->is_connection_terminated = false;
ctx->stop_hlc = false; //||
ctx->intl_emergency_shutdown = true;
ctx->current_v2g_msg = V2G_SESSION_SETUP_MSG; // &&
ctx->evse_v2g_data.session_id = 1;
ctx->ev_v2g_data.received_session_id = 2;
ctx->terminate_connection_on_failed_response = true;
EXPECT_NE(utils::din_validate_response_code(&tmp, conn.get()), V2G_EVENT_NO_EVENT);
}
TEST_F(DinServerTest, din_validate_response_code_EVENT_NO_EVENT_OK_bad_path_3) {
// OK code
auto tmp = din_responseCodeType_OK;
ctx->is_connection_terminated = false;
ctx->stop_hlc = false; //||
ctx->intl_emergency_shutdown = false;
ctx->current_v2g_msg = V2G_CERTIFICATE_INSTALLATION_MSG; // &&
ctx->evse_v2g_data.session_id = 1;
ctx->ev_v2g_data.received_session_id = 2;
ctx->terminate_connection_on_failed_response = true;
EXPECT_NE(utils::din_validate_response_code(&tmp, conn.get()), V2G_EVENT_NO_EVENT);
}
TEST_F(DinServerTest, din_validate_response_code_EVENT_NO_EVENT_OK_bad_path_4) {
// OK code
auto tmp = din_responseCodeType_OK;
ctx->is_connection_terminated = false;
ctx->stop_hlc = false; //||
ctx->intl_emergency_shutdown = true;
ctx->current_v2g_msg = V2G_SESSION_SETUP_MSG; // &&
ctx->evse_v2g_data.session_id = 6;
ctx->ev_v2g_data.received_session_id = 6;
ctx->terminate_connection_on_failed_response = true;
EXPECT_NE(utils::din_validate_response_code(&tmp, conn.get()), V2G_EVENT_NO_EVENT);
}
TEST_F(DinServerTest, din_validate_response_code_EVENT_SEND_AND_TERMINATE_1) {
auto tmp = din_responseCodeType_FAILED_WrongEnergyTransferType;
ctx->is_connection_terminated = false;
ctx->stop_hlc = true; //||
ctx->intl_emergency_shutdown = false;
ctx->current_v2g_msg = V2G_METERING_RECEIPT_MSG; // &&
ctx->evse_v2g_data.session_id = 6;
ctx->ev_v2g_data.received_session_id = 6;
ctx->terminate_connection_on_failed_response = true;
EXPECT_EQ(utils::din_validate_response_code(&tmp, conn.get()), V2G_EVENT_SEND_AND_TERMINATE);
}
TEST_F(DinServerTest, din_validate_response_code_EVENT_SEND_AND_TERMINATE_2) {
auto tmp = din_responseCodeType_FAILED_MeteringSignatureNotValid;
ctx->is_connection_terminated = false;
ctx->stop_hlc = false; //||
ctx->intl_emergency_shutdown = true;
ctx->current_v2g_msg = V2G_CHARGING_STATUS_MSG; // &&
ctx->evse_v2g_data.session_id = 1;
ctx->ev_v2g_data.received_session_id = 6;
ctx->terminate_connection_on_failed_response = true;
EXPECT_EQ(utils::din_validate_response_code(&tmp, conn.get()), V2G_EVENT_SEND_AND_TERMINATE);
}
TEST_F(DinServerTest, din_validate_response_code_EVENT_SEND_AND_TERMINATE_3) {
auto tmp = din_responseCodeType_OK_CertificateExpiresSoon;
ctx->is_connection_terminated = false;
ctx->stop_hlc = false; //||
ctx->intl_emergency_shutdown = false;
ctx->current_v2g_msg = V2G_UNKNOWN_MSG; // &&
ctx->evse_v2g_data.session_id = 1;
ctx->ev_v2g_data.received_session_id = 1;
ctx->terminate_connection_on_failed_response = true;
EXPECT_EQ(utils::din_validate_response_code(&tmp, conn.get()), V2G_EVENT_SEND_AND_TERMINATE);
}
TEST_F(DinServerTest, din_validate_response_code_EVENT_SEND_AND_TERMINATE_4) {
auto tmp = din_responseCodeType_OK_CertificateExpiresSoon;
ctx->is_connection_terminated = false;
ctx->stop_hlc = false; //||
ctx->intl_emergency_shutdown = false;
ctx->current_v2g_msg = V2G_UNKNOWN_MSG; // &&
ctx->evse_v2g_data.session_id = 1;
ctx->ev_v2g_data.received_session_id = 1;
ctx->terminate_connection_on_failed_response = true;
EXPECT_EQ(utils::din_validate_response_code(&tmp, conn.get()), V2G_EVENT_SEND_AND_TERMINATE);
}
TEST_F(DinServerTest, din_validate_response_code_EVENT_SEND_AND_TERMINATE_5) {
auto tmp = din_responseCodeType_OK_CertificateExpiresSoon;
ctx->is_connection_terminated = false;
ctx->stop_hlc = false; //||
ctx->intl_emergency_shutdown = false;
ctx->current_v2g_msg = V2G_CABLE_CHECK_MSG; // &&
ctx->evse_v2g_data.session_id = 1;
ctx->ev_v2g_data.received_session_id = 2;
ctx->terminate_connection_on_failed_response = true;
EXPECT_EQ(utils::din_validate_response_code(&tmp, conn.get()), V2G_EVENT_SEND_AND_TERMINATE);
}
TEST_F(DinServerTest, din_validate_response_code_EVENT_SEND_AND_TERMINATE_6) {
auto tmp = din_responseCodeType_FAILED_SequenceError;
ctx->is_connection_terminated = false;
ctx->stop_hlc = false; //||
ctx->intl_emergency_shutdown = false;
ctx->current_v2g_msg = V2G_SESSION_SETUP_MSG; // &&
ctx->evse_v2g_data.session_id = 1;
ctx->ev_v2g_data.received_session_id = 2;
ctx->terminate_connection_on_failed_response = true;
EXPECT_EQ(utils::din_validate_response_code(&tmp, conn.get()), V2G_EVENT_SEND_AND_TERMINATE);
}
TEST_F(DinServerTest, din_validate_response_code_V2G_DC_390) {
// The response message shall contain the ResponseCode “FAILED_SequenceError” if the
// SECC has received an unexpected request message.
auto given_response_code = din_responseCodeType_OK;
constexpr auto expected_response_code = din_responseCodeType_FAILED_SequenceError;
constexpr auto expected_response = V2G_EVENT_NO_EVENT;
ctx->is_connection_terminated = false;
ctx->terminate_connection_on_failed_response = false;
ctx->current_v2g_msg = V2G_UNKNOWN_MSG;
EXPECT_EQ(utils::din_validate_response_code(&given_response_code, conn.get()), expected_response);
// given response code should change in the function call
EXPECT_EQ(given_response_code, expected_response_code);
}
TEST_F(DinServerTest, din_validate_response_code_V2G_DC_391) {
// The response message shall contain the ResponseCode “FAILED_UnknownSession” if the
// SessionID in a request message does not match the SessionID provided by the SECC in the SessionSetupRes
// message.
auto given_response_code = din_responseCodeType_OK;
constexpr auto expected_response_code = din_responseCodeType_FAILED_UnknownSession;
constexpr auto expected_response = V2G_EVENT_NO_EVENT;
ctx->is_connection_terminated = false;
ctx->terminate_connection_on_failed_response = false;
ctx->evse_v2g_data.session_id = 1234;
ctx->ev_v2g_data.received_session_id = 5678;
EXPECT_EQ(utils::din_validate_response_code(&given_response_code, conn.get()), expected_response);
// given response code should change in the function call
EXPECT_EQ(given_response_code, expected_response_code);
}
TEST_F(DinServerTest, din_validate_response_code_V2G_DC_665) {
// If the SECC receives a request message that it expects according to the message sequence
// specified in this chapter, and if the SECC cannot process this request message, e. g. due to
// errors in the message parameters or due to impeding conditions in the EVSE, the SECC
// shall:
// [1.] without any delay, carry out an “EVSE-initiated emergency shutdown” as specified in
// IEC 61851-23, which includes turning off the CP oscillator, if it is turned on,
// [2.] respond with the corresponding response message with parameter ResponseCode
// equal to “FAILED”, if possible, and
// [3.] close the TCP connection according to [V2G-DC-116].
auto given_response_code = din_responseCodeType_FAILED;
constexpr auto min_expected_response_code = din_responseCodeType_FAILED;
constexpr auto expected_response = V2G_EVENT_SEND_AND_TERMINATE;
ctx->is_connection_terminated = false;
ctx->terminate_connection_on_failed_response = true;
EXPECT_EQ(utils::din_validate_response_code(&given_response_code, conn.get()), expected_response);
EXPECT_GE(given_response_code, min_expected_response_code);
}
} // namespace

View File

@@ -0,0 +1,58 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef EVSE_SECURITYINTFSTUB_H_
#define EVSE_SECURITYINTFSTUB_H_
#include <iostream>
#include "ModuleAdapterStub.hpp"
#include "generated/types/evse_security.hpp"
#include "utils/types.hpp"
#include <functional>
#include <generated/interfaces/evse_security/Interface.hpp>
#include <optional>
#include <string>
//-----------------------------------------------------------------------------
namespace module::stub {
class evse_securityIntfStub : public evse_securityIntf {
private:
std::map<const std::string, Result (evse_securityIntfStub::*)(const Requirement& req, const Parameters& args)>
functions;
public:
evse_securityIntfStub(ModuleAdapterStub* adapter) :
evse_securityIntf(adapter, Requirement{"", 0}, "EvseSecurity", std::nullopt) {
functions["get_verify_file"] = &evse_securityIntfStub::get_verify_file;
functions["get_leaf_certificate_info"] = &evse_securityIntfStub::get_leaf_certificate_info;
}
evse_securityIntfStub(ModuleAdapterStub& adapter) :
evse_securityIntf(&adapter, Requirement{"", 0}, "EvseSecurity", std::nullopt) {
functions["get_verify_file"] = &evse_securityIntfStub::get_verify_file;
functions["get_leaf_certificate_info"] = &evse_securityIntfStub::get_leaf_certificate_info;
}
virtual Result call_fn(const Requirement& req, const std::string& str, Parameters args) {
if (auto it = functions.find(str); it != functions.end()) {
return std::invoke(it->second, this, req, args);
}
std::printf("call_fn (%s)\n", str.c_str());
return std::nullopt;
}
virtual Result get_verify_file(const Requirement& req, const Parameters& args) {
std::cout << "evse_securityIntf::get_verify_file called" << std::endl;
return "";
}
virtual Result get_leaf_certificate_info(const Requirement& req, const Parameters& args) {
std::cout << "evse_securityIntf::get_leaf_certificate_info called" << std::endl;
return "";
}
};
} // namespace module::stub
#endif // EVSE_SECURITYINTFSTUB_H_

View File

@@ -0,0 +1,30 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef ISO15118_EXTENTSIONSIMPLSTUB_H
#define ISO15118_EXTENTSIONSIMPLSTUB_H
#include <iostream>
#include <generated/interfaces/iso15118_extensions/Implementation.hpp>
//-----------------------------------------------------------------------------
namespace module::stub {
class iso15118_extensionsImplStub : public iso15118_extensionsImplBase {
public:
iso15118_extensionsImplStub() : iso15118_extensionsImplBase(nullptr, "EvseV2G"){};
virtual void init() {
}
virtual void ready() {
}
virtual void handle_set_get_certificate_response(types::iso15118::ResponseExiStreamStatus& certificate_response) {
std::cout << "iso15118_extensionsImplBase::handle_set_get_certificate_response called" << std::endl;
}
};
} // namespace module::stub
#endif // ISO15118_EXTENTSIONSIMPLSTUB_H

View File

@@ -0,0 +1,21 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef ISO15118_VASINTFSTUB_H_
#define ISO15118_VASINTFSTUB_H_
#include "ModuleAdapterStub.hpp"
#include <generated/interfaces/ISO15118_vas/Interface.hpp>
namespace module::stub {
class iso15118_vasIntfStub : public ISO15118_vasIntf {
public:
explicit iso15118_vasIntfStub(ModuleAdapterStub& adapter) :
ISO15118_vasIntf(&adapter, Requirement{"", 0}, "EvseV2G", std::nullopt) {
}
};
} // namespace module::stub
#endif // ISO15118_VASINTFSTUB_H_

View File

@@ -0,0 +1,45 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#include "utest_log.hpp"
#include <cstdarg>
#include <cstdio>
#include <algorithm>
#include <array>
#include <map>
namespace {
std::map<dloglevel_t, std::vector<std::string>> logged_events;
void add_log(dloglevel_t loglevel, const std::string& event) {
logged_events[loglevel].push_back(event);
}
} // namespace
namespace module::stub {
std::vector<std::string>& get_logs(dloglevel_t loglevel) {
return logged_events[loglevel];
}
void clear_logs() {
logged_events.clear();
}
} // namespace module::stub
void dlog_func(const dloglevel_t loglevel, const char* filename, const int linenumber, const char* functionname,
const char* format, ...) {
va_list ap;
std::array<char, 256> buffer;
va_start(ap, format);
std::size_t len = std::vsnprintf(buffer.data(), buffer.size(), format, ap);
va_end(ap);
if (len > 0) {
auto s_len = std::min(len, buffer.size());
std::string event{buffer.data(), s_len};
(void)std::fprintf(stderr, "log: %s\n", event.c_str());
add_log(loglevel, event);
}
}

View File

@@ -0,0 +1,165 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#include "crypto_common.hpp"
#include "gtest/gtest.h"
#include <crypto_openssl.hpp>
#include <cstddef>
#include <cstring>
#include <everest/tls/openssl_util.hpp>
#include <iso_server.hpp>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <cbv2g/common/exi_bitstream.h>
#include <cbv2g/exi_v2gtp.h> //for V2GTP_HEADER_LENGTHs
#include <cbv2g/iso_2/iso2_msgDefDatatypes.h>
#include <cbv2g/iso_2/iso2_msgDefDecoder.h>
#include <cbv2g/iso_2/iso2_msgDefEncoder.h>
namespace {
template <typename T> constexpr void setCharacters(T& dest, const std::string& s) {
dest.charactersLen = s.size();
std::memcpy(&dest.characters[0], s.c_str(), s.size());
}
template <typename T> constexpr void setBytes(T& dest, const std::uint8_t* b, std::size_t len) {
dest.bytesLen = len;
std::memcpy(&dest.bytes[0], b, len);
}
struct test_vectors_t {
const char* input;
const std::uint8_t digest[32];
};
#if 0
// not used, useful to keep all the exi message test values together
constexpr std::uint8_t sign_test[] = {0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c,
0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55};
constexpr test_vectors_t sha_256_test[] = {
{"", {0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}},
{"abc", {0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad}}};
// Test vectors from ISO 15118-2 Section J.2
// checked okay (see iso_priv.pem)
constexpr std::uint8_t iso_private_key[] = {0xb9, 0x13, 0x49, 0x63, 0xf5, 0x1c, 0x44, 0x14, 0x73, 0x84, 0x35,
0x05, 0x7f, 0x97, 0xbb, 0xf1, 0x01, 0x0c, 0xab, 0xcb, 0x8d, 0xbd,
0xe9, 0xc5, 0xd4, 0x81, 0x38, 0x39, 0x6a, 0xa9, 0x4b, 0x9d};
// checked okay (see iso_priv.pem)
constexpr std::uint8_t iso_public_key[] = {0x43, 0xe4, 0xfc, 0x4c, 0xcb, 0x64, 0x39, 0x04, 0x27, 0x9c, 0x7a, 0x5e, 0x65,
0x76, 0xb3, 0x23, 0xe5, 0x5e, 0xc7, 0x9f, 0xf0, 0xe5, 0xa4, 0x05, 0x6e, 0x33,
0x40, 0x84, 0xcb, 0xc3, 0x36, 0xff, 0x46, 0xe4, 0x4c, 0x1a, 0xdd, 0xf6, 0x91,
0x62, 0xe5, 0x19, 0x2c, 0x2a, 0x83, 0xfc, 0x2b, 0xca, 0x9d, 0x8f, 0x46, 0xec,
0xf4, 0xb7, 0x80, 0x67, 0xc2, 0x47, 0x6f, 0x6b, 0x3f, 0x34, 0x60, 0x0e};
#endif
// EXI AuthorizationReq: checked okay (hash computes correctly)
constexpr std::uint8_t iso_exi_a[] = {0x80, 0x04, 0x01, 0x52, 0x51, 0x0c, 0x40, 0x82, 0x9b, 0x7b, 0x6b, 0x29, 0x02,
0x93, 0x0b, 0x73, 0x23, 0x7b, 0x69, 0x02, 0x23, 0x0b, 0xa3, 0x09, 0xe8};
// checked okay
constexpr std::uint8_t iso_exi_a_hash[] = {0xd1, 0xb5, 0xe0, 0x3d, 0x00, 0x65, 0xbe, 0xe5, 0x6b, 0x31, 0x79,
0x84, 0x45, 0x30, 0x51, 0xeb, 0x54, 0xca, 0x18, 0xfc, 0x0e, 0x09,
0x16, 0x17, 0x4f, 0x8b, 0x3c, 0x77, 0xa9, 0x8f, 0x4a, 0xa9};
#if 0
// not used, useful to keep all the exi message test values together
// EXI AuthorizationReq signature block: checked okay (hash computes correctly)
constexpr std::uint8_t iso_exi_b[] = {
0x80, 0x81, 0x12, 0xb4, 0x3a, 0x3a, 0x38, 0x1d, 0x17, 0x97, 0xbb, 0xbb, 0xbb, 0x97, 0x3b, 0x99, 0x97, 0x37, 0xb9,
0x33, 0x97, 0xaa, 0x29, 0x17, 0xb1, 0xb0, 0xb7, 0x37, 0xb7, 0x34, 0xb1, 0xb0, 0xb6, 0x16, 0xb2, 0xbc, 0x34, 0x97,
0xa1, 0xab, 0x43, 0xa3, 0xa3, 0x81, 0xd1, 0x79, 0x7b, 0xbb, 0xbb, 0xb9, 0x73, 0xb9, 0x99, 0x73, 0x7b, 0x93, 0x39,
0x79, 0x91, 0x81, 0x81, 0x89, 0x79, 0x81, 0xa1, 0x7b, 0xc3, 0x6b, 0x63, 0x23, 0x9b, 0x4b, 0x39, 0x6b, 0x6b, 0x7b,
0x93, 0x29, 0x1b, 0x2b, 0x1b, 0x23, 0x9b, 0x09, 0x6b, 0x9b, 0x43, 0x09, 0x91, 0xa9, 0xb2, 0x20, 0x62, 0x34, 0x94,
0x43, 0x10, 0x25, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72,
0x67, 0x2f, 0x54, 0x52, 0x2f, 0x63, 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x2d, 0x65, 0x78, 0x69, 0x2f,
0x48, 0x52, 0xd0, 0xe8, 0xe8, 0xe0, 0x74, 0x5e, 0x5e, 0xee, 0xee, 0xee, 0x5c, 0xee, 0x66, 0x5c, 0xde, 0xe4, 0xce,
0x5e, 0x64, 0x60, 0x60, 0x62, 0x5e, 0x60, 0x68, 0x5e, 0xf0, 0xda, 0xd8, 0xca, 0xdc, 0xc6, 0x46, 0xe6, 0xd0, 0xc2,
0x64, 0x6a, 0x6c, 0x84, 0x1a, 0x36, 0xbc, 0x07, 0xa0, 0x0c, 0xb7, 0xdc, 0xad, 0x66, 0x2f, 0x30, 0x88, 0xa6, 0x0a,
0x3d, 0x6a, 0x99, 0x43, 0x1f, 0x81, 0xc1, 0x22, 0xc2, 0xe9, 0xf1, 0x67, 0x8e, 0xf5, 0x31, 0xe9, 0x55, 0x23, 0x70};
#endif
// checked okay
constexpr std::uint8_t iso_exi_b_hash[] = {0xa4, 0xe9, 0x03, 0xe1, 0x82, 0x43, 0x04, 0x1b, 0x55, 0x4e, 0x11,
0x64, 0x7e, 0x10, 0x1e, 0xd2, 0x5f, 0xc9, 0xf2, 0x15, 0x2a, 0xf4,
0x67, 0x40, 0x14, 0xfe, 0x2a, 0xde, 0xac, 0x1e, 0x1c, 0xf7};
// checked okay (verifies iso_exi_b_hash with iso_priv.pem)
constexpr std::uint8_t iso_exi_sig[] = {0x4c, 0x8f, 0x20, 0xc1, 0x40, 0x0b, 0xa6, 0x76, 0x06, 0xaa, 0x48, 0x11, 0x57,
0x2a, 0x2f, 0x1a, 0xd3, 0xc1, 0x50, 0x89, 0xd9, 0x54, 0x20, 0x36, 0x34, 0x30,
0xbb, 0x26, 0xb4, 0x9d, 0xb1, 0x04, 0xf0, 0x8d, 0xfa, 0x8b, 0xf8, 0x05, 0x5e,
0x63, 0xa4, 0xb7, 0x5a, 0x8d, 0x31, 0x69, 0x20, 0x6f, 0xa8, 0xd5, 0x43, 0x08,
0xba, 0x58, 0xf0, 0x56, 0x6b, 0x96, 0xba, 0xf6, 0x92, 0xce, 0x59, 0x50};
#if 0
// not used, useful to keep all the exi message test values together
const char iso_exi_a_hash_b64[] = "0bXgPQBlvuVrMXmERTBR61TKGPwOCRYXT4s8d6mPSqk=";
const char iso_exi_a_hash_b64_nl[] = "0bXgPQBlvuVrMXmERTBR61TKGPwOCRYXT4s8d6mPSqk=\n";
const char iso_exi_sig_b64[] =
"TI8gwUALpnYGqkgRVyovGtPBUInZVCA2NDC7JrSdsQTwjfqL+AVeY6S3Wo0xaSBvqNVDCLpY8FZrlrr2ks5ZUA==";
const char iso_exi_sig_b64_nl[] =
"TI8gwUALpnYGqkgRVyovGtPBUInZVCA2NDC7JrSdsQTwjfqL+AVeY6S3Wo0xaSBv\nqNVDCLpY8FZrlrr2ks5ZUA==\n";
#endif
TEST(openssl, verifyIso) {
auto* bio = BIO_new_file("iso_priv.pem", "r");
ASSERT_NE(bio, nullptr);
auto* pkey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr);
ASSERT_NE(pkey, nullptr);
BIO_free(bio);
auto sig = openssl::bn_to_signature(&iso_exi_sig[0], &iso_exi_sig[32]);
EXPECT_TRUE(openssl::verify(pkey, sig.get(), sig.size(), &iso_exi_b_hash[0], sizeof(iso_exi_b_hash)));
EVP_PKEY_free(pkey);
}
TEST(isoExi, signature) {
// The message is:
// header { SessionID, Signature}
// body { AuthorizationReq }
// the test vector doesn't include the entire encoded message
auto* bio = BIO_new_file("iso_priv.pem", "r");
ASSERT_NE(bio, nullptr);
auto* pkey = PEM_read_bio_PrivateKey(bio, nullptr, nullptr, nullptr);
ASSERT_NE(pkey, nullptr);
BIO_free(bio);
// decode the test vector AuthorizationReq
struct iso2_exiFragment exi_a {};
init_iso2_exiFragment(&exi_a);
init_iso2_AuthorizationReqType(&exi_a.AuthorizationReq);
exi_bitstream_t stream;
exi_bitstream_init(&stream, const_cast<std::uint8_t*>(&iso_exi_a[0]), sizeof(iso_exi_a), 0, nullptr);
EXPECT_EQ(decode_iso2_exiFragment(&stream, &exi_a), 0);
// manually populate the Signature structure
struct iso2_SignatureType sig {};
init_iso2_SignatureType(&sig);
// SignedInfo
setCharacters(sig.SignedInfo.CanonicalizationMethod.Algorithm, "http://www.w3.org/TR/canonical-exi/");
setCharacters(sig.SignedInfo.SignatureMethod.Algorithm, "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256");
sig.SignedInfo.Reference.arrayLen = 1;
sig.SignedInfo.Reference.array[0].URI_isUsed = 1;
setCharacters(sig.SignedInfo.Reference.array[0].URI, "#ID1");
sig.SignedInfo.Reference.array[0].Transforms_isUsed = 1;
setCharacters(sig.SignedInfo.Reference.array[0].Transforms.Transform.Algorithm,
"http://www.w3.org/TR/canonical-exi/");
setCharacters(sig.SignedInfo.Reference.array[0].DigestMethod.Algorithm, "http://www.w3.org/2001/04/xmlenc#sha256");
setBytes(sig.SignedInfo.Reference.array[0].DigestValue, &iso_exi_a_hash[0], ::openssl::sha_256_digest_size);
// SignatureValue
setBytes(sig.SignatureValue.CONTENT, &iso_exi_sig[0], ::openssl::signature_size);
EXPECT_TRUE(crypto::openssl::check_iso2_signature(&sig, pkey, &exi_a));
EVP_PKEY_free(pkey);
}
} // namespace

View File

@@ -0,0 +1,10 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#include <string>
#include "utils/types.hpp"
bool operator<(const Requirement& lhs, const Requirement& rhs) {
return true;
}

View File

@@ -0,0 +1,104 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <gtest/gtest.h>
#include <sdp.hpp>
#include <tools.hpp>
namespace {
class SdpTest : public testing::Test {
protected:
SdpTest() {
}
};
TEST_F(SdpTest, sdp_set_dlink_ready_sets_flag) {
v2g_context ctx{};
EXPECT_FALSE(ctx.sdp_dlink_ready);
sdp_set_dlink_ready(&ctx, true);
EXPECT_TRUE(ctx.sdp_dlink_ready);
sdp_set_dlink_ready(&ctx, false);
EXPECT_FALSE(ctx.sdp_dlink_ready);
}
TEST_F(SdpTest, sdp_set_dlink_ready_captures_timestamp) {
v2g_context ctx{};
EXPECT_EQ(ctx.sdp_dlink_ready_time.load(), 0);
sdp_set_dlink_ready(&ctx, true);
EXPECT_GT(ctx.sdp_dlink_ready_time.load(), 0);
sdp_set_dlink_ready(&ctx, false);
EXPECT_EQ(ctx.sdp_dlink_ready_time.load(), 0);
}
TEST_F(SdpTest, sdp_set_dlink_ready_timestamp_is_monotonic) {
v2g_context ctx{};
sdp_set_dlink_ready(&ctx, true);
long long int first = ctx.sdp_dlink_ready_time.load();
sdp_set_dlink_ready(&ctx, false);
sdp_set_dlink_ready(&ctx, true);
long long int second = ctx.sdp_dlink_ready_time.load();
EXPECT_GE(second, first);
}
TEST_F(SdpTest, sdp_write_header) {
uint8_t buffer[20];
uint16_t payload_type = 0x9001;
uint32_t length = 367;
EXPECT_EQ(sdp_write_header(buffer, payload_type, length), 8);
EXPECT_EQ(buffer[0], 0x01);
EXPECT_EQ(buffer[1], 0xFE);
EXPECT_EQ(buffer[2], 0x90);
EXPECT_EQ(buffer[3], 0x01);
EXPECT_EQ(buffer[4], 0x00);
EXPECT_EQ(buffer[5], 0x00);
EXPECT_EQ(buffer[6], 0x01);
EXPECT_EQ(buffer[7], 0x6F);
}
TEST_F(SdpTest, timeout_detected_when_elapsed_exceeds_limit) {
v2g_context ctx{};
// Simulate dlink becoming ready 18001ms ago
long long int now = getmonotonictime();
ctx.sdp_dlink_ready_time = now - (V2G_COMMUNICATION_SETUP_TIMEOUT + 1);
long long int elapsed = now - ctx.sdp_dlink_ready_time.load();
EXPECT_GE(elapsed, V2G_COMMUNICATION_SETUP_TIMEOUT);
}
TEST_F(SdpTest, timeout_not_detected_when_elapsed_below_limit) {
v2g_context ctx{};
// Simulate dlink becoming ready just now
ctx.sdp_dlink_ready_time = getmonotonictime();
long long int elapsed = getmonotonictime() - ctx.sdp_dlink_ready_time.load();
EXPECT_LT(elapsed, V2G_COMMUNICATION_SETUP_TIMEOUT);
}
TEST_F(SdpTest, timeout_cancelled_when_connection_initiated) {
v2g_context ctx{};
sdp_set_dlink_ready(&ctx, true);
EXPECT_NE(ctx.sdp_dlink_ready_time.load(), 0);
// Simulate connection established — timeout should be cancelled
ctx.connection_initiated = true;
long long int dlink_ready_time = ctx.sdp_dlink_ready_time.load();
if (ctx.connection_initiated && dlink_ready_time != 0) {
ctx.sdp_dlink_ready_time = 0;
}
EXPECT_EQ(ctx.sdp_dlink_ready_time.load(), 0);
}
TEST_F(SdpTest, v2g_communication_setup_timeout_is_18000) {
EXPECT_EQ(V2G_COMMUNICATION_SETUP_TIMEOUT, 18000);
}
} // namespace

View File

@@ -0,0 +1,36 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <gtest/gtest.h>
#include <tools.hpp>
namespace {
template <typename T, std::size_t S> constexpr auto macStr(const T (&arg)[S]) {
return to_mac_address_str(reinterpret_cast<const uint8_t*>(arg), S - 1);
}
TEST(to_mac_address_str, various) {
auto result = to_mac_address_str(nullptr, 0);
EXPECT_TRUE(result.empty());
const char* txt = "123";
result = to_mac_address_str(reinterpret_cast<const uint8_t*>(txt), 0);
EXPECT_TRUE(result.empty());
result = macStr("");
EXPECT_TRUE(result.empty());
result = macStr("12345678901234567"); // too long
EXPECT_TRUE(result.empty());
result = macStr("A");
EXPECT_EQ(result, "41");
result = macStr("AB");
EXPECT_EQ(result, "41:42");
result = macStr("ABM");
EXPECT_EQ(result, "41:42:4D");
result = macStr("\xac\x91\xa1\x56\x5f\x46");
EXPECT_EQ(result, "AC:91:A1:56:5F:46");
result = macStr("1234567890123456"); // max length
EXPECT_EQ(result, "31:32:33:34:35:36:37:38:39:30:31:32:33:34:35:36");
}
} // namespace

View File

@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#pragma once
#include <log.hpp>
#include <string>
#include <vector>
namespace module::stub {
std::vector<std::string>& get_logs(dloglevel_t loglevel);
void clear_logs();
} // namespace module::stub

View File

@@ -0,0 +1,162 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <memory>
#include <v2g_ctx.hpp>
#include "ISO15118_chargerImplStub.hpp"
#include "ModuleAdapterStub.hpp"
#include "evse_securityIntfStub.hpp"
#include "iso15118_extensionsImplStub.hpp"
#include "iso15118_vasIntfStub.hpp"
#include "utest_log.hpp"
#include "v2g.hpp"
#include <gtest/gtest.h>
namespace {
struct v2g_contextDeleter {
void operator()(v2g_context* ptr) const {
v2g_ctx_free(ptr);
};
};
class V2gCtxTest : public testing::Test {
protected:
std::unique_ptr<v2g_context, v2g_contextDeleter> ctx;
module::stub::QuietModuleAdapterStub adapter;
module::stub::ISO15118_chargerImplStub charger;
module::stub::evse_securityIntfStub security;
module::stub::iso15118_extensionsImplStub extensions;
module::stub::iso15118_vasIntfStub vas_item;
V2gCtxTest() : charger(adapter), security(adapter), vas_item(adapter) {
}
void v2g_ctx_init_charging_state_cleared() {
// checks try to match the order is v2g.hpp
EXPECT_EQ(ctx->last_v2g_msg, V2G_UNKNOWN_MSG);
EXPECT_EQ(ctx->current_v2g_msg, V2G_UNKNOWN_MSG);
EXPECT_EQ(ctx->state, 0);
// not changed
// is_dc_charger
// debugMode
// supported_protocols
EXPECT_EQ(ctx->selected_protocol, V2G_UNKNOWN_PROTOCOL);
EXPECT_FALSE(ctx->intl_emergency_shutdown);
EXPECT_FALSE(ctx->stop_hlc);
// ctx->is_connection_terminated is updated rather than cleared
// not changed
// terminate_connection_on_failed_response
// contactor_is_closed
// many items in session not reset
EXPECT_FALSE(ctx->session.renegotiation_required);
EXPECT_FALSE(ctx->session.is_charging);
}
void SetUp() override {
auto ptr = v2g_ctx_create(&charger, &extensions, &security, {&vas_item});
ctx = std::unique_ptr<v2g_context, v2g_contextDeleter>(ptr, v2g_contextDeleter());
module::stub::clear_logs();
}
void TearDown() override {
}
};
TEST(RunFirst, v2g_ctx_init_charging_values) {
// must not be part of V2gCtxTest
// V2gCtxTest::SetUp() creates the v2g_context which would be the 1st
// call to v2g_ctx_init_charging_values()
// only called from v2g_ctx_init_charging_session()
// which is called from v2g_ctx_create()
// note v2g_ctx_init_charging_values() has a static bool so it
// performs different tidyup after the first time it is called
v2g_context ctx;
ctx.evse_v2g_data.charge_service.FreeService = 9;
v2g_ctx_init_charging_values(&ctx);
EXPECT_EQ(ctx.evse_v2g_data.charge_service.FreeService, 0);
ctx.evse_v2g_data.charge_service.FreeService = 10;
v2g_ctx_init_charging_values(&ctx);
EXPECT_EQ(ctx.evse_v2g_data.charge_service.FreeService, 10);
// reset back to a valid value as it will never be reset
ctx.evse_v2g_data.charge_service.FreeService = 0;
}
TEST_F(V2gCtxTest, v2g_ctx_init_charging_stateTrue) {
// called on session start in v2g_handle_connection()
ctx->last_v2g_msg = V2G_CABLE_CHECK_MSG;
ctx->current_v2g_msg = V2G_CHARGE_PARAMETER_DISCOVERY_MSG;
ctx->state = 10;
ctx->selected_protocol = V2G_PROTO_DIN70121;
ctx->intl_emergency_shutdown = true;
ctx->stop_hlc = true;
ctx->session.renegotiation_required = true;
ctx->session.is_charging = true;
v2g_ctx_init_charging_state(ctx.get(), true);
v2g_ctx_init_charging_state_cleared();
EXPECT_TRUE(ctx->is_connection_terminated);
}
TEST_F(V2gCtxTest, v2g_ctx_init_charging_stateFalse) {
// called on session end in v2g_handle_connection()
ctx->last_v2g_msg = V2G_CABLE_CHECK_MSG;
ctx->current_v2g_msg = V2G_CHARGE_PARAMETER_DISCOVERY_MSG;
ctx->state = 10;
ctx->selected_protocol = V2G_PROTO_DIN70121;
ctx->intl_emergency_shutdown = true;
ctx->stop_hlc = true;
ctx->session.renegotiation_required = true;
ctx->session.is_charging = true;
v2g_ctx_init_charging_state(ctx.get(), false);
v2g_ctx_init_charging_state_cleared();
EXPECT_FALSE(ctx->is_connection_terminated);
}
#if 0
// v2g_ctx_init_charging_session() is a trivial implementation
TEST_F(V2gCtxTest, v2g_ctx_init_charging_sessionTrue) {
// called in connection_teardown()
// calls v2g_ctx_init_charging_state
// calls v2g_ctx_init_charging_values
}
TEST_F(V2gCtxTest, v2g_ctx_init_charging_sessionFalse) {
// called in connection_teardown()
// calls v2g_ctx_init_charging_state
// calls v2g_ctx_init_charging_values
}
#endif
TEST(valgrind, memcheck) {
// v2g_ctx_free() now sets shutdown and joins the event thread,
// so there is no use-after-free during cleanup.
// run via valgrind to ensure that malloc/free are working
module::stub::QuietModuleAdapterStub adapter;
module::stub::ISO15118_chargerImplStub charger(adapter);
module::stub::evse_securityIntfStub security(adapter);
module::stub::iso15118_extensionsImplStub extensions;
module::stub::iso15118_vasIntfStub vas_item(adapter);
auto ptr = v2g_ctx_create(&charger, &extensions, &security, {&vas_item});
v2g_ctx_free(ptr);
}
} // namespace

View File

@@ -0,0 +1,175 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
/*
* testing options
* openssl s_client -connect [fe80::ae91:a1ff:fec9:a947%3]:64109 -verify 2 -CAfile server_root_cert.pem -cert
* client_cert.pem -cert_chain client_chain.pem -key client_priv.pem -verify_return_error -verify_hostname
* evse.pionix.de -status
*/
#include <array>
#include <cassert>
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <optional>
#include <thread>
#include <unistd.h>
#include "ISO15118_chargerImplStub.hpp"
#include "ModuleAdapterStub.hpp"
#include "evse_securityIntfStub.hpp"
#include "iso15118_extensionsImplStub.hpp"
#include "iso15118_vasIntfStub.hpp"
#include <connection.hpp>
#include <everest/tls/tls.hpp>
#include <v2g_ctx.hpp>
using namespace std::chrono_literals;
// needs to be in the global namespace
int v2g_handle_connection(struct v2g_connection* conn) {
assert(conn != nullptr);
assert(conn->read != nullptr);
assert(conn->write != nullptr);
std::array<unsigned char, 1024> buffer{};
bool bExit = false;
while (!bExit) {
const ssize_t readbytes = conn->read(conn, buffer.data(), buffer.size());
if (readbytes > 0) {
const ssize_t writebytes = conn->write(conn, buffer.data(), readbytes);
if (writebytes <= 0) {
bExit = true;
}
} else if (readbytes < 0) {
bExit = true;
}
}
return 0;
}
namespace {
const char* interface;
void parse_options(int argc, char** argv) {
interface = nullptr;
int c;
while ((c = getopt(argc, argv, "hi:")) != -1) {
switch (c) {
case 'i':
interface = optarg;
break;
case 'h':
case '?':
std::cout << "Usage: " << argv[0] << " -i <interface name>" << std::endl;
exit(1);
break;
default:
exit(2);
}
}
if (interface == nullptr) {
std::cerr << "Error: " << argv[0] << " requires -i <interface name>" << std::endl;
exit(3);
}
}
// EvseSecurity "implementation"
struct EvseSecurity : public module::stub::evse_securityIntfStub {
EvseSecurity(module::stub::ModuleAdapterStub& adapter) : module::stub::evse_securityIntfStub(&adapter) {
}
Result get_verify_file(const Requirement& req, const Parameters& args) override {
return "client_root_cert.pem";
}
virtual Result get_leaf_certificate_info(const Requirement& req, const Parameters& args) override {
// using types::evse_security::CertificateHashDataType;
using types::evse_security::CertificateInfo;
using types::evse_security::CertificateOCSP;
using types::evse_security::GetCertificateInfoResult;
using types::evse_security::GetCertificateInfoStatus;
using types::evse_security::HashAlgorithm;
CertificateInfo cert_info;
cert_info.key = "server_priv.pem";
cert_info.certificate = "server_chain.pem";
cert_info.certificate_count = 2;
cert_info.ocsp = {{
{HashAlgorithm::SHA256},
{"ocsp_response.der"},
},
{
{HashAlgorithm::SHA256},
{"ocsp_response.der"},
}};
const GetCertificateInfoResult res = {
GetCertificateInfoStatus::Accepted,
cert_info,
};
json jres = res;
return jres;
}
};
} // namespace
int main(int argc, char** argv) {
parse_options(argc, argv);
tls::Server tls_server;
module::stub::ModuleAdapterStub adapter;
module::stub::ISO15118_chargerImplStub charger(adapter);
EvseSecurity security(adapter);
module::stub::iso15118_extensionsImplStub extensions;
module::stub::iso15118_vasIntfStub vas_item(adapter);
auto* ctx = v2g_ctx_create(&charger, &extensions, &security, {&vas_item});
if (ctx == nullptr) {
std::cerr << "failed to create context" << std::endl;
} else {
ctx->tls_server = &tls_server;
ctx->if_name = interface;
ctx->tls_security = TLS_SECURITY_FORCE;
ctx->is_connection_terminated = false;
std::thread stop([ctx]() {
// there is a 60 second read timeout in connection.cpp
std::this_thread::sleep_for(75s);
std::cout << "shutdown" << std::endl;
ctx->is_connection_terminated = true;
ctx->shutdown = true;
});
std::cout << "connection_init" << std::endl;
if (::connection_init(ctx) != 0) {
std::cerr << "connection_init failed" << std::endl;
} else {
std::cout << "connection_init started" << std::endl;
}
std::cout << "connection_start_servers " << std::endl;
if (::connection_start_servers(ctx) != 0) {
std::cerr << "connection_start_servers failed" << std::endl;
} else {
std::cout << "connection_start_servers started" << std::endl;
}
stop.join();
tls::ServerConnection::wait_all_closed();
// wait for v2g_ctx_start_events thread to stop
std::this_thread::sleep_for(2s);
v2g_ctx_free(ctx);
}
return 0;
}