WebSocketServer: Start and stop

This commit is contained in:
tt2468 2021-04-28 10:27:32 -07:00
parent 0f7683af4e
commit 948750da6a
7 changed files with 102 additions and 30 deletions

View File

@ -5,7 +5,6 @@ set(OBS_WEBSOCKET_RPC_VERSION 1)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Prohibit in-source builds
file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" _LOC_PATH)
if(EXISTS "${LOC_PATH}")

View File

@ -19,5 +19,5 @@ class Config {
QString ServerPassword;
private:
;
};

View File

@ -1,15 +1,16 @@
#include <chrono>
#include <thread>
#include <QtConcurrent>
#include "WebSocketServer.h"
#include "obs-websocket.h"
#include "Config.h"
#include "requesthandler/RequestHandler.h"
#include "utils/Utils.h"
#include "plugin-macros.generated.h"
WebSocketServer::WebSocketServer() :
QObject(nullptr),
_sessionMutex(QMutex::Recursive),
_sessions()
{
_server.get_alog().clear_channels(websocketpp::log::alevel::frame_header | websocketpp::log::alevel::frame_payload | websocketpp::log::alevel::control);
@ -34,8 +35,6 @@ WebSocketServer::WebSocketServer() :
&WebSocketServer::onMessage, this, websocketpp::lib::placeholders::_1, websocketpp::lib::placeholders::_2
)
);
blog(LOG_INFO, "cocks");
}
WebSocketServer::~WebSocketServer()
@ -43,14 +42,82 @@ WebSocketServer::~WebSocketServer()
Stop();
}
void WebSocketServer::ServerRunner()
{
blog(LOG_INFO, "IO thread started.");
try {
_server.run();
} catch (websocketpp::exception const & e) {
blog(LOG_ERROR, "websocketpp instance returned an error: %s", e.what());
} catch (const std::exception & e) {
blog(LOG_ERROR, "websocketpp instance returned an error: %s", e.what());
} catch (...) {
blog(LOG_ERROR, "websocketpp instance returned an error");
}
blog(LOG_INFO, "IO thread exited.");
}
void WebSocketServer::Start()
{
;
if (_server.is_listening()) {
blog(LOG_WARNING, "Call to Start() but the server is already listening.");
return;
}
auto conf = GetConfig();
if (!conf) {
blog(LOG_ERROR, "Unable to retreive config!");
return;
}
_serverPort = conf->ServerPort;
_authenticationSalt = Utils::Crypto::GenerateSalt();
_authenticationSecret = Utils::Crypto::GenerateSecret(conf->ServerPassword.toStdString(), _authenticationSalt);
_server.reset();
websocketpp::lib::error_code errorCode;
_server.listen(_serverPort, errorCode);
if (errorCode) {
std::string errorCodeMessage = errorCode.message();
blog(LOG_INFO, "Listen failed: %s", errorCodeMessage.c_str());
return;
}
_server.start_accept();
_serverThread = std::thread(&WebSocketServer::ServerRunner, this);
blog(LOG_INFO, "Server started successfully on port %d", _serverPort);
}
void WebSocketServer::Stop()
{
;
if (!_server.is_listening()) {
return;
}
_server.stop_listening();
std::unique_lock<std::mutex> lock(_sessionMutex);
for (auto const& [hdl, session] : _sessions) {
_server.close(hdl, websocketpp::close::status::going_away, "Server stopping.");
}
lock.unlock();
_server.stop();
_threadPool.waitForDone();
// This can deadlock the thread that it is running on. Bad but kinda required.
while (_sessions.size() > 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
_serverThread.join();
blog(LOG_INFO, "Server stopped successfully");
}
void WebSocketServer::InvalidateSession(websocketpp::connection_hdl hdl)

View File

@ -2,7 +2,7 @@
#include <QObject>
#include <QThreadPool>
#include <QMutex>
#include <mutex>
#include <nlohmann/json.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
@ -12,10 +12,8 @@
using json = nlohmann::json;
class WebSocketServer : public QObject
class WebSocketServer
{
Q_OBJECT
public:
enum WebsocketCloseCode: std::uint16_t {
UnknownReason = 4000,
@ -66,19 +64,22 @@ class WebSocketServer : public QObject
std::string GetConnectUrl();
public Q_SLOTS:
void BroadcastEvent(uint64_t requiredIntent, std::string eventType, json eventData = nullptr);
private:
void ServerRunner();
WebSocketSession *GetWebSocketSession(websocketpp::connection_hdl hdl);
void onOpen(websocketpp::connection_hdl hdl);
void onClose(websocketpp::connection_hdl hdl);
void onMessage(websocketpp::connection_hdl hdl, websocketpp::server<websocketpp::config::asio>::message_ptr message);
std::thread _serverThread;
websocketpp::server<websocketpp::config::asio> _server;
QThreadPool _threadPool;
QMutex _sessionMutex;
std::mutex _sessionMutex;
std::map<websocketpp::connection_hdl, WebSocketSession, std::owner_less<websocketpp::connection_hdl>> _sessions;
uint16_t _serverPort;
std::string _authenticationSecret;
std::string _authenticationSalt;
};

View File

@ -57,12 +57,16 @@ bool obs_module_load(void)
// Loading finished
blog(LOG_INFO, "Module loaded.");
if (_config->ServerEnabled)
_webSocketServer->Start();
return true;
}
void obs_module_unload()
{
blog(LOG_INFO, "Shutting down...");
if (_webSocketServer->IsListening()) {
blog(LOG_INFO, "WebSocket server is running. Stopping...");
_webSocketServer->Stop();

View File

@ -5,7 +5,7 @@
#include "../plugin-macros.generated.h"
QString Utils::Crypto::GenerateSalt()
std::string Utils::Crypto::GenerateSalt()
{
// Generate 32 random chars
const size_t randomCount = 32;
@ -15,15 +15,15 @@ QString Utils::Crypto::GenerateSalt()
}
// Convert the 32 random chars to a base64 string
return randomChars.toBase64();
return randomChars.toBase64().toStdString();
}
QString Utils::Crypto::GenerateSecret(QString password, QString salt)
std::string Utils::Crypto::GenerateSecret(std::string password, std::string salt)
{
// Concatenate the password and the salt
QString passAndSalt = "";
passAndSalt += password;
passAndSalt += salt;
passAndSalt += QString::fromStdString(password);
passAndSalt += QString::fromStdString(salt);
// Generate a SHA256 hash of the password and salt
auto challengeHash = QCryptographicHash::hash(
@ -32,15 +32,15 @@ QString Utils::Crypto::GenerateSecret(QString password, QString salt)
);
// Encode SHA256 hash to Base64
return challengeHash.toBase64();
return challengeHash.toBase64().toStdString();
}
bool Utils::Crypto::CheckAuthenticationString(QString secret, QString challenge, QString authenticationString)
bool Utils::Crypto::CheckAuthenticationString(std::string secret, std::string challenge, std::string authenticationString)
{
// Concatenate auth secret with the challenge sent to the user
QString secretAndChallenge = "";
secretAndChallenge += secret;
secretAndChallenge += challenge;
secretAndChallenge += QString::fromStdString(secret);
secretAndChallenge += QString::fromStdString(challenge);
// Generate a SHA256 hash of secretAndChallenge
auto hash = QCryptographicHash::hash(
@ -49,7 +49,7 @@ bool Utils::Crypto::CheckAuthenticationString(QString secret, QString challenge,
);
// Encode the SHA256 hash to Base64
QString expectedAuthenticationString = hash.toBase64();
std::string expectedAuthenticationString = hash.toBase64().toStdString();
return (authenticationString == expectedAuthenticationString);
}

View File

@ -1,5 +1,6 @@
#pragma once
#include <string>
#include <QString>
#include <nlohmann/json.hpp>
#include <obs-data.h>
@ -11,11 +12,11 @@ namespace Utils {
bool JsonArrayIsValidObsArray(json j);
obs_data_t *JsonToObsData(json j);
json ObsDataToJson(obs_data_t *d, bool includeDefault = false);
};
}
namespace Crypto {
QString GenerateSalt();
QString GenerateSecret(QString password, QString salt);
bool CheckAuthenticationString(QString secret, QString challenge, QString authenticationString);
};
};
std::string GenerateSalt();
std::string GenerateSecret(std::string password, std::string salt);
bool CheckAuthenticationString(std::string secret, std::string challenge, std::string authenticationString);
}
}