Heart 1.4.918.d927d9c
Heart is base back end library for your c++ Qt projects.
Loading...
Searching...
No Matches
abstractnode.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2018-2025 QuasarApp.
3 * Distributed under the lgplv3 software license, see the accompanying
4 * Everyone is permitted to copy and distribute verbatim copies
5 * of this license document, but changing it is not allowed.
6*/
7
8#include "abstractnode.h"
9#include "datasender.h"
10#include "ping.h"
11#include "workstate.h"
12#include <QHostInfo>
13
14#include <badrequest.h>
15#include <quasarapp.h>
16#include <QTcpServer>
17
18#ifdef USE_HEART_SSL
19
20#include <sslsocket.h>
21#include <easyssl/x509.h>
22#include <easyssl/rsassl.h>
23
24#include <QSslConfiguration>
25#include <QSslCertificate>
26#include <QSslKey>
27#include <QSslSocket>
28
29#endif
30
31#include <QMetaObject>
32#include <QtConcurrent>
33#include <closeconnection.h>
34#include "tcpsocket.h"
35#include "asynclauncher.h"
36#include "receivedata.h"
37#include "abstracttask.h"
38
39#include <apiversion.h>
40#include <versionisreceived.h>
41#include <taskscheduler.h>
42#include <qaglobalutils.h>
43#include <bigdatawraper.h>
44#include <bigdataparser.h>
45#include <abstractnodeparser.h>
46#include <apiversionparser.h>
47
48namespace QH {
49
50using namespace PKG;
51
53 QTcpServer(ptr) {
54
55 initThreadId();
56
57 // All sockets and the data sender are created and work in the node worker thread (main thread).
58 _dataSender = new DataSender(mainThreadID());
59 _socketWorker = new AsyncLauncher(mainThreadID());
60 _tasksheduller = new TaskScheduler();
61 _apiVersionParser = new APIVersionParser(this);
62
64
65 auto abstractNodeParser = addApiParserNative<AbstractNodeParser>();
66 connect(abstractNodeParser.data(), &AbstractNodeParser::sigPingReceived,
67 this, &AbstractNode::receivePing, Qt::DirectConnection);
68
69 connect(_apiVersionParser, &APIVersionParser::sigNoLongerSupport,
70 this, &AbstractNode::sigNoLongerSupport, Qt::DirectConnection);
71
72 qRegisterMetaType<QSharedPointer<QH::AbstractTask>>();
73#ifdef USE_HEART_SSL
74 qRegisterMetaType<QList<QSslError>>();
75#endif
76
77 connect(_tasksheduller, &TaskScheduler::sigPushWork,
78 this, &AbstractNode::handleBeginWork);
79
80
81 initThreadPool();
82
83}
84
86
87 for (auto it: std::as_const(_receiveData)) {
88 delete it;
89 }
90
91 _receiveData.clear();
92
93 delete _dataSender;
94 delete _socketWorker;
95 delete _tasksheduller;
96 delete _apiVersionParser;
97}
98
99bool AbstractNode::run(const QString &addres, unsigned short port) {
100
101 if (!port)
102 return false;
103
104 HostAddress adr(addres, port);
105 if (addres.isEmpty()) {
106 adr = HostAddress{QHostAddress::Any, port};
107 }
108
109 if (!listen(adr)) {
110
111 qCritical() << "Run fail " << this->errorString();
112 qCritical() << "Address:: " << adr.toString();
113
114 return false;
115 }
116
117 initThreadPool();
118
119 return true;
120}
121
122QSharedPointer<iParser> AbstractNode::selectParser(unsigned short cmd,
123 AbstractNodeInfo *sender) const {
124 return _apiVersionParser->selectParser(cmd, sender);
125}
126
127QSharedPointer<iParser> AbstractNode::selectParser(const QString &type,
128 AbstractNodeInfo *sender) const {
129 return _apiVersionParser->selectParser(type, sender);
130}
131
132QSharedPointer<iParser> AbstractNode::selectParser(const QString &type, int version) const {
133 return _apiVersionParser->selectParser(type, version);
134}
135
137 close();
138
139 _connectionsMutex.lock();
140 for (const auto &i : std::as_const(_connections)) {
141 i->disconnect();
142 }
143 _connectionsMutex.unlock();
144
145 // Pool workers can be blocked while they wait for a job that was posted
146 // to the main thread (for example, socket writes executed by the data
147 // sender). Only this thread can deliver those jobs, so it must keep
148 // processing events until all workers are finished. Otherwise this
149 // method would wait for the workers and the workers would wait for this
150 // thread (deadlock).
151 const auto deadline = QDeadlineTimer(WAIT_TIME);
152 while (!deadline.hasExpired()) {
153 bool hasWork = false;
154 _workersMutex.lock();
155 for (const auto &worker : _workers) {
156 if (worker && !worker->isFinished()) {
157 hasWork = true;
158 break;
159 }
160 }
161 _workersMutex.unlock();
162
163 if (!hasWork) {
164 break;
165 }
166
167 QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
168 }
169
170 _workersMutex.lock();
171 for (auto it: std::as_const(_workers)) {
172 if (!it->isFinished()) {
173 it->cancel();
174 it->waitForFinished();
175 }
176 }
177 _workersMutex.unlock();
178
179 deinitThreadPool();
180}
181
183 QMutexLocker locer(&_connectionsMutex);
184
185 if (!_connections.contains(id)) {
186 return nullptr;
187 }
188
189 return dynamic_cast<AbstractNodeInfo*>(_connections[id]);
190}
191
192// fix me, if getInfoPtr invoced in an another thread and in some time main thread remove this object then
193// this method return invalid object and application crashed.
195 QMutexLocker locer(&_connectionsMutex);
196
197 if (!_connections.contains(id)) {
198 return nullptr;
199 }
200
201 return dynamic_cast<AbstractNodeInfo*>(_connections[id]);
202}
203
204void AbstractNode::ban(const HostAddress &target) {
205 auto info = getInfoPtr(target);
206 if (info)
207 info->ban();
208
209}
210
211void AbstractNode::unBan(const HostAddress &target) {
212 QMutexLocker locer(&_connectionsMutex);
213
214 if (!_connections.contains(target) || _connections[target]) {
215 return;
216 }
217
218 _connections[target]->unBan();
219}
220
222
223 AsyncLauncher::Job action = [this, address]() -> bool {
224 QAbstractSocket *socket;
225#ifdef USE_HEART_SSL
226 if (_mode == SslMode::NoSSL) {
227 socket = new TcpSocket(nullptr);
228 } else {
229 socket = new SslSocket(nullptr);
230 }
231
232#else
233 socket = new TcpSocket(nullptr);
234#endif
235
236 if (!registerSocket(socket, &address)) {
238 delete socket;
239 return false;
240 }
241
242 socket->connectToHost(address, address.port());
243
244 return true;
245 };
246
247 return _socketWorker->run(action);
248}
249
251 const std::function<void (QH::AbstractNodeInfo *)> &action,
252 NodeCoonectionStatus status) {
253
254 auto peer = getInfoPtr(address);
255
256 if (action && (!peer || peer->status() < status)) {
257 auto &actionsList = _connectActions[status];
258 actionsList[address] = action;
259 }
260
261 return addNode(address);
262}
263
264bool AbstractNode::addNode(const QString &domain, unsigned short port,
265 const std::function<void (QH::AbstractNodeInfo *)> &action,
266 NodeCoonectionStatus status) {
267
268 HostAddress address{domain, port};
269 if (address.isNull()) {
270
271 QHostInfo::lookupHost(domain, [this, port, domain, action, status](QHostInfo info) {
272
273 if (info.error() != QHostInfo::NoError) {
274
275 qCritical() << "The domain name :" + domain +
276 " has error: " + info.errorString();
278 return;
279 }
280
281 auto addresses = info.addresses();
282
283 if (addresses.size() > 1) {
284 qWarning() << "The domain name :" + domain +
285 " has more 1 ip addresses.";
286 }
287
288 if (action) {
289 addNode(HostAddress{addresses.first(), port}, action, status);
290 } else {
291 addNode(HostAddress{addresses.first(), port});
292 }
293
294 });
295
296
297 return true;
298 }
299
300
301 return addNode(address, action);
302}
303
304bool AbstractNode::removeNode(const HostAddress &nodeAdderess) {
305
306 if (AbstractNodeInfo *ptr = getInfoPtr(nodeAdderess)) {
307 return removeNode(ptr);
308 }
309
310 return false;
311}
312
314 if (!(node)) {
315 return false;
316 }
317
318 if (!node->isConnected())
319 return true;
320
321 if (node->isLocal()) {
322 node->removeSocket();
323 return true;
324 }
325
326 QTimer::singleShot(WAIT_CONFIRM_TIME, this,
327 std::bind(&AbstractNode::handleForceRemoveNode,
328 this, node->networkAddress()));
329
330 CloseConnection close;
331 return sendData(&close, node);
332}
333
335 return HostAddress{serverAddress(), serverPort()};
336}
337
338#ifdef USE_HEART_SSL
339QSslConfiguration AbstractNode::getSslConfig() const {
340 return _ssl;
341}
342
343QSslConfiguration AbstractNode::selfSignedSslConfiguration(const EasySSL::SslSrtData &sslData) {
344 QSslConfiguration res = QSslConfiguration::defaultConfiguration();
345
346 QSslKey pkey;
347 QSslCertificate crt;
348
349 EasySSL::X509 generator(QSharedPointer<EasySSL::RSASSL>::create());
350 EasySSL::SelfSignedSertificate certificate = generator.create(sslData);
351 res.setPrivateKey(certificate.key);
352 res.setLocalCertificate(certificate.crt);
353 res.setPeerVerifyMode(QSslSocket::VerifyNone);
354
355 return res;
356}
357
358bool AbstractNode::configureSslSocket(AbstractNodeInfo *node, bool fServer) {
359
360 if (!node)
361 return false;
362
363 auto socket = dynamic_cast<SslSocket*>(node->sct());
364 if (!socket) {
365
366 QuasarAppUtils::Params::log("Invalid ssl socket!! Connection not secure",
367 QuasarAppUtils::Error);
368 return false;
369 }
370
371 socket->setSslConfiguration(_ssl);
372
373 auto address = node->networkAddress();
374 connect(socket, &QSslSocket::encrypted, this ,[this, address]() {
375 handleEncrypted(getInfoPtr(address));
376 });
377
378 connect(socket, &SslSocket::sslErrorsOcurred,
379 this, &AbstractNode::handleSslErrorOcurredPrivate, Qt::DirectConnection);
380
381
382 AsyncLauncher::Job action = [socket, fServer]() -> bool {
383 if (fServer)
384 socket->startServerEncryption();
385 else
386 socket->startClientEncryption();
387
388 return true;
389 };
390
391 return _socketWorker->run(action);
392}
393
394const QList<QSslError> &AbstractNode::ignoreSslErrors() const {
395 return _ignoreSslErrors;
396}
397
398void AbstractNode::setIgnoreSslErrors(const QList<QSslError> &newIgnoreSslErrors) {
399 _ignoreSslErrors = newIgnoreSslErrors;
400};
401
402bool AbstractNode::useSelfSignedSslConfiguration(const EasySSL::SslSrtData &crtData) {
403
404 if (isListening()) {
405 return false;
406 }
407
408 _ssl = selfSignedSslConfiguration(crtData);
409 _mode = SslMode::InitSelfSigned;
410
411 if(!_ignoreSslErrors.contains(QSslError{QSslError::SelfSignedCertificate}))
412 _ignoreSslErrors.push_back(QSslError{QSslError::SelfSignedCertificate});
413
414 if(!_ignoreSslErrors.contains(QSslError{QSslError::SelfSignedCertificateInChain}))
415 _ignoreSslErrors.push_back(QSslError{QSslError::SelfSignedCertificateInChain});
416
417 return !_ssl.isNull();
418}
419
420bool AbstractNode::useSystemSslConfiguration(QSslConfiguration config) {
421 if (isListening()) {
422 return false;
423 }
424
425 _ssl = config;
426 _mode = SslMode::InitFromSystem;
427
428 return !_ssl.isNull();
429}
430
431bool AbstractNode::disableSSL() {
432 if (isListening()) {
433 return false;
434 }
435
436 _mode = SslMode::NoSSL;
437
438 return true;
439}
440
441void AbstractNode::handleEncrypted(AbstractNodeInfo *node) {
442 handleNodeStatusChanged(node, NodeCoonectionStatus::Connected);
443}
444
445void AbstractNode::handleSslErrorOcurredPrivate(SslSocket * sslScocket, const QList<QSslError> &errors) {
446
447 QList<QSslError> ignore;
448 for (auto &error : errors) {
449
450 if (!_ignoreSslErrors.contains(QSslError{error.error()})) {
451 handleSslErrorOcurred(sslScocket, error);
452 } else {
453 ignore += error;
454 }
455 }
456
457 if (ignore.isEmpty())
458 return;
459
460 if (sslScocket) {
461 sslScocket->ignoreSslErrors(ignore);
462 }
463}
464
465
466void AbstractNode::handleSslErrorOcurred(SslSocket *scket,
467 const QSslError &error) {
468 QuasarAppUtils::Params::log(scket->peerAddress().toString() + " : " + error.errorString(),
469 QuasarAppUtils::Error);
470
471 QuasarAppUtils::Params::log("Error code: " + QString::number(error.error()),
472 QuasarAppUtils::Error);
473}
474
475#endif
476
478 return _closeConnectionAfterBadRequest;
479}
480
481void AbstractNode::setCloseConnectionAfterBadRequest(bool newCloseConnectionAfterBadRequest) {
482 _closeConnectionAfterBadRequest = newCloseConnectionAfterBadRequest;
483}
484
486 return _sendBadRequestErrors;
487}
488
490 _sendBadRequestErrors = val;
491}
492
493const QSharedPointer<iParser> &
494AbstractNode::addApiParserImpl(const QSharedPointer<iParser> &parserObject) {
495 return _apiVersionParser->addApiParser(parserObject);
496}
497
499 const HostAddress* clientAddress) const {
500 return new AbstractNodeInfo(socket, clientAddress);
501}
502
503bool AbstractNode::registerSocket(QAbstractSocket *socket, const HostAddress* clientAddress) {
504
505 if (connectionsCount() >= maxPendingConnections()) {
506 return false;
507 }
508
509 HostAddress cliAddress;
510 if (clientAddress)
511 cliAddress = *clientAddress;
512 else
513 cliAddress = HostAddress{socket->peerAddress(), socket->peerPort()};
514
515
516 _connectionsMutex.lock();
517
518 if (_connections.contains(cliAddress)) {
519 auto info =_connections.value(cliAddress);
520 info->setSct(socket);
521 info->setIsLocal(clientAddress);
522
523 _connectionsMutex.unlock();
524
525 if (!info->isValid()) {
526 return false;
527 }
528
529 nodeAddedSucessful(info);
530 return true;
531 }
532
533 auto info = createNodeInfo(socket, &cliAddress);
534
535 info->setIsLocal(clientAddress);
536
537 _connections[cliAddress] = info;
538
539 _connectionsMutex.unlock();
540
541 connect(info, &AbstractNodeInfo::sigReadyRead,
542 this, &AbstractNode::avelableBytes, Qt::DirectConnection);
543
544 // using direct connection because socket clear all data of ip and port after disconnected.
545 connect(info, &AbstractNodeInfo::statusChaned,
546 this, &AbstractNode::handleNodeStatusChanged,
547 Qt::QueuedConnection);
548
549 if (info->status() != NodeCoonectionStatus::NotConnected) {
550 handleNodeStatusChanged(info, info->status());
551 }
552
553 // using direct connection because socket clear all data of ip and port after disconnected.
556 Qt::QueuedConnection);
557
558 // check node confirmed
559 QTimer::singleShot(WAIT_TIME, this, [this, cliAddress]() {
560 checkConfirmendOfNode(getInfoPtr(cliAddress));
561 });
562
563 nodeAddedSucessful(info);
564
565 return true;
566}
567
568ParserResult AbstractNode::parsePackage(const QSharedPointer<AbstractData> &pkg,
569 const Header &pkgHeader,
570 AbstractNodeInfo *sender) {
571 return _apiVersionParser->parsePackage(pkg, pkgHeader, sender);
572}
573
574QSharedPointer<AbstractData> AbstractNode::genPackage(unsigned short cmd,
575 AbstractNodeInfo *sender) const {
576 if (_apiVersionParser)
577 return _apiVersionParser->searchPackage(cmd, sender);
578
579 return nullptr;
580}
581
582bool AbstractNode::sendPackage(const Package &pkg, QAbstractSocket *target) const {
583 if (!pkg.isValid()) {
584 return false;
585 }
586
587 if (!target || !target->isValid()) {
588
589 qCritical() << "Destination server not valid!";
590 return false;
591 }
592
593 if (!target->waitForConnected()) {
594 qCritical() << "no connected to server! " + target->errorString();
595 return false;
596 }
597
598 return _dataSender->sendData(pkg.toBytes(), target, true);
599}
600
601unsigned int AbstractNode::sendData(const AbstractData *resp,
602 const HostAddress &addere,
603 const Header *req) {
604 return sendData(resp, getInfoPtr(addere), req);
605}
606
608 const AbstractNodeInfo *node,
609 const Header *req) {
610
611 if (!node) {
612 qDebug() << "Response not sent because client == null";
613 return 0;
614 }
615
616 if (!resp) {
617 return 0;
618 }
619
620 Package pkg;
621 bool convert = false;
622 if (req && req->isValid()) {
623 convert = resp->toPackage(pkg, node->multiVersionPackages().value(resp->cmd()),req->hash);
624 } else {
625 convert = resp->toPackage(pkg, node->multiVersionPackages().value(resp->cmd()));
626 }
627
628 if (!convert) {
629
630 if (static_cast<unsigned int>(pkg.data.size()) > Package::maximumSize()) {
631 // big data
632
633 auto wrap = QSharedPointer<BigDataWraper>::create();
634 wrap->setData(resp);
635 if ( parsePackage(wrap, {}, getInfoPtr(node->networkAddress())) != ParserResult::Processed) {
636 return 0;
637 }
638
639 return BIG_DATA_HASH_ID;
640 }
641 qCritical() << "Response not sent because dont create package from object";
642 return 0;
643 }
644
645 if (!sendPackage(pkg, node->sct())) {
646 qCritical() << "Response not sent!";
647 return 0;
648 }
649
650 return pkg.hdr.hash;
651}
652
654 const ErrorData &err, qint8 diff) {
655
656 if (!changeTrust(address, diff)) {
657
658
659 qCritical() << "Bad request detected, bud response command not sent!"
660 " because trust not changed";
661
662
663 qCritical() << "SECURITY LOG: Force block the " + address.toString() +
664 " because trust defined";
665
666 ban(address);
667
668 return;
669 }
670
671 auto node = getInfoPtr(address);
672 if (!isBanned(node)) {
673 auto bad = BadRequest(err);
674 if (!sendData(&bad, address, &req)) {
675 return;
676 }
677
678 qInfo() << "Bad request sendet to adderess: " + address.toString();
679
681 removeNode(node);
682 }
683 }
684}
685
687 WorkState state;
688
691 state.setMaxConnectionCount(maxPendingConnections());
692 state.setBanedList(banedList());
693 state.setIsRun(isListening());
694
695 return state;
696
697}
698
700 if (isListening()) {
701 if (connectionsCount() >= maxPendingConnections())
702 return "overload";
703 else {
704 return "Work";
705 }
706 }
707
708 return "Not running";
709}
710
712 return QString("%0 / %1").arg(connectionsCount()).arg(maxPendingConnections());
713}
714
715QList<HostAddress> AbstractNode::banedList() const {
716 QList<HostAddress> list = {};
717
718 QMutexLocker locer(&_connectionsMutex);
719
720 for (auto i = _connections.begin(); i != _connections.end(); ++i) {
721 if (i.value()->isBanned()) {
722 list.push_back(i.key());
723 }
724 }
725
726 return list;
727}
728
730 int count = 0;
731
732 QMutexLocker locer(&_connectionsMutex);
733
734 for (auto i : _connections) {
735 if (i->isConnected()) {
736 count++;
737 }
738 }
739 return count;
740}
741
743 int count = 0;
744
745 QMutexLocker locer(&_connectionsMutex);
746
747 for (auto i : _connections) {
748 if (i->status() == NodeCoonectionStatus::Confirmed) {
749 count++;
750 }
751 }
752 return count;
753}
754
756 Ping cmd;
757 return sendData(&cmd, address);
758
759}
760
762 if (!(info && info->isValid())) {
763 return false;
764 }
765
766 return info->isBanned();
767}
768
770 AsyncLauncher::Job action = [this, handle]() -> bool {
771 QAbstractSocket* socket = nullptr;
772
773#ifdef USE_HEART_SSL
774 if (_mode == SslMode::NoSSL) {
775 socket = new TcpSocket(nullptr);
776 } else {
777 socket = new SslSocket(nullptr);
778 }
779
780#else
781 socket = new TcpSocket(nullptr);
782#endif
783
784 socket->setSocketDescriptor(handle);
785
786 if (isBanned(getInfoPtr(HostAddress{socket->peerAddress(), socket->peerPort()}))) {
787 qCritical() << "Income connection from banned address";
788
789 delete socket;
790 return false;
791 }
792
793 if (!registerSocket(socket)) {
794
795 qCritical() << "Failed to register new socket";
796
797 delete socket;
798 return false;
799 }
800
801 return true;
802
803 };
804
805 _socketWorker->run(action);
806}
807
808bool AbstractNode::changeTrust(const HostAddress &id, int diff) {
809 auto ptr = getInfoPtr(id);
810 if (!ptr) {
811 return false;
812 }
813
814 auto objTrust = ptr->trust();
815
816 if (objTrust >= static_cast<int>(TrustNode::Undefined)) {
817 return false;
818 }
819
820 if (objTrust <= static_cast<int>(TrustNode::Baned)) {
821 return false;
822 }
823
824 ptr->setTrust(objTrust + diff);
825 return true;
826}
827
828void AbstractNode::avelableBytes(AbstractNodeInfo *sender) {
829
830 if (!sender) {
831 return;
832 }
833
834 auto id = sender->networkAddress();
835
836 if (!_connections.contains(id)) {
837 return;
838 }
839
840 if (!_receiveData.contains(id)) {
841 _receiveData.insert(id, new ReceiveData());
842 }
843
844 auto &pkg = _receiveData[id]->_pkg;
845 auto &hdrArray = _receiveData[id]->_hdrArray;
846
847 int workIndex = 0;
848 const int headerSize = sizeof(Header);
849
850 auto socket = sender->sct();
851 if (!socket) {
852 pkg.reset();
853 hdrArray.clear();
854 return;
855 }
856
857 // concat with old data of header.
858 auto array = hdrArray;
859 while (socket->bytesAvailable() > 0) {
860 array += socket->readAll();
861 };
862
863 const int arraySize = array.size();
864 hdrArray.clear();
865
866 while (arraySize > workIndex) {
867
868 int offset = arraySize - workIndex;
869
870 if (pkg.hdr.isValid()) {
871 // CASE 1: The Package data is still not collected, but the header is already collected. performs full or partial filling of packet data.
872
873 int dataLength = std::min(static_cast<int>(pkg.hdr.size - pkg.data.size()),
874 arraySize - workIndex);
875 pkg.data.append(array.mid(workIndex, dataLength));
876
877 workIndex += dataLength;
878
879
880 } else if (offset >= headerSize) {
881
882 // CASE 2: The header and package still do not exist and the amount of data allows you to create a new header. A header is created and will fill in all or part of the package data.
883
884 pkg.reset();
885
886 memcpy(&pkg.hdr,
887 array.data() + workIndex, headerSize);
888
889 if (!pkg.hdr.isValid())
890 return;
891
892 int dataLength = std::min(static_cast<int>(pkg.hdr.size),
893 arraySize - headerSize - workIndex);
894
895 pkg.data.append(array.mid(workIndex + headerSize, dataLength));
896
897 workIndex += headerSize + dataLength;
898
899 } else {
900 // CASE 3: There is not enough data to initialize the header. The data will be placed in temporary storage and will be processed the next time the data is received.
901
902 unsigned char dataLength = static_cast<unsigned char>(arraySize - workIndex);
903 hdrArray += array.mid(workIndex, dataLength);
904 workIndex += dataLength;
905 }
906
907 if (pkg.isValid()) {
908 newWork(pkg, sender, id);
909 pkg.reset();
910 hdrArray.clear();
911 } else if (static_cast<unsigned int>(pkg.data.size()) >= pkg.hdr.size) {
912 qWarning() << "Invalid Package received." + pkg.toString();
913 pkg.reset();
914 hdrArray.clear();
916
917 }
918 }
919}
920
921void AbstractNode::handleWorkerStoped() {
922 auto senderObject = dynamic_cast<QFutureWatcher <bool>*>(sender());
923
924 if (senderObject) {
925
926 _workersMutex.lock();
927 _workers.remove(senderObject);
928 _workersMutex.unlock();
929
930 delete senderObject;
931 }
932}
933
934void AbstractNode::handleForceRemoveNode(HostAddress node) {
935 AbstractNodeInfo* info = getInfoPtr(node);
936 if (info) {
937 info->removeSocket();
938 }
939}
940
941void AbstractNode::handleBeginWork(QSharedPointer<QH::AbstractTask> work) {
942
943 auto executeObject = [this, work]() -> bool {
944 if (!work)
945 return false;
946
947 return work->execute(this);
948 };
949
950 QMutexLocker locer(&_threadPoolMutex);
951
952 if (_threadPool) {
953 auto worker = new QFutureWatcher <bool>();
954 worker->setFuture(QtConcurrent::run(_threadPool, executeObject));
955
956 _workersMutex.lock();
957 _workers.insert(worker);
958 _workersMutex.unlock();
959
960
961 connect(worker, &QFutureWatcher<bool>::finished,
962 this, &AbstractNode::handleWorkerStoped);
963 }
964}
965
966bool AbstractNode::listen(const HostAddress &address) {
967 return QTcpServer::listen(address, address.port());
968}
969
970QSharedPointer<AbstractData>
972 AbstractNodeInfo *sender) const {
973
974 auto value = _apiVersionParser->searchPackage(pkg.hdr.command, sender);
975 if (!value) {
976 qDebug() << "Package type not registered!"
977 " Please use the registerPackageType method before parsing."
978 " Example invoke registerPackageType<MyData>() into constructor of you client and server nodes.";
979
980 return nullptr;
981 }
982
983 value->fromPakcage(pkg);
984 return value;
985}
986
987QList<HostAddress> AbstractNode::connectionsList() const {
988 QMutexLocker locer(&_connectionsMutex);
989
990 return _connections.keys();
991}
992
993QList<HostAddress> AbstractNode::activeConnectionsList() const {
994
995 QList<HostAddress> result;
996
997 QMutexLocker locer(&_connectionsMutex);
998 for (auto i : _connections) {
999 if (i->isConnected()) {
1000 result.push_back(i->networkAddress());
1001 }
1002 }
1003
1004 return result;
1005}
1006
1008
1009 switch (error) {
1011 qCritical() << "The remote host not found or dns server not responce.";
1012 break;
1013
1014 }
1015
1017 qCritical() << "The remote node is banned or serve is overload.";
1018 break;
1019
1020 }
1021 default: {
1022 qCritical() << "The unknown error ocurred.";
1023 }
1024 }
1025
1026}
1027
1031
1032bool AbstractNode::addApiParser(const QSharedPointer<iParser> &parser) {
1033
1034 if (!parser || parser->node() != this)
1035 return false;
1036
1037 return !addApiParserImpl(parser).isNull();
1038}
1039
1040void AbstractNode::receivePing(const QSharedPointer<PKG::Ping> &) {};
1041
1042bool AbstractNode::sheduleTask(const QSharedPointer<AbstractTask> &task) {
1043 return _tasksheduller->shedule(task);
1044}
1045
1047 _tasksheduller->remove(taskId);
1048}
1049
1051 return _tasksheduller->taskCount();
1052}
1053
1054void AbstractNode::newWork(const Package &pkg, AbstractNodeInfo *sender,
1055 const HostAddress& id) {
1056
1057 if (!sender)
1058 return;
1059
1060 auto executeObject = [pkg, sender, id, this]() {
1061
1062 auto data = prepareData(pkg, sender);
1063 if (!data)
1064 return false;
1065
1066 ParserResult parseResult = parsePackage(data, pkg.hdr, sender);
1067
1068#ifdef HEART_PRINT_PACKAGES
1069 QuasarAppUtils::Params::log(QString("Package received! %0").arg(data->toString()), QuasarAppUtils::Info);
1070#endif
1071
1072 if (parseResult != ParserResult::Processed) {
1073
1074 auto message = QString("Package not parsed! %0 \nresult: %1. \n%2").
1075 arg(pkg.toString(), iParser::pareseResultToString(parseResult), data->toString());
1076
1077 qInfo() << message;
1078
1079 if (parseResult == ParserResult::Error) {
1080
1081 if (fSendBadRequestErrors()) {
1082 badRequest(id, pkg.hdr, ErrorData{1, "Your send the invalid request."}, REQUEST_ERROR);
1083 } else {
1085 }
1086 }
1087
1088 if (parseResult == ParserResult::NotProcessed) {
1089 if (fSendBadRequestErrors()) {
1090 badRequest(id, pkg.hdr, ErrorData{2, "Your send the not supported command."}, NOTSUPPORT_ERROR);
1091 } else {
1093 }
1094
1095 }
1096
1097#ifdef QT_DEBUG
1098 qInfo() << _apiVersionParser->toString();
1099#endif
1100
1101 return false;
1102 }
1103
1104 _confirmNodeMutex.lock();
1105 sender->updateConfirmStatus();
1106 _confirmNodeMutex.unlock();
1107
1108
1109 return true;
1110 };
1111
1112
1113 QMutexLocker locer(&_threadPoolMutex);
1114
1115 if (_threadPool) {
1116 auto worker = new QFutureWatcher <bool>();
1117 worker->setFuture(QtConcurrent::run(_threadPool, executeObject));
1118
1119 _workersMutex.lock();
1120 _workers.insert(worker);
1121 _workersMutex.unlock();
1122
1123 connect(worker, &QFutureWatcher<bool>::finished,
1124 this, &AbstractNode::handleWorkerStoped);
1125 }
1126}
1127
1129 return _mode;
1130}
1131
1132QHash<HostAddress, AbstractNodeInfo *> AbstractNode::connections() const {
1133 return _connections;
1134}
1135
1139
1140void AbstractNode::handleNodeStatusChanged(AbstractNodeInfo *node, NodeCoonectionStatus status) {
1141
1142 if (status == NodeCoonectionStatus::NotConnected) {
1143 nodeDisconnected(node);
1144 } else if (status == NodeCoonectionStatus::Connected) {
1145
1146#ifdef USE_HEART_SSL
1147 if (_mode != SslMode::NoSSL) {
1148
1149 auto socket = dynamic_cast<SslSocket*>(node->sct());
1150
1151 if (!socket) {
1152 QuasarAppUtils::Params::log("Failed to preparet to configure ssl socket.",
1153 QuasarAppUtils::Error);
1154
1155 removeNode(node);
1156 return;
1157 }
1158
1159 if (!socket->isEncrypted()) {
1160 if (!configureSslSocket(node, !node->isLocal())) {
1161 QuasarAppUtils::Params::log("Failed to configure ssl socket.",
1162 QuasarAppUtils::Error);
1163 }
1164
1165 return;
1166 }
1167 }
1168#endif
1169
1170 nodeConnected(node);
1171 } else if (status == NodeCoonectionStatus::Confirmed) {
1172 nodeConfirmend(node);
1173 }
1174}
1175
1177
1178 auto &actions = _connectActions[NodeCoonectionStatus::Confirmed];
1179 auto action = actions.take(node->networkAddress());
1180 if (action)
1181 action(node);
1182}
1183
1185 if (!_apiVersionParser->sendSupportedAPI(node)) {
1186 qCritical() << "Failed to sent version information to dist node";
1187 }
1188
1189 auto &actions = _connectActions[NodeCoonectionStatus::Connected];
1190 auto action = actions.take(node->networkAddress());
1191 if (action)
1192 action(node);
1193}
1194
1196 Q_UNUSED(node)
1197}
1198
1200 QAbstractSocket::SocketError errorCode,
1201 QString errorString) {
1202 Q_UNUSED(errorCode)
1203
1204 QString message("Network error occured on the %0 node. Message: %1");
1205 qCritical() << message.arg(nodeInfo->networkAddress().toString(), errorString);
1206
1207 auto &actions = _connectActions[NodeCoonectionStatus::Connected];
1208 actions.remove(nodeInfo->networkAddress());
1209}
1210
1211void AbstractNode::checkConfirmendOfNode(AbstractNodeInfo *info) {
1212
1213 if(!info)
1214 return;
1215
1216 if (info->status() != NodeCoonectionStatus::Confirmed) {
1217 removeNode(info);
1218 }
1219}
1220
1221void AbstractNode::initThreadId() const {
1222 mainThreadID();
1223}
1224
1225void AbstractNode::initThreadPool() {
1226 deinitThreadPool();
1227
1228 QMutexLocker lock(&_threadPoolMutex);
1229 _threadPool = new QThreadPool();
1230 _threadPool->setObjectName("PackageWorker");
1231 _threadPool->setMaxThreadCount(QThread::idealThreadCount());
1232
1233}
1234
1235void AbstractNode::deinitThreadPool() {
1236 QMutexLocker lock(&_threadPoolMutex);
1237
1238 if (!_threadPool) {
1239 return;
1240 }
1241
1242 _threadPool->waitForDone(WAIT_TIME);
1243 delete _threadPool;
1244 _threadPool = nullptr;
1245}
1246
1248 static auto thread = QThread::currentThread();
1249
1250 return thread;
1251}
1252
1253} // namespace QH
#define REQUEST_ERROR
#define BIG_DATA_HASH_ID
#define NOTSUPPORT_ERROR
#define CRITICAL_ERROOR
QSharedPointer< PKG::AbstractData > searchPackage(unsigned short cmd, AbstractNodeInfo *sender) const
searchPackage This method search package recursive in all registered pararsers. Searching will be in ...
const QSharedPointer< QH::iParser > & addApiParser(const QSharedPointer< QH::iParser > &parserObject)
addApiParser This method add new Api parser for this node.
ParserResult parsePackage(const QSharedPointer< PKG::AbstractData > &pkg, const Header &pkgHeader, AbstractNodeInfo *sender) override
parsePackage This is main method of all childs classes of an AbstractNode class. This method work on ...
APIVersionParser(AbstractNode *node)
void sigNoLongerSupport(const QString &ApiKey, unsigned short version)
sigNoLongerSupport This signal will be emit when node receive incomplite versions.
The AbstractNodeInfo class contains information about client or server connection and tcp socket of n...
const PackagesVersionData & multiVersionPackages() const
multiVersionPackages This is list of packages of one api package tah support multiple versions.
bool isLocal() const
isLocal return true if connection opened on this node.
AbstractNodeInfo(QAbstractSocket *sct=nullptr, const HostAddress *address=nullptr)
AbstractNodeInfo.
NodeCoonectionStatus status() const
status This method return status of the node connection.
void sigReadyRead(QH::AbstractNodeInfo *thisNode)
sigReadyRead This is wrapper signal for the QAbstractSocket::readyRead signal.
HostAddress networkAddress() const
networkAddress This method return network address of current node or client.
void statusChaned(QH::AbstractNodeInfo *thisNode, QH::NodeCoonectionStatus status)
statusChaned This signal emitted when nodes status is changed.
virtual void removeSocket()
removeSocket This method use for remove socket. You can override this method for handle this event.
virtual bool isValid() const
isValid - Check valid of node. This method check connect status of socket.
QAbstractSocket * sct() const
sct This method return socket of connection.
virtual bool isBanned() const
isBanned - check node which banned.
void sigErrorOccurred(QH::AbstractNodeInfo *thisNode, QAbstractSocket::SocketError socketError, QString message)
sigErrorOccurred This is wrapper signal for the QAbstractSocket::errorOccurred signal.
virtual bool isConnected() const
isConnected - Check of node connect status.
void sigPingReceived(const QSharedPointer< QH::PKG::Ping > &ping)
sigPingReceived This method emited
int sheduledTaskCount() const
sheduledTaskCount This method return count of sheduled tasks.
QSharedPointer< ApiType > addApiParserNative(Args &&... arg)
addApiParserNative This is template metod that add sipport of new apiparser ApiType
QHash< HostAddress, AbstractNodeInfo * > connections() const
connections - Return hash map of all connections of this node.
QSharedPointer< PKG::AbstractData > prepareData(const Package &pkg, AbstractNodeInfo *sender) const
prepareData This is private method for preparing package from the byteArray.
QList< HostAddress > connectionsList() const
connectionsList This method return list of all node connections
bool fSendBadRequestErrors() const
fSendBadRequestErrors This property enable or disable sending feedback to connected node when them se...
virtual void nodeErrorOccured(QH::AbstractNodeInfo *nodeInfo, QAbstractSocket::SocketError errorCode, QString errorString)
nodeErrorOccured This slot invoked when error ocured in the nodeInfo.
virtual void addNodeFailed(AddNodeError error)
addNodeFailed This method will be invoked when trying to add new node are failed. So override this me...
virtual void nodeAddedSucessful(AbstractNodeInfo *node)
nodeAddedSucessful This method will be invoked when new node added successful.
QSharedPointer< QH::iParser > selectParser(unsigned short cmd, AbstractNodeInfo *sender) const
selectParser This method select parser by command and sender.
bool removeNode(const HostAddress &nodeAdderess)
removeNode - Remove node and disconnected forom node (server).
virtual void stop()
stop - Stopped this node and close all network connections.
virtual bool changeTrust(const HostAddress &id, int diff)
changeTrust This method change trust of connected node.
virtual void nodeConnected(AbstractNodeInfo *node)
nodeConnected This method invocked when the node status changed to "connected" default implementatio ...
QList< HostAddress > banedList() const
banedList This method retrun list of banned clients of nodes.
virtual QString connectionState() const
connectionState This method return string value about the cocction state.
virtual bool sendPackage(const Package &pkg, QAbstractSocket *target) const
sendPackage This method prepare and send to target address a package.
bool sheduleTask(const QSharedPointer< AbstractTask > &task)
sheduleTask This method shedule execute task on this node.
virtual bool isBanned(const AbstractNodeInfo *socket) const
isBanned This method checks if the node is banned.
static QThread * mainThreadID()
mainThreadID This method return the pointer to main thread
virtual AbstractNodeInfo * createNodeInfo(QAbstractSocket *socket, const HostAddress *clientAddress=nullptr) const
createNodeInfo This method create a nodeInfo object. override this method for create your own nodeInf...
bool addApiParser(const QSharedPointer< iParser > &parser)
addApiParser This method add new Api parser for this node.
bool addNode(const HostAddress &address)
addNode - Connect to node (server) with address.
void removeTask(int taskId)
removeTask This method remove task from sheduler.
~AbstractNode() override
virtual void ban(const HostAddress &target)
ban - This method set for target connection a trust property to 0 and target connection will been abo...
HostAddress address() const
address - Thim method return own network address of current node (server).
virtual void badRequest(const HostAddress &address, const Header &req, const PKG::ErrorData &err, qint8 diff=REQUEST_ERROR)
badRequest This method is send data about error of request.
SslMode getMode() const
getMode - This method return SSL mode of corrent node (server).
void prepareForDelete() override
prepareForDelete This method must be prepare object for delete. Override this for working main functi...
AbstractNode(QObject *ptr=nullptr)
AbstractNode - Base constructor of node.
virtual WorkState getWorkState() const
getWorkState - This method collect general information about this server. For more information about ...
void incomingConnection(qintptr handle) override final
incomingConnection This is ovverided method of QTCPServer.
QList< HostAddress > activeConnectionsList() const
activeConnectionsList This method return list of actived nodes connections
virtual AbstractNodeInfo * getInfoPtr(const HostAddress &id)
getInfoPtr - This method return information class pointer about netwok connection....
virtual bool registerSocket(QAbstractSocket *socket, const HostAddress *address=nullptr)
registerSocket This method registration new socket object.
void sigNoLongerSupport(const QString &ApiKey, unsigned short version)
sigNoLongerSupport is some as a APIVersionParser::sigNoLongerSupport. This signal just retronslate th...
virtual void nodeConfirmend(AbstractNodeInfo *node)
nodeConfirmend This method invocked when the node status changed to "confirmend" default implementati...
virtual QString getWorkStateString() const
getWorkStateString This method generate string about work state of server.
void setSendBadRequestErrors(bool value)
setSendBadRequestErrors This method enable or disable the fSendBadRequestErrors property.
virtual bool run(const QString &addres, unsigned short port)
run This method implement deployment a network node (server) on selected address.
virtual void unBan(const HostAddress &target)
unBan - This method set for target connection a trust property to 100.
bool ping(const HostAddress &address)
ping This method send ping package to address for testing connection.
virtual void nodeDisconnected(AbstractNodeInfo *node)
nodeConnected This method invocked when the node status changed to "disconnected" default implementat...
void setCloseConnectionAfterBadRequest(bool value)
setSendBadRequestErrors This method enable or disable the fcloseConnectionAfterBadRequest property.
int confirmendCount() const
connectionsCount - Return count of nodes with status confirmend.
bool fCloseConnectionAfterBadRequest() const
fCloseConnectionAfterBadRequest This propery enable or disable droping connection after badRequests....
int connectionsCount() const
connectionsCount - Return count fo connections (connections with status connected)
virtual unsigned int sendData(const PKG::AbstractData *resp, const HostAddress &address, const Header *req=nullptr)
sendData This method send data object another to node
virtual void receivePing(const QSharedPointer< QH::PKG::Ping > &ping)
receivePing This method invoked when node receive new ping object.
AsyncLauncher(QThread *thread)
AsyncLauncher This is base constructor of the AsyncLauncher object.
std::function< bool()> Job
Definition async.h:34
DataSender(QThread *thread)
The Host Address class this is wrapper of QHostAddress. Contains the NetworkAddress and network port.
Definition hostaddress.h:22
unsigned short port() const
The port method return port of node.
QString toString() const
toString this method convert the Host Address value to string value.
The AbstractData class is provide base functions for transport data by network For create you own pac...
virtual bool toPackage(Package &package, const DistVersion &reqVersion, unsigned int triggerHash=0) const
toPackage This method convert this class object to the package. For more info see Package class.
virtual unsigned short cmd() const =0
cmd - This is command of this object, (for generate cmd use macross QH_PACKAGE)
BadRequest(unsigned char errocode=ErrorCodes::InvalidRequest, const QString &err="")
BadRequest.
The CloseConnection class - This commanad is request for close connection on parent node of connectio...
The Ping class - test class for translate data on network.
Definition ping.h:22
The Package struct. This is base structure for transporting data by network between QH nodes....
Definition package.h:23
QByteArray data
data This is source data of package.
Definition package.h:33
Header hdr
hdr This is header of package. For more information see the Header struct.
Definition package.h:29
bool isValid() const override
isValid This method validation a current package. Default implementation is checked a header and comp...
Definition package.cpp:18
QString toString() const
toString This method convert a package information to a string label.
Definition package.cpp:38
static unsigned int maximumSize()
maximumSize This method return maximu size of pacakge. If pacakge large the maximum size then package...
Definition package.cpp:49
QByteArray toBytes() const
toBytes This method convert a current object to bytes array.
void sigPushWork(QSharedPointer< QH::AbstractTask > work)
sigPushWork This signal emited when the task work neet to execute.
TcpSocket(QObject *parent)
Definition tcpsocket.cpp:12
The WorkState class is simple class with data of work state of node.
Definition workstate.h:20
void setActiveConnections(const QList< HostAddress > &newActiveConnections)
setActiveConnections this method sets new list of active connections.
Definition workstate.cpp:34
void setMaxConnectionCount(int value)
setMaxConnectionCount this method set a new value of limit of connections.
Definition workstate.cpp:87
void setIsRun(bool value)
setIsRun This method set new value for run flag.
Definition workstate.cpp:18
void setBanedList(const QList< HostAddress > &banedList)
setBanedList -this method set banned list for this object.
Definition workstate.cpp:95
void setConnections(const QList< HostAddress > &connections)
setConnections This method sets connections list.
Definition workstate.cpp:26
static QString pareseResultToString(const ParserResult &parseResult)
pareseResultToString This method convert ParserResult value to string.
Definition iparser.cpp:22
#define WAIT_CONFIRM_TIME
Definition config.h:23
#define WAIT_TIME
Definition config.h:13
The PKG namesapce - this namespace contains all default packages of the Heart library....
Definition heart.h:211
The QH namespace - QuasarApp Heart namespace. This namespace contains all classes of the Heart librar...
Definition heart.cpp:13
ParserResult
The ParserResult enum. Error - parser detect a errorob package. NotProcessed - the parser does not kn...
Definition iparser.h:35
@ NotProcessed
the parser does not know what to do with the package or has not finished processing it.
Definition iparser.h:39
@ Error
parser detect a errorob package.
Definition iparser.h:37
@ Processed
the parser finished processing correctly.
Definition iparser.h:41
NodeCoonectionStatus
The AbstractNodeState enum - This is status of the known nodes or clients.
@ Connected
The node with this status has socket status is connected.
@ NotConnected
This node not sent data about its envirement and status of node socket is disconnected.
AddNodeError
The AddNodeError enum contains error code that can be occured after invoke the AbstractNode::addNode ...
@ RegisterSocketFailed
This error ocurred when you try add baned node or server is overrload.
@ HostNotFound
This error ocurred when DNS server not responce to node or node can't find the server ip address by h...
@ Baned
Node with this trust value is forbidden.
@ Undefined
Undefined node.
SslMode
The SslMode enum This enum contatins options for set ssl mode of node (server). For more information ...
@ NoSSL
This is not secure connection without ssl encription. It is default value of new any node see Abstrac...
The Header struct 32 bytes.
Definition header.h:19
bool isValid() const
isValid This method check header size and compare commands.
Definition header.cpp:18
unsigned int hash
hash This is unique id of a package. id calc with CRC32 function for Qt implementation.
Definition header.h:38
unsigned short command
command of package for more information see the AbstractData::toPackage method.
Definition header.h:23
The ErrorData struct is simple structure for contains data of the error.
Definition badrequest.h:20