diff --git a/CMakeLists.txt b/CMakeLists.txt index 67a36fba..b55cfc5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(hpcore src/hplog.cpp src/fbschema/common_helpers.cpp src/fbschema/p2pmsg_helpers.cpp + src/jsonschema/usrmsg_helpers.cpp src/sock/socket_client.cpp src/sock/socket_server.cpp src/sock/socket_session.cpp diff --git a/src/conf.cpp b/src/conf.cpp index b6382f48..6672276c 100644 --- a/src/conf.cpp +++ b/src/conf.cpp @@ -99,10 +99,10 @@ int create_contract() cfg.peerport = 22860; cfg.roundtime = 1000; cfg.pubport = 8080; - cfg.pubmaxsize = 65536; - cfg.pubmaxcpm = 1000; - cfg.peermaxsize = 65536; - cfg.peermaxcpm = 1000; + cfg.pubmaxsize = 0; + cfg.pubmaxcpm = 0; + cfg.peermaxsize = 0; + cfg.peermaxcpm = 0; #ifndef NDEBUG cfg.loglevel = "debug"; @@ -403,8 +403,7 @@ int validate_config() // Other required fields. if (cfg.binary.empty() || cfg.listenip.empty() || - cfg.peerport == 0 || cfg.roundtime == 0 || cfg.pubport == 0 || cfg.pubmaxsize == 0 || cfg.pubmaxcpm == 0 || cfg.peermaxsize == 0 || - cfg.peermaxcpm == 0 || cfg.loglevel.empty() || cfg.loggers.empty()) + cfg.peerport == 0 || cfg.roundtime == 0 || cfg.pubport == 0 || cfg.loglevel.empty() || cfg.loggers.empty()) { std::cout << "Required configuration fields missing at " << ctx.configFile << std::endl; return -1; diff --git a/src/cons/cons.cpp b/src/cons/cons.cpp index 57014c64..1daae7cb 100644 --- a/src/cons/cons.cpp +++ b/src/cons/cons.cpp @@ -138,8 +138,8 @@ void consensus() void populate_candidate_users_and_inputs() { // Lock the connected user list until we do this operation. - std::lock_guard lock(usr::users_mutex); - for (auto &[sid, con_user] : usr::users) + std::lock_guard lock(usr::ctx.users_mutex); + for (auto &[sid, con_user] : usr::ctx.users) { // Populate the user into candidate user inputs map. // We do this regardless of whether the user has any inputs or not. @@ -445,12 +445,12 @@ void apply_ledger(const p2p::proposal &cons_prop) outputtosend.swap(output); { - std::lock_guard lock(usr::users_mutex); + std::lock_guard lock(usr::ctx.users_mutex); // Find the user by session id. - const std::string sessionid = usr::sessionids[pubkey]; - auto itr = usr::users.find(sessionid); - if (itr != usr::users.end()) + const std::string sessionid = usr::ctx.sessionids[pubkey]; + auto itr = usr::ctx.users.find(sessionid); + if (itr != usr::ctx.users.end()) { const usr::connected_user &user = itr->second; usr::user_outbound_message outmsg(std::move(outputtosend)); diff --git a/src/jsonschema/usrmsg_helpers.cpp b/src/jsonschema/usrmsg_helpers.cpp new file mode 100644 index 00000000..0fb31558 --- /dev/null +++ b/src/jsonschema/usrmsg_helpers.cpp @@ -0,0 +1,149 @@ +#include +#include +#include "../util.hpp" +#include "../crypto.hpp" +#include "../hplog.hpp" +#include "usrmsg_helpers.hpp" + +namespace jsonschema::usrmsg +{ + +static const char *SCHEMA_VERSION = "0.1"; + +// These fields are used on json messages response validation. +static const char *FLD_TYPE = "type"; +static const char *FLD_CHALLENGE = "challenge"; +static const char *FLD_SIG = "sig"; +static const char *FLD_PUBKEY = "pubkey"; +static const char *FLD_INPUT = "input"; +static const char *FLD_CONTENT = "content"; +static const char *FLD_NONCE = "nonce"; + +// Message types +static const char *MSGTYPE_CHALLENGE = "public_challenge"; +static const char *MSGTYPE_CHALLENGE_RESP = "challenge_response"; +static const char *MSGTYPE_CONTRACT_INPUT = "contract_input"; + +// Length of user random challenge bytes. +static const size_t CHALLENGE_LEN = 16; + +/** + * Constructs user challenge message json and the challenge string required for + * initial user challenge handshake. This gets called when a user gets establishes + * a web sockets connection to HP. + * + * @param msg String reference to copy the generated json message string into. + * Message format: + * { + * "version": "", + * "type": "public_challenge", + * "challenge": "" + * } + * @param challenge String reference to copy the generated hex challenge string into. + */ +void create_user_challenge(std::string &msg, std::string &challengehex) +{ + // Use libsodium to generate the random challenge bytes. + unsigned char challenge_bytes[CHALLENGE_LEN]; + randombytes_buf(challenge_bytes, CHALLENGE_LEN); + + // We pass the hex challenge string separately to the caller even though + // we also include it in the challenge msg as well. + + util::bin2hex(challengehex, challenge_bytes, CHALLENGE_LEN); + + // Construct the challenge msg json. + // We do not use RapidJson here in favour of performance because this is a simple json message. + + // Since we know the rough size of the challenge massage we reserve adequate amount for the holder. + // Only Hot Pocket version number is variable length. Therefore message size is roughly 95 bytes + // so allocating 128bits for heap padding. + msg.reserve(128); + msg.append("{\"version\":\"") + .append(SCHEMA_VERSION) + .append("\",\"") + .append(FLD_TYPE) + .append("\":\"") + .append(MSGTYPE_CHALLENGE) + .append("\",\"") + .append(FLD_CHALLENGE) + .append("\":\"") + .append(challengehex) + .append("\"}"); +} + +/** + * Verifies the user challenge response with the original challenge issued to the user + * and the user public key contained in the response. + * + * @param extracted_pubkeyhex The hex public key extracted from the response. + * @param response The response bytes to verify. This will be parsed as json. + * Accepted response format: + * { + * "type": "challenge_response", + * "challenge": "", + * "sig": "", + * "pubkey": "" + * } + * @param original_challenge The original hex challenge string issued to the user. + * @return 0 if challenge response is verified. -1 if challenge not met or an error occurs. + */ +int verify_user_challenge_response(std::string &extracted_pubkeyhex, std::string_view response, std::string_view original_challenge) +{ + // We load response raw bytes into json document. + rapidjson::Document d; + + // Because we project the response message directly from the binary socket buffer in a zero-copy manner, the response + // string is not null terminated. 'kParseStopWhenDoneFlag' avoids rapidjson error in this case. + d.Parse(response.data()); + if (d.HasParseError()) + { + LOG_INFO << "Challenge response json parsing failed."; + return -1; + } + + // Validate msg type. + if (!d.HasMember(FLD_TYPE) || d[FLD_TYPE] != MSGTYPE_CHALLENGE_RESP) + { + LOG_INFO << "User challenge response type invalid. 'challenge_response' expected."; + return -1; + } + + // Compare the response challenge string with the original issued challenge. + if (!d.HasMember(FLD_CHALLENGE) || d[FLD_CHALLENGE] != original_challenge.data()) + { + LOG_INFO << "User challenge response challenge invalid."; + return -1; + } + + // Check for the 'sig' field existence. + if (!d.HasMember(FLD_SIG) || !d[FLD_SIG].IsString()) + { + LOG_INFO << "User challenge response signature invalid."; + return -1; + } + + // Check for the 'pubkey' field existence. + if (!d.HasMember(FLD_PUBKEY) || !d[FLD_PUBKEY].IsString()) + { + LOG_INFO << "User challenge response public key invalid."; + return -1; + } + + // Verify the challenge signature. We do this last due to signature verification cost. + std::string_view pubkeysv = util::getsv(d[FLD_PUBKEY]); + if (crypto::verify_hex( + original_challenge, + util::getsv(d[FLD_SIG]), + pubkeysv) != 0) + { + LOG_INFO << "User challenge response signature verification failed."; + return -1; + } + + extracted_pubkeyhex = pubkeysv; + + return 0; +} + +} // namespace jsonschema::usrmsg \ No newline at end of file diff --git a/src/jsonschema/usrmsg_helpers.hpp b/src/jsonschema/usrmsg_helpers.hpp new file mode 100644 index 00000000..25b8aa3c --- /dev/null +++ b/src/jsonschema/usrmsg_helpers.hpp @@ -0,0 +1,13 @@ +#ifndef _HP_JSONSCHEMA_USRMSG_HELPERS_H_ +#define _HP_JSONSCHEMA_USRMSG_HELPERS_H_ + +#include + +namespace jsonschema::usrmsg +{ +void create_user_challenge(std::string &msg, std::string &challengehex); + +int verify_user_challenge_response(std::string &extracted_pubkeyhex, std::string_view response, std::string_view original_challenge); +} // namespace jsonschema::usrmsg + +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 564b2ec2..678d1800 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,7 +68,6 @@ int parse_cmd(int argc, char **argv) */ void deinit() { - usr::deinit(); hplog::deinit(); } diff --git a/src/sock/socket_session.cpp b/src/sock/socket_session.cpp index ba2bb20f..124b0651 100644 --- a/src/sock/socket_session.cpp +++ b/src/sock/socket_session.cpp @@ -215,6 +215,8 @@ void socket_session::increment(util::SESSION_THRESHOLDS threshold_type, uint6 t.timestamp = 0; t.counter_value = 0; + LOG_INFO << "Session " << this->uniqueid << " threshold exceeded. (type:" << threshold_type << " limit:" << t.threshold_limit << ")"; + // Invoke the threshold monitor so any actions will be performed. threshold_monitor(threshold_type, t.threshold_limit, this); } diff --git a/src/usr/user_session_handler.cpp b/src/usr/user_session_handler.cpp index a2208d54..b4e7c2ec 100644 --- a/src/usr/user_session_handler.cpp +++ b/src/usr/user_session_handler.cpp @@ -13,9 +13,6 @@ namespace net = boost::asio; namespace beast = boost::beast; -using tcp = net::ip::tcp; -using error = boost::system::error_code; - namespace usr { @@ -40,17 +37,10 @@ void user_session_handler::on_connect(sock::socket_sessioninit_uniqueid(); - // Create an entry in pending_challenges for later tracking upon challenge response. - usr::pending_challenges.try_emplace(session->uniqueid, challengehex); - - user_outbound_message outmsg(std::move(msgstr)); + user_outbound_message outmsg(issue_challenge(session->uniqueid)); session->send(std::move(outmsg)); // Set the challenge-issued flag to help later checks in on_message. @@ -68,50 +58,8 @@ void user_session_handler::on_message( // Meaning we have previously issued a challenge to the client, if (session->flags[util::SESSION_FLAG::USER_CHALLENGE_ISSUED]) { - // The received message must be the challenge response. We need to verify it. - auto itr = usr::pending_challenges.find(session->uniqueid); - if (itr != usr::pending_challenges.end()) - { - std::string userpubkeyhex; - std::string_view original_challenge = itr->second; - if (usr::verify_user_challenge_response(userpubkeyhex, message, original_challenge) == 0) - { - // Challenge singature verification successful. - - // Decode hex pubkey and get binary pubkey. We area only going to keep - // the binary pubkey due to reduced memory footprint. - std::string userpubkey; - userpubkey.resize(userpubkeyhex.length() / 2); - util::hex2bin( - reinterpret_cast(userpubkey.data()), - userpubkey.length(), - userpubkeyhex); - - // Now check whether this user public key is duplicate. - if (usr::sessionids.count(userpubkey) == 0) - { - // All good. Unique public key. - // Promote the connection from pending-challenges to authenticated users. - - session->flags.reset(util::SESSION_FLAG::USER_CHALLENGE_ISSUED); // Clear challenge-issued flag - session->flags.set(util::SESSION_FLAG::USER_AUTHED); // Set the user-authed flag - usr::add_user(session, userpubkey); // Add the user to the global authed user list - usr::pending_challenges.erase(session->uniqueid); // Remove the stored challenge - - LOG_INFO << "User connection " << session->uniqueid << " authenticated. Public key " - << userpubkeyhex; - return; - } - else - { - LOG_INFO << "Duplicate user public key " << session->uniqueid; - } - } - else - { - LOG_INFO << "Challenge verification failed " << session->uniqueid; - } - } + if (verify_challenge(message, session)) + return; } // Check whether this session belongs to an authenticated (challenge-verified) user. else if (session->flags[util::SESSION_FLAG::USER_AUTHED]) @@ -119,19 +67,12 @@ void user_session_handler::on_message( // Check whether this user is among authenticated users // and perform authenticated msg processing. - auto itr = usr::users.find(session->uniqueid); - if (itr != usr::users.end()) + auto itr = ctx.users.find(session->uniqueid); + if (itr != ctx.users.end()) { // This is an authed user. - usr::connected_user &user = itr->second; - - { - std::lock_guard lock(users_mutex); - //Add to the hashed input buffer list. - user.inputs.push_back(util::hash_buffer(message, user.pubkey)); - } - - LOG_DBG << "Collected " << message.length() << " bytes from user"; + connected_user &user = itr->second; + handle_user_message(user, message); return; } } @@ -151,14 +92,14 @@ void user_session_handler::on_close(sock::socket_session // Session is awaiting challenge response. if (session->flags[util::SESSION_FLAG::USER_CHALLENGE_ISSUED]) { - usr::pending_challenges.erase(session->uniqueid); + ctx.pending_challenges.erase(session->uniqueid); } // Session belongs to an authed user. else if (session->flags[util::SESSION_FLAG::USER_AUTHED]) { // Wait for SC process completion before we remove existing user. proc::await_contract_execution(); - usr::remove_user(session->uniqueid); + remove_user(session->uniqueid); } LOG_INFO << "User disconnected " << session->uniqueid; diff --git a/src/usr/usr.cpp b/src/usr/usr.cpp index 7fcd4a89..81da75e4 100644 --- a/src/usr/usr.cpp +++ b/src/usr/usr.cpp @@ -1,11 +1,10 @@ #include #include #include -#include -#include #include #include "usr.hpp" #include "user_session_handler.hpp" +#include "../jsonschema/usrmsg_helpers.hpp" #include "../sock/socket_server.hpp" #include "../sock/socket_session_handler.hpp" #include "../util.hpp" @@ -13,65 +12,16 @@ #include "../crypto.hpp" #include "../hplog.hpp" +namespace jusrmsg = jsonschema::usrmsg; + namespace usr { -// The SSL context is required, and holds certificates -ssl::context ctx{ssl::context::tlsv13}; +// Holds global connected-users and related objects. +connected_context ctx; -/** - * Connected (authenticated) user list. (Exposed to other sub systems) - * Map key: User socket session id () - */ -std::unordered_map users; -std::mutex users_mutex; // Mutex for users access race conditions. - -/** - * Holds set of connected user session ids and public keys for lookups. - * This is used for pubkey duplicate checks as well. - * Map key: User binary pubkey - */ -std::unordered_map sessionids; - -/** - * Keep track of verification-pending challenges issued to newly connected users. - * Map key: User socket session id () - */ -std::unordered_map pending_challenges; - -/** - * User session handler instance. This instance's methods will be fired for any user socket activity. - */ -usr::user_session_handler global_usr_session_handler; - -/** - * The IO context used by the websocket listener. (not exposed out of this namespace) - */ -net::io_context ioc; - -/** - * The thread the websocket listener is running on. (not exposed out of this namespace) - */ -std::thread listener_thread; - -/** - * Used to pass down the default settings to the socket session - */ -sock::session_options sess_opts; - -// Challenge response fields. -// These fields are used on challenge response validation. -static const char *CHALLENGE_RESP_TYPE = "type"; -static const char *CHALLENGE_RESP_CHALLENGE = "challenge"; -static const char *CHALLENGE_RESP_SIG = "sig"; -static const char *CHALLENGE_RESP_PUBKEY = "pubkey"; - -// Message type for the user challenge. -static const char *CHALLENGE_MSGTYPE = "public_challenge"; -// Message type for the user challenge response. -static const char *CHALLENGE_RESP_MSGTYPE = "challenge_response"; -// Length of user random challenge bytes. -static const size_t CHALLENGE_LEN = 16; +// Holds objects used by socket listener. +listener_context listener_ctx; /** * Initializes the usr subsystem. Must be called once during application startup. @@ -81,129 +31,83 @@ int init() { // Start listening for incoming user connections. start_listening(); - return 0; } -/** - * Free any resources used by usr subsystem (eg. socket listeners). - */ -void deinit() +std::string issue_challenge(const std::string sessionid) { - stop_listening(); + std::string msgstr; + std::string challengehex; + jusrmsg::create_user_challenge(msgstr, challengehex); + + // Create an entry in pending_challenges for later tracking upon challenge response. + ctx.pending_challenges.try_emplace(std::move(sessionid), challengehex); + + return msgstr; } -/** - * Constructs user challenge message json and the challenge string required for - * initial user challenge handshake. This gets called when a user gets establishes - * a web sockets connection to HP. - * - * @param msg String reference to copy the generated json message string into. - * Message format: - * { - * "version": "", - * "type": "public_challenge", - * "challenge": "" - * } - * @param challenge String reference to copy the generated hex challenge string into. - */ -void create_user_challenge(std::string &msg, std::string &challengehex) +bool verify_challenge(std::string_view message, sock::socket_session *session) { - //Use libsodium to generate the random challenge bytes. - unsigned char challenge_bytes[CHALLENGE_LEN]; - randombytes_buf(challenge_bytes, CHALLENGE_LEN); + // The received message must be the challenge response. We need to verify it. + auto itr = ctx.pending_challenges.find(session->uniqueid); + if (itr == ctx.pending_challenges.end()) + { + LOG_DBG << "No challenge found for the session " << session->uniqueid; + return false; + } - //We pass the hex challenge string separately to the caller even though - //we also include it in the challenge msg as well. + std::string userpubkeyhex; + std::string_view original_challenge = itr->second; + if (jusrmsg::verify_user_challenge_response(userpubkeyhex, message, original_challenge) == 0) + { + // Challenge singature verification successful. - util::bin2hex(challengehex, challenge_bytes, CHALLENGE_LEN); + // Decode hex pubkey and get binary pubkey. We are only going to keep + // the binary pubkey due to reduced memory footprint. + std::string userpubkey; + userpubkey.resize(userpubkeyhex.length() / 2); + util::hex2bin( + reinterpret_cast(userpubkey.data()), + userpubkey.length(), + userpubkeyhex); - //Construct the challenge msg json. - // We do not use RapidJson here in favour of performance because this is a simple json message. + // Now check whether this user public key is duplicate. + if (ctx.sessionids.count(userpubkey) == 0) + { + // All good. Unique public key. + // Promote the connection from pending-challenges to authenticated users. - // Since we know the rough size of the challenge massage we reserve adequate amount for the holder. - // Only Hot Pocket version number is variable length. Therefore message size is roughly 95 bytes - // so allocating 128bits for heap padding. - msg.reserve(128); - msg.append("{\"version\":\"") - .append(util::HP_VERSION) - .append("\",\"type\":\"public_challenge\",\"challenge\":\"") - .append(challengehex) - .append("\"}"); + session->flags.reset(util::SESSION_FLAG::USER_CHALLENGE_ISSUED); // Clear challenge-issued flag + session->flags.set(util::SESSION_FLAG::USER_AUTHED); // Set the user-authed flag + add_user(session, userpubkey); // Add the user to the global authed user list + ctx.pending_challenges.erase(session->uniqueid); // Remove the stored challenge + + LOG_INFO << "User connection " << session->uniqueid << " authenticated. Public key " + << userpubkeyhex; + return true; + } + else + { + LOG_INFO << "Duplicate user public key " << session->uniqueid; + } + } + else + { + LOG_INFO << "Challenge verification failed " << session->uniqueid; + } + + return false; } -/** - * Verifies the user challenge response with the original challenge issued to the user - * and the user public key contained in the response. - * - * @param extracted_pubkeyhex The hex public key extracted from the response. - * @param response The response bytes to verify. This will be parsed as json. - * Accepted response format: - * { - * "type": "challenge_response", - * "challenge": "", - * "sig": "", - * "pubkey": "" - * } - * @param original_challenge The original hex challenge string issued to the user. - * @return 0 if challenge response is verified. -1 if challenge not met or an error occurs. - */ -int verify_user_challenge_response(std::string &extracted_pubkeyhex, std::string_view response, std::string_view original_challenge) +void handle_user_message(connected_user &user, std::string_view message) { - // We load response raw bytes into json document. - rapidjson::Document d; - - // Because we project the response message directly from the binary socket buffer in a zero-copy manner, the response - // string is not null terminated. 'kParseStopWhenDoneFlag' avoids rapidjson error in this case. - d.Parse(response.data()); - if (d.HasParseError()) { - LOG_INFO << "Challenge response json parsing failed."; - return -1; + std::lock_guard lock(ctx.users_mutex); + //Add to the hashed input buffer list. + user.inputs.push_back(util::hash_buffer(message, user.pubkey)); } - // Validate msg type. - if (!d.HasMember(CHALLENGE_RESP_TYPE) || d[CHALLENGE_RESP_TYPE] != CHALLENGE_RESP_MSGTYPE) - { - LOG_INFO << "User challenge response type invalid. 'challenge_response' expected."; - return -1; - } - - // Compare the response challenge string with the original issued challenge. - if (!d.HasMember(CHALLENGE_RESP_CHALLENGE) || d[CHALLENGE_RESP_CHALLENGE] != original_challenge.data()) - { - LOG_INFO << "User challenge response challenge invalid."; - return -1; - } - - // Check for the 'sig' field existence. - if (!d.HasMember(CHALLENGE_RESP_SIG) || !d[CHALLENGE_RESP_SIG].IsString()) - { - LOG_INFO << "User challenge response signature invalid."; - return -1; - } - - // Check for the 'pubkey' field existence. - if (!d.HasMember(CHALLENGE_RESP_PUBKEY) || !d[CHALLENGE_RESP_PUBKEY].IsString()) - { - LOG_INFO << "User challenge response public key invalid."; - return -1; - } - - // Verify the challenge signature. We do this last due to signature verification cost. - std::string_view pubkeysv = util::getsv(d[CHALLENGE_RESP_PUBKEY]); - if (crypto::verify_hex( - original_challenge, - util::getsv(d[CHALLENGE_RESP_SIG]), - pubkeysv) != 0) - { - LOG_INFO << "User challenge response signature verification failed."; - return -1; - } - - extracted_pubkeyhex = pubkeysv; - - return 0; + LOG_DBG << "Collected " << message.length() << " bytes from user"; } /** @@ -217,19 +121,19 @@ int verify_user_challenge_response(std::string &extracted_pubkeyhex, std::string int add_user(sock::socket_session *session, const std::string &pubkey) { const std::string &sessionid = session->uniqueid; - if (users.count(sessionid) == 1) + if (ctx.users.count(sessionid) == 1) { LOG_INFO << sessionid << " already exist. Cannot add user."; return -1; } { - std::lock_guard lock(users_mutex); - users.emplace(sessionid, usr::connected_user(session, pubkey)); + std::lock_guard lock(ctx.users_mutex); + ctx.users.emplace(sessionid, usr::connected_user(session, pubkey)); } // Populate sessionid map so we can lookup by user pubkey. - sessionids.try_emplace(pubkey, sessionid); + ctx.sessionids.try_emplace(pubkey, sessionid); return 0; } @@ -243,9 +147,9 @@ int add_user(sock::socket_session *session, const std::st */ int remove_user(const std::string &sessionid) { - auto itr = users.find(sessionid); + auto itr = ctx.users.find(sessionid); - if (itr == users.end()) + if (itr == ctx.users.end()) { LOG_INFO << sessionid << " does not exist. Cannot remove user."; return -1; @@ -254,11 +158,11 @@ int remove_user(const std::string &sessionid) usr::connected_user &user = itr->second; { - std::lock_guard lock(users_mutex); - sessionids.erase(user.pubkey); + std::lock_guard lock(ctx.users_mutex); + ctx.sessionids.erase(user.pubkey); } - users.erase(itr); + ctx.users.erase(itr); return 0; } @@ -269,28 +173,20 @@ void start_listening() { auto address = net::ip::make_address(conf::cfg.listenip); - sess_opts.max_message_size = conf::cfg.pubmaxsize; - sess_opts.max_bytes_per_minute = conf::cfg.pubmaxcpm; + listener_ctx.sess_opts.max_message_size = conf::cfg.pubmaxsize; + listener_ctx.sess_opts.max_bytes_per_minute = conf::cfg.pubmaxcpm; std::make_shared>( - ioc, - ctx, + listener_ctx.ioc, + listener_ctx.ssl_ctx, tcp::endpoint{address, conf::cfg.pubport}, - global_usr_session_handler, - sess_opts) + listener_ctx.global_usr_session_handler, + listener_ctx.sess_opts) ->run(); - listener_thread = std::thread([&] { ioc.run(); }); + listener_ctx.listener_thread = std::thread([&] { listener_ctx.ioc.run(); }); LOG_INFO << "Started listening for incoming user connections..."; } -/** - * Stops listening for incoming connections. - */ -void stop_listening() -{ - //TODO -} - } // namespace usr \ No newline at end of file diff --git a/src/usr/usr.hpp b/src/usr/usr.hpp index 946b0dd5..927feb08 100644 --- a/src/usr/usr.hpp +++ b/src/usr/usr.hpp @@ -33,40 +33,65 @@ struct connected_user sock::socket_session *session; /** + * @session * @param _pubkey The public key of the user in binary format. */ - connected_user(sock::socket_session *_session, std::string_view _pubkey) - : pubkey(_pubkey) + connected_user(sock::socket_session *session, std::string_view pubkey) + : pubkey(pubkey) { - session = _session; + this->session = session; } }; /** - * Connected (authenticated) user list. (Exposed to other sub systems) - * Map key: User socket session id () + * The context struct to hold global connected-users and related objects. */ -extern std::unordered_map users; -extern std::mutex users_mutex; // Mutex for users access race conditions. +struct connected_context +{ + // Connected (authenticated) user list. + // Map key: User socket session id () + std::unordered_map users; + std::mutex users_mutex; // Mutex for users access race conditions. + + // Holds set of connected user session ids and public keys for lookups. + // This is used for pubkey duplicate checks as well. + // Map key: User binary pubkey + std::unordered_map sessionids; + + // Keep track of verification-pending challenges issued to newly connected users. + // Map key: User socket session id () + std::unordered_map pending_challenges; +}; +extern connected_context ctx; /** - * Keep track of verification-pending challenges issued to newly connected users. - * Map key: User socket session id () + * Struct to hold objects used by socket listener. */ -extern std::unordered_map sessionids; +struct listener_context +{ + // The SSL context holds certificates to facilitate TLS connections. + ssl::context ssl_ctx{ssl::context::tlsv13}; -/** - * Keep track of verification-pending challenges issued to newly connected users. - */ -extern std::unordered_map pending_challenges; + // User session handler instance. This instance's methods will be fired for any user socket activity. + usr::user_session_handler global_usr_session_handler; + + // The IO context used by the websocket listener. (not exposed out of this namespace) + net::io_context ioc; + + // The thread the websocket listener is running on. (not exposed out of this namespace) + std::thread listener_thread; + + // Used to pass down the default settings to the socket session + sock::session_options sess_opts; +}; int init(); -void deinit(); +std::string issue_challenge(const std::string sessionid); -void create_user_challenge(std::string &msg, std::string &challengehex); +bool verify_challenge(std::string_view message, sock::socket_session *session); -int verify_user_challenge_response(std::string &extracted_pubkeyhex, std::string_view response, std::string_view original_challenge); +void handle_user_message(connected_user &user, std::string_view message); int add_user(sock::socket_session *session, const std::string &pubkey); @@ -74,8 +99,6 @@ int remove_user(const std::string &sessionid); void start_listening(); -void stop_listening(); - } // namespace usr #endif \ No newline at end of file