base: Refactor request stuff and finish more logic

This commit is contained in:
tt2468 2021-05-03 13:31:22 -07:00
parent 687f53bc6d
commit 807a1501b7
16 changed files with 250 additions and 189 deletions

View File

@ -75,7 +75,9 @@ set(obs-websocket_SOURCES
src/WebSocketProtocol.cpp src/WebSocketProtocol.cpp
src/WebSocketSession.cpp src/WebSocketSession.cpp
src/requesthandler/RequestHandler.cpp src/requesthandler/RequestHandler.cpp
src/requesthandler/RequestHandler_General.cpp
src/requesthandler/rpc/Request.cpp src/requesthandler/rpc/Request.cpp
src/requesthandler/rpc/RequestResult.cpp
src/forms/SettingsDialog.cpp src/forms/SettingsDialog.cpp
src/utils/Json.cpp src/utils/Json.cpp
src/utils/Crypto.cpp src/utils/Crypto.cpp
@ -89,6 +91,8 @@ set(obs-websocket_HEADERS
src/WebSocketSession.h src/WebSocketSession.h
src/requesthandler/RequestHandler.h src/requesthandler/RequestHandler.h
src/requesthandler/rpc/Request.h src/requesthandler/rpc/Request.h
src/requesthandler/rpc/RequestResult.h
src/requesthandler/rpc/RequestStatus.h
src/forms/SettingsDialog.h src/forms/SettingsDialog.h
src/utils/Utils.h) src/utils/Utils.h)

View File

