@ -30,14 +30,6 @@
@@ -30,14 +30,6 @@
# include <QNetworkReply>
# include <QRegularExpression>
namespace {
const QUrl NodeListAddress { " https://nodes.tox.chat/json " } ;
const QLatin1String jsonNodeArrayName { " nodes " } ;
const QLatin1String emptyAddress { " - " } ;
const QRegularExpression ToxPkRegEx ( QString ( " (^| \\ s) [ A - Fa - f0 - 9 ] { % 1 } ( $ | \ \ s ) " ).arg(64));
const QLatin1String builtinNodesFile { " :/conf/nodes.json " } ;
} // namespace
namespace NodeFields {
const QLatin1String status_udp { " status_udp " } ;
const QLatin1String status_tcp { " status_tcp " } ;
@ -51,95 +43,14 @@ const QLatin1String tcp_ports{"tcp_ports"};
@@ -51,95 +43,14 @@ const QLatin1String tcp_ports{"tcp_ports"};
const QStringList neededFields { status_udp , status_tcp , ipv4 , ipv6 , public_key , port , maintainer } ;
} // namespace NodeFields
/**
* @ brief Fetches a list of currently online bootstrap nodes from node . tox . chat
* @ param proxy Proxy to use for the lookup , must outlive this object
*/
BootstrapNodeUpdater : : BootstrapNodeUpdater ( const QNetworkProxy & proxy , Paths & _paths , QObject * parent )
: proxy { proxy }
, paths { _paths }
, QObject { parent }
{ }
QList < DhtServer > BootstrapNodeUpdater : : getBootstrapnodes ( )
{
return loadDefaultBootstrapNodes ( ) ;
}
void BootstrapNodeUpdater : : requestBootstrapNodes ( )
{
nam . setProxy ( proxy ) ;
connect ( & nam , & QNetworkAccessManager : : finished , this , & BootstrapNodeUpdater : : onRequestComplete ) ;
QNetworkRequest request { NodeListAddress } ;
request . setHeader ( QNetworkRequest : : ContentTypeHeader , " application/json " ) ;
nam . get ( request ) ;
}
/**
* @ brief Loads the list of built in boostrap nodes
* @ return List of bootstrap nodes on success , empty list on error
*/
QList < DhtServer > BootstrapNodeUpdater : : loadDefaultBootstrapNodes ( )
{
QFile nodesFile { builtinNodesFile } ;
if ( ! nodesFile . open ( QIODevice : : ReadOnly | QIODevice : : Text ) ) {
qWarning ( ) < < " Couldn't read bootstrap nodes " ;
return { } ;
}
QString nodesJson = nodesFile . readAll ( ) ;
nodesFile . close ( ) ;
QJsonDocument d = QJsonDocument : : fromJson ( nodesJson . toUtf8 ( ) ) ;
if ( d . isNull ( ) ) {
qWarning ( ) < < " Failed to parse JSON document " ;
return { } ;
}
return jsonToNodeList ( d ) ;
}
QList < DhtServer > BootstrapNodeUpdater : : loadUserBootrapNodes ( )
{
QFile nodesFile { builtinNodesFile } ;
if ( ! nodesFile . open ( QIODevice : : ReadOnly | QIODevice : : Text ) ) {
qWarning ( ) < < " Couldn't read bootstrap nodes " ;
return { } ;
}
QString nodesJson = nodesFile . readAll ( ) ;
nodesFile . close ( ) ;
QJsonDocument d = QJsonDocument : : fromJson ( nodesJson . toUtf8 ( ) ) ;
if ( d . isNull ( ) ) {
qWarning ( ) < < " Failed to parse JSON document " ;
return { } ;
}
return jsonToNodeList ( d ) ;
}
void BootstrapNodeUpdater : : onRequestComplete ( QNetworkReply * reply )
{
if ( reply - > error ( ) ! = QNetworkReply : : NoError ) {
nam . clearAccessCache ( ) ;
emit availableBootstrapNodes ( { } ) ;
return ;
}
// parse the reply JSON
QJsonDocument jsonDocument = QJsonDocument : : fromJson ( reply - > readAll ( ) ) ;
if ( jsonDocument . isNull ( ) ) {
emit availableBootstrapNodes ( { } ) ;
return ;
}
QList < DhtServer > result = jsonToNodeList ( jsonDocument ) ;
emit availableBootstrapNodes ( result ) ;
}
namespace {
const QUrl NodeListAddress { " https://nodes.tox.chat/json " } ;
const QLatin1String jsonNodeArrayName { " nodes " } ;
const QLatin1String emptyAddress { " - " } ;
const QRegularExpression ToxPkRegEx ( QString ( " (^| \\ s) [ A - Fa - f0 - 9 ] { % 1 } ( $ | \ \ s ) " ).arg(64));
const QLatin1String builtinNodesFile { " :/conf/nodes.json " } ;
void BootstrapNodeUpdater : : jsonNodeToDhtServer ( const QJsonObject & node , QList < DhtServer > & outList )
void jsonNodeToDhtServer ( const QJsonObject & node , QList < DhtServer > & outList )
{
// first check if the node in question has all needed fields
bool found = true ;
@ -148,6 +59,7 @@ void BootstrapNodeUpdater::jsonNodeToDhtServer(const QJsonObject& node, QList<Dh
@@ -148,6 +59,7 @@ void BootstrapNodeUpdater::jsonNodeToDhtServer(const QJsonObject& node, QList<Dh
}
if ( ! found ) {
qDebug ( ) < < " Node is missing required fields. " ;
return ;
}
@ -170,45 +82,47 @@ void BootstrapNodeUpdater::jsonNodeToDhtServer(const QJsonObject& node, QList<Dh
@@ -170,45 +82,47 @@ void BootstrapNodeUpdater::jsonNodeToDhtServer(const QJsonObject& node, QList<Dh
ipv4_address = QString { } ;
}
if ( ipv4_address . isEmpty ( ) & & ipv6_address . isEmpty ( ) ) {
qWarning ( ) < < " Both ipv4 and ipv4 addresses are empty for " < < public_key ;
}
const QString maintainer = node [ NodeFields : : maintainer ] . toString ( { } ) ;
if ( port < 1 | | port > std : : numeric_limits < uint16_t > : : max ( ) ) {
qDebug ( ) < < " Invalid port in nodes list: " < < port ;
return ;
}
const quint16 port_u16 = static_cast < quint16 > ( port ) ;
if ( ! public_key . contains ( ToxPkRegEx ) ) {
qDebug ( ) < < " Invalid public key in nodes list " < < public_key ;
return ;
}
DhtServer server ;
server . statusUdp = true ;
server . statusTcp = node [ NodeFields : : status_udp ] . toBool ( false ) ;
server . userId = public_key ;
server . port = port_u16 ;
server . name = maintainer ;
if ( ! ipv4_address . isEmpty ( ) ) {
server . address = ipv4_address ;
outList . append ( server ) ;
}
// avoid adding the same server twice in case they use the same dns name for v6 and v4
if ( ! ipv6_address . isEmpty ( ) & & ipv4_address ! = ipv6_address ) {
server . address = ipv6_address ;
outList . append ( server ) ;
}
server . maintainer = maintainer ;
server . ipv4 = ipv4_address ;
server . ipv6 = ipv6_address ;
outList . append ( server ) ;
return ;
}
QList < DhtServer > BootstrapNodeUpdater : : jsonToNodeList ( const QJsonDocument & nodeList )
QList < DhtServer > jsonToNodeList ( const QJsonDocument & nodeList )
{
QList < DhtServer > result ;
if ( ! nodeList . isObject ( ) ) {
qWarning ( ) < < " Bootstrap JSON is missing root object " ;
return result ;
}
QJsonObject rootObj = nodeList . object ( ) ;
if ( ! ( rootObj . contains ( jsonNodeArrayName ) & & rootObj [ jsonNodeArrayName ] . isArray ( ) ) ) {
qWarning ( ) < < " Bootstrap JSON is missing nodes array " ;
return result ;
}
QJsonArray nodes = rootObj [ jsonNodeArrayName ] . toArray ( ) ;
@ -220,3 +134,114 @@ QList<DhtServer> BootstrapNodeUpdater::jsonToNodeList(const QJsonDocument& nodeL
@@ -220,3 +134,114 @@ QList<DhtServer> BootstrapNodeUpdater::jsonToNodeList(const QJsonDocument& nodeL
return result ;
}
QList < DhtServer > loadNodesFile ( QString file )
{
QFile nodesFile { file } ;
if ( ! nodesFile . open ( QIODevice : : ReadOnly | QIODevice : : Text ) ) {
qWarning ( ) < < " Couldn't read bootstrap nodes " ;
return { } ;
}
QString nodesJson = nodesFile . readAll ( ) ;
nodesFile . close ( ) ;
auto jsonDoc = QJsonDocument : : fromJson ( nodesJson . toUtf8 ( ) ) ;
if ( jsonDoc . isNull ( ) ) {
qWarning ( ) < < " Failed to parse JSON document " ;
return { } ;
}
return jsonToNodeList ( jsonDoc ) ;
}
QByteArray serialize ( QList < DhtServer > nodes )
{
QJsonArray jsonNodes ;
for ( auto & node : nodes ) {
QJsonObject nodeJson ;
nodeJson . insert ( NodeFields : : status_udp , node . statusUdp ) ;
nodeJson . insert ( NodeFields : : status_tcp , node . statusTcp ) ;
nodeJson . insert ( NodeFields : : ipv4 , node . ipv4 ) ;
nodeJson . insert ( NodeFields : : ipv6 , node . ipv6 ) ;
nodeJson . insert ( NodeFields : : public_key , node . userId ) ;
nodeJson . insert ( NodeFields : : port , node . port ) ;
nodeJson . insert ( NodeFields : : maintainer , node . maintainer ) ;
jsonNodes . append ( nodeJson ) ;
}
QJsonObject rootObj ;
rootObj . insert ( " nodes " , jsonNodes ) ;
QJsonDocument doc { rootObj } ;
return doc . toJson ( QJsonDocument : : Indented ) ;
}
} // namespace
/**
* @ brief Fetches a list of currently online bootstrap nodes from node . tox . chat
* @ param proxy Proxy to use for the lookup , must outlive this object
*/
BootstrapNodeUpdater : : BootstrapNodeUpdater ( const QNetworkProxy & proxy , Paths & _paths , QObject * parent )
: proxy { proxy }
, paths { _paths }
, QObject { parent }
{ }
QList < DhtServer > BootstrapNodeUpdater : : getBootstrapnodes ( )
{
auto userFilePath = paths . getUserNodesFilePath ( ) ;
if ( ! QFile ( userFilePath ) . exists ( ) ) {
qInfo ( ) < < " Bootstrap node list not found, creating one with default nodes. " ;
// deserialize and reserialize instead of just copying to strip out any unnecessary json, making it easier for
// users to edit
auto buildInNodes = loadNodesFile ( builtinNodesFile ) ;
auto serializedNodes = serialize ( buildInNodes ) ;
QFile outFile ( userFilePath ) ;
outFile . open ( QIODevice : : WriteOnly | QIODevice : : Text ) ;
outFile . write ( serializedNodes . data ( ) , serializedNodes . size ( ) ) ;
outFile . close ( ) ;
}
return loadNodesFile ( userFilePath ) ;
}
void BootstrapNodeUpdater : : requestBootstrapNodes ( )
{
nam . setProxy ( proxy ) ;
connect ( & nam , & QNetworkAccessManager : : finished , this , & BootstrapNodeUpdater : : onRequestComplete ) ;
QNetworkRequest request { NodeListAddress } ;
request . setHeader ( QNetworkRequest : : ContentTypeHeader , " application/json " ) ;
nam . get ( request ) ;
}
/**
* @ brief Loads the list of built in boostrap nodes
* @ return List of bootstrap nodes on success , empty list on error
*/
QList < DhtServer > BootstrapNodeUpdater : : loadDefaultBootstrapNodes ( )
{
return loadNodesFile ( builtinNodesFile ) ;
}
void BootstrapNodeUpdater : : onRequestComplete ( QNetworkReply * reply )
{
if ( reply - > error ( ) ! = QNetworkReply : : NoError ) {
nam . clearAccessCache ( ) ;
emit availableBootstrapNodes ( { } ) ;
return ;
}
// parse the reply JSON
QJsonDocument jsonDocument = QJsonDocument : : fromJson ( reply - > readAll ( ) ) ;
if ( jsonDocument . isNull ( ) ) {
emit availableBootstrapNodes ( { } ) ;
return ;
}
QList < DhtServer > result = jsonToNodeList ( jsonDocument ) ;
emit availableBootstrapNodes ( result ) ;
}