Skip to content

Commit

Permalink
Make libwebsockets also work with security profile 1 (so that the url…
Browse files Browse the repository at this point in the history
… is not set to wss). Extra check if websocket is already stopped. Extra check if a reconnection is already taking place (in connectivity manager).

Signed-off-by: Maaike <[email protected]>
  • Loading branch information
maaikez committed Mar 27, 2024
1 parent 0ffdeb4 commit b3a2043
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 82 deletions.
1 change: 1 addition & 0 deletions dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ libwebsockets:
- LWS_WITHOUT_TEST_SERVER_EXTPOLL ON
- LWS_WITHOUT_TEST_PING ON
- LWS_WITHOUT_TEST_CLIENT ON
- LWS_WITH_NETWORK ON
gtest:
# GoogleTest now follows the Abseil Live at Head philosophy. We recommend updating to the latest commit in the main branch as often as possible.
git: https://github.com/google/googletest.git
Expand Down
2 changes: 2 additions & 0 deletions doc/build-with-fetchcontent/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ macro(find_package PACKAGE_NAME)
find_nlohmann_json_schema_validator()
elseif("${PACKAGE_NAME}" STREQUAL "websocketpp")
find_websocketpp()
elseif("${PACKAGE_NAME}" STREQUAL "libwebsockets")
find_libwebsockets()
elseif("${PACKAGE_NAME}" STREQUAL "fsm")
find_fsm()
elseif("${PACKAGE_NAME}" STREQUAL "everest-timer")
Expand Down
1 change: 0 additions & 1 deletion include/ocpp/common/websocket/websocket_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ class WebsocketBase {
std::function<void(ConnectionFailedReason)> connection_failed_callback;
std::unique_ptr<Everest::SteadyTimer> ping_timer;
websocketpp::connection_hdl handle;
std::mutex reconnect_mutex;
std::mutex connection_mutex;
bool shutting_down;

Check notice on line 62 in include/ocpp/common/websocket/websocket_base.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/ocpp/common/websocket/websocket_base.hpp#L62

class member 'WebsocketBase::shutting_down' is never used.

Expand Down
9 changes: 5 additions & 4 deletions include/ocpp/common/websocket/websocket_libwebsockets.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class WebsocketTlsTPM final : public WebsocketBase {
explicit WebsocketTlsTPM(const WebsocketConnectionOptions& connection_options,
std::shared_ptr<EvseSecurity> evse_security);

~WebsocketTlsTPM();
virtual ~WebsocketTlsTPM() override;

void set_connection_options(const WebsocketConnectionOptions& connection_options) override;

Expand All @@ -38,10 +38,10 @@ class WebsocketTlsTPM final : public WebsocketBase {
/// \brief Reconnects the websocket using the delay, a reason for this reconnect can be provided with the
/// \param reason parameter
/// \param delay delay of the reconnect attempt
void reconnect(std::error_code reason, long delay) override;
void reconnect() override;

/// \brief closes the websocket
void close(WebsocketCloseReason code, const std::string& reason) override;
void close(WebsocketCloseReason code, const std::string& reason, const bool stop_perpetual = false) override;

/// \brief send a \p message over the websocket
/// \returns true if the message was sent successfully
Expand Down Expand Up @@ -82,7 +82,6 @@ class WebsocketTlsTPM final : public WebsocketBase {
std::shared_ptr<EvseSecurity> evse_security;

// Connection related data
Everest::SteadyTimer reconnect_timer_tpm;
std::unique_ptr<std::thread> websocket_thread;
std::shared_ptr<ConnectionData> conn_data;
std::condition_variable conn_cv;
Expand All @@ -97,6 +96,8 @@ class WebsocketTlsTPM final : public WebsocketBase {
std::mutex recv_mutex;
std::queue<std::string> recv_message_queue;
std::condition_variable recv_message_cv;

std::atomic_bool stopped;
};

} // namespace ocpp
Expand Down
2 changes: 2 additions & 0 deletions include/ocpp/common/websocket/websocket_tls.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class WebsocketTLS final : public WebsocketBase {
tls_client wss_client;
std::shared_ptr<EvseSecurity> evse_security;
websocketpp::lib::shared_ptr<websocketpp::lib::thread> websocket_thread;
std::atomic_bool closed;

/// \brief Called when a TLS websocket connection gets initialized, manages the supported TLS versions, cipher lists
/// and how verification of the server certificate is handled
tls_context on_tls_init(std::string hostname, websocketpp::connection_hdl hdl, int32_t security_profile);
Expand Down
2 changes: 2 additions & 0 deletions include/ocpp/v201/connectivity_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class ConnectivityManager {
int connection_attempts;

Check notice on line 72 in include/ocpp/v201/connectivity_manager.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/ocpp/v201/connectivity_manager.hpp#L72

class member 'ConnectivityManager::connection_attempts' is never used.
/// \brief The last reconnect wait time.
std::chrono::milliseconds reconnect_backoff_ms;
/// \brief Something is waiting to reconnect, do not reconnect in the meantime.
std::atomic_bool wait_for_reconnect;

/* Callbacks for networking */
/// \brief The message callback.
Expand Down
8 changes: 7 additions & 1 deletion lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,16 @@ target_link_libraries(ocpp
)

if(LIBOCPP_ENABLE_LIBWEBSOCKETS)
set(LIBWEBSOCKETS_LIBRARY)
if(LWS_WITH_STATIC)
set(LIBOCPP_LIBWEBSOCKETS_LIBRARY ${LIBWEBSOCKETS_LIBRARIES_STATIC})
else()
set(LIBOCPP_LIBWEBSOCKETS_LIBRARY ${LIBWEBSOCKETS_LIBRARIES_SHARED})
endif()
find_package(libwebsockets REQUIRED)
target_link_libraries(ocpp
PUBLIC
websockets_shared
${LIBOCPP_LIBWEBSOCKETS_LIBRARY}
)
target_compile_definitions(ocpp
PRIVATE
Expand Down
103 changes: 34 additions & 69 deletions lib/ocpp/common/websocket/websocket_libwebsockets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ static bool verify_csms_cn(const std::string& hostname, bool preverified, const

WebsocketTlsTPM::WebsocketTlsTPM(const WebsocketConnectionOptions& connection_options,
std::shared_ptr<EvseSecurity> evse_security) :
WebsocketBase(), evse_security(evse_security) {
WebsocketBase(), evse_security(evse_security), stopped(false) {

set_connection_options(connection_options);

Expand All @@ -232,17 +232,18 @@ void WebsocketTlsTPM::set_connection_options(const WebsocketConnectionOptions& c
switch (connection_options.security_profile) { // `switch` used to lint on missing enum-values
case security::SecurityProfile::OCPP_1_6_ONLY_UNSECURED_TRANSPORT_WITHOUT_BASIC_AUTHENTICATION:
case security::SecurityProfile::UNSECURED_TRANSPORT_WITH_BASIC_AUTHENTICATION:
set_connection_options_base(connection_options);
this->connection_options.csms_uri.set_secure(false);
break;
case security::SecurityProfile::TLS_WITH_BASIC_AUTHENTICATION:
case security::SecurityProfile::TLS_WITH_CLIENT_SIDE_CERTIFICATES:
set_connection_options_base(connection_options);
this->connection_options.csms_uri.set_secure(true);
break;
default:
throw std::invalid_argument("unknown `security_profile`, value = " +
std::to_string(connection_options.security_profile));
}

set_connection_options_base(connection_options);

this->connection_options.csms_uri.set_secure(true);
}

static int callback_minimal(struct lws* wsi, enum lws_callback_reasons reason, void* user, void* in, size_t len) {
Expand Down Expand Up @@ -362,6 +363,10 @@ void WebsocketTlsTPM::tls_init(SSL_CTX* ctx, const std::string& path_chain, cons
}

void WebsocketTlsTPM::recv_loop() {
if (stopped) {
return;
}

std::shared_ptr<ConnectionData> local_data = conn_data;

if (local_data == nullptr) {
Expand Down Expand Up @@ -402,6 +407,10 @@ void WebsocketTlsTPM::recv_loop() {
}

void WebsocketTlsTPM::client_loop() {
if (stopped) {
return;
}

std::shared_ptr<ConnectionData> local_data = conn_data;

if (local_data == nullptr) {
Expand All @@ -428,6 +437,15 @@ void WebsocketTlsTPM::client_loop() {

info.fd_limit_per_thread = 1 + 1 + 1;

#ifdef LWS_WITH_NETWORK
if (connection_options.iface_or_ip.has_value()) {
info.iface = connection_options.iface_or_ip.value().c_str();
// TODO make bind_iface work.
// See ticket https://github.com/EVerest/libocpp/issues/542
// info.bind_iface = 1;
}
#endif

if (this->connection_options.security_profile == 2 || this->connection_options.security_profile == 3) {
// Setup context - need to know the key type first
std::string path_key;
Expand Down Expand Up @@ -598,12 +616,6 @@ bool WebsocketTlsTPM::connect() {
this->recv_message_thread->join();
}

// Stop any pending reconnect timer
{
std::lock_guard<std::mutex> lk(this->reconnect_mutex);
this->reconnect_timer_tpm.stop();
}

// Clear any pending messages on a new connection
{
std::lock_guard<std::mutex> lock(queue_mutex);
Expand All @@ -617,20 +629,6 @@ bool WebsocketTlsTPM::connect() {
empty.swap(recv_message_queue);
}

// Bind reconnect callback
this->reconnect_callback = [this](const websocketpp::lib::error_code& ec) {
EVLOG_info << "Reconnecting to TLS websocket at uri: " << this->connection_options.csms_uri.string()
<< " with security profile: " << this->connection_options.security_profile;

// close connection before reconnecting
if (this->m_is_connected) {
EVLOG_info << "Closing websocket connection before reconnecting";
this->close(websocketpp::close::status::abnormal_close, "Reconnect");
}

this->connect();
};

std::unique_lock<std::mutex> lock(connection_mutex);

// Release other threads
Expand Down Expand Up @@ -666,37 +664,20 @@ bool WebsocketTlsTPM::connect() {
return (connected);
}

void WebsocketTlsTPM::reconnect(std::error_code reason, long delay) {
EVLOG_info << "Attempting TLS TPM reconnect with reason: " << reason << " and delay:" << delay;
// void WebsocketTlsTPM::reconnect(std::error_code reason, long delay) {
void WebsocketTlsTPM::reconnect() {

if (this->shutting_down) {
EVLOG_info << "Not reconnecting because the websocket is being shutdown.";
return;
}

if (this->m_is_connected) {
EVLOG_info << "Closing websocket connection before reconnecting";
this->close(websocketpp::close::status::abnormal_close, "Reconnect");
this->connect();
}

EVLOG_info << "Reconnecting in: " << delay << "ms"
<< ", attempt: " << this->connection_attempts;

{
std::lock_guard<std::mutex> lk(this->reconnect_mutex);
this->reconnect_timer_tpm.timeout([this]() { this->reconnect_callback(websocketpp::lib::error_code()); },
std::chrono::milliseconds(delay));
}
}

void WebsocketTlsTPM::close(websocketpp::close::status::value code, const std::string& reason) {
void WebsocketTlsTPM::close(WebsocketCloseReason code, const std::string& reason, const bool /*stop_perpetual*/) {
EVLOG_info << "Closing TLS TPM websocket with reason: " << reason;

{
std::lock_guard<std::mutex> lk(this->reconnect_mutex);
this->reconnect_timer_tpm.stop();
}

std::shared_ptr<ConnectionData> local_data = conn_data;
if (local_data != nullptr) {
// Set the trigger from us
Expand All @@ -707,16 +688,15 @@ void WebsocketTlsTPM::close(websocketpp::close::status::value code, const std::s
conn_data.reset();

this->m_is_connected = false;
std::thread closing([this]() { this->closed_callback(websocketpp::close::status::normal); });
std::thread closing([this]() { this->closed_callback(WebsocketCloseReason::Normal); });
closing.detach();
stopped = true;
}

void WebsocketTlsTPM::on_conn_connected() {
EVLOG_info << "OCPP client successfully connected to TLS websocket server";

this->connection_attempts = 1; // reset connection attempts
this->m_is_connected = true;
this->reconnecting = false;

std::thread connected([this]() { this->connected_callback(this->connection_options.security_profile); });
connected.detach();
Expand All @@ -727,35 +707,20 @@ void WebsocketTlsTPM::on_conn_close() {

std::lock_guard<std::mutex> lk(this->connection_mutex);
this->m_is_connected = false;
this->disconnected_callback();
this->cancel_reconnect_timer();

std::thread closing([this]() { this->closed_callback(websocketpp::close::status::normal); });
std::thread closing([this]() { this->closed_callback(WebsocketCloseReason::Normal); });
closing.detach();
}

void WebsocketTlsTPM::on_conn_fail() {
EVLOG_error << "OCPP client connection failed to TLS websocket server";

std::lock_guard<std::mutex> lk(this->connection_mutex);
if (this->m_is_connected) {
std::thread disconnect([this]() { this->disconnected_callback(); });
disconnect.detach();
}

this->m_is_connected = false;

// -1 indicates to always attempt to reconnect
if (this->connection_options.max_connection_attempts == -1 or
this->connection_attempts <= this->connection_options.max_connection_attempts) {
this->reconnect(std::error_code(), this->get_reconnect_interval());
std::thread disconnect([this]() { this->failed_callback(WebsocketCloseReason::Normal); });
disconnect.detach();

// Increment reconn attempts
this->connection_attempts += 1;
} else {
EVLOG_info << "Closed TLS websocket, reconnect attempts exhausted";
this->close(websocketpp::close::status::normal, "Connection failed");
}
this->m_is_connected = false;
}

void WebsocketTlsTPM::on_message(void* msg, size_t len) {
Expand Down Expand Up @@ -1217,4 +1182,4 @@ int WebsocketTlsTPM::process_callback(void* wsi_ptr, int callback_reason, void*
return 0;
}

} // namespace ocpp
} // namespace ocpp
19 changes: 12 additions & 7 deletions lib/ocpp/v201/connectivity_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ ConnectivityManager::ConnectivityManager(DeviceModel& device_model, std::shared_
current_connection_options(),
connection_attempts(0), // TODO don't forget to reset when new websocket is created.
reconnect_backoff_ms(0),
wait_for_reconnect(false),
message_callback(message_callback) {
}

Expand Down Expand Up @@ -198,18 +199,15 @@ void ConnectivityManager::set_websocket_connection_options(const WebsocketConnec
}
}

void ConnectivityManager::set_websocket_connection_options_without_reconnect()
{
void ConnectivityManager::set_websocket_connection_options_without_reconnect() {
if (this->websocket == nullptr) {
return;
}

const int32_t active_slot = this->get_active_network_configuration_slot();

WebsocketConnectionOptions connection_options =
this->get_ws_connection_options(active_slot);
if (this->current_connection_options.iface_or_ip.has_value())
{
WebsocketConnectionOptions connection_options = this->get_ws_connection_options(active_slot);
if (this->current_connection_options.iface_or_ip.has_value()) {
// This is set later and not retrieved from the get_ws_connection_options function, so copy from the current
// connection options.
connection_options.iface_or_ip = this->current_connection_options.iface_or_ip.value();
Expand Down Expand Up @@ -377,7 +375,7 @@ bool ConnectivityManager::init_websocket(std::optional<int32_t> config_slot) {
}

// TODO this sometimes let the application hang (a thread not closing or something???)
this->websocket = nullptr;
// this->websocket = nullptr;

const auto& active_network_profile_cv = ControllerComponentVariables::ActiveNetworkProfile;
if (active_network_profile_cv.variable.has_value()) {
Expand Down Expand Up @@ -637,8 +635,10 @@ bool ConnectivityManager::is_higher_priority_profile(const int32_t new_configura

void ConnectivityManager::set_retry_connection_timer(const std::chrono::milliseconds timeout) {
EVLOG_info << "Trying to reconnect in " << timeout.count() / 1000 << " seconds";
this->wait_for_reconnect = true;
this->reconnect_timer.timeout(
[this]() {
this->wait_for_reconnect = false;
EVLOG_debug << "Timer timed out, reconnecting.";
std::unique_lock<std::mutex> lock(this->reconnect_mutex);
// Notify main thread that it should reconnect.
Expand Down Expand Up @@ -678,6 +678,11 @@ void ConnectivityManager::reconnect(const int32_t configuration_slot, const bool
return;
}

if (this->wait_for_reconnect) {
// Already waiting for reconnection, wait for that so it is not reconnecting again.
return;
}

if (!next_profile && (this->current_connection_options.max_connection_attempts == -1 ||
this->connection_attempts <= this->current_connection_options.max_connection_attempts)) {
std::unique_lock<std::recursive_mutex> lock(this->config_slot_mutex);
Expand Down

0 comments on commit b3a2043

Please sign in to comment.