qTox  Version: nightly | Commit: bc751c8e1cac455f9690654fcfe0f560d2d7dfdd
bootstrapnodeupdater.cpp
Go to the documentation of this file.
1 /*
2  Copyright © 2019 by The qTox Project Contributors
3 
4  This file is part of qTox, a Qt-based graphical interface for Tox.
5 
6  qTox is libre software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  qTox is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with qTox. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "bootstrapnodeupdater.h"
21 
22 #include "src/persistence/paths.h"
23 #include "src/core/toxpk.h"
24 #include "src/core/toxid.h"
25 
26 #include <QDirIterator>
27 #include <QFile>
28 #include <QJsonArray>
29 #include <QJsonDocument>
30 #include <QJsonObject>
31 #include <QNetworkAccessManager>
32 #include <QNetworkReply>
33 #include <QRegularExpression>
34 #include <QJsonArray>
35 
36 #include <cstdint>
37 
38 namespace NodeFields {
39 const QLatin1String status_udp{"status_udp"};
40 const QLatin1String status_tcp{"status_tcp"};
41 const QLatin1String ipv4{"ipv4"};
42 const QLatin1String ipv6{"ipv6"};
43 const QLatin1String public_key{"public_key"};
44 const QLatin1String udp_port{"port"};
45 const QLatin1String maintainer{"maintainer"};
46 const QLatin1String tcp_ports{"tcp_ports"};
48 } // namespace NodeFields
49 
50 namespace {
51 const QUrl NodeListAddress{"https://nodes.tox.chat/json"};
52 const QLatin1String jsonNodeArrayName{"nodes"};
53 const QLatin1String emptyAddress{"-"};
54 const QRegularExpression ToxPkRegEx(QString("(^|\\s)[A-Fa-f0-9]{%1}($|\\s)").arg(64));
55 const QLatin1String builtinNodesFile{":/conf/nodes.json"};
56 
57 void jsonNodeToDhtServer(const QJsonObject& node, QList<DhtServer>& outList)
58 {
59  // first check if the node in question has all needed fields
60  bool found = true;
61  for (const auto& key : NodeFields::neededFields) {
62  found &= node.contains(key);
63  }
64 
65  if (!found) {
66  qDebug() << "Node is missing required fields.";
67  return;
68  }
69 
70  const QString public_key = node[NodeFields::public_key].toString({});
71  const auto udp_port = node[NodeFields::udp_port].toInt(-1);
72  const auto status_udp = node[NodeFields::status_udp].toBool(false);
73  const auto status_tcp = node[NodeFields::status_tcp].toBool(false);
74  const QString maintainer = node[NodeFields::maintainer].toString({});
75 
76  std::vector<uint16_t> tcp_ports;
77  const auto jsonTcpPorts = node[NodeFields::tcp_ports].toArray();
78  for (int i = 0; i < jsonTcpPorts.count(); ++i) {
79  const auto port = jsonTcpPorts.at(i).toInt();
80  if (port < 1 || port > std::numeric_limits<uint16_t>::max()) {
81  qDebug () << "Invalid TCP port in nodes list:" << port;
82  return;
83  }
84  tcp_ports.emplace_back(static_cast<uint16_t>(port));
85  }
86 
87  // nodes.tox.chat doesn't use empty strings for empty addresses
88  QString ipv6_address = node[NodeFields::ipv6].toString({});
89  if (ipv6_address == emptyAddress) {
90  ipv6_address = QString{};
91  }
92 
93  QString ipv4_address = node[NodeFields::ipv4].toString({});
94  if (ipv4_address == emptyAddress) {
95  ipv4_address = QString{};
96  }
97 
98  if (ipv4_address.isEmpty() && ipv6_address.isEmpty()) {
99  qWarning() << "Both ipv4 and ipv4 addresses are empty for" << public_key;
100  }
101 
102  if (status_udp && udp_port == -1) {
103  qWarning() << "UDP enabled but no UDP port for" << public_key;
104  }
105 
106  if (status_tcp && tcp_ports.empty()) {
107  qWarning() << "TCP enabled but no TCP ports for:" << public_key;
108  }
109 
110  if (udp_port < 1 || udp_port > std::numeric_limits<uint16_t>::max()) {
111  qDebug() << "Invalid port in nodes list:" << udp_port;
112  return;
113  }
114  const quint16 udp_port_u16 = static_cast<quint16>(udp_port);
115 
116  if (!public_key.contains(ToxPkRegEx)) {
117  qDebug() << "Invalid public key in nodes list" << public_key;
118  return;
119  }
120 
121  DhtServer server;
122  server.statusUdp = true;
123  server.statusTcp = status_tcp;
124  server.tcpPorts = tcp_ports;
125  server.publicKey = ToxPk{public_key};
126  server.udpPort = udp_port_u16;
127  server.maintainer = maintainer;
128  server.ipv4 = ipv4_address;
129  server.ipv6 = ipv6_address;
130  outList.append(server);
131  return;
132 }
133 
134 QList<DhtServer> jsonToNodeList(const QJsonDocument& nodeList)
135 {
136  QList<DhtServer> result;
137 
138  if (!nodeList.isObject()) {
139  qWarning() << "Bootstrap JSON is missing root object";
140  return result;
141  }
142 
143  QJsonObject rootObj = nodeList.object();
144  if (!(rootObj.contains(jsonNodeArrayName) && rootObj[jsonNodeArrayName].isArray())) {
145  qWarning() << "Bootstrap JSON is missing nodes array";
146  return result;
147  }
148  QJsonArray nodes = rootObj[jsonNodeArrayName].toArray();
149  for (const QJsonValueRef node : nodes) {
150  if (node.isObject()) {
151  jsonNodeToDhtServer(node.toObject(), result);
152  }
153  }
154 
155  return result;
156 }
157 
158 QList<DhtServer> loadNodesFile(QString file)
159 {
160  QFile nodesFile{file};
161  if (!nodesFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
162  qWarning() << "Couldn't read bootstrap nodes";
163  return {};
164  }
165 
166  QString nodesJson = nodesFile.readAll();
167  nodesFile.close();
168 
169  auto jsonDoc = QJsonDocument::fromJson(nodesJson.toUtf8());
170  if (jsonDoc.isNull()) {
171  qWarning() << "Failed to parse JSON document";
172  return {};
173  }
174 
175  return jsonToNodeList(jsonDoc);
176 }
177 
178 QByteArray serialize(QList<DhtServer> nodes)
179 {
180  QJsonArray jsonNodes;
181  for (auto& node : nodes) {
182  QJsonObject nodeJson;
183  nodeJson.insert(NodeFields::status_udp, node.statusUdp);
184  nodeJson.insert(NodeFields::status_tcp, node.statusTcp);
185  nodeJson.insert(NodeFields::ipv4, node.ipv4);
186  nodeJson.insert(NodeFields::ipv6, node.ipv6);
187  nodeJson.insert(NodeFields::public_key, node.publicKey.toString());
188  nodeJson.insert(NodeFields::udp_port, node.udpPort);
189  nodeJson.insert(NodeFields::maintainer, node.maintainer);
190 
191  QJsonArray tcp_ports;
192  for (size_t i = 0; i < node.tcpPorts.size(); ++i) {
193  tcp_ports.push_back(node.tcpPorts.at(i));
194  }
195  nodeJson.insert(NodeFields::tcp_ports, tcp_ports);
196  jsonNodes.append(nodeJson);
197  }
198  QJsonObject rootObj;
199  rootObj.insert("nodes", jsonNodes);
200 
201  QJsonDocument doc{rootObj};
202  return doc.toJson(QJsonDocument::Indented);
203 }
204 } // namespace
205 
210 BootstrapNodeUpdater::BootstrapNodeUpdater(const QNetworkProxy& proxy, Paths& _paths, QObject* parent)
211  : proxy{proxy}
212  , paths{_paths}
213  , QObject{parent}
214 {}
215 
217 {
218  auto userFilePath = paths.getUserNodesFilePath();
219  if (!QFile(userFilePath).exists()) {
220  qInfo() << "Bootstrap node list not found, creating one with default nodes.";
221  // deserialize and reserialize instead of just copying to strip out any unnecessary json, making it easier for
222  // users to edit
223  auto buildInNodes = loadNodesFile(builtinNodesFile);
224  auto serializedNodes = serialize(buildInNodes);
225 
226  QFile outFile(userFilePath);
227  outFile.open(QIODevice::WriteOnly | QIODevice::Text);
228  outFile.write(serializedNodes.data(), serializedNodes.size());
229  outFile.close();
230  }
231 
232  return loadNodesFile(userFilePath);
233 }
234 
236 {
237  nam.setProxy(proxy);
238  connect(&nam, &QNetworkAccessManager::finished, this, &BootstrapNodeUpdater::onRequestComplete);
239 
240  QNetworkRequest request{NodeListAddress};
241  request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
242 
243  nam.get(request);
244 }
245 
251 {
252  return loadNodesFile(builtinNodesFile);
253 }
254 
255 void BootstrapNodeUpdater::onRequestComplete(QNetworkReply* reply)
256 {
257  if (reply->error() != QNetworkReply::NoError) {
258  nam.clearAccessCache();
259  emit availableBootstrapNodes({});
260  return;
261  }
262 
263  // parse the reply JSON
264  QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll());
265  if (jsonDocument.isNull()) {
266  emit availableBootstrapNodes({});
267  return;
268  }
269 
270  QList<DhtServer> result = jsonToNodeList(jsonDocument);
271 
272  emit availableBootstrapNodes(result);
273 }
NodeFields::neededFields
const QStringList neededFields
Definition: bootstrapnodeupdater.cpp:47
NodeFields::public_key
const QLatin1String public_key
Definition: bootstrapnodeupdater.cpp:43
toxid.h
BootstrapNodeUpdater::onRequestComplete
void onRequestComplete(QNetworkReply *reply)
Definition: bootstrapnodeupdater.cpp:255
NodeFields::ipv4
const QLatin1String ipv4
Definition: bootstrapnodeupdater.cpp:41
Paths
Definition: paths.h:28
HistMessageContentType::file
@ file
NodeFields::status_udp
const QLatin1String status_udp
Definition: bootstrapnodeupdater.cpp:39
BootstrapNodeUpdater::proxy
QNetworkProxy proxy
Definition: bootstrapnodeupdater.h:52
DhtServer::tcpPorts
std::vector< uint16_t > tcpPorts
Definition: dhtserver.h:36
BootstrapNodeUpdater::availableBootstrapNodes
void availableBootstrapNodes(QList< DhtServer > nodes)
DhtServer::ipv6
QString ipv6
Definition: dhtserver.h:32
QList
Definition: friendlist.h:25
toxpk.h
NodeFields::maintainer
const QLatin1String maintainer
Definition: bootstrapnodeupdater.cpp:45
NodeFields::udp_port
const QLatin1String udp_port
Definition: bootstrapnodeupdater.cpp:44
BootstrapNodeUpdater::getBootstrapnodes
QList< DhtServer > getBootstrapnodes() const override
Definition: bootstrapnodeupdater.cpp:216
bootstrapnodeupdater.h
DhtServer::statusUdp
bool statusUdp
Definition: dhtserver.h:29
BootstrapNodeUpdater::paths
Paths & paths
Definition: bootstrapnodeupdater.h:54
paths.h
ToxPk
This class represents a Tox Public Key, which is a part of Tox ID.
Definition: toxpk.h:26
NodeFields::status_tcp
const QLatin1String status_tcp
Definition: bootstrapnodeupdater.cpp:40
DhtServer::maintainer
QString maintainer
Definition: dhtserver.h:33
BootstrapNodeUpdater::loadDefaultBootstrapNodes
static QList< DhtServer > loadDefaultBootstrapNodes()
Loads the list of built in boostrap nodes.
Definition: bootstrapnodeupdater.cpp:250
DhtServer::udpPort
quint16 udpPort
Definition: dhtserver.h:35
BootstrapNodeUpdater::requestBootstrapNodes
void requestBootstrapNodes()
Definition: bootstrapnodeupdater.cpp:235
DhtServer::ipv4
QString ipv4
Definition: dhtserver.h:31
BootstrapNodeUpdater::nam
QNetworkAccessManager nam
Definition: bootstrapnodeupdater.h:53
NodeFields
Definition: bootstrapnodeupdater.cpp:38
Paths::getUserNodesFilePath
QString getUserNodesFilePath() const
Definition: paths.cpp:368
DhtServer
Definition: dhtserver.h:27
DhtServer::statusTcp
bool statusTcp
Definition: dhtserver.h:30
NodeFields::tcp_ports
const QLatin1String tcp_ports
Definition: bootstrapnodeupdater.cpp:46
BootstrapNodeUpdater::BootstrapNodeUpdater
BootstrapNodeUpdater(const QNetworkProxy &proxy, Paths &_paths, QObject *parent=nullptr)
Fetches a list of currently online bootstrap nodes from node.tox.chat.
Definition: bootstrapnodeupdater.cpp:210
NodeFields::ipv6
const QLatin1String ipv6
Definition: bootstrapnodeupdater.cpp:42
DhtServer::publicKey
ToxPk publicKey
Definition: dhtserver.h:34