1 /*
2 * Copyright 2017 CodiLime
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18 #include "client/networkclient.h"
19
20 #include <cstring>
21 #include <functional>
22 #include <memory>
23 #include <string>
24
25 #include <QHostAddress>
26 #include <QSslConfiguration>
27 #include <QSslSocket>
28
29 #include "client/node.h"
30 #include "client/nodetree.h"
31 #include "proto/exceptions.h"
32
33 namespace veles {
34 namespace client {
35
36 /*****************************************************************************/
37 /* NetworkClient */
38 /*****************************************************************************/
39
connStatusStr(ConnectionStatus status)40 QString NetworkClient::connStatusStr(ConnectionStatus status) {
41 switch (status) {
42 case ConnectionStatus::Connected:
43 return QString("Connected");
44 break;
45 case ConnectionStatus::Connecting:
46 return QString("Connecting");
47 break;
48 case ConnectionStatus::NotConnected:
49 return QString("Not Connected");
50 break;
51 default:
52 return QString("Unknown status");
53 break;
54 }
55 }
56
NetworkClient(QObject * parent)57 NetworkClient::NetworkClient(QObject* parent)
58 : QObject(parent),
59 node_tree_(nullptr),
60 status_(ConnectionStatus::NotConnected),
61 server_name_("127.0.0.1"),
62 server_port_(3135),
63 protocol_version_(1),
64 client_name_(""),
65 client_version_("[unspecified version]"),
66 client_description_(""),
67 client_type_(""),
68 authentication_key_(""),
69 fingerprint_(""),
70 quit_on_close_(false),
71 ssl_enabled_(true),
72 qid_(0) {
73 NetworkClient::NetworkClient::registerMessageHandlers();
74 }
75
~NetworkClient()76 NetworkClient::~NetworkClient() {}
77
connectionStatus()78 NetworkClient::ConnectionStatus NetworkClient::connectionStatus() {
79 return status_;
80 }
81
connect(const QString & server_url,const QString & client_name,const QString & client_version,const QString & client_description,const QString & client_type,bool quit_on_close)82 void NetworkClient::connect(const QString& server_url,
83 const QString& client_name,
84 const QString& client_version,
85 const QString& client_description,
86 const QString& client_type, bool quit_on_close) {
87 QString scheme = server_url.section("://", 0, 0).toLower();
88 if (scheme == SCHEME_SSL) {
89 if (QSslSocket::supportsSsl()) {
90 ssl_enabled_ = true;
91 } else {
92 if (output() != nullptr) {
93 *output() << "NetworkClient:: SSL error - check if "
94 "OpenSSL is available"
95 << endl;
96 }
97 return;
98 }
99 } else if (scheme == SCHEME_TCP) {
100 ssl_enabled_ = false;
101 } else if (scheme == SCHEME_UNIX) {
102 if (output() != nullptr) {
103 *output() << "NetworkClient:: Unix sockets are "
104 "currently unsupported"
105 << endl;
106 }
107 return;
108 } else {
109 if (output() != nullptr) {
110 *output() << "NetworkClient:: ERROR: unknown scheme provided!" << endl;
111 }
112 return;
113 }
114 QString url = server_url.section("://", 1);
115 QString auth = url.section("@", 0, 0);
116 QString loc = url.section("@", 1);
117
118 server_name_ = loc.section(":", 0, -2);
119 server_port_ = loc.section(":", -1, -1).toInt();
120 client_name_ = client_name;
121 client_version_ = client_version;
122 client_description_ = client_description;
123 client_type_ = client_type;
124 quit_on_close_ = quit_on_close;
125
126 authentication_key_ = QByteArray::fromHex(auth.section(":", 0, 0).toUtf8());
127 fingerprint_ = auth.section(":", 1).replace(":", "").toLower();
128 const int target_size = 64;
129 int key_size = authentication_key_.size();
130 authentication_key_.resize(target_size);
131 for (int i = key_size; i < target_size; ++i) {
132 authentication_key_[i] = 0;
133 }
134
135 if (status_ != ConnectionStatus::Connected &&
136 status_ != ConnectionStatus::Connecting) {
137 client_socket_ = new QSslSocket(this);
138 if (ssl_enabled_) {
139 QObject::connect(client_socket_, &QSslSocket::encrypted, this,
140 &NetworkClient::socketConnected, Qt::QueuedConnection);
141 QObject::connect(
142 client_socket_,
143 static_cast<void (QSslSocket::*)(const QList<QSslError>&)>(
144 &QSslSocket::sslErrors),
145 this, &NetworkClient::checkFingerprint);
146 } else {
147 QObject::connect(client_socket_, &QAbstractSocket::connected, this,
148 &NetworkClient::socketConnected, Qt::QueuedConnection);
149 }
150 QObject::connect(client_socket_, &QAbstractSocket::disconnected, this,
151 &NetworkClient::socketDisconnected, Qt::QueuedConnection);
152 QObject::connect(client_socket_, &QIODevice::readyRead, this,
153 &NetworkClient::newDataAvailable);
154 QObject::connect(
155 client_socket_,
156 static_cast<void (QAbstractSocket::*)(QAbstractSocket::SocketError)>(
157 &QAbstractSocket::error),
158 this, &NetworkClient::socketError);
159
160 if (output() != nullptr) {
161 *output() << "Connecting to " << server_name_ << ":" << server_port_
162 << "..." << endl;
163 }
164
165 if (ssl_enabled_) {
166 client_socket_->connectToHostEncrypted(server_name_, server_port_);
167 } else {
168 client_socket_->connectToHost(server_name_, server_port_);
169 }
170 setConnectionStatus(ConnectionStatus::Connecting);
171 }
172 }
173
disconnect()174 void NetworkClient::disconnect() {
175 if (output() != nullptr) {
176 *output() << "NetworkClient: Disconnect." << endl;
177 }
178
179 setConnectionStatus(ConnectionStatus::NotConnected);
180
181 if (client_socket_ != nullptr) {
182 client_socket_->disconnectFromHost();
183 }
184 }
185
nodeTree()186 std::unique_ptr<NodeTree> const& NetworkClient::nodeTree() {
187 return node_tree_;
188 }
189
nextQid()190 uint64_t NetworkClient::nextQid() { return ++qid_; }
191
protocolVersion()192 unsigned int NetworkClient::protocolVersion() { return protocol_version_; }
193
clientName()194 QString NetworkClient::clientName() { return client_name_; }
195
clientVersion()196 QString NetworkClient::clientVersion() { return client_version_; }
197
clientDescription()198 QString NetworkClient::clientDescription() { return client_description_; }
199
clientType()200 QString NetworkClient::clientType() { return client_type_; }
201
authenticationKey()202 QString NetworkClient::authenticationKey() { return authentication_key_; }
203
output()204 QTextStream* NetworkClient::output() { return output_stream_; }
205
setOutput(QTextStream * stream)206 void NetworkClient::setOutput(QTextStream* stream) { output_stream_ = stream; }
207
sendMsgConnect()208 void NetworkClient::sendMsgConnect() {
209 std::shared_ptr<std::string> client_name_ptr(
210 new std::string(client_name_.toStdString()));
211 std::shared_ptr<std::string> client_version_ptr(
212 new std::string(client_version_.toStdString()));
213 std::shared_ptr<std::string> client_description_ptr(
214 new std::string(client_description_.toStdString()));
215 std::shared_ptr<std::string> client_type_ptr(
216 new std::string(client_type_.toStdString()));
217
218 msg_ptr msg(new proto::MsgConnect(
219 1, pair_str(true, client_name_ptr), pair_str(true, client_version_ptr),
220 pair_str(true, client_description_ptr), pair_str(true, client_type_ptr),
221 quit_on_close_));
222
223 sendMessage(msg);
224 }
225
registerMessageHandlers()226 void NetworkClient::registerMessageHandlers() {
227 message_handlers_["subscription_cancelled"] =
228 &NetworkClient::handleNodeTreeRelatedMessage;
229 message_handlers_["get_reply"] = &NetworkClient::handleNodeTreeRelatedMessage;
230 message_handlers_["connected"] = &NetworkClient::handleConnectedMessage;
231 message_handlers_["proto_error"] = &NetworkClient::handleProtoErrorMessage;
232 message_handlers_["connections_reply"] =
233 &NetworkClient::handleConnectionsMessage;
234 message_handlers_["registry_reply"] =
235 &NetworkClient::handleRegistryReplyMessage;
236 message_handlers_["method_result"] = &NetworkClient::handleMthdResMessage;
237 message_handlers_["method_error"] = &NetworkClient::handleMthdResMessage;
238 message_handlers_["broadcast_result"] = &NetworkClient::handleMthdResMessage;
239 message_handlers_["plugin_trigger_run"] =
240 &NetworkClient::handlePluginTriggerRunMessage;
241 message_handlers_["request_error"] =
242 &NetworkClient::handleNodeTreeRelatedMessage;
243 message_handlers_["get_list_reply"] =
244 &NetworkClient::handleNodeTreeRelatedMessage;
245 message_handlers_["get_data_reply"] =
246 &NetworkClient::handleNodeTreeRelatedMessage;
247 message_handlers_["get_query_reply"] =
248 &NetworkClient::handleNodeTreeRelatedMessage;
249 message_handlers_["query_error"] =
250 &NetworkClient::handleNodeTreeRelatedMessage;
251 message_handlers_["request_ack"] =
252 &NetworkClient::handleNodeTreeRelatedMessage;
253 message_handlers_["get_bindata_reply"] =
254 &NetworkClient::handleNodeTreeRelatedMessage;
255 message_handlers_["connection_error"] =
256 &NetworkClient::handleConnErrorMessage;
257 message_handlers_["plugin_method_run"] =
258 &NetworkClient::handlePluginMethodRunMessage;
259 message_handlers_["plugin_query_get"] =
260 &NetworkClient::handlePluginQueryGetMessage;
261 message_handlers_["plugin_broadcast_run"] =
262 &NetworkClient::handleBroadcastRunMessage;
263 message_handlers_["plugin_handler_unregistered"] =
264 &NetworkClient::handlePluginHandlerUnregisteredMessage;
265 }
266
handleNodeTreeRelatedMessage(const msg_ptr & msg)267 void NetworkClient::handleNodeTreeRelatedMessage(const msg_ptr& msg) {
268 nodeTree()->addRemoteNodeTreeRelatedMessage(msg);
269 }
270
handleConnectedMessage(const msg_ptr &)271 void NetworkClient::handleConnectedMessage(const msg_ptr& /*msg*/) {
272 if (connectionStatus() != ConnectionStatus::Connecting) {
273 if (output() != nullptr) {
274 *output() << "NetworkClient: Very confusing... "
275 "Received \"connected\" message while already connected."
276 << endl;
277 }
278 } else {
279 if (output() != nullptr) {
280 *output() << "NetworkClient: Received \"connected\" message." << endl;
281 }
282
283 setConnectionStatus(ConnectionStatus::Connected);
284 }
285 }
286
handleProtoErrorMessage(const msg_ptr & msg)287 void NetworkClient::handleProtoErrorMessage(const msg_ptr& msg) {
288 auto* mpe = dynamic_cast<proto::MsgProtoError*>(msg.get());
289 if (mpe != nullptr) {
290 if (output() != nullptr) {
291 *output() << "Received protocol error message. Aborting connection..."
292 << endl
293 << " code: " << mpe->err->code.c_str()
294 << " msg: " << mpe->err->msg.c_str() << endl;
295 }
296
297 disconnect();
298 }
299 }
300
handleConnectionsMessage(const msg_ptr & msg)301 void NetworkClient::handleConnectionsMessage(const msg_ptr& msg) {
302 if (output() != nullptr) {
303 *output() << "NetworkClient: Received \""
304 << QString::fromStdString(msg->object_type) << "\" message."
305 << endl;
306 }
307
308 // TODO(altran01): Is this something that client should implement in a
309 // subclass?
310 }
311
handleRegistryReplyMessage(const msg_ptr & msg)312 void NetworkClient::handleRegistryReplyMessage(const msg_ptr& msg) {
313 if (output() != nullptr) {
314 *output() << "NetworkClient: Received \""
315 << QString::fromStdString(msg->object_type) << "\" message."
316 << endl;
317 }
318
319 // TODO(altran01): Is this something that client should implement in a
320 // subclass?
321 }
322
handleMthdResMessage(const msg_ptr & msg)323 void NetworkClient::handleMthdResMessage(const msg_ptr& msg) {
324 if (output() != nullptr) {
325 *output() << "NetworkClient: Received \""
326 << QString::fromStdString(msg->object_type) << "\" message."
327 << endl;
328 }
329 // TODO(altran01): Is this something that client should implement in a
330 // subclass?
331 }
332
handlePluginTriggerRunMessage(const msg_ptr & msg)333 void NetworkClient::handlePluginTriggerRunMessage(const msg_ptr& msg) {
334 if (output() != nullptr) {
335 *output() << "NetworkClient: Received \""
336 << QString::fromStdString(msg->object_type) << "\" message."
337 << endl;
338 }
339
340 // TODO(altran01): Is this something that client should implement in a
341 // subclass?
342 }
343
handleConnErrorMessage(const msg_ptr & msg)344 void NetworkClient::handleConnErrorMessage(const msg_ptr& msg) {
345 auto* cem = dynamic_cast<proto::MsgConnectionError*>(msg.get());
346 if (cem != nullptr) {
347 if (output() != nullptr) {
348 *output() << "Received connection error message. Aborting connection..."
349 << endl
350 << " code: " << cem->err->code.c_str()
351 << " msg: " << cem->err->msg.c_str() << endl;
352 }
353
354 disconnect();
355 }
356 }
357
handlePluginMethodRunMessage(const msg_ptr & msg)358 void NetworkClient::handlePluginMethodRunMessage(const msg_ptr& msg) {
359 if (output() != nullptr) {
360 *output() << "NetworkClient: Received \""
361 << QString::fromStdString(msg->object_type) << "\" message."
362 << endl;
363 }
364
365 // TODO(altran01): Is this something that client should implement in a
366 // subclass?
367 }
368
handlePluginQueryGetMessage(const msg_ptr & msg)369 void NetworkClient::handlePluginQueryGetMessage(const msg_ptr& msg) {
370 if (output() != nullptr) {
371 *output() << "NetworkClient: Received \""
372 << QString::fromStdString(msg->object_type) << "\" message."
373 << endl;
374 }
375
376 // TODO(altran01): Is this something that client should implement in a
377 // subclass?
378 }
379
handleBroadcastRunMessage(const msg_ptr & msg)380 void NetworkClient::handleBroadcastRunMessage(const msg_ptr& msg) {
381 if (output() != nullptr) {
382 *output() << "NetworkClient: Received \""
383 << QString::fromStdString(msg->object_type) << "\" message."
384 << endl;
385 }
386
387 // TODO(altran01): Is this something that client should implement in a
388 // subclass?
389 }
390
handlePluginHandlerUnregisteredMessage(const msg_ptr & msg)391 void NetworkClient::handlePluginHandlerUnregisteredMessage(const msg_ptr& msg) {
392 if (output() != nullptr) {
393 *output() << "NetworkClient: Received \""
394 << QString::fromStdString(msg->object_type) << "\" message."
395 << endl;
396 }
397
398 // TODO(altran01): Is this something that client should implement in a
399 // subclass?
400 }
401
sendMessage(const msg_ptr & msg)402 void NetworkClient::sendMessage(const msg_ptr& msg) {
403 if (client_socket_ != nullptr && client_socket_->isValid()) {
404 msgpack::sbuffer buf;
405 msgpack::packer<msgpack::sbuffer> packer(buf);
406 messages::MsgpackWrapper::dumpObject(packer, msg);
407 client_socket_->write(buf.data(), buf.size());
408 }
409 }
410
setConnectionStatus(ConnectionStatus connection_status)411 void NetworkClient::setConnectionStatus(ConnectionStatus connection_status) {
412 if (status_ != connection_status) {
413 status_ = connection_status;
414 if (output() != nullptr) {
415 *output() << "NetworkClient: New connection status: "
416 << connStatusStr(connection_status) << "." << endl;
417 }
418 emit connectionStatusChanged(status_);
419 }
420 }
421
socketConnected()422 void NetworkClient::socketConnected() {
423 if (ssl_enabled_) {
424 QSslCertificate cert = client_socket_->peerCertificate();
425 if (cert.isNull()) {
426 if (output() != nullptr) {
427 *output() << "NetworkClient: received null certificate!" << endl;
428 }
429 disconnect();
430 return;
431 }
432 QByteArray remote_fingerprint =
433 cert.digest(QCryptographicHash::Algorithm::Sha256).toHex();
434 if (fingerprint_ != remote_fingerprint) {
435 if (output() != nullptr) {
436 *output()
437 << "NetworkClient: Certificate fingerprint mismatch! Expected: "
438 << fingerprint_ << ", got: " << remote_fingerprint << endl;
439 }
440 disconnect();
441 return;
442 }
443 }
444
445 if (output() != nullptr) {
446 *output() << "NetworkClient: TCP socket connected - sending an "
447 "authentication key and \"connect\" message."
448 << endl;
449 }
450
451 node_tree_ = std::make_unique<NodeTree>(this);
452 client_socket_->write(authentication_key_);
453 sendMsgConnect();
454 }
455
socketDisconnected()456 void NetworkClient::socketDisconnected() {
457 setConnectionStatus(ConnectionStatus::NotConnected);
458 if (output() != nullptr) {
459 *output() << "NetworkClient: TCP socket disconnected." << endl;
460 }
461
462 if (node_tree_) {
463 node_tree_.reset();
464 }
465
466 if (client_socket_ != nullptr) {
467 client_socket_->deleteLater();
468 client_socket_ = nullptr;
469 }
470 }
471
newDataAvailable()472 void NetworkClient::newDataAvailable() {
473 while (client_socket_ != nullptr) {
474 msg_ptr msg = nullptr;
475 try {
476 msg = msgpack_wrapper_.loadMessage(client_socket_);
477 } catch (proto::SchemaError& schema_error) {
478 if (output() != nullptr) {
479 *output() << "NetworkClient: SchemaError - "
480 << QString::fromStdString(schema_error.msg) << endl;
481 }
482 }
483
484 if (msg) {
485 auto handler_iter = message_handlers_.find(msg->object_type);
486 if (handler_iter != message_handlers_.end()) {
487 MessageHandler handler = handler_iter->second;
488 (this->*handler)(msg);
489 } else {
490 if (output() != nullptr) {
491 *output() << "NetworkClient: Received message of not handled "
492 "type: \""
493 << msg->object_type.c_str() << "\"." << endl;
494 }
495 }
496 emit messageReceived(msg);
497 } else {
498 break;
499 }
500 }
501 }
502
socketError(QAbstractSocket::SocketError)503 void NetworkClient::socketError(QAbstractSocket::SocketError /*socketError*/) {
504 setConnectionStatus(ConnectionStatus::NotConnected);
505 if (output() != nullptr && client_socket_ != nullptr) {
506 *output() << "NetworkClient: Socket error - "
507 << client_socket_->errorString() << endl;
508 }
509 }
510
checkFingerprint(const QList<QSslError> & errors)511 void NetworkClient::checkFingerprint(const QList<QSslError>& errors) {
512 for (const auto& err : errors) {
513 if (err.error() != QSslError::SelfSignedCertificate &&
514 err.error() != QSslError::HostNameMismatch &&
515 err.error() != QSslError::CertificateUntrusted) {
516 if (output() != nullptr) {
517 *output() << "NetworkClient: unexpected error: " << err.errorString()
518 << endl;
519 }
520 return;
521 }
522 }
523 client_socket_->ignoreSslErrors(errors);
524 }
525
526 } // namespace client
527 } // namespace veles
528