Skip to content

Commit

Permalink
Replace WebSocket implementation (#1819)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lord-Grey authored Dec 29, 2024
1 parent d16142d commit 0aa7df4
Show file tree
Hide file tree
Showing 19 changed files with 210 additions and 587 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
if: ${{ matrix.language == 'cpp' }}
run: |
sudo apt-get update
sudo apt-get install --yes git build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-image0-dev libxcb-util0-dev libxcb-shm0-dev libxcb-render0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev libftdi1-dev
sudo apt-get install --yes git build-essential qtbase5-dev libqt5serialport5-dev libqt5websockets5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-image0-dev libxcb-util0-dev libxcb-shm0-dev libxcb-render0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev libftdi1-dev
- name: Temporarily downgrade CMake to 3.28.3 # Please remove if GitHub has updated Cmake (greater than 3.30.0)
uses: jwlawson/actions-setup-cmake@v2
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/qt5_6.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,9 @@ jobs:
- name: 📥 Install Qt
uses: jurplel/install-qt-action@v4
with:
version: ${{ inputs.qt_version == '6' && '6.7' || '5.15.*' }}
version: ${{ inputs.qt_version == '6' && '6.8' || '5.15.*' }}
target: 'desktop'
modules: ${{ inputs.qt_version == '6' && 'qtserialport' || '' }}
modules: ${{ inputs.qt_version == '6' && 'qtserialport qtwebsockets' || '' }}
cache: 'true'
cache-key-prefix: 'cache-qt-windows'

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed: Python 3.12 crashes (#1747)
- osX Grabber: Use ScreenCaptureKit under macOS 15 and above
- Removed maximum LED number constraint from Matrix layout schema which was not synced with the UI behaviour (#1804)
- Fixed bespoke WebSocket implementation by using of QWebSockets (#1816, #1448, #1247, #1130)

**JSON-API**
- Refactored JSON-API to ensure consistent authorization behaviour across sessions and single requests with token authorization.
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ message(STATUS "Found Qt Version: ${QT_VERSION}")
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
set(QT_MIN_VERSION "6.2.2")
else()
set(QT_MIN_VERSION "5.5.0")
set(QT_MIN_VERSION "5.9.0")
endif()

if("${QT_VERSION}" VERSION_LESS "${QT_MIN_VERSION}")
Expand Down
14 changes: 7 additions & 7 deletions doc/development/CompileHowto.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@ wget -qN https://raw.github.com/hyperion-project/hyperion.ng/master/bin/scripts/

### Ubuntu

**amd64 (Jammy):**
**amd64 (Noble Numbat):**
```console
wget -qN https://raw.github.com/hyperion-project/hyperion.ng/master/bin/scripts/docker-compile.sh && chmod +x *.sh && ./docker-compile.sh --name jammy
wget -qN https://raw.github.com/hyperion-project/hyperion.ng/master/bin/scripts/docker-compile.sh && chmod +x *.sh && ./docker-compile.sh --name noble
```

### Fedora

**amd64 (39):**
**amd64 (41):**
```console
wget -qN https://raw.github.com/hyperion-project/hyperion.ng/master/bin/scripts/docker-compile.sh && chmod +x *.sh && ./docker-compile.sh --name 39
wget -qN https://raw.github.com/hyperion-project/hyperion.ng/master/bin/scripts/docker-compile.sh && chmod +x *.sh && ./docker-compile.sh --name 41
```

## Cross compilation on amd64 for developers
Expand All @@ -61,14 +61,14 @@ cd $HYPERION_HOME

```console
sudo apt-get update
sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev libftdi1-dev
sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5websockets5-dev libqt5sql5-sqlite libqt5svg5-dev libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev libftdi1-dev
```

**Ubuntu (22.04+) - Qt6 based**

```console
sudo apt-get update
sudo apt-get install git cmake build-essential qt6-base-dev libqt6serialport6-dev libxkbcommon-dev libvulkan-dev libgl1-mesa-dev libusb-1.0-0-dev python3-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev pkg-config libftdi1-dev
sudo apt-get install git cmake build-essential qt6-base-dev libqt6serialport6-dev libqt6websockets6-dev libxkbcommon-dev libvulkan-dev libgl1-mesa-dev libusb-1.0-0-dev python3-dev libasound2-dev libturbojpeg0-dev libjpeg-dev libssl-dev pkg-config libftdi1-dev
```

**For Linux X11/XCB grabber support**
Expand Down Expand Up @@ -110,7 +110,7 @@ See [AUR](https://aur.archlinux.org/packages/?O=0&SeB=nd&K=hyperion&outdated=&SB
The following dependencies are needed to build hyperion.ng on fedora.
```console
sudo dnf -y groupinstall "Development Tools"
sudo dnf install python3-devel qt-devel qt5-qtbase-devel qt5-qtserialport-devel xrandr xcb-util-image-devel qt5-qtx11extras-devel alsa-lib-devel turbojpeg-devel libusb-devel xcb-util-devel dbus-devel openssl-devel fedora-packager rpmdevtools gcc libcec-devel libftdi1-dev
sudo dnf install python3-devel qt-devel qt6-qtbase-devel qt6-qtserialport-devel qt6-qtwebsockets-devel xrandr xcb-util-image-devel qt5-qtx11extras-devel alsa-lib-devel turbojpeg-devel libusb-devel xcb-util-devel dbus-devel openssl-devel fedora-packager rpmdevtools gcc libcec-devel libftdi1-dev
```
After installing the dependencies, you can continue with the compile instructions later on this page (the more detailed way..).

Expand Down
8 changes: 6 additions & 2 deletions libsrc/api/JsonAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -932,13 +932,17 @@ void JsonAPI::handleSchemaGetCommand(const QJsonObject& /*message*/, const JsonA
// Add infor about the type of setting elements
QJsonObject settingTypes;
QJsonArray globalSettingTypes;
for (const QString &type : SettingsTable().getGlobalSettingTypes()) {

SettingsTable settingsTable;
for (const QString &type : settingsTable.getGlobalSettingTypes())
{
globalSettingTypes.append(type);
}
settingTypes.insert("globalProperties", globalSettingTypes);

QJsonArray instanceSettingTypes;
for (const QString &type : SettingsTable().getInstanceSettingTypes()) {
for (const QString &type : settingsTable.getInstanceSettingTypes())
{
instanceSettingTypes.append(type);
}
settingTypes.insert("instanceProperties", instanceSettingTypes);
Expand Down
21 changes: 10 additions & 11 deletions libsrc/leddevice/dev_ftdi/ProviderFtdi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,12 @@ int ProviderFtdi::open()

Debug(_log, "Opening FTDI device=%s", QSTRING_CSTR(_deviceName));

FTDI_CHECK_RESULT((rc = ftdi_usb_open_string(_ftdic, QSTRING_CSTR(_deviceName))) < 0);
FTDI_CHECK_RESULT((rc = ftdi_usb_open_string(_ftdic, QSTRING_CSTR(_deviceName))) < 0)
/* doing this disable resets things if they were in a bad state */
FTDI_CHECK_RESULT((rc = ftdi_disable_bitbang(_ftdic)) < 0);
FTDI_CHECK_RESULT((rc = ftdi_setflowctrl(_ftdic, SIO_DISABLE_FLOW_CTRL)) < 0);
FTDI_CHECK_RESULT((rc = ftdi_set_bitmode(_ftdic, 0x00, BITMODE_RESET)) < 0);
FTDI_CHECK_RESULT((rc = ftdi_set_bitmode(_ftdic, 0xff, BITMODE_MPSSE)) < 0);
FTDI_CHECK_RESULT((rc = ftdi_disable_bitbang(_ftdic)) < 0)
FTDI_CHECK_RESULT((rc = ftdi_setflowctrl(_ftdic, SIO_DISABLE_FLOW_CTRL)) < 0)
FTDI_CHECK_RESULT((rc = ftdi_set_bitmode(_ftdic, 0x00, BITMODE_RESET)) < 0)
FTDI_CHECK_RESULT((rc = ftdi_set_bitmode(_ftdic, 0xff, BITMODE_MPSSE)) < 0)

double reference_clock = 60e6;
int divisor = (reference_clock / 2 / _baudRate_Hz) - 1;
Expand All @@ -86,7 +86,7 @@ int ProviderFtdi::open()
pinDirection
};

FTDI_CHECK_RESULT((rc = ftdi_write_data(_ftdic, buf.data(), buf.size())) != buf.size());
FTDI_CHECK_RESULT(static_cast<size_t>(rc = ftdi_write_data(_ftdic, buf.data(), static_cast<int>(buf.size()))) != buf.size())

_isDeviceReady = true;
return rc;
Expand Down Expand Up @@ -134,7 +134,7 @@ int ProviderFtdi::writeBytes(const qint64 size, const uint8_t *data)
// SET_BITS_LOW takes 2 arguments, so we're inserting data in -3 position from the end
buf.insert(buf.end() - 3, &data[0], &data[size]);

FTDI_CHECK_RESULT((rc = ftdi_write_data(_ftdic, buf.data(), buf.size())) != buf.size());
FTDI_CHECK_RESULT(static_cast<size_t>(rc = ftdi_write_data(_ftdic, buf.data(), static_cast<int>(buf.size()))) != buf.size())
return rc;
}

Expand All @@ -152,7 +152,7 @@ QJsonObject ProviderFtdi::discover(const QJsonObject & /*params*/)
struct ftdi_device_list *curdev = devlist;
QMap<QString, uint8_t> deviceIndexes;

while (curdev)
while (curdev != nullptr)
{
libusb_device_descriptor desc;
int rc = libusb_get_device_descriptor(curdev->dev, &desc);
Expand All @@ -161,8 +161,7 @@ QJsonObject ProviderFtdi::discover(const QJsonObject & /*params*/)
QString vendorIdentifier = QString("0x%1").arg(desc.idVendor, 4, 16, QChar{'0'});
QString productIdentifier = QString("0x%1").arg(desc.idProduct, 4, 16, QChar{'0'});
QString vendorAndProduct = QString("%1:%2")
.arg(vendorIdentifier)
.arg(productIdentifier);
.arg(vendorIdentifier,productIdentifier);
uint8_t deviceIndex = deviceIndexes.value(vendorAndProduct, 0);

char serial_string[128] = {0};
Expand All @@ -174,7 +173,7 @@ QJsonObject ProviderFtdi::discover(const QJsonObject & /*params*/)
QString ftdiOpenString;
if(!serialNumber.isEmpty())
{
ftdiOpenString = QString("s:%1:%2").arg(vendorAndProduct).arg(serialNumber);
ftdiOpenString = QString("s:%1:%2").arg(vendorAndProduct, serialNumber);
}
else
{
Expand Down
2 changes: 1 addition & 1 deletion libsrc/python/PythonProgram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ PythonProgram::PythonProgram(const QString& name, Logger* log) :
PyEval_RestoreThread(mainThreadState);
_tstate = Py_NewInterpreter();
#else
PyThreadState* prev = PyThreadState_Swap(NULL);
PyThreadState_Swap(NULL);

// Create a new interpreter configuration object
PyInterpreterConfig config{};
Expand Down
11 changes: 5 additions & 6 deletions libsrc/utils/ImageResampler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,15 @@ void ImageResampler::processImage(const uint8_t * data, int width, int height, i

outputImage.resize(outputWidth, outputHeight);

int xDestStart, xDestEnd;
int yDestStart, yDestEnd;
int xDestStart {0};
int xDestEnd = {outputWidth-1};
int yDestStart = {0};
int yDestEnd = {outputWidth-1};

switch (_flipMode)
{
case FlipMode::NO_CHANGE:
xDestStart = 0;
xDestEnd = outputWidth-1;
yDestStart = 0;
yDestEnd = outputHeight-1;
//use the initalized values
break;
case FlipMode::HORIZONTAL:
xDestStart = 0;
Expand Down
9 changes: 5 additions & 4 deletions libsrc/webserver/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS WebSockets REQUIRED)

file(GLOB_RECURSE webFiles RELATIVE ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/assets/webconfig/*)
file(RELATIVE_PATH webConfigPath ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/assets/webconfig)
Expand Down Expand Up @@ -28,13 +29,13 @@ add_library(webserver
${CMAKE_SOURCE_DIR}/libsrc/webserver/StaticFileServing.cpp
${CMAKE_SOURCE_DIR}/libsrc/webserver/WebJsonRpc.h
${CMAKE_SOURCE_DIR}/libsrc/webserver/WebJsonRpc.cpp
${CMAKE_SOURCE_DIR}/libsrc/webserver/WebSocketClient.h
${CMAKE_SOURCE_DIR}/libsrc/webserver/WebSocketClient.cpp
${CMAKE_SOURCE_DIR}/libsrc/webserver/WebSocketUtils.h
${CMAKE_SOURCE_DIR}/libsrc/webserver/WebSocketJsonHandler.h
${CMAKE_SOURCE_DIR}/libsrc/webserver/WebSocketJsonHandler.cpp
${CMAKE_BINARY_DIR}/WebConfig.qrc
)
)

target_link_libraries(webserver
Qt${QT_VERSION_MAJOR}::WebSockets
hyperion
hyperion-utils
hyperion-api
Expand Down
46 changes: 38 additions & 8 deletions libsrc/webserver/QtHttpClientWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include "QtHttpReply.h"
#include "QtHttpServer.h"
#include "QtHttpHeader.h"
#include "WebSocketClient.h"
#include "WebSocketJsonHandler.h"
#include "WebJsonRpc.h"

#include <QCryptographicHash>
Expand All @@ -27,8 +27,9 @@ QtHttpClientWrapper::QtHttpClientWrapper (QTcpSocket * sock, const bool& localCo
, m_localConnection(localConnection)
, m_websocketClient(nullptr)
, m_webJsonRpc (nullptr)
, m_websocketServer (nullptr)
{
connect (m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
connect(m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
}

QString QtHttpClientWrapper::getGuid (void)
Expand All @@ -50,6 +51,11 @@ void QtHttpClientWrapper::onClientDataReceived (void)
{
if (m_sockClient != Q_NULLPTR)
{
if (!m_sockClient->isTransactionStarted())
{
m_sockClient->startTransaction();
}

while (m_sockClient->bytesAvailable () != 0)
{
QByteArray line = m_sockClient->readLine ();
Expand Down Expand Up @@ -162,22 +168,30 @@ void QtHttpClientWrapper::onClientDataReceived (void)
{
case RequestParsed: // a valid request has ben fully parsed
{
// Catch websocket header "Upgrade"
if(m_currentRequest->getHeader(QtHttpHeader::Upgrade).toLower() == "websocket")
const auto& upgradeValue = m_currentRequest->getHeader(QtHttpHeader::Upgrade).toLower();
if (upgradeValue == "websocket")
{
if(m_websocketClient == Q_NULLPTR)
{
// disconnect this slot from socket for further requests
disconnect(m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
// disabling packet bunching
m_sockClient->setSocketOption(QAbstractSocket::LowDelayOption, 1);
m_sockClient->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
m_websocketClient = new WebSocketClient(m_currentRequest, m_sockClient, m_localConnection, this);
m_sockClient->rollbackTransaction();

QString servername = QCoreApplication::applicationName() + QLatin1Char('/') + HYPERION_VERSION;
QWebSocketServer::SslMode secureMode = m_serverHandle->isSecure() ? QWebSocketServer::SecureMode : QWebSocketServer::NonSecureMode;
m_websocketServer.reset(new QWebSocketServer(servername, secureMode));
connect(m_websocketServer.get(), &QWebSocketServer::newConnection,
this, &QtHttpClientWrapper::onNewWebSocketConnection);

m_websocketServer->handleConnection(m_sockClient);
emit m_sockClient->readyRead();
return;
}

break;
}

m_sockClient->commitTransaction();
// add post data to request and catch /jsonrpc subroute url
if ( m_currentRequest->getCommand() == "POST")
{
Expand Down Expand Up @@ -227,6 +241,8 @@ void QtHttpClientWrapper::onClientDataReceived (void)
case ParsingError: // there was an error durin one of parsing steps
{
m_sockClient->readAll (); // clear remaining buffer to ignore content
m_sockClient->commitTransaction();

QtHttpReply reply (m_serverHandle);
reply.setStatusCode (QtHttpReply::BadRequest);
reply.appendRawData (QByteArrayLiteral ("<h1>Bad Request (HTTP parsing error) !</h1>"));
Expand Down Expand Up @@ -365,3 +381,17 @@ void QtHttpClientWrapper::closeConnection()
}
m_sockClient->close ();
}

void QtHttpClientWrapper::onNewWebSocketConnection() {

// Handle the pending connection
QWebSocket* webSocket = m_websocketServer->nextPendingConnection();
if (webSocket) {
// Manage the WebSocketJsonHandler for this connection
WebSocketJsonHandler* handler = new WebSocketJsonHandler(webSocket);
connect(webSocket, &QWebSocket::disconnected, handler, &QObject::deleteLater);
}
else {
qWarning() << "No pending WebSocket connection!";
}
}
8 changes: 8 additions & 0 deletions libsrc/webserver/QtHttpClientWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

#include <QObject>
#include <QString>
#include <QWebSocketServer>
#include <QCoreApplication>
#include <QScopedPointer>

class QTcpSocket;

Expand Down Expand Up @@ -39,8 +42,12 @@ class QtHttpClientWrapper : public QObject {
///
void closeConnection();

signals:
void newWebSocketConnection();

private slots:
void onClientDataReceived (void);
void onNewWebSocketConnection();

protected:
ParsingStatus sendReplyToClient (QtHttpReply * reply);
Expand All @@ -59,6 +66,7 @@ protected slots:
WebSocketClient * m_websocketClient;
WebJsonRpc * m_webJsonRpc;
QByteArray m_fragment;
QScopedPointer<QWebSocketServer> m_websocketServer;
};

#endif // QTHTTPCLIENTWRAPPER_H
28 changes: 14 additions & 14 deletions libsrc/webserver/QtHttpServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ QtHttpServer::QtHttpServer (QObject * parent)
, m_netOrigin (NetOrigin::getInstance())
{
m_sockServer = new QtHttpServerWrapper (this);
connect (m_sockServer, &QtHttpServerWrapper::newConnection, this, &QtHttpServer::onClientConnected);
connect (m_sockServer, &QtHttpServerWrapper::newConnection, this, &QtHttpServer::onClientConnected, Qt::UniqueConnection);
}

void QtHttpServer::start (quint16 port)
Expand Down Expand Up @@ -81,27 +81,27 @@ void QtHttpServer::onClientConnected (void)
{
if (QTcpSocket * sock = m_sockServer->nextPendingConnection ())
{
if(m_netOrigin->accessAllowed(sock->peerAddress(), sock->localAddress()))
if (m_netOrigin->accessAllowed(sock->peerAddress(), sock->localAddress()))
{
connect (sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected);
connect(sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected);

if (m_useSsl)
{
if (QSslSocket * ssl = qobject_cast<QSslSocket *> (sock))
if (QSslSocket* ssl = qobject_cast<QSslSocket*> (sock))
{
connect (ssl, SslErrorSignal (&QSslSocket::sslErrors), this, &QtHttpServer::onClientSslErrors);
connect (ssl, &QSslSocket::encrypted, this, &QtHttpServer::onClientSslEncrypted);
connect (ssl, &QSslSocket::peerVerifyError, this, &QtHttpServer::onClientSslPeerVerifyError);
connect (ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged);
ssl->setLocalCertificateChain (m_sslCerts);
ssl->setPrivateKey (m_sslKey);
ssl->setPeerVerifyMode (QSslSocket::AutoVerifyPeer);
ssl->startServerEncryption ();
connect(ssl, SslErrorSignal(&QSslSocket::sslErrors), this, &QtHttpServer::onClientSslErrors);
connect(ssl, &QSslSocket::encrypted, this, &QtHttpServer::onClientSslEncrypted);
connect(ssl, &QSslSocket::peerVerifyError, this, &QtHttpServer::onClientSslPeerVerifyError);
connect(ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged);
ssl->setLocalCertificateChain(m_sslCerts);
ssl->setPrivateKey(m_sslKey);
ssl->setPeerVerifyMode(QSslSocket::AutoVerifyPeer);
ssl->startServerEncryption();
}
}

QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, m_netOrigin->isLocalAddress(sock->peerAddress(), sock->localAddress()), this);
m_socksClientsHash.insert (sock, wrapper);
QtHttpClientWrapper* wrapper = new QtHttpClientWrapper(sock, m_netOrigin->isLocalAddress(sock->peerAddress(), sock->localAddress()), this);
m_socksClientsHash.insert(sock, wrapper);
emit clientConnected (wrapper->getGuid ());
}
else
Expand Down
Loading

0 comments on commit 0aa7df4

Please sign in to comment.