diff --git a/CMakeLists.txt b/CMakeLists.txt index 9486a27..1655bc2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) -project(smsub LANGUAGES CXX VERSION 0.9) +project(smsub LANGUAGES CXX VERSION 0.10) set(CMAKE_INCLUDE_CURRENT_DIR ON) @@ -18,6 +18,18 @@ else() find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) endif() find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Network WebSockets REQUIRED) +set(SMSUB_LIBS Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::WebSockets) + +option(WITH_BOOST "Use Boost library" ON) +if(WITH_BOOST) + find_package(Boost COMPONENTS json REQUIRED) + list(APPEND SMSUB_LIBS + Boost::json + ) + list(APPEND SMSUB_DEFINES + BOOST_JSON + ) +endif() set(SMSUB_SOURCES main.cpp @@ -31,11 +43,7 @@ set(SMSUB_HEADERS SMSubServer.h ) -add_executable(smsub - ${SMSUB_HEADERS} - ${SMSUB_SOURCES} -) - -target_link_libraries(smsub PRIVATE Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::WebSockets) - +add_executable(smsub ${SMSUB_HEADERS} ${SMSUB_SOURCES}) +target_compile_definitions(smsub PRIVATE ${SMSUB_DEFINES}) +target_link_libraries(smsub PRIVATE ${SMSUB_LIBS}) install(TARGETS smsub DESTINATION bin) diff --git a/SMSubServer.cpp b/SMSubServer.cpp index d08f727..8548165 100644 --- a/SMSubServer.cpp +++ b/SMSubServer.cpp @@ -21,6 +21,12 @@ #include #include "SMSubServer.h" #include "smsub.h" +#ifdef BOOST_JSON +#include +#else +#include +#include +#endif SMSubServer::SMSubServer(SMSubServerSettings *serverSettings, const QString &socket) : serverSettings(serverSettings) { @@ -40,6 +46,7 @@ SMSubServer::SMSubServer(SMSubServerSettings *serverSettings, const QString &soc SMSubServer::SMSubServer(SMSubServerSettings *serverSettings, const QString &serverName, const quint16 &port) : serverSettings(serverSettings) { QWebSocketServer *webSocketServer = new QWebSocketServer(serverName, QWebSocketServer::NonSecureMode, this); + webSocketServer->setSupportedSubprotocols(QStringList() << "smsub" << "smsub_json"); webSocketServer->listen(QHostAddress::LocalHost, port); QObject::connect(webSocketServer, &QWebSocketServer::newConnection, this, &SMSubServer::newConnection); @@ -53,7 +60,7 @@ SMSubServer::SMSubServer(SMSubServerSettings *serverSettings, const QString &ser bool SMSubServer::isListening() { - if (Q_LIKELY(type == ServerType::Local)) { + if (type == ServerType::Local) { return static_cast(server)->isListening(); } else if (type == ServerType::WebSocket) { @@ -80,6 +87,9 @@ void SMSubServer::newConnection() webSocket->sendBinaryMessage(QString("SMSub Version %1\n").arg(QCoreApplication::applicationVersion()).toUtf8()); socket = webSocket; QTextStream(stderr) << QString("WebSocket %1:%2 connected!").arg(webSocket->peerAddress().toString(), QString::number(webSocket->peerPort())) << smsub_endl; + + if (webSocket->subprotocol() == "smsub_json") + socket->setProperty("ReceiveJson", true); } else { // Just for being sure @@ -100,7 +110,7 @@ bool SMSubServer::messageReceived(QObject *socket, const QByteArray &message) { // Only allow commands being sent if authenticated const bool isAuthenticated = socket->property("Authenticated").toBool(); - if (Q_LIKELY(isAuthenticated)) { + if (isAuthenticated) { if (message == "+dbg") { socket->setProperty("ReceiveDbgMsg", true); sendMessage(socket, "Debug messages enabled!\n"); @@ -109,6 +119,14 @@ bool SMSubServer::messageReceived(QObject *socket, const QByteArray &message) socket->setProperty("ReceiveDbgMsg", false); sendMessage(socket, "Debug messages disabled!\n"); } + else if (message == "+json") { + socket->setProperty("ReceiveJson", true); + debugOutput(socket, "JSON output enabled!"); + } + else if (message == "-json") { + socket->setProperty("ReceiveJson", false); + debugOutput(socket, "JSON output disabled!"); + } else if (message == "+log") { socket->setProperty("ReceiveLog", true); debugOutput(socket, "Log output enabled!"); @@ -117,8 +135,16 @@ bool SMSubServer::messageReceived(QObject *socket, const QByteArray &message) socket->setProperty("ReceiveLog", false); debugOutput(socket, "Log output disabled!"); } + else if (message == "+status") { + socket->setProperty("ReceiveStatus", true); + debugOutput(socket, "Status updates enabled!"); + } + else if (message == "-status") { + socket->setProperty("ReceiveStatus", false); + debugOutput(socket, "Status updates disabled!"); + } else if (message == "+reg") { - if (Q_LIKELY(serverSettings->canRegister)) { + if (serverSettings->canRegister) { QByteArray authUuid = QUuid::createUuid().toByteArray(QUuid::Id128) + QUuid::createUuid().toByteArray(QUuid::Id128); authUuid = QByteArray::fromHex(authUuid).toBase64(QByteArray::OmitTrailingEquals); @@ -130,11 +156,26 @@ bool SMSubServer::messageReceived(QObject *socket, const QByteArray &message) } } else if (message == "status") { - if (status) { - sendMessage(socket, QString("Status: on\nLast Start: %1\nLast Stop: %2\n").arg(QString::number(startTime), QString::number(stopTime)).toUtf8()); + if (isVariantTrue(socket->property("ReceiveJson"))) { +#ifdef BOOST_JSON + boost::json::object object; + object["type"] = "status"; + object["status"] = status ? "on" : "off"; + object["start"] = startTime; + object["stop"] = stopTime; + const std::string json = boost::json::serialize(object) + '\n'; + sendMessage(socket, QByteArray::fromRawData(json.data(), json.size())); +#else + QJsonObject object; + object["type"] = "status"; + object["status"] = status ? "on" : "off"; + object["start"] = startTime; + object["stop"] = stopTime; + sendMessage(socket, QJsonDocument(object).toJson(QJsonDocument::Compact) + '\n'); +#endif } else { - sendMessage(socket, QString("Status: off\nLast Start: %1\nLast Stop: %2\n").arg(QString::number(startTime), QString::number(stopTime)).toUtf8()); + sendMessage(socket, QString("Status: %1\nLast Start: %2\nLast Stop: %3\n").arg(status ? "on" : "off", QString::number(startTime), QString::number(stopTime)).toUtf8()); } } else if (message == "start") { @@ -153,20 +194,24 @@ bool SMSubServer::messageReceived(QObject *socket, const QByteArray &message) QTimer::singleShot(0, qApp, &QCoreApplication::quit); debugOutput(socket, "Qutting smsub!"); } + else if (message == "wl") { + emit inputWritten("\n"); + debugOutput(socket, "Writing line!"); + } else if (message.startsWith("wl ")) { const QByteArray writeData = message.mid(3); emit inputWritten(writeData + '\n'); - debugOutput(socket, "Write line: " + writeData); + debugOutput(socket, "Writing line: " + writeData); } else if (message.startsWith("w ")) { const QByteArray writeData = message.mid(2); emit inputWritten(writeData); - debugOutput(socket, "Write: " + writeData); + debugOutput(socket, "Writing: " + writeData); } } else { // Authenticate when token is valid, otherwise disconnect - if (Q_UNLIKELY(tokens.contains(QString::fromUtf8(message)))) { + if (tokens.contains(QString::fromUtf8(message))) { // Set client as authenticated and add it to vector socket->setProperty("Authenticated", true); sendMessage(socket, "Login successful!\n"); @@ -174,7 +219,7 @@ bool SMSubServer::messageReceived(QObject *socket, const QByteArray &message) } else { // Stop receiving data and disconnect socket - if (Q_LIKELY(type == ServerType::Local)) { + if (type == ServerType::Local) { QLocalSocket *localSocket = static_cast(socket); QObject::disconnect(localSocket, &QLocalSocket::readyRead, this, &SMSubServer::lsReadyRead); localSocket->write("Incorrect token!\n"); @@ -193,6 +238,18 @@ bool SMSubServer::messageReceived(QObject *socket, const QByteArray &message) return true; } +bool SMSubServer::isVariantTrue(const QVariant &variant) +{ +#if QT_VERSION >= 0x060000 + if (variant.typeId() == QMetaType::Bool) { +#else + if (variant.type() == QVariant::Bool) { +#endif + return variant.toBool(); + } + return false; +} + void SMSubServer::wsMessageReceived(const QByteArray &message) { QWebSocket *socket = static_cast(sender()); @@ -226,14 +283,22 @@ void SMSubServer::deleteSocket() void SMSubServer::debugOutput(QObject *socket, const QByteArray &message) { // Only send debug messages when the client opted-in - const QVariant variant = socket->property("ReceiveDbgMsg"); -#if QT_VERSION >= 0x060000 - if (Q_UNLIKELY(variant.typeId() == QMetaType::Bool)) { + if (isVariantTrue(socket->property("ReceiveDbgMsg"))) { + if (isVariantTrue(socket->property("ReceiveJson"))) { +#ifdef BOOST_JSON + boost::json::object object; + object["type"] = "debug"; + object["message"] = message.toBase64(QByteArray::OmitTrailingEquals).constData(); + const std::string json = boost::json::serialize(object) + '\n'; + sendMessage(socket, QByteArray::fromRawData(json.data(), json.size())); #else - if (Q_UNLIKELY(variant.type() == QVariant::Bool)) { + QJsonObject object; + object["type"] = "debug"; + object["message"] = QString::fromUtf8(message.toBase64(QByteArray::OmitTrailingEquals)); + sendMessage(socket, QJsonDocument(object).toJson(QJsonDocument::Compact) + '\n'); #endif - bool receiveDbgMsg = variant.toBool(); - if (Q_LIKELY(receiveDbgMsg)) { + } + else { sendMessage(socket, message + '\n'); } } @@ -242,15 +307,26 @@ void SMSubServer::debugOutput(QObject *socket, const QByteArray &message) void SMSubServer::writeOutput(const QByteArray &output) { // Read process output when client opted-in for log + QByteArray json_output; for (auto it = sockets.constBegin(); it != sockets.constEnd(); it++) { - const QVariant variant = (*it)->property("ReceiveLog"); -#if QT_VERSION >= 0x060000 - if (Q_UNLIKELY(variant.typeId() == QMetaType::Bool)) { + if (isVariantTrue((*it)->property("ReceiveLog"))) { + if (isVariantTrue((*it)->property("ReceiveJson"))) { + if (json_output.isEmpty()) { +#ifdef BOOST_JSON + boost::json::object object; + object["type"] = "log"; + object["message"] = output.toBase64(QByteArray::OmitTrailingEquals).constData(); + json_output = QByteArray::fromStdString(boost::json::serialize(object) + '\n'); #else - if (Q_UNLIKELY(variant.type() == QVariant::Bool)) { + QJsonObject object; + object["type"] = "log"; + object["message"] = QString::fromUtf8(output.toBase64(QByteArray::OmitTrailingEquals)); + json_output = QJsonDocument(object).toJson(QJsonDocument::Compact) + '\n'; #endif - bool receiveLog = variant.toBool(); - if (Q_LIKELY(receiveLog)) { + } + sendMessage(*it, json_output); + } + else { sendMessage(*it, output); } } @@ -259,7 +335,7 @@ void SMSubServer::writeOutput(const QByteArray &output) void SMSubServer::sendMessage(QObject *socket, const QByteArray &message) { - if (Q_LIKELY(type == ServerType::Local)) { + if (type == ServerType::Local) { QLocalSocket *localSocket = static_cast(socket); localSocket->write(message); } @@ -282,4 +358,36 @@ void SMSubServer::statusUpdated(const bool status_, const qint64 time) { status = status_; status ? (startTime = time) : (stopTime = time); + + QByteArray output, json_output; + for (auto it = sockets.constBegin(); it != sockets.constEnd(); it++) { + if (isVariantTrue((*it)->property("ReceiveStatus"))) { + if (isVariantTrue((*it)->property("ReceiveJson"))) { + if (json_output.isEmpty()) { +#ifdef BOOST_JSON + boost::json::object object; + object["type"] = "status"; + object["status"] = status ? "on" : "off"; + object["start"] = startTime; + object["stop"] = stopTime; + json_output = QByteArray::fromStdString(boost::json::serialize(object) + '\n'); +#else + QJsonObject object; + object["type"] = "status"; + object["status"] = status ? "on" : "off"; + object["start"] = startTime; + object["stop"] = stopTime; + json_output = QJsonDocument(object).toJson(QJsonDocument::Compact) + '\n'; +#endif + } + sendMessage(*it, json_output); + } + else { + if (output.isEmpty()) { + output = QString("Status: %1\nLast Start: %2\nLast Stop: %3\n").arg(status ? "on" : "off", QString::number(startTime), QString::number(stopTime)).toUtf8(); + } + sendMessage(*it, json_output); + } + } + } } diff --git a/SMSubServer.h b/SMSubServer.h index dc1b743..f86786e 100644 --- a/SMSubServer.h +++ b/SMSubServer.h @@ -57,6 +57,7 @@ private: inline void debugOutput(QObject *socket, const QByteArray &message); inline void sendMessage(QObject *socket, const QByteArray &message); bool messageReceived(QObject *socket, const QByteArray &message); + inline bool isVariantTrue(const QVariant &variant); SMSubServerSettings *serverSettings; QVector sockets; QVector tokens; diff --git a/main.cpp b/main.cpp index 619be37..815a281 100644 --- a/main.cpp +++ b/main.cpp @@ -134,7 +134,7 @@ int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); a.setApplicationName("Server Manager Subprocess"); - a.setApplicationVersion("0.9"); + a.setApplicationVersion("0.10"); #ifdef Q_OS_UNIX catchUnixSignals({SIGINT, SIGHUP, SIGQUIT, SIGTERM});