Delete websuckets
This commit is contained in:
@@ -25,7 +25,7 @@ before_install:
|
||||
- sudo add-apt-repository ppa:beineri/opt-qt-5.14.2-xenial -y
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install libsodium-dev pkg-config
|
||||
- sudo apt-get install qt514base qt514websockets libgl1-mesa-dev
|
||||
- sudo apt-get install qt514base libgl1-mesa-dev
|
||||
- source /opt/qt514/bin/qt514-env.sh
|
||||
- chmod +x res/libsodium/buildlibsodium.sh
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ Compiling can take some time, so be patient and wait for it to finish. It will t
|
||||
|
||||
##### Ubuntu 18.04 and 20.04:
|
||||
```shell script
|
||||
sudo apt-get -y install build-essential qt5-default qt5-qmake libqt5websockets5-dev qtcreator qttools5-dev-tools
|
||||
sudo apt-get -y install build-essential qt5-default qt5-qmake qtcreator qttools5-dev-tools
|
||||
git clone https://git.hush.is/hush/SilentDragonLite
|
||||
cd SilentDragonLite
|
||||
./build.sh linguist
|
||||
|
||||
@@ -56,7 +56,7 @@ mkdir ~/git && cd ~/git
|
||||
git clone https://github.com/mxe/mxe.git
|
||||
cd mxe
|
||||
|
||||
make -j$(nproc) MXE_TARGETS=x86_64-w64-mingw32.static qtbase qtwebsockets
|
||||
make -j$(nproc) MXE_TARGETS=x86_64-w64-mingw32.static qtbase
|
||||
|
||||
```
|
||||
# Build SilentDragonLite .exe
|
||||
|
||||
BIN
res/libsodium.a
BIN
res/libsodium.a
Binary file not shown.
@@ -13,7 +13,6 @@ CONFIG += precompile_header
|
||||
PRECOMPILED_HEADER = src/precompiled.h
|
||||
|
||||
QT += widgets
|
||||
QT += websockets
|
||||
|
||||
TARGET = SilentDragonLite
|
||||
TEMPLATE = app
|
||||
@@ -59,7 +58,6 @@ SOURCES += \
|
||||
src/addressbook.cpp \
|
||||
src/logger.cpp \
|
||||
src/addresscombo.cpp \
|
||||
src/websockets.cpp \
|
||||
src/mobileappconnector.cpp \
|
||||
src/recurring.cpp \
|
||||
src/requestdialog.cpp \
|
||||
@@ -104,7 +102,6 @@ HEADERS += \
|
||||
src/addressbook.h \
|
||||
src/logger.h \
|
||||
src/addresscombo.h \
|
||||
src/websockets.h \
|
||||
src/mobileappconnector.h \
|
||||
src/recurring.h \
|
||||
src/requestdialog.h \
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "controller.h"
|
||||
#include "../lib/silentdragonlitelib.h"
|
||||
#include "precompiled.h"
|
||||
#include <QThreadPool>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "mainwindow.h"
|
||||
#include "ui_connection.h"
|
||||
#include "precompiled.h"
|
||||
#include <QRunnable>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "settings.h"
|
||||
#include "version.h"
|
||||
#include "camount.h"
|
||||
#include "websockets.h"
|
||||
#include "Model/ChatItem.h"
|
||||
#include "DataStore/DataStore.h"
|
||||
|
||||
@@ -896,9 +895,6 @@ void Controller::refreshBalances()
|
||||
model->setBalVerified(balVerified);
|
||||
model->setBalSpendable(balSpendable);
|
||||
|
||||
// This is for the websockets
|
||||
AppDataModel::getInstance()->setBalances(balT, balZ);
|
||||
|
||||
// This is for the datamodel
|
||||
CAmount balAvailable = balT + balVerified;
|
||||
model->setAvailableBalance(balAvailable);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include "camount.h"
|
||||
#include "precompiled.h"
|
||||
|
||||
#include <QReadLocker>
|
||||
|
||||
struct UnspentOutput {
|
||||
QString address;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "mainwindow.h"
|
||||
#include "controller.h"
|
||||
#include "settings.h"
|
||||
#include <QCommandLineParser>
|
||||
|
||||
#include "version.h"
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "ui_startupencryption.h"
|
||||
#include "ui_removeencryption.h"
|
||||
#include "ui_seedrestore.h"
|
||||
#include "websockets.h"
|
||||
#include "sodium.h"
|
||||
#include "sodium/crypto_generichash_blake2b.h"
|
||||
#include <QRegularExpression>
|
||||
@@ -305,16 +304,6 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
|
||||
restoreSavedStates();
|
||||
|
||||
if (AppDataServer::getInstance()->isAppConnected()) {
|
||||
qDebug() << __func__ << ": app is connected to wormhole";
|
||||
auto ads = AppDataServer::getInstance();
|
||||
|
||||
QString wormholecode = "";
|
||||
if (ads->getAllowInternetConnection())
|
||||
wormholecode = ads->getWormholeCode(ads->getSecretHex());
|
||||
|
||||
createWebsocket(wormholecode);
|
||||
}
|
||||
}
|
||||
|
||||
bool MainWindow::fileExists(QString path)
|
||||
@@ -323,36 +312,6 @@ bool MainWindow::fileExists(QString path)
|
||||
return (check_file.exists() && check_file.isFile());
|
||||
}
|
||||
|
||||
void MainWindow::createWebsocket(QString wormholecode) {
|
||||
qDebug() << "Listening for app connections on port 8777";
|
||||
// Create the websocket server, for listening to direct connections
|
||||
wsserver = new WSServer(8777, false, this);
|
||||
|
||||
if (!wormholecode.isEmpty()) {
|
||||
// Connect to the wormhole service
|
||||
wormhole = new WormholeClient(this, wormholecode);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::stopWebsocket() {
|
||||
delete wsserver;
|
||||
wsserver = nullptr;
|
||||
|
||||
delete wormhole;
|
||||
wormhole = nullptr;
|
||||
|
||||
qDebug() << "Websockets for app connections shut down";
|
||||
}
|
||||
|
||||
bool MainWindow::isWebsocketListening() {
|
||||
return wsserver != nullptr;
|
||||
}
|
||||
|
||||
void MainWindow::replaceWormholeClient(WormholeClient* newClient) {
|
||||
delete wormhole;
|
||||
wormhole = newClient;
|
||||
}
|
||||
|
||||
void MainWindow::restoreSavedStates() {
|
||||
QSettings s;
|
||||
restoreGeometry(s.value("geometry").toByteArray());
|
||||
@@ -2735,8 +2694,6 @@ MainWindow::~MainWindow()
|
||||
delete loadingMovie;
|
||||
delete logger;
|
||||
|
||||
delete wsserver;
|
||||
delete wormhole;
|
||||
}
|
||||
void MainWindow::on_givemeZaddr_clicked()
|
||||
{
|
||||
|
||||
@@ -14,8 +14,6 @@ using json = nlohmann::json;
|
||||
// Forward declare to break circular dependency.
|
||||
class Controller;
|
||||
class Settings;
|
||||
class WSServer;
|
||||
class WormholeClient;
|
||||
class ChatModel;
|
||||
|
||||
|
||||
@@ -70,10 +68,6 @@ public:
|
||||
|
||||
|
||||
|
||||
void replaceWormholeClient(WormholeClient* newClient);
|
||||
bool isWebsocketListening();
|
||||
void createWebsocket(QString wormholecode);
|
||||
void stopWebsocket();
|
||||
void saveContact();
|
||||
void saveandsendContact();
|
||||
void showRequesthush();
|
||||
@@ -200,10 +194,6 @@ private:
|
||||
bool uiPaymentsReady = false;
|
||||
QString pendingURIPayment;
|
||||
|
||||
WSServer* wsserver = nullptr;
|
||||
WormholeClient* wormhole = nullptr;
|
||||
|
||||
|
||||
Controller* rpc = nullptr;
|
||||
|
||||
QCompleter* labelCompleter = nullptr;
|
||||
|
||||
@@ -64,7 +64,6 @@
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtWebSockets/QtWebSockets>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
|
||||
@@ -40,7 +40,7 @@ RUN cd /opt && rm qt-everywhere-src-5.11.2.tar.xz && rm -rf qt-everywhere-src-5.
|
||||
RUN cd /opt && \
|
||||
git clone https://github.com/mxe/mxe.git && \
|
||||
cd /opt/mxe && \
|
||||
make -j$(nproc) MXE_TARGETS=x86_64-w64-mingw32.static qtbase qtwebsockets
|
||||
make -j$(nproc) MXE_TARGETS=x86_64-w64-mingw32.static qtbase
|
||||
|
||||
# Add rust
|
||||
RUN apt install -y gcc-aarch64-linux-gnu
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "settings.h"
|
||||
#include "camount.h"
|
||||
#include "../lib/silentdragonlitelib.h"
|
||||
#include <QUrlQuery>
|
||||
|
||||
Settings* Settings::instance = nullptr;
|
||||
|
||||
|
||||
@@ -1,940 +0,0 @@
|
||||
// Copyright 2019-2023 The Hush developers
|
||||
// Released under the GPLv3
|
||||
#include "websockets.h"
|
||||
#include "controller.h"
|
||||
#include "settings.h"
|
||||
#include "ui_mobileappconnector.h"
|
||||
#include "version.h"
|
||||
|
||||
// Weap the sendTextMessage to check if the connection is valid and that the parent WebServer didn't close this connection
|
||||
// for some reason.
|
||||
void ClientWebSocket::sendTextMessage(QString m) {
|
||||
if (client) {
|
||||
if (server && !server->isValidConnection(client)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (client->isValid())
|
||||
client->sendTextMessage(m);
|
||||
}
|
||||
}
|
||||
|
||||
WSServer::WSServer(quint16 port, bool debug, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_pWebSocketServer(new QWebSocketServer(QStringLiteral("Direct Connection Server"),
|
||||
QWebSocketServer::NonSecureMode, this)),
|
||||
m_debug(debug)
|
||||
{
|
||||
m_mainWindow = (MainWindow *) parent;
|
||||
if (m_pWebSocketServer->listen(QHostAddress::AnyIPv4, port)) {
|
||||
if (m_debug)
|
||||
qDebug() << "Echoserver listening on port" << port;
|
||||
connect(m_pWebSocketServer, &QWebSocketServer::newConnection,
|
||||
this, &WSServer::onNewConnection);
|
||||
connect(m_pWebSocketServer, &QWebSocketServer::closed, this, &WSServer::closed);
|
||||
}
|
||||
}
|
||||
|
||||
WSServer::~WSServer()
|
||||
{
|
||||
qDebug() << "Closing websocket server";
|
||||
m_pWebSocketServer->close();
|
||||
qDeleteAll(m_clients.begin(), m_clients.end());
|
||||
qDebug() << "Deleted all websocket clients";
|
||||
}
|
||||
|
||||
void WSServer::onNewConnection()
|
||||
{
|
||||
qDebug() << "Websocket server: new connection";
|
||||
QWebSocket *pSocket = m_pWebSocketServer->nextPendingConnection();
|
||||
|
||||
connect(pSocket, &QWebSocket::textMessageReceived, this, &WSServer::processTextMessage);
|
||||
connect(pSocket, &QWebSocket::binaryMessageReceived, this, &WSServer::processBinaryMessage);
|
||||
connect(pSocket, &QWebSocket::disconnected, this, &WSServer::socketDisconnected);
|
||||
|
||||
m_clients << pSocket;
|
||||
}
|
||||
|
||||
void WSServer::processTextMessage(QString message)
|
||||
{
|
||||
QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
|
||||
if (m_debug)
|
||||
qDebug() << "Message received:" << message;
|
||||
|
||||
if (pClient) {
|
||||
std::shared_ptr<ClientWebSocket> client = std::make_shared<ClientWebSocket>(pClient, this);
|
||||
AppDataServer::getInstance()->processMessage(message, m_mainWindow, client, AppConnectionType::DIRECT);
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::processBinaryMessage(QByteArray message)
|
||||
{
|
||||
//QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
|
||||
if (m_debug)
|
||||
qDebug() << "Binary Message received:" << message;
|
||||
|
||||
}
|
||||
|
||||
void WSServer::socketDisconnected()
|
||||
{
|
||||
QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
|
||||
if (m_debug)
|
||||
qDebug() << "socketDisconnected:" << pClient;
|
||||
if (pClient) {
|
||||
m_clients.removeAll(pClient);
|
||||
pClient->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
//===============================
|
||||
// WormholeClient
|
||||
//===============================
|
||||
WormholeClient::WormholeClient(MainWindow* p, QString wormholeCode) {
|
||||
this->parent = p;
|
||||
this->code = wormholeCode;
|
||||
connect();
|
||||
qDebug() << "New wormhole client after connect()";
|
||||
}
|
||||
|
||||
WormholeClient::~WormholeClient() {
|
||||
qDebug() << "WormholeClient destructor";
|
||||
shuttingDown = true;
|
||||
|
||||
if (m_webSocket && m_webSocket->isValid()) {
|
||||
qDebug() << "Wormhole closing!";
|
||||
m_webSocket->close();
|
||||
|
||||
}
|
||||
|
||||
if (timer) {
|
||||
qDebug() << "Wormhole timer stopping";
|
||||
timer->stop();
|
||||
}
|
||||
|
||||
delete timer;
|
||||
qDebug() << "Wormhole timer deleted";
|
||||
}
|
||||
|
||||
void WormholeClient::connect() {
|
||||
qDebug() << "Wormhole::connect";
|
||||
delete m_webSocket;
|
||||
m_webSocket = new QWebSocket();
|
||||
|
||||
if (m_webSocket) {
|
||||
QObject::connect(m_webSocket, &QWebSocket::connected, this, &WormholeClient::onConnected);
|
||||
QObject::connect(m_webSocket, &QWebSocket::disconnected, this, &WormholeClient::closed);
|
||||
} else {
|
||||
qDebug() << "Invalid websocket object!";
|
||||
}
|
||||
|
||||
m_webSocket->open(QUrl("wss://wormhole.hush.is:443"));
|
||||
//m_webSocket->open(QUrl("ws://127.0.0.1:7070"));
|
||||
}
|
||||
|
||||
void WormholeClient::retryConnect() {
|
||||
QTimer::singleShot(5 * 1000 * pow(2, retryCount), [=]() {
|
||||
if (retryCount < 10) {
|
||||
qDebug() << "Retrying websocket connection";
|
||||
this->retryCount++;
|
||||
connect();
|
||||
} else {
|
||||
qDebug() << "Retry count exceeded, will not attempt retry any more";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Called when the websocket is closed. If this was closed without our explicitly closing it,
|
||||
// then we need to try and reconnect
|
||||
void WormholeClient::closed() {
|
||||
if (!shuttingDown) {
|
||||
retryConnect();
|
||||
}
|
||||
}
|
||||
|
||||
void WormholeClient::onConnected()
|
||||
{
|
||||
qDebug() << "WebSocket connected";
|
||||
retryCount = 0;
|
||||
qDebug() << "WebSocket connected, retryCount=" << retryCount;
|
||||
QObject::connect(m_webSocket, &QWebSocket::textMessageReceived,
|
||||
this, &WormholeClient::onTextMessageReceived);
|
||||
|
||||
auto payload = QJsonDocument( QJsonObject {
|
||||
{"register", code}
|
||||
}).toJson();
|
||||
|
||||
m_webSocket->sendTextMessage(payload);
|
||||
|
||||
// On connected, we'll also create a timer to ping it every 4 minutes, since the websocket
|
||||
// will timeout after 5 minutes
|
||||
if (m_webSocket && m_webSocket->isValid()) {
|
||||
m_webSocket->sendTextMessage(payload);
|
||||
qDebug() << "Sent registration message with code=" << code;
|
||||
|
||||
// On connected, we'll also create a timer to ping it every 4 minutes, since the websocket
|
||||
// will timeout after 5 minutes
|
||||
timer = new QTimer(parent);
|
||||
qDebug() << "Created QTimer";
|
||||
QObject::connect(timer, &QTimer::timeout, [=]() {
|
||||
qDebug() << "Timer timeout!";
|
||||
if (!shuttingDown && m_webSocket && m_webSocket->isValid()) {
|
||||
auto payload = QJsonDocument(QJsonObject { {"ping", "ping"} }).toJson();
|
||||
qint64 bytes = m_webSocket->sendTextMessage(payload);
|
||||
qDebug() << "Sent ping, " << bytes << " bytes";
|
||||
}
|
||||
});
|
||||
unsigned int interval = 4*60*1000;
|
||||
timer->start(interval); // 4 minutes
|
||||
qDebug() << "Started timer with interval=" << interval;
|
||||
} else {
|
||||
qDebug() << "Invalid websocket object onConnected!";
|
||||
}
|
||||
}
|
||||
|
||||
void WormholeClient::onTextMessageReceived(QString message)
|
||||
{
|
||||
AppDataServer::getInstance()->processMessage(message, parent, std::make_shared<ClientWebSocket>(m_webSocket), AppConnectionType::INTERNET);
|
||||
qDebug() << "Destroyed tempWormholeClient and ui";
|
||||
}
|
||||
|
||||
|
||||
// ==============================
|
||||
// AppDataServer
|
||||
// ==============================
|
||||
AppDataServer* AppDataServer::instance = nullptr;
|
||||
|
||||
QString AppDataServer::getWormholeCode(QString secretHex) {
|
||||
unsigned char* secret = new unsigned char[crypto_secretbox_KEYBYTES];
|
||||
sodium_hex2bin(secret, crypto_secretbox_KEYBYTES, secretHex.toStdString().c_str(), crypto_secretbox_KEYBYTES*2,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
unsigned char* out1 = new unsigned char[crypto_hash_sha256_BYTES];
|
||||
crypto_hash_sha256(out1, secret, crypto_secretbox_KEYBYTES);
|
||||
|
||||
unsigned char* out2 = new unsigned char[crypto_hash_sha256_BYTES];
|
||||
crypto_hash_sha256(out2, out1, crypto_hash_sha256_BYTES);
|
||||
|
||||
char* wmcode = new char[crypto_hash_sha256_BYTES*2 + 1];
|
||||
sodium_bin2hex(wmcode, crypto_hash_sha256_BYTES*2 + 1, out2, crypto_hash_sha256_BYTES);
|
||||
|
||||
QString wmcodehex(wmcode);
|
||||
|
||||
delete[] wmcode;
|
||||
delete[] out2;
|
||||
delete[] out1;
|
||||
delete[] secret;
|
||||
|
||||
return wmcodehex;
|
||||
}
|
||||
|
||||
QString AppDataServer::getSecretHex() {
|
||||
QSettings s;
|
||||
|
||||
return s.value("mobileapp/secret", "").toString();
|
||||
}
|
||||
|
||||
void AppDataServer::saveNewSecret(QString secretHex) {
|
||||
QSettings().setValue("mobileapp/secret", secretHex);
|
||||
|
||||
if (secretHex.isEmpty())
|
||||
setAllowInternetConnection(false);
|
||||
}
|
||||
|
||||
bool AppDataServer::getAllowInternetConnection() {
|
||||
return QSettings().value("mobileapp/allowinternet", false).toBool();
|
||||
}
|
||||
|
||||
void AppDataServer::setAllowInternetConnection(bool allow) {
|
||||
QSettings().setValue("mobileapp/allowinternet", allow);
|
||||
}
|
||||
|
||||
void AppDataServer::saveLastConnectedOver(AppConnectionType type) {
|
||||
QSettings().setValue("mobileapp/lastconnectedover", type);
|
||||
}
|
||||
|
||||
AppConnectionType AppDataServer::getLastConnectionType() {
|
||||
return (AppConnectionType) QSettings().value("mobileapp/lastconnectedover", AppConnectionType::DIRECT).toInt();
|
||||
}
|
||||
|
||||
void AppDataServer::saveLastSeenTime() {
|
||||
QSettings().setValue("mobileapp/lastseentime", QDateTime::currentSecsSinceEpoch());
|
||||
}
|
||||
|
||||
QDateTime AppDataServer::getLastSeenTime() {
|
||||
return QDateTime::fromSecsSinceEpoch(QSettings().value("mobileapp/lastseentime", 0).toLongLong());
|
||||
}
|
||||
|
||||
void AppDataServer::setConnectedName(QString name) {
|
||||
QSettings().setValue("mobileapp/connectedname", name);
|
||||
}
|
||||
|
||||
QString AppDataServer::getConnectedName() {
|
||||
return QSettings().value("mobileapp/connectedname", "").toString();
|
||||
}
|
||||
|
||||
bool AppDataServer::isAppConnected() {
|
||||
return !getConnectedName().isEmpty() &&
|
||||
getLastSeenTime().daysTo(QDateTime::currentDateTime()) < 14;
|
||||
}
|
||||
|
||||
void AppDataServer::connectAppDialog(MainWindow* parent) {
|
||||
QDialog d(parent);
|
||||
ui = new Ui_MobileAppConnector();
|
||||
ui->setupUi(&d);
|
||||
Settings::saveRestore(&d);
|
||||
|
||||
updateUIWithNewQRCode(parent);
|
||||
updateConnectedUI();
|
||||
|
||||
QObject::connect(ui->btnDisconnect, &QPushButton::clicked, [=] () {
|
||||
QSettings().setValue("mobileapp/connectedname", "");
|
||||
saveNewSecret("");
|
||||
|
||||
updateConnectedUI();
|
||||
});
|
||||
|
||||
QObject::connect(ui->txtConnStr, &QLineEdit::cursorPositionChanged, [=](int, int) {
|
||||
ui->txtConnStr->selectAll();
|
||||
});
|
||||
|
||||
QObject::connect(ui->chkInternetConn, &QCheckBox::stateChanged, [=] (int state) {
|
||||
if (state == Qt::Checked) {
|
||||
|
||||
}
|
||||
updateUIWithNewQRCode(parent);
|
||||
});
|
||||
|
||||
// If we're not listening for the app, then start the websockets
|
||||
if (!parent->isWebsocketListening()) {
|
||||
QString wormholecode = "";
|
||||
if (getAllowInternetConnection())
|
||||
wormholecode = AppDataServer::getInstance()->getWormholeCode(AppDataServer::getInstance()->getSecretHex());
|
||||
|
||||
parent->createWebsocket(wormholecode);
|
||||
}
|
||||
|
||||
d.exec();
|
||||
|
||||
// If there is nothing connected when the dialog exits, then shutdown the websockets
|
||||
if (!isAppConnected()) {
|
||||
parent->stopWebsocket();
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
tempSecret = "";
|
||||
|
||||
delete tempWormholeClient;
|
||||
tempWormholeClient = nullptr;
|
||||
|
||||
delete ui;
|
||||
ui = nullptr;
|
||||
}
|
||||
|
||||
void AppDataServer::updateUIWithNewQRCode(MainWindow* mainwindow) {
|
||||
// Get the address of the localhost
|
||||
auto addrList = QNetworkInterface::allAddresses();
|
||||
|
||||
// Find a suitable address
|
||||
QString ipv4Addr;
|
||||
for (auto addr : addrList) {
|
||||
if (addr.isLoopback() || addr.protocol() == QAbstractSocket::IPv6Protocol)
|
||||
continue;
|
||||
|
||||
ipv4Addr = addr.toString();
|
||||
break;
|
||||
}
|
||||
|
||||
if (ipv4Addr.isEmpty())
|
||||
return;
|
||||
|
||||
QString uri = "ws://" + ipv4Addr + ":8777";
|
||||
qDebug() << "Websocket URI: " << uri;
|
||||
|
||||
// Get a new secret
|
||||
unsigned char* secretBin = new unsigned char[crypto_secretbox_KEYBYTES];
|
||||
randombytes_buf(secretBin, crypto_secretbox_KEYBYTES);
|
||||
char* secretHex = new char[crypto_secretbox_KEYBYTES*2 + 1];
|
||||
sodium_bin2hex(secretHex, crypto_secretbox_KEYBYTES*2+1, secretBin, crypto_secretbox_KEYBYTES);
|
||||
|
||||
QString secretStr(secretHex);
|
||||
QString codeStr = uri + "," + secretStr;
|
||||
|
||||
if (ui->chkInternetConn->isChecked()) {
|
||||
codeStr = codeStr + ",1";
|
||||
}
|
||||
|
||||
registerNewTempSecret(secretStr, ui->chkInternetConn->isChecked(), mainwindow);
|
||||
|
||||
ui->qrcode->setQrcodeString(codeStr);
|
||||
ui->txtConnStr->setText(codeStr);
|
||||
qDebug() << "New QR="<<codeStr;
|
||||
}
|
||||
|
||||
void AppDataServer::registerNewTempSecret(QString tmpSecretHex, bool allowInternet, MainWindow* main) {
|
||||
qDebug() << "Registering new tempSecret, allowInternet=" << allowInternet;
|
||||
tempSecret = tmpSecretHex;
|
||||
|
||||
delete tempWormholeClient;
|
||||
tempWormholeClient = nullptr;
|
||||
|
||||
if (allowInternet)
|
||||
tempWormholeClient = new WormholeClient(main, getWormholeCode(tempSecret));
|
||||
qDebug() << "Created new wormhole client";
|
||||
}
|
||||
|
||||
|
||||
QString AppDataServer::connDesc(AppConnectionType t) {
|
||||
if (t == AppConnectionType::DIRECT) {
|
||||
return QObject::tr("Connected directly");
|
||||
}
|
||||
else {
|
||||
return QObject::tr("Connected over the internet via silentdragon wormhole service");
|
||||
}
|
||||
}
|
||||
|
||||
void AppDataServer::updateConnectedUI() {
|
||||
if (ui == nullptr)
|
||||
return;
|
||||
|
||||
auto remoteName = getConnectedName();
|
||||
|
||||
ui->lblRemoteName->setText(remoteName.isEmpty() ? "(Not connected to any device)" : remoteName);
|
||||
ui->lblLastSeen->setText(remoteName.isEmpty() ? "" : getLastSeenTime().toString(Qt::SystemLocaleLongDate));
|
||||
ui->lblConnectionType->setText(remoteName.isEmpty() ? "" : connDesc(getLastConnectionType()));
|
||||
|
||||
ui->btnDisconnect->setEnabled(!remoteName.isEmpty());
|
||||
}
|
||||
|
||||
QString AppDataServer::getNonceHex(NonceType nt) {
|
||||
QSettings s;
|
||||
QString hex;
|
||||
if (nt == NonceType::LOCAL) {
|
||||
// The default local nonce starts from 1, to always keep it odd
|
||||
auto defaultLocalNonce = "01" + QString("00").repeated(crypto_secretbox_NONCEBYTES-1);
|
||||
hex = s.value("mobileapp/localnoncehex", defaultLocalNonce).toString();
|
||||
}
|
||||
else {
|
||||
hex = s.value("mobileapp/remotenoncehex", QString("00").repeated(crypto_secretbox_NONCEBYTES)).toString();
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
void AppDataServer::saveNonceHex(NonceType nt, QString noncehex) {
|
||||
QSettings s;
|
||||
assert(noncehex.length() == crypto_secretbox_NONCEBYTES * 2);
|
||||
if (nt == NonceType::LOCAL) {
|
||||
s.setValue("mobileapp/localnoncehex", noncehex);
|
||||
}
|
||||
else {
|
||||
s.setValue("mobileapp/remotenoncehex", noncehex);
|
||||
}
|
||||
s.sync();
|
||||
}
|
||||
|
||||
// Encrypt an outgoing message with the stored secret key.
|
||||
QString AppDataServer::encryptOutgoing(QString msg) {
|
||||
int padding = 16*1024;
|
||||
qDebug() << "Encrypt msg(pad="<<padding<<") prepad len=" << msg.length();
|
||||
if (msg.length() % padding > 0) {
|
||||
msg = msg + QString(" ").repeated(padding - (msg.length() % padding));
|
||||
}
|
||||
qDebug() << "Encrypt msg postpad len=" << msg.length();
|
||||
|
||||
QString localNonceHex = getNonceHex(NonceType::LOCAL);
|
||||
|
||||
unsigned char* noncebin = new unsigned char[crypto_secretbox_NONCEBYTES];
|
||||
sodium_hex2bin(noncebin, crypto_secretbox_NONCEBYTES, localNonceHex.toStdString().c_str(), localNonceHex.length(),
|
||||
NULL, NULL, NULL);
|
||||
|
||||
// Increment the nonce +2 and save
|
||||
sodium_increment(noncebin, crypto_secretbox_NONCEBYTES);
|
||||
sodium_increment(noncebin, crypto_secretbox_NONCEBYTES);
|
||||
|
||||
char* newLocalNonce = new char[crypto_secretbox_NONCEBYTES*2 + 1];
|
||||
sodium_memzero(newLocalNonce, crypto_secretbox_NONCEBYTES*2 + 1);
|
||||
sodium_bin2hex(newLocalNonce, crypto_secretbox_NONCEBYTES*2+1, noncebin, crypto_box_NONCEBYTES);
|
||||
|
||||
saveNonceHex(NonceType::LOCAL, QString(newLocalNonce));
|
||||
|
||||
unsigned char* secret = new unsigned char[crypto_secretbox_KEYBYTES];
|
||||
sodium_hex2bin(secret, crypto_secretbox_KEYBYTES, getSecretHex().toStdString().c_str(), crypto_secretbox_KEYBYTES*2,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
int msgSize = strlen(msg.toStdString().c_str());
|
||||
unsigned char* encrpyted = new unsigned char[ msgSize + crypto_secretbox_MACBYTES];
|
||||
|
||||
crypto_secretbox_easy(encrpyted, (const unsigned char *)msg.toStdString().c_str(), msgSize, noncebin, secret);
|
||||
|
||||
int encryptedHexSize = (msgSize + crypto_secretbox_MACBYTES) * 2 + 1;
|
||||
char * encryptedHex = new char[encryptedHexSize];
|
||||
sodium_memzero(encryptedHex, encryptedHexSize);
|
||||
sodium_bin2hex(encryptedHex, encryptedHexSize, encrpyted, msgSize + crypto_secretbox_MACBYTES);
|
||||
|
||||
auto json = QJsonDocument(QJsonObject{
|
||||
{"nonce", QString(newLocalNonce)},
|
||||
{"payload", QString(encryptedHex)},
|
||||
{"to", getWormholeCode(getSecretHex())}
|
||||
});
|
||||
|
||||
delete[] noncebin;
|
||||
delete[] newLocalNonce;
|
||||
delete[] secret;
|
||||
delete[] encrpyted;
|
||||
delete[] encryptedHex;
|
||||
|
||||
return json.toJson();
|
||||
}
|
||||
|
||||
/**
|
||||
Attempt to decrypt a message. If the decryption fails, it returns the string "error", the decrypted message otherwise.
|
||||
It will use the given secret to attempt decryption. In addition, it will enforce that the nonce is greater than the last seen nonce,
|
||||
unless the skipNonceCheck = true, which is used when attempting decrtption with a temp secret key.
|
||||
*/
|
||||
QString AppDataServer::decryptMessage(QJsonDocument msg, QString secretHex, QString lastRemoteNonceHex) {
|
||||
// Decrypt and then process
|
||||
QString noncehex = msg.object().value("nonce").toString();
|
||||
QString encryptedhex = msg.object().value("payload").toString();
|
||||
|
||||
// Enforce limits on the size of the message
|
||||
if (noncehex.length() > ((int)crypto_secretbox_NONCEBYTES * 2) ||
|
||||
encryptedhex.length() > 2 * 50 * 1024 /*50kb*/) {
|
||||
return "error";
|
||||
}
|
||||
|
||||
// Check to make sure that the nonce is greater than the last known remote nonce
|
||||
unsigned char* lastRemoteBin = new unsigned char[crypto_secretbox_NONCEBYTES];
|
||||
sodium_hex2bin(lastRemoteBin, crypto_secretbox_NONCEBYTES, lastRemoteNonceHex.toStdString().c_str(), lastRemoteNonceHex.length(),
|
||||
NULL, NULL, NULL);
|
||||
|
||||
unsigned char* noncebin = new unsigned char[crypto_secretbox_NONCEBYTES];
|
||||
sodium_hex2bin(noncebin, crypto_secretbox_NONCEBYTES, noncehex.toStdString().c_str(), noncehex.length(),
|
||||
NULL, NULL, NULL);
|
||||
|
||||
assert(crypto_secretbox_KEYBYTES == crypto_hash_sha256_BYTES);
|
||||
if (sodium_compare(lastRemoteBin, noncebin, crypto_secretbox_NONCEBYTES) != -1) {
|
||||
// Refuse to accept a lower nonce, return an error
|
||||
delete[] lastRemoteBin;
|
||||
delete[] noncebin;
|
||||
return "error";
|
||||
}
|
||||
|
||||
unsigned char* secret = new unsigned char[crypto_secretbox_KEYBYTES];
|
||||
sodium_hex2bin(secret, crypto_secretbox_KEYBYTES, secretHex.toStdString().c_str(), crypto_secretbox_KEYBYTES*2,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
unsigned char* encrypted = new unsigned char[encryptedhex.length() / 2];
|
||||
sodium_hex2bin(encrypted, encryptedhex.length() / 2, encryptedhex.toStdString().c_str(), encryptedhex.length(),
|
||||
NULL, NULL, NULL);
|
||||
|
||||
int decryptedLen = encryptedhex.length() / 2 - crypto_secretbox_MACBYTES;
|
||||
unsigned char* decrypted = new unsigned char[decryptedLen];
|
||||
int result = crypto_secretbox_open_easy(decrypted, encrypted, encryptedhex.length() / 2, noncebin, secret);
|
||||
|
||||
QString payload;
|
||||
if (result == -1) {
|
||||
payload = "error";
|
||||
} else {
|
||||
// Update the last seen remote hex
|
||||
saveNonceHex(NonceType::REMOTE, noncehex);
|
||||
saveLastSeenTime();
|
||||
|
||||
char* decryptedStr = new char[decryptedLen + 1];
|
||||
sodium_memzero(decryptedStr, decryptedLen + 1);
|
||||
memcpy(decryptedStr, decrypted, decryptedLen);
|
||||
|
||||
payload = QString(decryptedStr);
|
||||
|
||||
delete[] decryptedStr;
|
||||
}
|
||||
|
||||
delete[] secret;
|
||||
delete[] lastRemoteBin;
|
||||
delete[] noncebin;
|
||||
delete[] encrypted;
|
||||
delete[] decrypted;
|
||||
|
||||
qDebug() << "Returning decrypted payload="<<payload;
|
||||
return payload;
|
||||
}
|
||||
|
||||
// Process an incoming text message. The message has to be encrypted with the secret key (or the temporary secret key)
|
||||
void AppDataServer::processMessage(QString message, MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient, AppConnectionType connType) {
|
||||
qDebug() << "processMessage message";
|
||||
//qDebug() << "processMessage message=" << message; // this can log sensitive info
|
||||
auto replyWithError = [=]() {
|
||||
auto r = QJsonDocument(QJsonObject{
|
||||
{"error", "Encryption error"},
|
||||
{"to", getWormholeCode(getSecretHex())}
|
||||
}).toJson();
|
||||
pClient->sendTextMessage(r);
|
||||
return;
|
||||
};
|
||||
|
||||
// First, extract the command from the message
|
||||
auto msg = QJsonDocument::fromJson(message.toUtf8());
|
||||
|
||||
// Check if we got an error from the websocket
|
||||
if (msg.object().contains("error")) {
|
||||
qDebug() << "Error:" << msg.toJson();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the message is a ping, just ignore it
|
||||
if (msg.object().contains("ping")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Then, check if the message is encrpted
|
||||
if (!msg.object().contains("nonce")) {
|
||||
replyWithError();
|
||||
return;
|
||||
}
|
||||
|
||||
auto decrypted = decryptMessage(msg, getSecretHex(), getNonceHex(NonceType::REMOTE));
|
||||
|
||||
// If the decryption failed, maybe this is a new connection, so see if the dialog is open and a
|
||||
// temp secret is in place
|
||||
if (decrypted == "error") {
|
||||
// If the dialog is open, then there might be a temporary, new secret key. Attempt to decrypt
|
||||
// with that.
|
||||
if (!tempSecret.isEmpty()) {
|
||||
// Since this is a temp secret, the last seen nonce will be "0", so basically we'll accept any nonce
|
||||
QString zeroNonce = QString("00").repeated(crypto_secretbox_NONCEBYTES);
|
||||
decrypted = decryptMessage(msg, tempSecret, zeroNonce);
|
||||
if (decrypted == "error") {
|
||||
// Oh, well. Just return an error
|
||||
replyWithError();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// This is a new connection. So, update the the secret. Note the last seen remote nonce has already been updated by
|
||||
// decryptMessage()
|
||||
saveNewSecret(tempSecret);
|
||||
setAllowInternetConnection(tempWormholeClient != nullptr);
|
||||
|
||||
// Swap out the wormhole connection
|
||||
mainWindow->replaceWormholeClient(tempWormholeClient);
|
||||
tempWormholeClient = nullptr;
|
||||
|
||||
saveLastConnectedOver(connType);
|
||||
processDecryptedMessage(decrypted, mainWindow, pClient);
|
||||
|
||||
// If the Connection UI is showing, we have to update the UI as well
|
||||
if (ui != nullptr) {
|
||||
// Update the connected phone information
|
||||
updateConnectedUI();
|
||||
|
||||
// Update with a new QR Code for safety, so this secret isn't used by anyone else
|
||||
updateUIWithNewQRCode(mainWindow);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
replyWithError();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
saveLastConnectedOver(connType);
|
||||
processDecryptedMessage(decrypted, mainWindow, pClient);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypted method will be executed here.
|
||||
void AppDataServer::processDecryptedMessage(QString message, MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient) {
|
||||
//qDebug() << "processDecryptedMessage message=" << message;
|
||||
// First, extract the command from the message
|
||||
auto msg = QJsonDocument::fromJson(message.toUtf8());
|
||||
|
||||
if (!msg.object().contains("command")) {
|
||||
auto r = QJsonDocument(QJsonObject{
|
||||
{"errorCode", -1},
|
||||
{"errorMessage", "Unknown JSON format"}
|
||||
}).toJson();
|
||||
pClient->sendTextMessage(encryptOutgoing(r));
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.object()["command"] == "getInfo") {
|
||||
processGetInfo(msg.object(), mainWindow, pClient);
|
||||
}
|
||||
else if (msg.object()["command"] == "getTransactions") {
|
||||
processGetTransactions(mainWindow, pClient);
|
||||
}
|
||||
else if (msg.object()["command"] == "sendTx") {
|
||||
processSendTx(msg.object()["tx"].toObject(), mainWindow, pClient);
|
||||
}
|
||||
else if (msg.object()["command"] == "sendmanyTx") {
|
||||
processSendManyTx(msg.object()["tx"].toObject(), mainWindow, pClient);
|
||||
}
|
||||
else {
|
||||
auto r = QJsonDocument(QJsonObject{
|
||||
{"errorCode", -1},
|
||||
{"errorMessage", "Command not found:" + msg.object()["command"].toString()}
|
||||
}).toJson();
|
||||
pClient->sendTextMessage(encryptOutgoing(r));
|
||||
}
|
||||
}
|
||||
|
||||
// "sendTx" command. This method will actually send money, so be careful with everything
|
||||
void AppDataServer::processSendTx(QJsonObject sendTx, MainWindow* mainwindow, std::shared_ptr<ClientWebSocket> pClient) {
|
||||
qDebug() << "processSendTx with to=" << sendTx["to"].toString();
|
||||
auto error = [=](QString reason) {
|
||||
auto r = QJsonDocument(QJsonObject{
|
||||
{"errorCode", -1},
|
||||
{"errorMessage", "Couldn't send Tx:" + reason}
|
||||
}).toJson();
|
||||
pClient->sendTextMessage(encryptOutgoing(r));
|
||||
return;
|
||||
};
|
||||
|
||||
// Refuse to send if the node is still syncing
|
||||
if (Settings::getInstance()->isSyncing()) {
|
||||
error(QObject::tr("Node is still syncing."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a Tx Object
|
||||
Tx tx;
|
||||
tx.fee = Settings::getMinerFee();
|
||||
|
||||
// Find a from address that has at least the sending amout
|
||||
CAmount amt = CAmount::fromDecimalString(sendTx["amount"].toString());
|
||||
auto allBalances = mainwindow->getRPC()->getModel()->getAllBalances();
|
||||
QList<QPair<QString, CAmount>> bals;
|
||||
for (auto i : allBalances.keys()) {
|
||||
// Filter out balances that don't have the requisite amount
|
||||
if (allBalances.value(i) < amt)
|
||||
continue;
|
||||
|
||||
bals.append(QPair<QString, CAmount>(i, allBalances.value(i)));
|
||||
}
|
||||
|
||||
if (bals.isEmpty()) {
|
||||
error(QObject::tr("No sapling or transparent addresses with enough balance to spend."));
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(bals.begin(), bals.end(), [=](const QPair<QString, CAmount>a, const QPair<QString, CAmount> b) -> bool {
|
||||
// Sort z addresses first
|
||||
return a.first > b.first;
|
||||
});
|
||||
|
||||
tx.fromAddr = bals[0].first;
|
||||
tx.toAddrs = { ToFields{ sendTx["to"].toString(), amt, sendTx["memo"].toString()} };
|
||||
|
||||
// TODO: Respect the autoshield change setting
|
||||
|
||||
QString validation = mainwindow->doSendTxValidations(tx);
|
||||
if (!validation.isEmpty()) {
|
||||
error(validation);
|
||||
return;
|
||||
}
|
||||
|
||||
json params = json::array();
|
||||
mainwindow->getRPC()->fillTxJsonParams(params, tx);
|
||||
std::cout << std::setw(2) << params << std::endl;
|
||||
|
||||
// And send the Tx
|
||||
mainwindow->getRPC()->executeTransaction(tx,
|
||||
[=] (QString txid) {
|
||||
auto r = QJsonDocument(QJsonObject{
|
||||
{"version", 1.0},
|
||||
{"command", "sendTxSubmitted"},
|
||||
{"txid", txid}
|
||||
}).toJson();
|
||||
pClient->sendTextMessage(encryptOutgoing(r));
|
||||
},
|
||||
// Errored while submitting Tx
|
||||
[=] (QString, QString errStr) {
|
||||
auto r = QJsonDocument(QJsonObject{
|
||||
{"version", 1.0},
|
||||
{"command", "sendTxFailed"},
|
||||
{"err", errStr}
|
||||
}).toJson();
|
||||
pClient->sendTextMessage(encryptOutgoing(r));
|
||||
}
|
||||
);
|
||||
|
||||
auto r = QJsonDocument(QJsonObject{
|
||||
{"version", 1.0},
|
||||
{"command", "sendTx"},
|
||||
{"result", "success"}
|
||||
}).toJson();
|
||||
pClient->sendTextMessage(encryptOutgoing(r));
|
||||
}
|
||||
|
||||
// "sendmanyTx" command. This method will actually send money, so be careful with everything
|
||||
void AppDataServer::processSendManyTx(QJsonObject sendmanyTx, MainWindow* mainwindow, std::shared_ptr<ClientWebSocket> pClient) {
|
||||
qDebug() << "processSendManyTx with to=" << sendmanyTx["to"].toString();
|
||||
auto error = [=](QString reason) {
|
||||
auto r = QJsonDocument(QJsonObject{
|
||||
{"errorCode", -1},
|
||||
{"errorMessage", "Couldn't send Tx:" + reason}
|
||||
}).toJson();
|
||||
pClient->sendTextMessage(encryptOutgoing(r));
|
||||
return;
|
||||
};
|
||||
|
||||
// Refuse to send if the node is still syncing
|
||||
if (Settings::getInstance()->isSyncing()) {
|
||||
error(QObject::tr("Node is still syncing."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a Tx Object
|
||||
Tx tx;
|
||||
tx.fee = Settings::getMinerFee();
|
||||
|
||||
// Find a from address that has at least the sending amout
|
||||
CAmount amt = CAmount::fromDecimalString(sendmanyTx["amount"].toString());
|
||||
auto allBalances = mainwindow->getRPC()->getModel()->getAllBalances();
|
||||
QList<QPair<QString, CAmount>> bals;
|
||||
for (auto i : allBalances.keys()) {
|
||||
// Filter out balances that don't have the requisite amount
|
||||
if (allBalances.value(i) < amt)
|
||||
continue;
|
||||
|
||||
bals.append(QPair<QString, CAmount>(i, allBalances.value(i)));
|
||||
}
|
||||
|
||||
if (bals.isEmpty()) {
|
||||
error(QObject::tr("No sapling or transparent addresses with enough balance to spend."));
|
||||
return;
|
||||
}
|
||||
|
||||
std::sort(bals.begin(), bals.end(), [=](const QPair<QString, CAmount>a, const QPair<QString, CAmount> b) -> bool {
|
||||
// Sort z addresses first
|
||||
return a.first > b.first;
|
||||
});
|
||||
|
||||
//send to more then one Receipent
|
||||
|
||||
int totalSendManyItems = sendmanyTx.size();
|
||||
for (int i=0; i < totalSendManyItems; i++) {
|
||||
|
||||
amt = CAmount::fromDecimalString(sendmanyTx["amount"].toString() % QString::number(i+1));
|
||||
QString addr = sendmanyTx["to"].toString() % QString::number(i+1);
|
||||
QString memo = sendmanyTx["memo"].toString() % QString::number(i+1);
|
||||
|
||||
tx.fromAddr = bals[0].first;
|
||||
tx.toAddrs = { ToFields{ addr, amt, memo} };
|
||||
}
|
||||
// TODO: Respect the autoshield change setting
|
||||
|
||||
QString validation = mainwindow->doSendTxValidations(tx);
|
||||
if (!validation.isEmpty()) {
|
||||
error(validation);
|
||||
return;
|
||||
}
|
||||
|
||||
json params = json::array();
|
||||
mainwindow->getRPC()->fillTxJsonParams(params, tx);
|
||||
std::cout << std::setw(2) << params << std::endl;
|
||||
|
||||
// And send the Tx
|
||||
mainwindow->getRPC()->executeTransaction(tx,
|
||||
[=] (QString txid) {
|
||||
auto r = QJsonDocument(QJsonObject{
|
||||
{"version", 1.0},
|
||||
{"command", "sendTxSubmitted"},
|
||||
{"txid", txid}
|
||||
}).toJson();
|
||||
pClient->sendTextMessage(encryptOutgoing(r));
|
||||
},
|
||||
// Errored while submitting Tx
|
||||
[=] (QString, QString errStr) {
|
||||
auto r = QJsonDocument(QJsonObject{
|
||||
{"version", 1.0},
|
||||
{"command", "sendTxFailed"},
|
||||
{"err", errStr}
|
||||
}).toJson();
|
||||
pClient->sendTextMessage(encryptOutgoing(r));
|
||||
}
|
||||
);
|
||||
|
||||
auto r = QJsonDocument(QJsonObject{
|
||||
{"version", 1.0},
|
||||
{"command", "sendTx"},
|
||||
{"result", "success"}
|
||||
}).toJson();
|
||||
pClient->sendTextMessage(encryptOutgoing(r));
|
||||
}
|
||||
|
||||
|
||||
// "getInfo" command
|
||||
void AppDataServer::processGetInfo(QJsonObject jobj, MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient) {
|
||||
auto connectedName = jobj["name"].toString();
|
||||
|
||||
if (mainWindow == nullptr || mainWindow->getRPC() == nullptr) {
|
||||
pClient->close(QWebSocketProtocol::CloseCodeNormal, "Not yet ready");
|
||||
return;
|
||||
}
|
||||
|
||||
// Max spendable safely from a z address and from any address
|
||||
CAmount maxZSpendable;
|
||||
CAmount maxSpendable;
|
||||
for (auto a : mainWindow->getRPC()->getModel()->getAllBalances().keys()) {
|
||||
if (Settings::getInstance()->isSaplingAddress(a)) {
|
||||
if (mainWindow->getRPC()->getModel()->getAllBalances().value(a) > maxZSpendable) {
|
||||
maxZSpendable = mainWindow->getRPC()->getModel()->getAllBalances().value(a);
|
||||
}
|
||||
}
|
||||
if (mainWindow->getRPC()->getModel()->getAllBalances().value(a) > maxSpendable) {
|
||||
maxSpendable = mainWindow->getRPC()->getModel()->getAllBalances().value(a);
|
||||
}
|
||||
}
|
||||
|
||||
setConnectedName(connectedName);
|
||||
|
||||
auto r = QJsonDocument(QJsonObject {
|
||||
{"version", 1.0},
|
||||
{"command", "getInfo"},
|
||||
{"saplingAddress", mainWindow->getRPC()->getDefaultSaplingAddress()},
|
||||
{"tAddress", mainWindow->getRPC()->getDefaultTAddress()},
|
||||
{"balance", AppDataModel::getInstance()->getTotalBalance().toDecimalDouble()},
|
||||
{"maxspendable", maxSpendable.toDecimalDouble()},
|
||||
{"maxzspendable", maxZSpendable.toDecimalDouble()},
|
||||
{"tokenName", Settings::getTokenName()},
|
||||
// changing this to hushprice is a backward incompatible change that requires
|
||||
// changing SDL, litewalletd and SDA in unison, and would break older clients
|
||||
// so we just leave it for now
|
||||
{"zecprice", Settings::getInstance()->getHUSHPrice()},
|
||||
{"serverversion", QString(APP_VERSION)}
|
||||
}).toJson();
|
||||
pClient->sendTextMessage(encryptOutgoing(r));
|
||||
}
|
||||
|
||||
void AppDataServer::processGetTransactions(MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient) {
|
||||
QJsonArray txns;
|
||||
auto model = mainWindow->getRPC()->getTransactionsModel();
|
||||
qDebug() << "processGetTransactions";
|
||||
|
||||
|
||||
// Add transactions
|
||||
for (int i = 0; i < model->rowCount(QModelIndex()) && i < Settings::getMaxMobileAppTxns(); i++) {
|
||||
txns.append(QJsonObject{
|
||||
{"type", model->getType(i)},
|
||||
{"datetime", model->getDate(i)},
|
||||
{"amount", model->getAmt(i)},
|
||||
{"txid", model->getTxId(i)},
|
||||
{"address", model->getAddr(i)},
|
||||
// {"memo", model->getMemo(i)},
|
||||
{"confirmations", model->getConfirmations(i)}
|
||||
});
|
||||
}
|
||||
|
||||
auto r = QJsonDocument(QJsonObject{
|
||||
{"version", 1.0},
|
||||
{"command", "getTransactions"},
|
||||
{"transactions", txns}
|
||||
}).toJson();
|
||||
pClient->sendTextMessage(encryptOutgoing(r));
|
||||
}
|
||||
|
||||
// ==============================
|
||||
// AppDataModel
|
||||
// ==============================
|
||||
AppDataModel* AppDataModel::instance = nullptr;
|
||||
179
src/websockets.h
179
src/websockets.h
@@ -1,179 +0,0 @@
|
||||
// Copyright 2019-2023 The Hush developers
|
||||
// Released under the GPLv3
|
||||
#ifndef WEBSOCKETS_H
|
||||
#define WEBSOCKETS_H
|
||||
|
||||
#include "precompiled.h"
|
||||
#include "camount.h"
|
||||
#include "mainwindow.h"
|
||||
#include "ui_mobileappconnector.h"
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
|
||||
QT_FORWARD_DECLARE_CLASS(QWebSocket)
|
||||
|
||||
class WSServer;
|
||||
|
||||
// We're going to wrap the websocket in this class, because the underlying QWebSocket might get closed
|
||||
// or deleted while a callback is waiting to get the data back. Therefore, we write a custom "sendTextMessage"
|
||||
// class that checks all this before sending.
|
||||
class ClientWebSocket {
|
||||
public:
|
||||
ClientWebSocket(QWebSocket* c, WSServer* s = nullptr) { client = c; server = s; }
|
||||
|
||||
void sendTextMessage(QString m);
|
||||
void close(QWebSocketProtocol::CloseCode code, const QString& msg) { client->close(code, msg); }
|
||||
private:
|
||||
QWebSocket* client;
|
||||
WSServer* server;
|
||||
};
|
||||
|
||||
class WSServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WSServer(quint16 port, bool debug = false, QObject *parent = nullptr);
|
||||
bool isValidConnection(QWebSocket* c) { return m_clients.contains(c); }
|
||||
~WSServer();
|
||||
|
||||
Q_SIGNALS:
|
||||
void closed();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onNewConnection();
|
||||
void processTextMessage(QString message);
|
||||
void processBinaryMessage(QByteArray message);
|
||||
void socketDisconnected();
|
||||
|
||||
private:
|
||||
QWebSocketServer *m_pWebSocketServer;
|
||||
MainWindow *m_mainWindow;
|
||||
QList<QWebSocket *> m_clients;
|
||||
bool m_debug;
|
||||
};
|
||||
|
||||
class WormholeClient : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private Q_SLOTS:
|
||||
void onConnected();
|
||||
void onTextMessageReceived(QString message);
|
||||
void closed();
|
||||
|
||||
public:
|
||||
WormholeClient(MainWindow* parent, QString wormholeCode);
|
||||
~WormholeClient();
|
||||
|
||||
void connect();
|
||||
void retryConnect();
|
||||
|
||||
private:
|
||||
MainWindow* parent = nullptr;
|
||||
QWebSocket* m_webSocket = nullptr;
|
||||
|
||||
QTimer* timer = nullptr;
|
||||
|
||||
QString code;
|
||||
int retryCount = 0;
|
||||
bool shuttingDown = false;
|
||||
};
|
||||
|
||||
enum NonceType {
|
||||
LOCAL = 1,
|
||||
REMOTE
|
||||
};
|
||||
|
||||
enum AppConnectionType {
|
||||
DIRECT = 1,
|
||||
INTERNET
|
||||
};
|
||||
|
||||
class AppDataServer {
|
||||
public:
|
||||
static AppDataServer* getInstance() {
|
||||
if (instance == nullptr) {
|
||||
instance = new AppDataServer();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
void connectAppDialog(MainWindow* parent);
|
||||
void updateConnectedUI();
|
||||
void updateUIWithNewQRCode(MainWindow* mainwindow);
|
||||
|
||||
void processSendTx(QJsonObject sendTx, MainWindow* mainwindow, std::shared_ptr<ClientWebSocket> pClient);
|
||||
void processSendManyTx(QJsonObject sendmanyTx, MainWindow* mainwindow, std::shared_ptr<ClientWebSocket> pClient);
|
||||
void processMessage(QString message, MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient, AppConnectionType connType);
|
||||
void processGetInfo(QJsonObject jobj, MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient);
|
||||
void processDecryptedMessage(QString message, MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient);
|
||||
void processGetTransactions(MainWindow* mainWindow, std::shared_ptr<ClientWebSocket> pClient);
|
||||
|
||||
QString decryptMessage(QJsonDocument msg, QString secretHex, QString lastRemoteNonceHex);
|
||||
QString encryptOutgoing(QString msg);
|
||||
|
||||
QString getWormholeCode(QString secretHex);
|
||||
QString getSecretHex();
|
||||
void saveNewSecret(QString secretHex);
|
||||
|
||||
void registerNewTempSecret(QString tmpSecretHex, bool allowInternet, MainWindow* main);
|
||||
|
||||
QString getNonceHex(NonceType nt);
|
||||
void saveNonceHex(NonceType nt, QString noncehex);
|
||||
|
||||
bool getAllowInternetConnection();
|
||||
void setAllowInternetConnection(bool allow);
|
||||
|
||||
void saveLastSeenTime();
|
||||
QDateTime getLastSeenTime();
|
||||
|
||||
void setConnectedName(QString name);
|
||||
QString getConnectedName();
|
||||
bool isAppConnected();
|
||||
|
||||
QString connDesc(AppConnectionType t);
|
||||
|
||||
void saveLastConnectedOver(AppConnectionType type);
|
||||
AppConnectionType getLastConnectionType();
|
||||
|
||||
private:
|
||||
AppDataServer() = default;
|
||||
|
||||
static AppDataServer* instance;
|
||||
Ui_MobileAppConnector* ui;
|
||||
|
||||
QString tempSecret;
|
||||
WormholeClient* tempWormholeClient = nullptr;
|
||||
};
|
||||
|
||||
class AppDataModel {
|
||||
public:
|
||||
static AppDataModel* getInstance() {
|
||||
if (instance == NULL)
|
||||
instance = new AppDataModel();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
CAmount getTBalance() { return balTransparent; }
|
||||
CAmount getZBalance() { return balShielded; }
|
||||
CAmount getTotalBalance() { return balTotal; }
|
||||
|
||||
void setBalances(CAmount transparent, CAmount shielded) {
|
||||
balTransparent = transparent;
|
||||
balShielded = shielded;
|
||||
balTotal = balTransparent + balShielded;
|
||||
}
|
||||
|
||||
private:
|
||||
AppDataModel() = default; // Private, for singleton
|
||||
|
||||
CAmount balTransparent;
|
||||
CAmount balShielded;
|
||||
CAmount balTotal;
|
||||
|
||||
QString saplingAddress;
|
||||
|
||||
static AppDataModel* instance;
|
||||
};
|
||||
|
||||
|
||||
#endif // WEBSOCKETS_H
|
||||
Reference in New Issue
Block a user