From 84bf0f66f70ef44d129b225a5f515d2e1f0fe6f1 Mon Sep 17 00:00:00 2001 From: Nils Schimmelmann Date: Sat, 2 Nov 2019 00:34:38 -0700 Subject: [PATCH] WASM WIP --- CMakeLists.txt | 3 ++ external/wasm.cmake | 59 +++++++++++++++++++++++++++++ src/CMakeLists.txt | 13 ++++++- src/configuration/configuration.h | 2 +- src/display/Textures.cpp | 2 +- src/main.cpp | 6 +++ src/pandoragroup/GroupClient.cpp | 2 + src/pandoragroup/GroupServer.cpp | 4 ++ src/pandoragroup/GroupSocket.cpp | 10 +++++ src/pandoragroup/GroupSocket.h | 17 +++++++++ src/pandoragroup/groupauthority.cpp | 12 ++++++ src/pandoragroup/groupauthority.h | 7 ++++ src/pandoragroup/mmapper2group.cpp | 4 ++ src/proxy/mumesocket.cpp | 13 +++++++ src/proxy/mumesocket.h | 16 +++++++- wasm.txt | 20 ++++++++++ 16 files changed, 185 insertions(+), 5 deletions(-) create mode 100644 external/wasm.cmake create mode 100644 wasm.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index dbdb816a4..85f306a6f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,9 @@ option(USE_CODE_COVERAGE "Run code coverage reporting" OFF) if(WIN32 AND MINGW) option(WITH_DRMINGW "Include Dr. Mingw crash dumping (Windows only)" ON) endif() +if(EMSCRIPTEN) + include(external/wasm.cmake) +endif() find_package(Qt5 COMPONENTS REQUIRED Core Widgets Network OpenGL Test) diff --git a/external/wasm.cmake b/external/wasm.cmake new file mode 100644 index 000000000..405fa4c5b --- /dev/null +++ b/external/wasm.cmake @@ -0,0 +1,59 @@ +# Configure WebAssembly build settings +# For some reason, the build settings need to be provided through the linker. + +# Activate Embind C/C++ bindings +# https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html +add_link_options(--bind) + +# Activate WebGL 2 (in addition to WebGL 1) +# https://emscripten.org/docs/porting/multimedia_and_graphics/OpenGL-support.html#webgl-friendly-subset-of-opengl-es-2-0-3-0 +#add_link_options("SHELL:-s USE_WEBGL2=1") + +# Emulate missing OpenGL ES2/ES3 features +# https://emscripten.org/docs/porting/multimedia_and_graphics/OpenGL-support.html#opengl-es-2-0-3-0-emulation +add_link_options("SHELL:-s FULL_ES2=1") +#add_link_options("SHELL:-s FULL_ES3=1") + +# Enable demangling of C++ stack traces +# https://emscripten.org/docs/porting/Debugging.html +add_link_options("SHELL:-s DEMANGLE_SUPPORT=1") + +# Run static dtors at teardown +# https://emscripten.org/docs/getting_started/FAQ.html#what-does-exiting-the-runtime-mean-why-don-t-atexit-s-run +add_link_options("SHELL:-s EXIT_RUNTIME=1") + +# Allows amount of memory used to change +# https://emscripten.org/docs/optimizing/Optimizing-Code.html#memory-growth +add_link_options("SHELL:-s ALLOW_MEMORY_GROWTH=1") + +# Enable C++ exception catching +# https://emscripten.org/docs/optimizing/Optimizing-Code.html#c-exceptions +add_link_options("SHELL:-s DISABLE_EXCEPTION_CATCHING=0") + +add_link_options("SHELL:-s ASSERTIONS=1") + +# Generate HTML file for each executable +#SET(CMAKE_EXECUTABLE_SUFFIX ".html") + +# Also search for packages beneath filesystem root (in addition to /emsdk_portable/sdk/system) +list(APPEND CMAKE_FIND_ROOT_PATH "/") + +# Link to missing Qt libraries. +# Temporary solution until Qt ship with proper CMake support for WebAssembly. +function(link_qt_static target) + target_link_libraries(${target} PRIVATE + "${_qt5Core_install_prefix}/plugins/platforms/libqwasm.a" # QWasmIntegrationPlugin + "${_qt5Core_install_prefix}/lib/libQt5EventDispatcherSupport.a" + "${_qt5Core_install_prefix}/lib/libQt5FontDatabaseSupport.a" + "${_qt5Core_install_prefix}/lib/libqtfreetype.a" + ) + + # copy in Qt HTML/JS launch files + set(APPNAME ${target}) + configure_file("${_qt5Core_install_prefix}/plugins/platforms/wasm_shell.html" + "${target}.html") + configure_file("${_qt5Core_install_prefix}/plugins/platforms/qtloader.js" + qtloader.js COPYONLY) + configure_file("${_qt5Core_install_prefix}/plugins/platforms/qtlogo.svg" + qtlogo.svg COPYONLY) +endfunction() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 86738adff..979e3b242 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -492,13 +492,17 @@ add_executable(mmapper WIN32 MACOSX_BUNDLE ${mmapper_DATA} ) -target_link_libraries(mmapper PUBLIC +target_link_libraries(mmapper PRIVATE Qt5::Core Qt5::Widgets Qt5::Network Qt5::OpenGL ) +if(EMSCRIPTEN) + link_qt_static(mmapper) +endif() + set_target_properties( mmapper PROPERTIES CXX_STANDARD 17 @@ -761,6 +765,13 @@ if(WIN32) set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/src/resources/LICENSE.GPL2") endif(WIN32) +if(EMSCRIPTEN) + set(APPNAME mmapper) + configure_file("${_qt5Core_install_prefix}/plugins/platforms/wasm_shell.html" "mmapper.html") + configure_file("${_qt5Core_install_prefix}/plugins/platforms/qtloader.js" "qtloader.js" COPYONLY) + configure_file("${_qt5Core_install_prefix}/plugins/platforms/qtlogo.svg" "qtlogo.svg" COPYONLY) +endif() + # Apple Install Settings if(APPLE) if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) diff --git a/src/configuration/configuration.h b/src/configuration/configuration.h index b4c508aec..a6c0e3010 100644 --- a/src/configuration/configuration.h +++ b/src/configuration/configuration.h @@ -40,7 +40,7 @@ NODISCARD static inline constexpr PlatformEnum getCurrentPlatform() return PlatformEnum::Windows; #elif defined(Q_OS_MAC) return PlatformEnum::Mac; -#elif defined(Q_OS_LINUX) +#elif defined(Q_OS_LINUX) or defined(Q_OS_WASM) return PlatformEnum::Linux; #else throw std::runtime_error("unsupported platform"); diff --git a/src/display/Textures.cpp b/src/display/Textures.cpp index b7fbf673b..d005c0e15 100644 --- a/src/display/Textures.cpp +++ b/src/display/Textures.cpp @@ -182,7 +182,7 @@ NODISCARD static SharedMMTexture createDottedWall(const ExitDirEnum dir) tex.create(); tex.setSize(SIZE, SIZE, 1); tex.setMipLevels(tex.maximumMipLevels()); - tex.setFormat(QOpenGLTexture::TextureFormat::RGBA8_UNorm); + tex.setFormat(QOpenGLTexture::TextureFormat::RGBAFormat); tex.allocateStorage(QOpenGLTexture::PixelFormat::RGBA, QOpenGLTexture::PixelType::UInt8); tex.setData(QOpenGLTexture::PixelFormat::RGBA, diff --git a/src/main.cpp b/src/main.cpp index d67077263..8cd10196c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,6 +24,11 @@ #include #endif +#ifdef Q_OS_WASM +#include +Q_IMPORT_PLUGIN(QWasmIntegrationPlugin) +#endif + // REVISIT: move splash files somewhere else? // (presumably not in the "root" src/ directory?) struct NODISCARD ISplash @@ -176,6 +181,7 @@ int main(int argc, char **argv) } QApplication app(argc, argv); + QCoreApplication::addLibraryPath("./"); tryInitDrMingw(); auto tryLoadingWinSock = std::make_unique(); setSurfaceFormat(); diff --git a/src/pandoragroup/GroupClient.cpp b/src/pandoragroup/GroupClient.cpp index e59b194bf..09862a317 100644 --- a/src/pandoragroup/GroupClient.cpp +++ b/src/pandoragroup/GroupClient.cpp @@ -318,9 +318,11 @@ void GroupClient::slot_connectionEncrypted() getAuthority()->setMetadata(secret, GroupMetadataEnum::LAST_LOGIN, QDateTime::currentDateTime().toString()); +#ifndef Q_OS_WASM getAuthority()->setMetadata(secret, GroupMetadataEnum::CERTIFICATE, socket.getPeerCertificate().toPem()); +#endif getAuthority()->setMetadata(secret, GroupMetadataEnum::PORT, QString("%1").arg(socket.getPeerPort())); diff --git a/src/pandoragroup/GroupServer.cpp b/src/pandoragroup/GroupServer.cpp index 52f2fae52..eca740728 100644 --- a/src/pandoragroup/GroupServer.cpp +++ b/src/pandoragroup/GroupServer.cpp @@ -370,11 +370,13 @@ void GroupServer::parseLoginInformation(GroupSocket *socket, const QVariantMap & for (const auto &target : clientsList) { if (socket == target) continue; +#ifndef Q_OS_WASM if (socket->getPeerCertificate() == target->getPeerCertificate()) { kickConnection(target, "Someone reconnected to the server using your secret!"); reconnect = true; break; } +#endif } } else { @@ -423,9 +425,11 @@ void GroupServer::parseLoginInformation(GroupSocket *socket, const QVariantMap & getAuthority()->setMetadata(secret, GroupMetadataEnum::LAST_LOGIN, QDateTime::currentDateTime().toString()); +#ifndef Q_OS_WASM getAuthority()->setMetadata(secret, GroupMetadataEnum::CERTIFICATE, socket->getPeerCertificate().toPem()); +#endif } // Strip protocolVersion from original QVariantMap diff --git a/src/pandoragroup/GroupSocket.cpp b/src/pandoragroup/GroupSocket.cpp index e1d893ccc..d170298f3 100644 --- a/src/pandoragroup/GroupSocket.cpp +++ b/src/pandoragroup/GroupSocket.cpp @@ -31,6 +31,7 @@ GroupSocket::GroupSocket(GroupAuthority *authority, QObject *parent) timer.setSingleShot(true); connect(&timer, &QTimer::timeout, this, &GroupSocket::slot_onTimeout); +#ifndef Q_OS_WASM const auto get_ssl_config = [this, authority]() { auto config = socket.sslConfiguration(); config.setCaCertificates({}); @@ -47,6 +48,7 @@ GroupSocket::GroupSocket(GroupAuthority *authority, QObject *parent) }; socket.setSslConfiguration(get_ssl_config()); socket.setPeerVerifyName(GROUP_COMMON_NAME); +#endif connect(&socket, &QAbstractSocket::hostFound, this, [this]() { emit sig_sendLog("Host found..."); }); @@ -60,6 +62,7 @@ GroupSocket::GroupSocket(GroupAuthority *authority, QObject *parent) emit sig_sendLog("Connection established..."); emit sig_connectionEstablished(this); }); +#ifndef Q_OS_WASM connect(&socket, &QSslSocket::encrypted, this, [this]() { timer.stop(); secret @@ -67,6 +70,7 @@ GroupSocket::GroupSocket(GroupAuthority *authority, QObject *parent) emit sig_sendLog("Connection successfully encrypted..."); emit sig_connectionEncrypted(this); }); +#endif connect(&socket, &QAbstractSocket::disconnected, this, [this]() { timer.stop(); emit sig_connectionClosed(this); @@ -76,7 +80,9 @@ GroupSocket::GroupSocket(GroupAuthority *authority, QObject *parent) QOverload::of(&QAbstractSocket::errorOccurred), this, &GroupSocket::slot_onError); +#ifndef Q_OS_WASM connect(&socket, &QSslSocket::peerVerifyError, this, &GroupSocket::slot_onPeerVerifyError); +#endif } GroupSocket::~GroupSocket() @@ -175,6 +181,7 @@ void GroupSocket::slot_onError(QAbstractSocket::SocketError e) } } +#ifndef Q_OS_WASM void GroupSocket::slot_onPeerVerifyError(const QSslError &error) { // Ignore expected warnings @@ -185,6 +192,7 @@ void GroupSocket::slot_onPeerVerifyError(const QSslError &error) qWarning() << "onPeerVerifyError" << static_cast(socket.error()) << socket.errorString() << error.errorString(); } +#endif void GroupSocket::slot_onTimeout() { @@ -193,6 +201,7 @@ void GroupSocket::slot_onTimeout() switch (protocolState) { case ProtocolStateEnum::Unconnected: case ProtocolStateEnum::AwaitingLogin: +#ifndef Q_OS_WASM if (!socket.isEncrypted()) { const QString msg = socket.isEncrypted() ? socket.errorString() : "Connection not successfully encrypted"; @@ -202,6 +211,7 @@ void GroupSocket::slot_onTimeout() goto continue_common_timeout; continue_common_timeout: +#endif case ProtocolStateEnum::AwaitingInfo: emit sig_errorInConnection(this, "Login timed out"); break; diff --git a/src/pandoragroup/GroupSocket.h b/src/pandoragroup/GroupSocket.h index 17a95ad44..452c5a4d7 100644 --- a/src/pandoragroup/GroupSocket.h +++ b/src/pandoragroup/GroupSocket.h @@ -8,7 +8,11 @@ #include #include #include +#ifndef Q_OS_WASM #include +#else +#include +#endif #include #include #include @@ -31,15 +35,22 @@ class GroupSocket final : public QObject void setSocket(qintptr socketDescriptor); void connectToHost(); void disconnectFromHost(); +#ifndef Q_OS_WASM void startServerEncrypted() { socket.startServerEncryption(); } void startClientEncrypted() { socket.startClientEncryption(); } +#else + void startServerEncrypted() { assert(false); } + void startClientEncrypted() { assert(false); } +#endif NODISCARD QByteArray getSecret() const { return secret; } NODISCARD QString getPeerName() const; NODISCARD quint16 getPeerPort() const { return socket.peerPort(); } NODISCARD QAbstractSocket::SocketError getSocketError() const { return socket.error(); } +#ifndef Q_OS_WASM NODISCARD QSslCertificate getPeerCertificate() const { return socket.peerCertificate(); } +#endif void setProtocolState(ProtocolStateEnum val); NODISCARD ProtocolStateEnum getProtocolState() const { return protocolState; } @@ -54,7 +65,9 @@ class GroupSocket final : public QObject protected slots: void slot_onError(QAbstractSocket::SocketError socketError); +#ifndef Q_OS_WASM void slot_onPeerVerifyError(const QSslError &error); +#endif void slot_onReadyRead(); void slot_onTimeout(); @@ -71,7 +84,11 @@ protected slots: void sendLog(const QString &msg) { emit sig_sendLog(msg); } private: +#ifndef Q_OS_WASM QSslSocket socket; +#else + QTcpSocket socket; +#endif QTimer timer; GroupAuthority *const authority; void onReadInternal(char c); diff --git a/src/pandoragroup/groupauthority.cpp b/src/pandoragroup/groupauthority.cpp index 4de7ad73b..728760229 100644 --- a/src/pandoragroup/groupauthority.cpp +++ b/src/pandoragroup/groupauthority.cpp @@ -215,10 +215,12 @@ void GroupAuthority::refresh() #else void GroupAuthority::refresh() { +#ifndef Q_OS_WASM certificate.clear(); setConfig().groupManager.certificate = ""; key.clear(); setConfig().groupManager.privateKey = ""; +#endif } #endif @@ -248,6 +250,7 @@ GroupAuthority::GroupAuthority(QObject *const parent) : QObject(parent) { qRegisterMetaType("GroupSecret"); +#ifndef Q_OS_WASM // Always utilize a temporary keychain qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1"); @@ -279,12 +282,17 @@ GroupAuthority::GroupAuthority(QObject *const parent) // Prime model model.setStringList(groupManager.authorizedSecrets); +#endif } GroupSecret GroupAuthority::getSecret() const { +#ifndef Q_OS_WASM // SHA-1 isn't very secure but at 40 characters it fits within a line for tells return certificate.digest(QCryptographicHash::Algorithm::Sha1).toHex(); +#else + return ""; +#endif } bool GroupAuthority::add(const GroupSecret &secret) @@ -339,6 +347,7 @@ bool GroupAuthority::validSecret(const GroupSecret &secret) const bool GroupAuthority::validCertificate(const GroupSocket *connection) const { +#ifndef Q_OS_WASM const GroupSecret &targetSecret = connection->getSecret(); const QString &storedCertificate = getMetadata(targetSecret, GroupMetadataEnum::CERTIFICATE); if (storedCertificate.isEmpty()) @@ -348,6 +357,9 @@ bool GroupAuthority::validCertificate(const GroupSocket *connection) const const bool certificatesMatch = targetCertficiate.compare(storedCertificate, Qt::CaseInsensitive) == 0; return certificatesMatch; +#else + return false; +#endif } QString GroupAuthority::getMetadata(const GroupSecret &secret, GroupMetadataEnum meta) const diff --git a/src/pandoragroup/groupauthority.h b/src/pandoragroup/groupauthority.h index bca9c461f..467e1ff31 100644 --- a/src/pandoragroup/groupauthority.h +++ b/src/pandoragroup/groupauthority.h @@ -4,8 +4,10 @@ // Author: Nils Schimmelmann (Jahara) #include +#ifndef Q_OS_WASM #include #include +#endif #include #include "../global/macros.h" @@ -34,8 +36,11 @@ public slots: public: NODISCARD GroupSecret getSecret() const; + +#ifndef Q_OS_WASM NODISCARD QSslCertificate getLocalCertificate() const { return certificate; } NODISCARD QSslKey getPrivateKey() const { return key; } +#endif public: NODISCARD QAbstractItemModel *getItemModel() { return &model; } @@ -54,6 +59,8 @@ public slots: private: QStringListModel model; +#ifndef Q_OS_WASM QSslCertificate certificate; QSslKey key; +#endif }; diff --git a/src/pandoragroup/mmapper2group.cpp b/src/pandoragroup/mmapper2group.cpp index 68cb62b4e..8ca01dd2f 100644 --- a/src/pandoragroup/mmapper2group.cpp +++ b/src/pandoragroup/mmapper2group.cpp @@ -27,7 +27,11 @@ #include "groupselection.h" #include "mmapper2character.h" +#ifndef Q_OS_WASM static constexpr const bool THREADED = true; +#else +static constexpr const bool THREADED = false; +#endif static constexpr const auto ONE_MINUTE = 60; static constexpr const int THIRTY_MINUTES = 30 * ONE_MINUTE; static constexpr const int DEFAULT_EXPIRE = THIRTY_MINUTES; diff --git a/src/proxy/mumesocket.cpp b/src/proxy/mumesocket.cpp index 09566be34..d0f55bbc9 100644 --- a/src/proxy/mumesocket.cpp +++ b/src/proxy/mumesocket.cpp @@ -74,24 +74,30 @@ MumeSslSocket::MumeSslSocket(QObject *parent) , m_socket{this} , m_timer{this} { +#ifndef Q_OS_WASM const auto get_ssl_config = [this]() { auto config = m_socket.sslConfiguration(); config.setPeerVerifyMode(QSslSocket::QueryPeer); return config; }; m_socket.setSslConfiguration(get_ssl_config()); +#endif connect(&m_socket, QOverload<>::of(&QAbstractSocket::connected), this, &MumeSslSocket::slot_onConnect); connect(&m_socket, &QIODevice::readyRead, this, &MumeSslSocket::slot_onReadyRead); connect(&m_socket, &QAbstractSocket::disconnected, this, &MumeSslSocket::slot_onDisconnect); +#ifndef Q_OS_WASM connect(&m_socket, &QSslSocket::encrypted, this, &MumeSslSocket::slot_onEncrypted); +#endif connect(&m_socket, QOverload::of(&QAbstractSocket::errorOccurred), this, &MumeSslSocket::slot_onError); +#ifndef Q_OS_WASM connect(&m_socket, &QSslSocket::peerVerifyError, this, &MumeSslSocket::slot_onPeerVerifyError); +#endif m_timer.setInterval(TIMEOUT_MILLIS); m_timer.setSingleShot(true); @@ -107,9 +113,12 @@ MumeSslSocket::~MumeSslSocket() void MumeSslSocket::virt_connectToHost() { const auto &settings = getConfig().connection; +#ifndef Q_OS_WASM m_socket.connectToHostEncrypted(settings.remoteServerName, settings.remotePort, QIODevice::ReadWrite); + m_socket.connectToHost(settings.remoteServerName, settings.remotePort, QIODevice::ReadWrite); +#endif m_timer.start(); } @@ -142,6 +151,7 @@ void MumeSslSocket::virt_onError(QAbstractSocket::SocketError e) } } +#ifndef Q_OS_WASM void MumeSslSocket::slot_onEncrypted() { m_timer.stop(); @@ -184,6 +194,7 @@ void MumeSslSocket::slot_onPeerVerifyError(const QSslError &error) "\n"); emit sig_processMudStream(byteArray); } +#endif void MumeSslSocket::slot_onReadyRead() { @@ -199,6 +210,7 @@ void MumeSslSocket::slot_checkTimeout() { switch (m_socket.state()) { case QAbstractSocket::ConnectedState: +#ifndef Q_OS_WASM if (!m_socket.isEncrypted()) { slot_onError2(QAbstractSocket::SslHandshakeFailedError, "Timeout during encryption handshake."); @@ -206,6 +218,7 @@ void MumeSslSocket::slot_checkTimeout() } else { // Race condition? Connection was successfully encrypted } +#endif break; case QAbstractSocket::HostLookupState: m_socket.abort(); diff --git a/src/proxy/mumesocket.h b/src/proxy/mumesocket.h index 51ff2865b..142e56f07 100644 --- a/src/proxy/mumesocket.h +++ b/src/proxy/mumesocket.h @@ -8,7 +8,11 @@ #include #include #include +#ifndef Q_OS_WASM #include +#else +#include +#endif #include #include #include @@ -78,13 +82,21 @@ class MumeSslSocket : public MumeSocket protected slots: void slot_onReadyRead(); + void slot_checkTimeout(); + +#ifndef Q_OS_WASM +public slots: void slot_onEncrypted(); void slot_onPeerVerifyError(const QSslError &error); - void slot_checkTimeout(); protected: - io::buffer<(1 << 13)> m_buffer; QSslSocket m_socket; +#else +protected: + QTcpSocket m_socket; +#endif + + io::buffer<(1 << 13)> m_buffer; QTimer m_timer; }; diff --git a/wasm.txt b/wasm.txt new file mode 100644 index 000000000..948333521 --- /dev/null +++ b/wasm.txt @@ -0,0 +1,20 @@ +docker run --rm -it -v $(pwd):/root/mmapper --entrypoint /bin/bash maukalinow/qtwasm_builder:5.13_latest + +export CMAKE_PREFIX_PATH="/usr/local/Qt/" +export Qt5_DIR="/usr/local/Qt/lib/cmake/Qt5" +# Might not be required ^^ + +export EMSCRIPTEN="/root/dev/emsdk/fastcomp/emscripten/" + +#Sources +# https://forum.qt.io/topic/106515/cmake-project-builds-target-webassembly/4 +# https://bugreports.qt.io/browse/QTBUG-78647 +# https://forum.qt.io/topic/100248/problems-compiling-qt-5-13-0-alpha-for-webassembly/8 +# https://wiki.qt.io/Qt_for_WebAssembly + +#apt install -y build-essential git libgles2-mesa-dev zlib1g-dev cmake + +Cmake 3.13 + +cmake .. -DCMAKE_TOOLCHAIN_FILE=/root/dev/emsdk/fastcomp/emscripten/cmake/Modules/Platform/Emscripten.cmake -DEMSCRIPTEN_ROOT_PATH=/root/dev/emsdk/fastcomp/emscripten/ -DWITH_ZLIB=OFF -DWITH_OPENSSL=OFF -DWITH_MINIUPNPC=OFF -DWITH_TESTS=OFF -DCMAKE_BUILD_TYPE=Release +