Make implementation that uses only libwebsockets

This commit is contained in:
Bartosz Wieczorek 2025-09-23 14:32:15 +02:00
parent db1ef38292
commit e8cc0a38b4
11 changed files with 353 additions and 59 deletions

View File

@ -15,7 +15,10 @@ set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO) set(CMAKE_CXX_EXTENSIONS NO)
set(CMAKE_POSITION_INDEPENDENT_CODE YES) set(CMAKE_POSITION_INDEPENDENT_CODE YES)
add_compile_options(-Wall -Wextra -Wno-format-security) include(CheckIPOSupported)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
add_compile_options(-Wall -Wextra -Wno-format-security -fvisibility=hidden -fno-exceptions)
# add_compile_options(-g -fsanitize=address,undefined,float-divide-by-zero,float-cast-overflow,null -fsanitize-address-use-after-scope -fno-sanitize-recover=all -fno-sanitize=alignment -fno-omit-frame-pointer) # add_compile_options(-g -fsanitize=address,undefined,float-divide-by-zero,float-cast-overflow,null -fsanitize-address-use-after-scope -fno-sanitize-recover=all -fno-sanitize=alignment -fno-omit-frame-pointer)
# add_link_options(-g -fsanitize=address,undefined,float-divide-by-zero,float-cast-overflow,null -fsanitize-address-use-after-scope -fno-sanitize-recover=all -fno-sanitize=alignment -fno-omit-frame-pointer) # add_link_options(-g -fsanitize=address,undefined,float-divide-by-zero,float-cast-overflow,null -fsanitize-address-use-after-scope -fno-sanitize-recover=all -fno-sanitize=alignment -fno-omit-frame-pointer)

View File

