Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Web Assembly support #319

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
59 changes: 59 additions & 0 deletions external/wasm.cmake
Original file line number Diff line number Diff line change
@@ -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()
13 changes: 12 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/configuration/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
2 changes: 1 addition & 1 deletion src/display/Textures.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
#include <exchndl.h>
#endif

#ifdef Q_OS_WASM
#include <QtPlugin>
Q_IMPORT_PLUGIN(QWasmIntegrationPlugin)
#endif

// REVISIT: move splash files somewhere else?
// (presumably not in the "root" src/ directory?)
struct NODISCARD ISplash
Expand Down Expand Up @@ -176,6 +181,7 @@ int main(int argc, char **argv)
}

QApplication app(argc, argv);
QCoreApplication::addLibraryPath("./");
tryInitDrMingw();
auto tryLoadingWinSock = std::make_unique<WinSock>();
setSurfaceFormat();
Expand Down
2 changes: 2 additions & 0 deletions src/pandoragroup/GroupClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down
4 changes: 4 additions & 0 deletions src/pandoragroup/GroupServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions src/pandoragroup/GroupSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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({});
Expand All @@ -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...");
});
Expand All @@ -60,13 +62,15 @@ 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
= socket.peerCertificate().digest(QCryptographicHash::Algorithm::Sha1).toHex().toLower();
emit sig_sendLog("Connection successfully encrypted...");
emit sig_connectionEncrypted(this);
});
#endif
connect(&socket, &QAbstractSocket::disconnected, this, [this]() {
timer.stop();
emit sig_connectionClosed(this);
Expand All @@ -76,7 +80,9 @@ GroupSocket::GroupSocket(GroupAuthority *authority, QObject *parent)
QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::errorOccurred),
this,
&GroupSocket::slot_onError);
#ifndef Q_OS_WASM
connect(&socket, &QSslSocket::peerVerifyError, this, &GroupSocket::slot_onPeerVerifyError);
#endif
}

GroupSocket::~GroupSocket()
Expand Down Expand Up @@ -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
Expand All @@ -185,6 +192,7 @@ void GroupSocket::slot_onPeerVerifyError(const QSslError &error)
qWarning() << "onPeerVerifyError" << static_cast<int>(socket.error()) << socket.errorString()
<< error.errorString();
}
#endif

void GroupSocket::slot_onTimeout()
{
Expand All @@ -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";
Expand All @@ -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;
Expand Down
17 changes: 17 additions & 0 deletions src/pandoragroup/GroupSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
#include <QByteArray>
#include <QMap>
#include <QObject>
#ifndef Q_OS_WASM
#include <QSslSocket>
#else
#include <QTcpSocket>
#endif
#include <QString>
#include <QTimer>
#include <QtCore>
Expand All @@ -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; }
Expand All @@ -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();

Expand All @@ -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);
Expand Down
12 changes: 12 additions & 0 deletions src/pandoragroup/groupauthority.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -248,6 +250,7 @@ GroupAuthority::GroupAuthority(QObject *const parent)
: QObject(parent)
{
qRegisterMetaType<GroupSecret>("GroupSecret");
#ifndef Q_OS_WASM

// Always utilize a temporary keychain
qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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())
Expand All @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/pandoragroup/groupauthority.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
// Author: Nils Schimmelmann <nschimme@gmail.com> (Jahara)

#include <QObject>
#ifndef Q_OS_WASM
#include <QSslCertificate>
#include <QSslKey>
#endif
#include <QStringListModel>

#include "../global/macros.h"
Expand Down Expand Up @@ -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; }
Expand All @@ -54,6 +59,8 @@ public slots:

private:
QStringListModel model;
#ifndef Q_OS_WASM
QSslCertificate certificate;
QSslKey key;
#endif
};
Loading