@ -1,3 +1,4 @@
// Copyright 2019 The Hush developers
# include "websockets.h"
# include "rpc.h"
@ -5,7 +6,7 @@
# include "ui_mobileappconnector.h"
# include "version.h"
// We ap the sendTextMessage to check if the connection is valid and that the parent WebServer didn't close this connection
// Wr ap 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 ) {
@ -36,13 +37,15 @@ WSServer::WSServer(quint16 port, bool debug, QObject *parent) :
WSServer : : ~ WSServer ( )
{
qDebug ( ) < < " Closing websocket " ;
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 ) ;
@ -90,34 +93,76 @@ 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 - > isValid ( ) ) {
if ( m_webSocket & & m_webSocket - > isValid ( ) ) {
qDebug ( ) < < " Wormhole closing! " ;
m_webSocket - > close ( ) ;
}
if ( timer )
if ( timer ) {
qDebug ( ) < < " Wormhole timer stopping " ;
timer - > stop ( ) ;
}
qDebug ( ) < < " Wormhole client destroyed " ;
delete timer ;
qDebug ( ) < < " Wormhole timer deleted " ;
}
void ws_error ( ) {
qDebug ( ) < < " websocket error! " ;
}
void WormholeClient : : sslerrors ( const QList < QSslError > & )
{
qDebug ( ) < < " SSL errors occurred! " ;
//TODO: don't do this in prod
//m_webSocket->ignoreSslErrors();
}
void WormholeClient : : connect ( ) {
qDebug ( ) < < " Wormhole::connect " ;
delete m_webSocket ;
m_webSocket = new QWebSocket ( ) ;
QUrl wormhole = QUrl ( " wss://wormhole.myhush.org:443 " ) ;
if ( m_webSocket ) {
QObject : : connect ( m_webSocket , & QWebSocket : : connected , this , & WormholeClient : : onConnected ) ;
QObject : : connect ( m_webSocket , & QWebSocket : : disconnected , this , & WormholeClient : : closed ) ;
QObject : : connect ( m_webSocket , QOverload < const QList < QSslError > & > : : of ( & QWebSocket : : sslErrors ) , this , & WormholeClient : : sslerrors ) ;
qDebug ( ) < < " Opening connection to the SilentDragonWormhole " ;
m_webSocket - > open ( wormhole ) ;
qDebug ( ) < < " Opened connection to " < < wormhole ;
//TODO: use env var to over-ride
//m_webSocket->open(QUrl("ws://127.0.0.1:7070"));
} else {
qDebug ( ) < < " Invalid websocket object! " ;
}
}
QObject : : connect ( m_webSocket , & QWebSocket : : connected , this , & WormholeClient : : onConnected ) ;
QObject : : connect ( m_webSocket , & QWebSocket : : disconnected , this , & WormholeClient : : closed ) ;
m_webSocket - > open ( QUrl ( " wss://wormhole.myhush.org:443 " ) ) ;
//TODO: use env var to over-ride
//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, count= " < < this - > retryCount ;
this - > retryCount + + ;
connect ( ) ;
} else {
qDebug ( ) < < " Retry count exceeded, will not attempt retry any more " ;
}
} ) ;
}
void WormholeClient : : retryConnect ( ) {
/*
void WormholeClient : : retryConnect ( ) {
int max_retries = 10 ;
qDebug ( ) < < " Websocket retryConnect, retryCount= " < < retryCount ;
@ -135,6 +180,7 @@ void WormholeClient::retryConnect() {
qDebug ( ) < < " Invalid retryCount= " < < retryCount < < " detected! " ;
}
}
*/
// Called when the websocket is closed. If this was closed without our explicitly closing it,
// then we need to try and reconnect
@ -147,30 +193,36 @@ void WormholeClient::closed() {
void WormholeClient : : onConnected ( )
{
qDebug ( ) < < " WebSocket connected " ;
retryCount = 0 ;
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
timer = new QTimer ( parent ) ;
QObject : : connect ( timer , & QTimer : : timeout , [ = ] ( ) {
if ( ! shuttingDown & & m_webSocket - > isValid ( ) ) {
auto payload = QJsonDocument ( QJsonObject { { " ping " , " ping " } } ) . toJson ( ) ;
qint64 bytes = m_webSocket - > sendTextMessage ( payload ) ;
qDebug ( ) < < " Sent ping, " < < bytes < < " bytes " ;
}
} ) ;
qDebug ( ) < < " Starting timer " ;
timer - > start ( 4 * 60 * 1000 ) ; // 4 minutes
qDebug ( ) < < " WebSocket connected, retryCount= " < < retryCount ;
QObject : : connect ( m_webSocket , & QWebSocket : : textMessageReceived , this , & WormholeClient : : onTextMessageReceived ) ;
auto payload = QJsonDocument ( QJsonObject { { " register " , code } } ) . toJson ( ) ;
qDebug ( ) < < " Sending register " ;
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 )
@ -183,11 +235,12 @@ void WormholeClient::onTextMessageReceived(QString message)
// ==============================
// AppDataServer
// ==============================
AppDataServer * AppDataServer : : instance = nullptr ;
AppDataServer * AppDataServer : : instance = nullptr ;
QString AppDataServer : : getWormholeCode ( QString secretHex ) {
qDebug ( ) < < " AppDataServer::getWormholeCode " ;
unsigned char * secret = new unsigned char [ crypto_secretbox_KEYBYTES ] ;
sodium_hex2bin ( secret , crypto_secretbox_KEYBYTES , secretHex . toStdString ( ) . c_str ( ) , crypto_secretbox_KEYBYTES * 2 ,
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 ] ;
@ -197,7 +250,7 @@ QString AppDataServer::getWormholeCode(QString secretHex) {
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 ) ;
sodium_bin2hex ( wmcode , crypto_hash_sha256_BYTES * 2 + 1 , out2 , crypto_hash_sha256_BYTES ) ;
QString wmcodehex ( wmcode ) ;
@ -206,7 +259,7 @@ QString AppDataServer::getWormholeCode(QString secretHex) {
delete [ ] out1 ;
delete [ ] secret ;
qDebug ( ) < < " Created wormhole secretHex " ;
qDebug ( ) < < " Created wormhole secretHex= " < < wmcodehex ;
return wmcodehex ;
}
@ -265,17 +318,18 @@ void AppDataServer::connectAppDialog(MainWindow* parent) {
ui = new Ui_MobileAppConnector ( ) ;
ui - > setupUi ( & d ) ;
Settings : : saveRestore ( & d ) ;
qDebug ( ) < < " connectAppDialog " ;
updateUIWithNewQRCode ( parent ) ;
updateConnectedUI ( ) ;
QObject : : connect ( ui - > btnDisconnect , & QPushButton : : clicked , [ = ] ( ) {
qDebug ( ) < < " Disconnecting " ;
QSettings ( ) . setValue ( " mobileapp/connectedname " , " " ) ;
saveNewSecret ( " " ) ;
updateConnectedUI ( ) ;
} ) ;
QObject : : connect ( ui - > txtConnStr , & QLineEdit : : cursorPositionChanged , [ = ] ( int , int ) {
ui - > txtConnStr - > selectAll ( ) ;
} ) ;
@ -284,33 +338,39 @@ void AppDataServer::connectAppDialog(MainWindow* parent) {
if ( state = = Qt : : Checked ) {
}
qDebug ( ) < < " Updating QR " ;
updateUIWithNewQRCode ( parent ) ;
} ) ;
// If we're not listening for the app, then start the websockets
if ( ! parent - > isWebsocketListening ( ) ) {
qDebug ( ) < < " websocket not listening " ;
QString wormholecode = " " ;
if ( getAllowInternetConnection ( ) )
if ( getAllowInternetConnection ( ) ) {
wormholecode = AppDataServer : : getInstance ( ) - > getWormholeCode ( AppDataServer : : getInstance ( ) - > getSecretHex ( ) ) ;
qDebug ( ) < < " Generated wormholecode= " < < wormholecode ;
}
parent - > createWebsocket ( wormholecode ) ;
} else {
qDebug ( ) < < " no websocket not listening " ;
}
d . exec ( ) ;
// If there is nothing connected when the dialog exits, then shutdown the websockets
if ( ! isAppConnected ( ) ) {
qDebug ( ) < < " no app connected, stopping websockets " ;
parent - > stopWebsocket ( ) ;
}
// Cleanup
tempSecret = " " ;
delete tempWormholeClient ;
tempWormholeClient = nullptr ;
delete ui ;
ui = nullptr ;
qDebug ( ) < < " Destroyed tempWormholeClient and ui " ;
}
void AppDataServer : : updateUIWithNewQRCode ( MainWindow * mainwindow ) {
@ -329,9 +389,9 @@ void AppDataServer::updateUIWithNewQRCode(MainWindow* mainwindow) {
if ( ipv4Addr . isEmpty ( ) )
return ;
QString uri = " ws:// " + ipv4Addr + " :8777 " ;
qDebug ( ) < < " Websocket URI: " < < uri ;
qDebug ( ) < < " Websocket URI: " < < uri ;
// Get a new secret
unsigned char * secretBin = new unsigned char [ crypto_secretbox_KEYBYTES ] ;
@ -350,10 +410,11 @@ void AppDataServer::updateUIWithNewQRCode(MainWindow* 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 ;
qDebug ( ) < < " Registering new tempSecret, allowInternet= " < < allowInternet ;
tempSecret = tmpSecretHex ;
delete tempWormholeClient ;
@ -361,8 +422,8 @@ void AppDataServer::registerNewTempSecret(QString tmpSecretHex, bool allowIntern
if ( allowInternet ) {
tempWormholeClient = new WormholeClient ( main , getWormholeCode ( tempSecret ) ) ;
qDebug ( ) < < " Created new wormhole client " ;
}
qDebug ( ) < < " Created new wormhole client " ;
}
}
QString AppDataServer : : connDesc ( AppConnectionType t ) {
@ -415,6 +476,7 @@ void AppDataServer::saveNonceHex(NonceType nt, QString noncehex) {
// Encrypt an outgoing message with the stored secret key.
QString AppDataServer : : encryptOutgoing ( QString msg ) {
qDebug ( ) < < " Encrypting msg " ;
if ( msg . length ( ) % 256 > 0 ) {
msg = msg + QString ( " " ) . repeated ( 256 - ( msg . length ( ) % 256 ) ) ;
}
@ -454,7 +516,6 @@ QString AppDataServer::encryptOutgoing(QString msg) {
{ " payload " , QString ( encryptedHex ) } ,
{ " to " , getWormholeCode ( getSecretHex ( ) ) }
} ) ;
delete [ ] noncebin ;
delete [ ] newLocalNonce ;
delete [ ] secret ;
@ -470,6 +531,7 @@ QString AppDataServer::encryptOutgoing(QString msg) {
unless the skipNonceCheck = true , which is used when attempting decrtption with a temp secret key .
*/
QString AppDataServer : : decryptMessage ( QJsonDocument msg , QString secretHex , QString lastRemoteNonceHex ) {
qDebug ( ) < < " Decrypting message " ;
// Decrypt and then process
QString noncehex = msg . object ( ) . value ( " nonce " ) . toString ( ) ;
QString encryptedhex = msg . object ( ) . value ( " payload " ) . toString ( ) ;
@ -531,12 +593,15 @@ QString AppDataServer::decryptMessage(QJsonDocument msg, QString secretHex, QStr
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 " } ,
@ -620,6 +685,7 @@ void AppDataServer::processMessage(QString message, MainWindow* mainWindow, std:
// 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 ( ) ) ;
@ -652,7 +718,7 @@ void AppDataServer::processDecryptedMessage(QString message, MainWindow* mainWin
// "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 " ;
qDebug ( ) < < " processSendTx with to= " < < sendTx [ " to " ] . toString ( ) ;
auto error = [ = ] ( QString reason ) {
auto r = QJsonDocument ( QJsonObject {
{ " errorCode " , - 1 } ,
@ -681,6 +747,7 @@ void AppDataServer::processSendTx(QJsonObject sendTx, MainWindow* mainwindow, st
if ( Settings : : getInstance ( ) - > isSproutAddress ( i ) )
continue ;
// Filter out balances that don't have the requisite amount
// TODO: should this be amt+tx.fee?
if ( allBalances - > value ( i ) < amt )
continue ;