@ -8,6 +8,7 @@ set(INC
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/core_handler.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/core_handler.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/core_handler_interface.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/core_handler_interface.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/curl.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/curl.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/ws_http.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/error_handler.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/error_handler.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/error.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/error.hpp
${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/finish.hpp ${CMAKE_CURRENT_SOURCE_DIR}/include/rublon/finish.hpp
@ -68,6 +69,7 @@ set(LWS_WITH_SHARED OFF)
set(LWS_ROLE_RAW_FILE OFF) set(LWS_ROLE_RAW_FILE OFF)
set(LWS_ROLE_DBUS OFF) set(LWS_ROLE_DBUS OFF)
set(LWS_WITHOUT_DAEMONIZE ON) set(LWS_WITHOUT_DAEMONIZE ON)
set(LWS_WITHOUT_SERVER ON) set(LWS_WITHOUT_SERVER ON)
set(LWS_WITHOUT_TESTAPPS ON) set(LWS_WITHOUT_TESTAPPS ON)
@ -77,12 +79,16 @@ set(LWS_WITHOUT_TEST_SERVER ON)
set(LWS_WITHOUT_TEST_SERVER_EXTPOLL ON) set(LWS_WITHOUT_TEST_SERVER_EXTPOLL ON)
set(LWS_UNIX_SOCK OFF) set(LWS_UNIX_SOCK OFF)
set(LWS_WITH_DIR OFF) set(LWS_WITH_DIR OFF)
set(LWS_WITH_FILE_OPS OFF) set(LWS_WITH_FILE_OPS OFF)
set(LWS_FOR_GITOHASHI OFF) set(LWS_FOR_GITOHASHI OFF)
set(LWS_WITH_HTTP2 OFF) set(LWS_WITH_HTTP2 OFF)
set(LWS_WITH_HTTP_BASIC_AUTH OFF) set(LWS_WITH_HTTP_BASIC_AUTH OFF)
set(LWS_WITH_HTTP_DIGEST_AUTH OFF)
set(LWS_WITH_HTTP_UNCOMMON_HEADERS OFF)
set(LWS_WITH_SYS_STATE OFF)
set(LWS_WITH_SYS_SMD OFF)
set(LWS_WITH_JPEG OFF) set(LWS_WITH_JPEG OFF)
set(LWS_WITH_JSONRPC OFF) set(LWS_WITH_JSONRPC OFF)
set(LWS_WITH_LEJP OFF) set(LWS_WITH_LEJP OFF)
@ -99,11 +105,14 @@ set(LWS_WITH_UDP OFF)
set(LWS_WITH_HTTP_STREAM_COMPRESSION OFF) set(LWS_WITH_HTTP_STREAM_COMPRESSION OFF)
set(LWS_WITH_HTTP_BROTLI OFF) set(LWS_WITH_HTTP_BROTLI OFF)
set(LWS_WITH_ZLIB OFF) set(LWS_WITH_ZLIB OFF)
set(LWS_WITH_SEQUENCER OFF)
set(LWS_WITH_LWSAC OFF)
set(RAPIDJSON_BUILD_DOC OFF CACHE BOOL "" FORCE) set(RAPIDJSON_BUILD_DOC OFF CACHE BOOL "" FORCE)
set(RAPIDJSON_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) set(RAPIDJSON_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(RAPIDJSON_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(RAPIDJSON_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(RAPIDJSON_BUILD_CXX17 ON CACHE BOOL "" FORCE) set(RAPIDJSON_BUILD_CXX17 ON CACHE BOOL "" FORCE)
set(RAPIDJSON_HAS_STDSTRING OFF)
cmake_policy(SET CMP0077 NEW) cmake_policy(SET CMP0077 NEW)
@ -118,13 +127,6 @@ FetchContent_Declare(
) )
FetchContent_MakeAvailable(libwebsockets) FetchContent_MakeAvailable(libwebsockets)
set(RAPIDJSON_BUILD_DOC OFF)
set(RAPIDJSON_BUILD_EXAMPLES OFF)
set(RAPIDJSON_BUILD_TESTS OFF)
set(RAPIDJSON_HAS_STDSTRING OFF)
FetchContent_Declare( FetchContent_Declare(
RapidJSON RapidJSON
URL https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.zip URL https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.zip

View File

@ -6,7 +6,6 @@ target_link_libraries(rublon_application
PUBLIC PUBLIC
rublon-ssh-ifc rublon-ssh-ifc
websockets websockets
-lcurl
-lssl -lssl
-lcrypto -lcrypto
) )

View File

@ -70,14 +70,14 @@ int main(int argc, const char ** argv) {
return printAuthMessageAndExit(rublon::ErrorHandler{pam, session.config()}.printErrorDetails(error)); return printAuthMessageAndExit(rublon::ErrorHandler{pam, session.config()}.printErrorDetails(error));
}; };
{ // {
CheckApplication ca{session}; // CheckApplication ca{session};
auto ret = ca.call(CH, session.config().systemToken).or_else(mapError); // auto ret = ca.call(CH, session.config().systemToken).or_else(mapError);
if(not ret.has_value()) { // if(not ret.has_value()) {
log(LogLevel::Error, "Check Application step failed, check configration"); // log(LogLevel::Error, "Check Application step failed, check configration");
return PAM_MAXTRIES; // return PAM_MAXTRIES;
} // }
} // }
auto ret = Init{session} auto ret = Init{session}
.handle(CH, pam) // .handle(CH, pam) //

View File

@ -1,7 +1,9 @@
#pragma once #pragma once
#include <rublon/core_handler.hpp> #include <rublon/core_handler.hpp>
#include <rublon/curl.hpp> #include <rublon/curl.hpp>
#include <rublon/ws_http.hpp>
#ifndef RUBLON_USE_STDOUT #ifndef RUBLON_USE_STDOUT
#include <rublon/pam.hpp> #include <rublon/pam.hpp>
@ -18,5 +20,5 @@ using Pam_t = PamStub;
#endif #endif
namespace rublon { namespace rublon {
using CoreHandler_t = CoreHandler< CURL >; using CoreHandler_t = CoreHandler< LWS >;
} // namespace rublon } // namespace rublon

View File

@ -13,6 +13,7 @@
namespace rublon { namespace rublon {
template < typename HttpHandler = CURL > template < typename HttpHandler = CURL >
class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
std::reference_wrapper< const Configuration > _config; std::reference_wrapper< const Configuration > _config;
@ -163,9 +164,9 @@ class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > {
request.headers["Accept"] = "application/json"; request.headers["Accept"] = "application/json";
stringifyTo(body, request.body); stringifyTo(body, request.body);
signRequest(request); signRequest(request);
std::pmr::string uri{&memoryResource}; std::pmr::string uri{&memoryResource};
uri.reserve(conservative_estimate(config().apiServer, path.size())); uri.reserve(conservative_estimate(config().apiServer, path.size()));
uri += config().apiServer; uri += config().apiServer;
uri += path.data(); uri += path.data();

View File

@ -18,38 +18,6 @@ namespace {
} }
} // namespace } // namespace
struct Request {
std::pmr::memory_resource * _mr;
std::pmr::map< std::pmr::string, std::pmr::string > headers;
std::pmr::string body;
public:
Request(std::pmr::memory_resource * mr) : _mr{mr}, headers{_mr}, body{_mr} {};
Request(const Request & res) = delete;
Request & operator=(const Request & res) = delete;
Request(Request && res) = delete;
Request & operator=(Request &&) = delete;
};
struct Response {
std::pmr::memory_resource * _mr;
std::pmr::map< std::pmr::string, std::pmr::string, ci_less > headers;
std::pmr::string body;
public:
Response(std::pmr::memory_resource * mr) : _mr{mr}, headers{_mr}, body{_mr} {};
Response(const Response & res) = delete;
Response & operator=(const Response & res) = delete;
Response(Response && res) noexcept = default;
Response & operator=(Response && res) noexcept = default;
};
class CURL { class CURL {
std::unique_ptr< ::CURL, void (*)(::CURL *) > curl; std::unique_ptr< ::CURL, void (*)(::CURL *) > curl;
std::reference_wrapper< const Configuration > _config; std::reference_wrapper< const Configuration > _config;

View File

@ -21,6 +21,7 @@
#include <security/pam_modules.h> #include <security/pam_modules.h>
namespace rublon { namespace rublon {
inline bool fileGood(const std::filesystem::path & path) { inline bool fileGood(const std::filesystem::path & path) {
std::ifstream file(path); std::ifstream file(path);
return file.good(); return file.good();
@ -290,6 +291,39 @@ struct ci_less {
} }
}; };
struct Request {
std::pmr::memory_resource * _mr;
std::pmr::map< std::pmr::string, std::pmr::string > headers;
std::pmr::string body;
public:
Request(std::pmr::memory_resource * mr) : _mr{mr}, headers{_mr}, body{_mr} {};
Request(const Request & res) = delete;
Request & operator=(const Request & res) = delete;
Request(Request && res) = delete;
Request & operator=(Request &&) = delete;
};
struct Response {
std::pmr::memory_resource * _mr;
std::pmr::map< std::pmr::string, std::pmr::string, ci_less > headers;
std::pmr::string body;
public:
Response(std::pmr::memory_resource * mr) : _mr{mr}, headers{_mr}, body{_mr} {};
Response(const Response & res) = delete;
Response & operator=(const Response & res) = delete;
Response(Response && res) noexcept = default;
Response & operator=(Response && res) noexcept = default;
};
template < typename Out, typename... Types > template < typename Out, typename... Types >
constexpr std::array< Out, sizeof...(Types) > make_array(Types... names) { constexpr std::array< Out, sizeof...(Types) > make_array(Types... names) {
return {std::forward< Types >(names)...}; return {std::forward< Types >(names)...};

View File

@ -24,7 +24,7 @@ enum TransactionConfirmationStatus { transactionConfirmed, transactionDenied };
struct RublonEventData { struct RublonEventData {
TransactionConfirmationStatus status; TransactionConfirmationStatus status;
StaticString< 32 > transactionID; /// TODO tid verification? StaticString< 32 > transactionID; /// TODO: tid verification?
std::optional< StaticString< 60 > > accessToken; std::optional< StaticString< 60 > > accessToken;
}; };
@ -300,5 +300,6 @@ class WebSocket {
const struct lws_protocols WebSocket::protocols[] = { // const struct lws_protocols WebSocket::protocols[] = { //
{"wss", WebSocket::callback_ws, 1024, 1024, 0, NULL, 0}, {"wss", WebSocket::callback_ws, 1024, 1024, 0, NULL, 0},
{nullptr, nullptr, 0, 0, 0, nullptr, 0}}; {nullptr, nullptr, 0, 0, 0, nullptr, 0}
};
} // namespace rublon } // namespace rublon

View File

@ -0,0 +1,285 @@
#pragma once
#include <cstring>
#include <rublon/configuration.hpp>
#include <rublon/error.hpp>
#include <rublon/utils.hpp>
#include <tl/expected.hpp>
#include <libwebsockets.h>
#include <string>
namespace rublon {
// Struktura do obsługi połączenia WebSocket
class LWS {
std::reference_wrapper< const Configuration > _config;
const Configuration & conf() const noexcept {
return _config.get();
}
struct session_data {
const Request & request;
Response & response;
int status{};
bool done{false};
};
static int callback(struct lws * wsi, enum lws_callback_reasons reason, void * user, void * in, size_t len) {
auto * data = reinterpret_cast< session_data * >(user);
int n;
switch(reason) {
/* because we are protocols[0] ... */
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
log(LogLevel::Error, "CLIENT_CONNECTION_ERROR: %s\n", in ? ( char * ) in : "(null)");
if(data)
data->done = true;
lws_cancel_service(lws_get_context(wsi));
return 0;
case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
if(data)
data->done = true;
lws_cancel_service(lws_get_context(wsi));
return 0;
/* ...callbacks related to receiving the result... */
case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: {
data->status = lws_http_client_http_response(wsi);
log(LogLevel::Info, "Connected with server response: %d\n", data->status);
char tmp[512];
auto copy_header = [&](const auto name) {
if (lws_hdr_custom_length(wsi, name.data(), name.size()) > 0 &&
lws_hdr_custom_copy(wsi, tmp, sizeof(tmp), name.data(), name.size()) > 0) {
data->response.headers[{name.data(), name.size()-1}] = tmp;
}
};
using namespace std::string_view_literals;
copy_header("x-ratelimit-limit:"sv);
copy_header("x-ratelimit-remaining:"sv);
copy_header("x-rublon-signature:"sv);
break;
}
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: {
log(LogLevel::Info, "RECEIVE_CLIENT_HTTP_READ: read %d\n", ( int ) len);
if(data && in && len) {
data->response.body.append(static_cast< const char * >(in), len);
}
return 0; /* don't passthru */
}
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: {
// Drain the socket; lws will subsequently trigger READ callbacks with 'in/len'
char buf[LWS_PRE + 2048];
char * p = buf + LWS_PRE;
int n = ( int ) sizeof(buf) - LWS_PRE;
if(lws_http_client_read(wsi, &p, &n) < 0)
return -1;
return 0; /* don't passthru */
}
case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
log(LogLevel::Info, "LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
if(data)
data->done = true;
lws_cancel_service(lws_get_context(wsi));
return 0;
case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: {
if(!lws_http_is_redirected_to_get(wsi)) {
unsigned char ** pp = ( unsigned char ** ) in;
unsigned char * end = (*pp) + len;
// Add user headers (ensure names include ':' e.g. "Content-Type:")
for(const auto & [name, value] : data->request.headers) {
if(lws_add_http_header_by_name(wsi,
( const unsigned char * ) name.c_str(),
( const unsigned char * ) value.c_str(),
( int ) value.size(),
pp,
end)) {
log(LogLevel::Error, "Header add error");
return -1;
}
}
// Ensure Content-Length if sending a body
if(!data->request.body.empty()) {
std::string cl = std::to_string(data->request.body.size());
if(lws_add_http_header_by_name(wsi,
( const unsigned char * ) "Content-Length:",
( const unsigned char * ) cl.c_str(),
( int ) cl.size(),
pp,
end)) {
log(LogLevel::Error, "Header Content-Length error");
return -1;
}
}
// Tell lws the body will follow
if(!data->request.body.empty())
lws_client_http_body_pending(wsi, 1);
lws_callback_on_writable(wsi);
}
return 0;
}
case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
// according to doc, there is no need for LWS_PRE when using HTTP_WRITE
n = lws_write(wsi, ( unsigned char * ) data->request.body.c_str(), data->request.body.size(), LWS_WRITE_HTTP_FINAL);
if(n < 0)
return -1;
/* we only had one thing to send, so inform lws we are donefi
* if we had more to send, call lws_callback_on_writable(wsi);
* and just return 0 from callback. On having sent the last
* part, call the below api instead.*/
lws_client_http_body_pending(wsi, 0);
return 0;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
};
static inline lws_protocols protocols[] = {{
"http-client", // const char *name
LWS::callback, // lws_callback_function *
0, // size_t per_session_data_size
0, // size_t rx_buffer_size
0, // unsigned int id
0, // void *user
0 // size_t tx_packet_size
},
{nullptr, nullptr, 0, 0, 0, nullptr, 0}};
public:
lws_context_creation_info info{};
lws_context * _context{nullptr};
LWS(const Configuration & config) : _config{config} {
std::memset(&info, 0, sizeof(info));
info.port = CONTEXT_PORT_NO_LISTEN;
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.protocols = protocols;
_context = lws_create_context(&info);
assert(_context != nullptr);
auto lws_log_emit = [](int level, const char * line) {
LogLevel rlevel{LLL_DEBUG};
if(level == LLL_ERR)
rlevel = LogLevel::Error;
if(level == LLL_WARN)
rlevel = LogLevel::Warning;
if(level == LLL_INFO)
rlevel = LogLevel::Info;
if(level == LLL_NOTICE)
rlevel = LogLevel::Debug;
if(level == LLL_DEBUG)
rlevel = LogLevel::Debug;
log(rlevel, "libwesockets: %s", line);
};
// if(_config.get().logging) {
lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | LLL_DEBUG | LLL_USER | LLL_HEADER | LLL_CLIENT, lws_log_emit);
// } else {
// lws_set_log_level(LLL_ERR | LLL_WARN, lws_log_emit);
// }
}
LWS(const LWS &) = delete;
LWS(LWS &&) = delete;
LWS & operator=(const LWS &) = delete;
LWS & operator=(LWS &&) = delete;
tl::expected< std::reference_wrapper< Response >, ConnectionError >
request(std::string_view uri, const Request & request, Response & response) const {
using namespace memory::literals;
memory::Monotonic_8k_Resource memoryResource;
// --- Parse URL ---
std::pmr::string protocol = "https";
std::pmr::string host{&memoryResource};
std::pmr::string path{"/", &memoryResource};
std::string_view sv_uri(uri);
if(auto pos = sv_uri.find("://"); pos != std::string_view::npos) {
protocol = std::string(sv_uri.substr(0, pos));
sv_uri.remove_prefix(pos + 3);
}
auto slash_pos = sv_uri.find('/');
if(slash_pos != std::string_view::npos) {
host = std::pmr::string(sv_uri.substr(0, slash_pos), &memoryResource);
path = std::pmr::string(sv_uri.substr(slash_pos), &memoryResource);
} else {
host = std::pmr::string(sv_uri, &memoryResource);
}
const int use_ssl = (protocol == "https") ? LCCSCF_USE_SSL : 0;
// --- Callback funkcji obsługującej klienta ---
session_data client_data{request, response};
lws_client_connect_info ccinfo = {};
ccinfo.alpn = "http/1.1";
ccinfo.context = _context;
ccinfo.address = host.c_str();
ccinfo.port = (protocol == "https") ? 443 : 80;
ccinfo.path = path.c_str();
ccinfo.ssl_connection = use_ssl;
ccinfo.host = host.c_str();
ccinfo.origin = host.c_str();
ccinfo.method = "POST";
ccinfo.protocol = protocols[0].name;
ccinfo.userdata = &client_data;
ccinfo.ietf_version_or_minus_one = -1;
ccinfo.pwsi = nullptr;
struct lws * wsi = lws_client_connect_via_info(&ccinfo);
if(!wsi) {
lws_context_destroy(_context);
return tl::unexpected{ConnectionError{ConnectionError::Timeout, 0}};
}
// --- Pętla serwisu ---
while(lws_service(_context, 1000) >= 0) {
if(!response.body.empty())
break; // odebrano dane
}
const auto http_code = client_data.status;
if(http_code >= 500) {
log(LogLevel::Error, "%s response with code %d ", "CURL", http_code);
return tl::unexpected{ConnectionError{ConnectionError::HttpError, http_code}};
}
for(const auto & [name, value] : response.headers) {
log(LogLevel::Debug, "Header %s:%s", name.c_str(), value.c_str());
}
log(LogLevel::Debug, "Body %s", response.body.c_str());
return response;
}
~LWS() {
if(_context) {
lws_context_destroy(_context);
_context = nullptr;
}
}
};
} // namespace rublon

View File

@ -26,7 +26,6 @@ target_link_libraries(rublon-ssh
PUBLIC PUBLIC
rublon-ssh-ifc rublon-ssh-ifc
websockets websockets
-lcurl
-lssl -lssl
-lcrypto -lcrypto
) )