@ -1,7 +1,8 @@
#include "WebSocketProtocol.h" #include "WebSocketProtocol.h"
#include "obs-websocket.h" #include "requesthandler/RequestHandler.h"
#include "utils/Utils.h" #include "requesthandler/rpc/RequestStatus.h"
#include "utils/Utils.h"
#include "plugin-macros.generated.h" #include "plugin-macros.generated.h"
bool IsSupportedRpcVersion(uint8_t requestedVersion) bool IsSupportedRpcVersion(uint8_t requestedVersion)
@ -98,25 +99,22 @@ WebSocketProtocol::ProcessResult WebSocketProtocol::ProcessMessage(SessionPtr se
return ret; return ret;
} }
auto requestHandler = RequestHandler(session); RequestHandler requestHandler;
RequestHandler::RequestResult result; Request request(session->RpcVersion(), session->IgnoreNonFatalRequestChecks(), incomingMessage["requestType"], incomingMessage["requestData"]);
if (incomingMessage.contains("requestData")) {
result = requestHandler.ProcessRequest(incomingMessage["requestType"], incomingMessage["requestData"]); RequestResult requestResult = requestHandler.ProcessRequest(request);
} else {
result = requestHandler.ProcessRequest(incomingMessage["requestType"]);
}
ret.result["messageType"] = "RequestResponse"; ret.result["messageType"] = "RequestResponse";
ret.result["requestType"] = incomingMessage["requestType"]; ret.result["requestType"] = incomingMessage["requestType"];
ret.result["requestId"] = incomingMessage["requestId"]; ret.result["requestId"] = incomingMessage["requestId"];
ret.result["requestStatus"] = { ret.result["requestStatus"] = {
{"result", result.statusCode == RequestHandler::RequestStatus::Success}, {"result", requestResult.StatusCode == RequestStatus::Success},
{"code", result.statusCode} {"code", requestResult.StatusCode}
}; };
if (result.comment != "") if (!requestResult.Comment.empty())
ret.result["requestStatus"]["comment"] = result.comment; ret.result["requestStatus"]["comment"] = requestResult.Comment;
if (!result.responseData.is_null()) if (requestResult.ResponseData.is_object())
ret.result["responseData"] = result.responseData; ret.result["responseData"] = requestResult.ResponseData;
return ret; return ret;
} else if (messageType == "RequestBatch") { } else if (messageType == "RequestBatch") {
@ -131,19 +129,13 @@ WebSocketProtocol::ProcessResult WebSocketProtocol::ProcessMessage(SessionPtr se
return ret; return ret;
} }
auto webSocketServer = GetWebSocketServer(); if (session->AuthenticationRequired()) {
if (!webSocketServer) {
blog(LOG_ERROR, "[WebSocketProtocol::ProcessMessage] Unable to fetch websocket server instance!");
return ret;
}
if (webSocketServer->AuthenticationRequired) {
if (!incomingMessage.contains("authentication")) { if (!incomingMessage.contains("authentication")) {
ret.closeCode = WebSocketServer::WebSocketCloseCode::InvalidIdentifyParameter; ret.closeCode = WebSocketServer::WebSocketCloseCode::InvalidIdentifyParameter;
ret.closeReason = "Your `Identify` payload is missing an `authentication` string, however authentication is required."; ret.closeReason = "Your `Identify` payload is missing an `authentication` string, however authentication is required.";
return ret; return ret;
} }
if (!Utils::Crypto::CheckAuthenticationString(webSocketServer->AuthenticationSecret, session->Challenge(), incomingMessage["authentication"])) { if (!Utils::Crypto::CheckAuthenticationString(session->Secret(), session->Challenge(), incomingMessage["authentication"])) {
ret.closeCode = WebSocketServer::WebSocketCloseCode::AuthenticationFailed; ret.closeCode = WebSocketServer::WebSocketCloseCode::AuthenticationFailed;
ret.closeReason = "Authentication failed."; ret.closeReason = "Authentication failed.";
return ret; return ret;

View File

@ -6,7 +6,6 @@
#include "WebSocketServer.h" #include "WebSocketServer.h"
#include "WebSocketSession.h" #include "WebSocketSession.h"
#include "requesthandler/RequestHandler.h"
namespace WebSocketProtocol { namespace WebSocketProtocol {
const std::vector<uint8_t> SupportedRpcVersions{ const std::vector<uint8_t> SupportedRpcVersions{

View File

@ -4,11 +4,11 @@
#include <QDateTime> #include <QDateTime>
#include <QTime> #include <QTime>
#include "obs-websocket.h"
#include "WebSocketServer.h" #include "WebSocketServer.h"
#include "WebSocketProtocol.h" #include "WebSocketProtocol.h"
#include "obs-websocket.h"
#include "Config.h" #include "Config.h"
#include "utils/Utils.h"
#include "plugin-macros.generated.h" #include "plugin-macros.generated.h"
@ -260,6 +260,7 @@ void WebSocketServer::onOpen(websocketpp::connection_hdl hdl)
// Configure session details // Configure session details
session->SetRemoteAddress(conn->get_remote_endpoint()); session->SetRemoteAddress(conn->get_remote_endpoint());
session->SetConnectedAt(QDateTime::currentSecsSinceEpoch()); session->SetConnectedAt(QDateTime::currentSecsSinceEpoch());
session->SetAuthenticationRequired(AuthenticationRequired);
std::string contentType = conn->get_request_header("Content-Type"); std::string contentType = conn->get_request_header("Content-Type");
if (contentType == "") { if (contentType == "") {
; ;
@ -280,6 +281,7 @@ void WebSocketServer::onOpen(websocketpp::connection_hdl hdl)
helloMessage["availableRequests"] = WebSocketProtocol::GetRequestList(); helloMessage["availableRequests"] = WebSocketProtocol::GetRequestList();
helloMessage["availableEvents"] = WebSocketProtocol::GetEventList(); helloMessage["availableEvents"] = WebSocketProtocol::GetEventList();
if (AuthenticationRequired) { if (AuthenticationRequired) {
session->SetSecret(AuthenticationSecret);
std::string sessionChallenge = Utils::Crypto::GenerateSalt(); std::string sessionChallenge = Utils::Crypto::GenerateSalt();
session->SetChallenge(sessionChallenge); session->SetChallenge(sessionChallenge);
helloMessage["authentication"] = {}; helloMessage["authentication"] = {};

View File

@ -5,20 +5,18 @@
#include <QString> #include <QString>
#include <mutex> #include <mutex>
#include <nlohmann/json.hpp> #include "utils/Utils.h"
#include <websocketpp/config/asio_no_tls.hpp> #include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp> #include <websocketpp/server.hpp>
#include "WebSocketSession.h" #include "WebSocketSession.h"
using json = nlohmann::json;
class WebSocketServer : QObject class WebSocketServer : QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
enum WebSocketCloseCode: uint16_t { enum WebSocketCloseCode {
// Internal only // Internal only
DontClose = 0, DontClose = 0,
// Reserved // Reserved
@ -45,7 +43,7 @@ class WebSocketServer : QObject
InvalidContentType = 4010, InvalidContentType = 4010,
}; };
enum WebSocketEncoding: uint8_t { enum WebSocketEncoding {
Json, Json,
MsgPack MsgPack
}; };

View File

@ -1,5 +1,3 @@
#include <obs-module.h>
#include "WebSocketSession.h" #include "WebSocketSession.h"
#include "plugin-macros.generated.h" #include "plugin-macros.generated.h"
@ -72,6 +70,29 @@ void WebSocketSession::SetEncoding(uint8_t encoding)
_encoding.store(encoding); _encoding.store(encoding);
} }
bool WebSocketSession::AuthenticationRequired()
{
return _authenticationRequired.load();
}
void WebSocketSession::SetAuthenticationRequired(bool required)
{
_authenticationRequired.store(required);
}
std::string WebSocketSession::Secret()
{
std::lock_guard<std::mutex> lock(_secretMutex);
std::string ret(_secret);
return ret;
}
void WebSocketSession::SetSecret(std::string secret)
{
std::lock_guard<std::mutex> lock(_secretMutex);
_secret = secret;
}
std::string WebSocketSession::Challenge() std::string WebSocketSession::Challenge()
{ {
std::lock_guard<std::mutex> lock(_challengeMutex); std::lock_guard<std::mutex> lock(_challengeMutex);

View File

@ -28,6 +28,12 @@ class WebSocketSession
uint8_t Encoding(); uint8_t Encoding();
void SetEncoding(uint8_t encoding); void SetEncoding(uint8_t encoding);
bool AuthenticationRequired();
void SetAuthenticationRequired(bool required);
std::string Secret();
void SetSecret(std::string secret);
std::string Challenge(); std::string Challenge();
void SetChallenge(std::string challenge); void SetChallenge(std::string challenge);
@ -55,6 +61,9 @@ class WebSocketSession
std::atomic<uint64_t> _incomingMessages; std::atomic<uint64_t> _incomingMessages;
std::atomic<uint64_t> _outgoingMessages; std::atomic<uint64_t> _outgoingMessages;
std::atomic<uint8_t> _encoding; std::atomic<uint8_t> _encoding;
std::atomic<bool> _authenticationRequired;
std::mutex _secretMutex;
std::string _secret;
std::mutex _challengeMutex; std::mutex _challengeMutex;
std::string _challenge; std::string _challenge;
std::atomic<uint8_t> _rpcVersion; std::atomic<uint8_t> _rpcVersion;

View File

@ -18,10 +18,11 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#pragma once #pragma once
#include <util/base.h>
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
#define OBS_WEBSOCKET_VERSION "5.0.0" #define OBS_WEBSOCKET_VERSION "5.0.0"
#define OBS_WEBSOCKET_RPC_VERSION 1 #define OBS_WEBSOCKET_RPC_VERSION 1
#define QT_TO_UTF8(str) str.toUtf8().constData() #define QT_TO_UTF8(str) str.toUtf8().constData()
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)

View File

@ -1,27 +1,23 @@
#include <obs.hpp>
#include <obs-frontend-api.h>
#include "RequestHandler.h" #include "RequestHandler.h"
#include "../plugin-macros.generated.h" #include "../plugin-macros.generated.h"
RequestHandler::RequestHandler(bool ignoreNonFatalRequestChecks, uint8_t rpcVersion) : const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
_ignoreNonFatalRequestChecks(ignoreNonFatalRequestChecks),
_rpcVersion(rpcVersion)
{ {
} {"GetVersion", &RequestHandler::GetVersion},
};
RequestHandler::RequestHandler(SessionPtr session) : RequestResult RequestHandler::ProcessRequest(const Request& request)
_ignoreNonFatalRequestChecks(session->IgnoreNonFatalRequestChecks()),
_rpcVersion(session->RpcVersion())
{ {
} if (!request.RequestData.is_null() && !request.RequestData.is_object())
return RequestResult::Error(RequestStatus::InvalidRequestParameterDataType, "Your request data is not an object.");
RequestHandler::RequestResult RequestHandler::ProcessRequest(std::string requestType, json requestData) RequestMethodHandler handler;
{ try {
RequestHandler::RequestResult ret; handler = _handlerMap.at(request.RequestType);
} catch (const std::out_of_range& oor) {
return RequestResult::Error(RequestStatus::UnknownRequestType, "Your request type is not valid.");
}
ret.statusCode = RequestHandler::RequestStatus::Success; return std::bind(handler, this, std::placeholders::_1)(request);
return ret;
} }

View File

@ -1,139 +1,20 @@
#pragma once #pragma once
#include <nlohmann/json.hpp> #include <map>
#include "rpc/Request.h" #include "rpc/Request.h"
#include "../WebSocketSession.h" #include "rpc/RequestResult.h"
#include "../utils/Utils.h"
using json = nlohmann::json; class RequestHandler;
typedef RequestResult(RequestHandler::*RequestMethodHandler)(const Request&);
class RequestHandler { class RequestHandler {
public: public:
enum RequestStatus: uint16_t { RequestResult ProcessRequest(const Request& request);
Unknown = 0,
// For internal use to signify a successful parameter check
NoError = 10,
Success = 100,
// The request is denied because the client is not authenticated
AuthenticationMissing = 200,
// Connection has already been authenticated (for modules utilizing a request to provide authentication)
AlreadyAuthenticated = 201,
// Authentication request was denied (for modules utilizing a request to provide authentication)
AuthenticationDenied = 202,
// The `requestType` field is missing from the request data
RequestTypeMissing = 203,
// The request type is invalid (does not exist)
InvalidRequestType = 204,
// Generic error code (comment is expected to be provided)
GenericError = 205,
// A required request parameter is missing
MissingRequestParameter = 300,
// Generic invalid request parameter message
InvalidRequestParameter = 400,
// A request parameter has the wrong data type
InvalidRequestParameterDataType = 401,
// A request parameter (float or int) is out of valid range
RequestParameterOutOfRange = 402,
// A request parameter (string or array) is empty and cannot be
RequestParameterEmpty = 403,
// An output is running and cannot be in order to perform the request (generic)
OutputRunning = 500,
// An output is not running and should be
OutputNotRunning = 501,
// Stream is running and cannot be
StreamRunning = 502,
// Stream is not running and should be
StreamNotRunning = 503,
// Record is running and cannot be
RecordRunning = 504,
// Record is not running and should be
RecordNotRunning = 505,
// Record is paused and cannot be
RecordPaused = 506,
// Replay buffer is running and cannot be
ReplayBufferRunning = 507,
// Replay buffer is not running and should be
ReplayBufferNotRunning = 508,
// Replay buffer is disabled and cannot be
ReplayBufferDisabled = 509,
// Studio mode is active and cannot be
StudioModeActive = 510,
// Studio mode is not active and should be
StudioModeNotActive = 511,
// The specified source (obs_source_t) was of the invalid type (Eg. input instead of scene)
InvalidSourceType = 600,
// The specified source (obs_source_t) was not found (generic for input, filter, transition, scene)
SourceNotFound = 601,
// The specified source (obs_source_t) already exists. Applicable to inputs, filters, transitions, scenes
SourceAlreadyExists = 602,
// The specified input (obs_source_t-OBS_SOURCE_TYPE_FILTER) was not found
InputNotFound = 603,
// The specified input (obs_source_t-OBS_SOURCE_TYPE_INPUT) had the wrong kind
InvalidInputKind = 604,
// The specified filter (obs_source_t-OBS_SOURCE_TYPE_FILTER) was not found
FilterNotFound = 605,
// The specified transition (obs_source_t-OBS_SOURCE_TYPE_TRANSITION) was not found
TransitionNotFound = 606,
// The specified transition (obs_source_t-OBS_SOURCE_TYPE_TRANSITION) does not support setting its position (transition is of fixed type)
TransitionDurationFixed = 607,
// The specified scene (obs_source_t-OBS_SOURCE_TYPE_SCENE), (obs_scene_t) was not found
SceneNotFound = 608,
// The specified scene item (obs_sceneitem_t) was not found
SceneItemNotFound = 609,
// The specified scene collection was not found
SceneCollectionNotFound = 610,
// The specified profile was not found
ProfileNotFound = 611,
// The specified output (obs_output_t) was not found
OutputNotFound = 612,
// The specified encoder (obs_encoder_t) was not found
EncoderNotFound = 613,
// The specified service (obs_service_t) was not found
ServiceNotFound = 614,
// The specified hotkey was not found
HotkeyNotFound = 615,
// The specified directory was not found
DirectoryNotFound = 616,
// The specified config item (obs_config_t) was not found. Could be section or parameter name.
ConfigParameterNotFound = 617,
// The specified property (obs_properties_t) was not found
PropertyNotFound = 618,
// Processing the request failed unexpectedly
RequestProcessingFailed = 700,
// Starting the Output failed
OutputStartFailed = 701,
// Duplicating the scene item failed
SceneItemDuplicationFailed = 702,
// Rendering the screenshot failed
ScreenshotRenderFailed = 703,
// Encoding the screenshot failed
ScreenshotEncodeFailed = 704,
// Saving the screenshot failed
ScreenshotSaveFailed = 705,
// Creating the directory failed
DirectoryCreationFailed = 706,
};
struct RequestResult {
RequestStatus statusCode;
json responseData = nullptr;
std::string comment;
};
RequestHandler(bool ignoreNonFatalRequestChecks, uint8_t rpcVersion);
RequestHandler(SessionPtr session);
RequestResult ProcessRequest(std::string requestType, json requestData = json::object());
private: private:
bool _ignoreNonFatalRequestChecks; RequestResult GetVersion(const Request&);
uint8_t _rpcVersion;
}; static const std::map<std::string, RequestMethodHandler> _handlerMap;
};

View File

@ -0,0 +1,9 @@
#include "RequestHandler.h"
#include "../plugin-macros.generated.h"
RequestResult RequestHandler::GetVersion(const Request& request)
{
json ret{{"test", "pp"}};
return RequestResult::Success(ret);
}

View File

@ -1,3 +1,14 @@
#include "Request.h" #include "Request.h"
#include "../../plugin-macros.generated.h" #include "../../plugin-macros.generated.h"
Request::Request(uint8_t rpcVersion, bool ignoreNonFatalRequestChecks, std::string requestType, json requestData) :
RpcVersion(rpcVersion),
IgnoreNonFatalRequestChecks(ignoreNonFatalRequestChecks),
RequestType(requestType)
{
if (!requestData.is_object())
RequestData = json::object();
else
RequestData = requestData;
}

View File

@ -1,13 +1,13 @@
#pragma once #pragma once
#include <nlohmann/json.hpp> #include "../../utils/Utils.h"
#include "../RequestHandler.h" struct Request
class Request
{ {
public: Request(uint8_t rpcVersion, bool ignoreNonFatalRequestChecks, std::string requestType, json requestData = nullptr);
;
private: uint8_t RpcVersion;
; bool IgnoreNonFatalRequestChecks;
std::string RequestType;
json RequestData;
}; };

View File

@ -0,0 +1,18 @@
#include "RequestResult.h"
RequestResult::RequestResult(RequestStatus statusCode, json responseData, std::string comment) :
StatusCode(statusCode),
ResponseData(responseData),
Comment(comment)
{
}
RequestResult RequestResult::Success(json responseData)
{
return RequestResult(RequestStatus::Success, responseData, "");
}
RequestResult RequestResult::Error(RequestStatus statusCode, std::string comment)
{
return RequestResult(statusCode, nullptr, comment);
}

View File

@ -0,0 +1,14 @@
#pragma once
#include "RequestStatus.h"
#include "../../utils/Utils.h"
struct RequestResult
{
RequestResult(RequestStatus statusCode = RequestStatus::Success, json responseData = nullptr, std::string comment = "");
static RequestResult Success(json responseData = nullptr);
static RequestResult Error(RequestStatus statusCode, std::string comment = "");
RequestStatus StatusCode;
json ResponseData;
std::string Comment;
};

View File

@ -0,0 +1,106 @@
#pragma once
enum RequestStatus {
Unknown = 0,
// For internal use to signify a successful parameter check
NoError = 10,
Success = 100,
// The request type is invalid (does not exist)
UnknownRequestType = 204,
// Generic error code (comment is expected to be provided)
GenericError = 205,
// A required request parameter is missing
MissingRequestParameter = 300,
// Generic invalid request parameter message
InvalidRequestParameter = 400,
// A request parameter has the wrong data type
InvalidRequestParameterDataType = 401,
// A request parameter (float or int) is out of valid range
RequestParameterOutOfRange = 402,
// A request parameter (string or array) is empty and cannot be
RequestParameterEmpty = 403,
// An output is running and cannot be in order to perform the request (generic)
OutputRunning = 500,
// An output is not running and should be
OutputNotRunning = 501,
// Stream is running and cannot be
StreamRunning = 502,
// Stream is not running and should be
StreamNotRunning = 503,
// Record is running and cannot be
RecordRunning = 504,
// Record is not running and should be
RecordNotRunning = 505,
// Record is paused and cannot be
RecordPaused = 506,
// Replay buffer is running and cannot be
ReplayBufferRunning = 507,
// Replay buffer is not running and should be
ReplayBufferNotRunning = 508,
// Replay buffer is disabled and cannot be
ReplayBufferDisabled = 509,
// Studio mode is active and cannot be
StudioModeActive = 510,
// Studio mode is not active and should be
StudioModeNotActive = 511,
// The specified source (obs_source_t) was of the invalid type (Eg. input instead of scene)
InvalidSourceType = 600,
// The specified source (obs_source_t) was not found (generic for input, filter, transition, scene)
SourceNotFound = 601,
// The specified source (obs_source_t) already exists. Applicable to inputs, filters, transitions, scenes
SourceAlreadyExists = 602,
// The specified input (obs_source_t-OBS_SOURCE_TYPE_FILTER) was not found
InputNotFound = 603,
// The specified input (obs_source_t-OBS_SOURCE_TYPE_INPUT) had the wrong kind
InvalidInputKind = 604,
// The specified filter (obs_source_t-OBS_SOURCE_TYPE_FILTER) was not found
FilterNotFound = 605,
// The specified transition (obs_source_t-OBS_SOURCE_TYPE_TRANSITION) was not found
TransitionNotFound = 606,
// The specified transition (obs_source_t-OBS_SOURCE_TYPE_TRANSITION) does not support setting its position (transition is of fixed type)
TransitionDurationFixed = 607,
// The specified scene (obs_source_t-OBS_SOURCE_TYPE_SCENE), (obs_scene_t) was not found
SceneNotFound = 608,
// The specified scene item (obs_sceneitem_t) was not found
SceneItemNotFound = 609,
// The specified scene collection was not found
SceneCollectionNotFound = 610,
// The specified profile was not found
ProfileNotFound = 611,
// The specified output (obs_output_t) was not found
OutputNotFound = 612,
// The specified encoder (obs_encoder_t) was not found
EncoderNotFound = 613,
// The specified service (obs_service_t) was not found
ServiceNotFound = 614,
// The specified hotkey was not found
HotkeyNotFound = 615,
// The specified directory was not found
DirectoryNotFound = 616,
// The specified config item (obs_config_t) was not found. Could be section or parameter name.
ConfigParameterNotFound = 617,
// The specified property (obs_properties_t) was not found
PropertyNotFound = 618,
// Processing the request failed unexpectedly
RequestProcessingFailed = 700,
// Starting the Output failed
OutputStartFailed = 701,
// Duplicating the scene item failed
SceneItemDuplicationFailed = 702,
// Rendering the screenshot failed
ScreenshotRenderFailed = 703,
// Encoding the screenshot failed
ScreenshotEncodeFailed = 704,
// Saving the screenshot failed
ScreenshotSaveFailed = 705,
// Creating the directory failed
DirectoryCreationFailed = 706,
};