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:
259
tools/EVerest-main/modules/API/RpcApi/server/WebsocketServer.cpp
Normal file
259
tools/EVerest-main/modules/API/RpcApi/server/WebsocketServer.cpp
Normal file
@@ -0,0 +1,259 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright chargebyte GmbH and Contributors to EVerest
|
||||
|
||||
#include "WebsocketServer.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <everest/helpers/helpers.hpp>
|
||||
#include <everest/logging.hpp>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace server {
|
||||
|
||||
static const int PER_SESSION_DATA_SIZE{4096};
|
||||
|
||||
static void log_callback(int level, const char* line) {
|
||||
switch (level) {
|
||||
case LLL_ERR:
|
||||
EVLOG_error << line;
|
||||
break;
|
||||
case LLL_WARN:
|
||||
EVLOG_warning << line;
|
||||
break;
|
||||
default:
|
||||
case LLL_DEBUG:
|
||||
EVLOG_debug << line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int WebSocketServer::callback_ws(struct lws* wsi, enum lws_callback_reasons reason, [[maybe_unused]] void* user,
|
||||
void* in, size_t len) {
|
||||
struct lws_context* context = lws_get_context(wsi);
|
||||
WebSocketServer* server = static_cast<WebSocketServer*>(lws_context_user(context));
|
||||
|
||||
if (!server) {
|
||||
throw std::runtime_error("Error: WebSocketServer instance not found!");
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(server->m_clients_mutex); // To protect access to m_clients
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wswitch-enum"
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_ESTABLISHED: {
|
||||
// Generate a random UUID for the client
|
||||
std::string client_id = everest::helpers::get_uuid();
|
||||
server->m_clients[client_id] = wsi;
|
||||
|
||||
char ip_address_buf[INET6_ADDRSTRLEN]{0};
|
||||
if (lws_get_peer_simple(wsi, ip_address_buf, sizeof(ip_address_buf)) == NULL) {
|
||||
ip_address_buf[0] = '\0'; // ensure empty string
|
||||
EVLOG_warning << "Failed to get client IP address";
|
||||
}
|
||||
|
||||
std::string ip_address(ip_address_buf, strnlen(ip_address_buf, sizeof(ip_address_buf)));
|
||||
|
||||
lock.unlock(); // Unlock before calling the callback
|
||||
server->on_client_connected(client_id, ip_address); // Call the on_client_connected callback
|
||||
lock.lock(); // Lock again after the callback
|
||||
EVLOG_info << "Client " << client_id << " connected" << (ip_address.empty() ? "" : (" from " + ip_address));
|
||||
break;
|
||||
}
|
||||
case LWS_CALLBACK_CLOSED: {
|
||||
auto it = std::find_if(server->m_clients.begin(), server->m_clients.end(),
|
||||
[wsi](const auto& client) { return client.second == wsi; });
|
||||
if (it != server->m_clients.end()) {
|
||||
const auto client_id = it->first;
|
||||
lock.unlock(); // Unlock before calling the callback
|
||||
EVLOG_info << "Client " << client_id << " disconnected";
|
||||
server->on_client_disconnected(client_id); // Call the on_client_disconnected callback
|
||||
lock.lock(); // Lock again after the callback
|
||||
server->m_clients.erase(it);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LWS_CALLBACK_RECEIVE: {
|
||||
auto it = std::find_if(server->m_clients.begin(), server->m_clients.end(),
|
||||
[wsi](const auto& client) { return client.second == wsi; });
|
||||
if (it != server->m_clients.end()) {
|
||||
const auto client_id = it->first;
|
||||
auto* data = static_cast<unsigned char*>(in);
|
||||
std::vector<uint8_t> received_data(data, data + len);
|
||||
lock.unlock(); // Unlock before calling the callback
|
||||
server->on_data_available(client_id, received_data); // Call the on_data_available callback
|
||||
lock.lock(); // Lock again after the callback
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
lock.unlock(); // Unlock the mutex after processing the callback
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
WebSocketServer::WebSocketServer(bool ssl_enabled, int port, const std::string& iface) : m_ssl_enabled(ssl_enabled) {
|
||||
// Constructor implementation
|
||||
memset(&m_info, 0, sizeof(m_info));
|
||||
lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | LLL_DEBUG, log_callback);
|
||||
m_info.port = port;
|
||||
// interface: must not be empty (else nothing to do)
|
||||
if (iface.empty()) {
|
||||
throw std::runtime_error("WebSocketServer: parameter 'websocket_interface' must not be empty");
|
||||
}
|
||||
if (iface != "all") {
|
||||
// create a local C-string
|
||||
char* iface_ptr = new char[iface.size() + 1];
|
||||
std::strcpy(iface_ptr, iface.c_str());
|
||||
// store it persistently in a member, and free the local variable
|
||||
m_iface = std::shared_ptr<char>(iface_ptr, [](char* ptr) {
|
||||
delete[] ptr; // custom deleter to free the char array
|
||||
});
|
||||
|
||||
m_info.iface = m_iface.get();
|
||||
}
|
||||
m_lws_protocols[0] = {"EVerestRpcApi", callback_ws, PER_SESSION_DATA_SIZE, 0, 0, NULL, 0};
|
||||
m_lws_protocols[1] = LWS_PROTOCOL_LIST_TERM;
|
||||
|
||||
m_info.protocols = m_lws_protocols;
|
||||
m_info.options = LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND;
|
||||
m_info.options |= (m_ssl_enabled ? LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT : 0);
|
||||
m_info.user = this; // To access WebSocketServer instance in callback
|
||||
|
||||
const std::string compiled_lws_version{LWS_LIBRARY_VERSION};
|
||||
const char* linked_version_cstr = lws_get_library_version();
|
||||
const std::string linked_lws_version = linked_version_cstr ? linked_version_cstr : "unknown";
|
||||
EVLOG_info << "libwebsockets version (compiled/runtime): " << compiled_lws_version << " / " << linked_lws_version;
|
||||
}
|
||||
|
||||
WebSocketServer::~WebSocketServer() {
|
||||
stop_server();
|
||||
}
|
||||
|
||||
bool WebSocketServer::running() const {
|
||||
return m_running;
|
||||
}
|
||||
|
||||
// send data to all connected clients
|
||||
void WebSocketServer::send_data(const std::vector<uint8_t>& data) {
|
||||
std::lock_guard<std::mutex> lock(m_clients_mutex);
|
||||
|
||||
for (const auto& client : m_clients) {
|
||||
struct lws* wsi = client.second;
|
||||
send_data(wsi, data);
|
||||
}
|
||||
}
|
||||
|
||||
// send data to client identified by ClientId
|
||||
void WebSocketServer::send_data(const ClientId& client_id, const std::vector<uint8_t>& data) {
|
||||
try {
|
||||
std::lock_guard<std::mutex> lock(m_clients_mutex);
|
||||
|
||||
auto it = m_clients.find(client_id);
|
||||
if (it == m_clients.end()) {
|
||||
EVLOG_error << "Client " << client_id << " not found";
|
||||
return;
|
||||
}
|
||||
|
||||
struct lws* wsi = it->second;
|
||||
send_data(wsi, data);
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Exception occurred while sending data to client " << client_id << ": " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
// send data to client identified by libwebsockets wsi
|
||||
void WebSocketServer::send_data(struct lws* wsi, const std::vector<uint8_t>& data) {
|
||||
try {
|
||||
std::vector<unsigned char> buf(LWS_PRE + data.size());
|
||||
memcpy(buf.data() + LWS_PRE, data.data(), data.size());
|
||||
|
||||
if (lws_write(wsi, buf.data() + LWS_PRE, data.size(), LWS_WRITE_BINARY) < 0) {
|
||||
EVLOG_error << "Failed to send data to client";
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
// Note: the code in the try{} block probably cannot throw an exception
|
||||
EVLOG_error << "Exception occurred while sending data to client: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketServer::kill_client_connection(const ClientId& client_id, const std::string& kill_reason) {
|
||||
std::lock_guard<std::mutex> lock(m_clients_mutex);
|
||||
|
||||
auto it = m_clients.find(client_id);
|
||||
if (it != m_clients.end()) {
|
||||
struct lws* wsi = it->second;
|
||||
std::string close_reason = kill_reason.empty() ? "Connection closed by server" : kill_reason;
|
||||
lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR,
|
||||
reinterpret_cast<unsigned char*>(const_cast<char*>(close_reason.data())), close_reason.size());
|
||||
lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, LWS_TO_KILL_ASYNC); // Set timeout to close the connection
|
||||
lws_callback_on_writable(wsi); // Notify the event loop to close the connection
|
||||
EVLOG_info << "Client " << client_id << " connection closed (reason: " << kill_reason << ")";
|
||||
m_clients.erase(it); // Remove client from map
|
||||
} else {
|
||||
EVLOG_error << "Client ID " << client_id << " not found!";
|
||||
}
|
||||
}
|
||||
|
||||
uint WebSocketServer::connections_count() const {
|
||||
std::lock_guard<std::mutex> lock(m_clients_mutex);
|
||||
return m_clients.size();
|
||||
}
|
||||
|
||||
bool WebSocketServer::start_server() {
|
||||
if (m_running) {
|
||||
EVLOG_warning << "WebSocket Server is already running";
|
||||
return true;
|
||||
}
|
||||
|
||||
m_context = lws_create_context(&m_info);
|
||||
if (!m_context) {
|
||||
EVLOG_error << "Failed to create WebSocket context";
|
||||
return false;
|
||||
}
|
||||
|
||||
EVLOG_info << "WebSocket Server running on port " << m_info.port
|
||||
<< (m_info.iface ? " (interface \"" + std::string(m_info.iface) + "\" only)" : "")
|
||||
<< (m_ssl_enabled ? " with" : " without") << " TLS";
|
||||
|
||||
m_server_thread = std::thread([this]() {
|
||||
m_running = true;
|
||||
while (m_running) {
|
||||
lws_service(m_context, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
while (!m_running) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Wait for server to start
|
||||
}
|
||||
|
||||
return true; // Server started successfully
|
||||
}
|
||||
|
||||
bool WebSocketServer::stop_server() {
|
||||
if (!m_running) {
|
||||
return true;
|
||||
}
|
||||
m_running = false;
|
||||
|
||||
lws_cancel_service(m_context); // To unblock the server thread loop immediately
|
||||
|
||||
if (m_server_thread.joinable()) {
|
||||
m_server_thread.join(); // Wait for server thread to finish
|
||||
}
|
||||
|
||||
if (m_context) {
|
||||
lws_context_destroy(m_context);
|
||||
m_context = nullptr;
|
||||
}
|
||||
EVLOG_info << "WebSocket Server stopped";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace server
|
||||
Reference in New Issue
Block a user