1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00
qTox/src/net/bootstrapnodeupdater.cpp

196 lines
5.9 KiB
C++
Raw Normal View History

/*
Copyright © 2019 by The qTox Project Contributors
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include "bootstrapnodeupdater.h"
#include <QDirIterator>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#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"};
const QLatin1String ipv4{"ipv4"};
const QLatin1String ipv6{"ipv6"};
const QLatin1String public_key{"public_key"};
const QLatin1String port{"port"};
const QLatin1String maintainer{"maintainer"};
// TODO(sudden6): make use of this field once we differentiate between TCP nodes, and bootstrap nodes
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, QObject* parent)
: QObject{parent}
, proxy{proxy}
{}
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 bootstrapnodes 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);
}
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);
}
void BootstrapNodeUpdater::jsonNodeToDhtServer(const QJsonObject& node, QList<DhtServer>& outList)
{
// first check if the node in question has all needed fields
bool found = true;
for (const auto& key : NodeFields::neededFields) {
found &= node.contains(key);
}
if (!found) {
return;
}
// only use nodes that provide at least UDP connection
if (!node[NodeFields::status_udp].toBool(false)) {
return;
}
const QString public_key = node[NodeFields::public_key].toString({});
const int port = node[NodeFields::port].toInt(-1);
// nodes.tox.chat doesn't use empty strings for empty addresses
QString ipv6_address = node[NodeFields::ipv6].toString({});
if (ipv6_address == emptyAddress) {
ipv6_address = QString{};
}
QString ipv4_address = node[NodeFields::ipv4].toString({});
if (ipv4_address == emptyAddress) {
ipv4_address = QString{};
}
const QString maintainer = node[NodeFields::maintainer].toString({});
if (port < 1 || port > std::numeric_limits<uint16_t>::max()) {
return;
}
const quint16 port_u16 = static_cast<quint16>(port);
if (!public_key.contains(ToxPkRegEx)) {
return;
}
DhtServer server;
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);
}
return;
}
QList<DhtServer> BootstrapNodeUpdater::jsonToNodeList(const QJsonDocument& nodeList)
{
QList<DhtServer> result;
if (!nodeList.isObject()) {
return result;
}
QJsonObject rootObj = nodeList.object();
if (!(rootObj.contains(jsonNodeArrayName) && rootObj[jsonNodeArrayName].isArray())) {
return result;
}
QJsonArray nodes = rootObj[jsonNodeArrayName].toArray();
chore: Various code cleanups. * Reorder class data members and/or constructor initialisers to match, reducing confusion about when members will be initialised. * Remove (most) unused variables. Not removed: some global variables with `TODO(sudden6)` on them for using them in the future. I don't know how far into the future sudden6 wants to use them, so I left them there for now. * Distinguish different bootstrap nodes in the logs by index in the bootstrap node list. Originally, we used to log the address/port of the node we're bootstrapping to. This was removed out of privacy concerns (even though the bootstrap nodes are public). This made the logs much less useful when debugging why the client isn't connecting. Having indices makes it easier to see that different nodes are being selected, and makes it possible to determine which node was selected. * Explicitly cast unused results of Tox API functions to `void` when all we want is to know whether the function succeeds or not. * Don't try to `#include <unistd.h>` on Windows. It does not exist on MSVC. * Remove extra `;` after function definitions. * Remove reference indirection of QJsonValueRef, since a copy of that ref (small pointer-like object) has to be made anyway when iterating over QJsonArrays. * Make some file-scope global state `static`. * Use `nullptr` instead of `NULL`. * Add `#if DESKTOP_NOTIFICATIONS` around the code that implements desktop notifications, so it becomes a bit easier to compile everything with a single compiler command - useful for manually running static analysers. * Fix an error on MSVC where `disconnect` is looked up to be a non-static member function and the `this` capture is missing. * Consistently use `struct` and `class` tags for types. * Use references in ranged-for where it reduces copies. * Move private static data members out of the Style class and into file-local scope. There is no need for them to be in the class. Also marked them `const` where possible. * Removed unused lambda capture. * Ensure qTox can compile under NDEBUG with `-Wunused-variable` by inlining the unused variable into the `assert` that was its only target. * Minor reformatting in core_test.cpp.
2020-04-16 23:59:29 +08:00
for (const QJsonValueRef node : nodes) {
if (node.isObject()) {
jsonNodeToDhtServer(node.toObject(), result);
}
}
return result;
}