MythTV master
mythsocket.cpp
Go to the documentation of this file.
1// Qt
2#include <QtGlobal>
3#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
4#include <QtSystemDetection>
5#endif
6#include <QNetworkInterface> // for QNetworkInterface::allAddresses ()
7#include <QCoreApplication>
8#include <QWaitCondition>
9#include <QSharedPointer>
10#include <QByteArray>
11#include <QTcpSocket>
12#include <QHostInfo>
13#include <QThread>
14#include <QMetaType>
15
16// setsockopt
17#ifdef Q_OS_WINDOWS
18#include <winsock2.h>
19#include <ws2tcpip.h>
20#include <cstdio>
21#else
22#include <sys/socket.h>
23#endif
24#include <algorithm> // for max
25#include <thread>
26#include <vector> // for vector
27
28// MythTV
29#include "mythsocket.h"
30#include "mythtimer.h"
31#include "mythevent.h"
32#include "mythversion.h"
33#include "mythlogging.h"
34#include "mythcorecontext.h"
35#include "portchecker.h"
36
37const int MythSocket::kSocketReceiveBufferSize = 128 * 1024;
38
40QHash<QString, QHostAddress::SpecialAddress> MythSocket::s_loopbackCache;
41
45
46Q_DECLARE_METATYPE ( const QStringList * );
47Q_DECLARE_METATYPE ( QStringList * );
48Q_DECLARE_METATYPE ( const char * );
52Q_DECLARE_METATYPE ( QHostAddress );
53static int x0 = qRegisterMetaType< const QStringList * >();
54static int x1 = qRegisterMetaType< QStringList * >();
55static int x2 = qRegisterMetaType< const char * >();
56static int x3 = qRegisterMetaType< char * >();
57static int x4 = qRegisterMetaType< bool * >();
58static int x5 = qRegisterMetaType< int * >();
59static int x6 = qRegisterMetaType< QHostAddress >();
61 x0 + x1 + x2 + x3 + x4 + x5 + x6;
62
63static QString to_sample(const QByteArray &payload)
64{
65 QString sample("");
66 for (uint i = 0; (i<60) && (i<(uint)payload.length()); i++)
67 {
68 sample += QChar(payload[i]).isPrint() ?
69 QChar(payload[i]) : QChar('?');
70 }
71 sample += (payload.length() > 60) ? "..." : "";
72 return sample;
73}
74
76 qintptr socket, MythSocketCBs *cb, bool use_shared_thread) :
77 ReferenceCounter(QString("MythSocket(%1)").arg(socket)),
78 m_tcpSocket(new QTcpSocket()),
79 m_callback(cb),
80 m_useSharedThread(use_shared_thread)
81{
82 LOG(VB_SOCKET, LOG_INFO, LOC() + QString("MythSocket(%1, 0x%2) ctor")
83 .arg(socket).arg((intptr_t)(cb),0,16));
84
85 if (socket != -1)
86 {
87 m_tcpSocket->setSocketDescriptor(
88 socket, QAbstractSocket::ConnectedState,
89 QAbstractSocket::ReadWrite);
91 {
92 m_tcpSocket->abort();
93 m_connected = false;
94 m_useSharedThread = false;
95 return;
96 }
97 ConnectHandler(); // already called implicitly above?
98 }
99
100 // Use direct connections so m_tcpSocket can be used
101 // in the handlers safely since they will be running
102 // in the same thread as all other m_tcpSocket users.
103
104 connect(m_tcpSocket, &QAbstractSocket::connected,
106 Qt::DirectConnection);
107 connect(m_tcpSocket, &QAbstractSocket::errorOccurred,
109 Qt::DirectConnection);
110 connect(m_tcpSocket, &QIODevice::aboutToClose,
112 connect(m_tcpSocket, &QAbstractSocket::disconnected,
114 Qt::DirectConnection);
115 connect(m_tcpSocket, &QIODevice::readyRead,
117 Qt::DirectConnection);
118
119 connect(this, &MythSocket::CallReadyRead,
121 Qt::QueuedConnection);
122
123 if (!use_shared_thread)
124 {
125 m_thread = new MThread(QString("MythSocketThread(%1)").arg(socket));
126 m_thread->start();
127 }
128 else
129 {
130 QMutexLocker locker(&s_thread_lock);
131 if (!s_thread)
132 {
133 s_thread = new MThread("SharedMythSocketThread");
134 s_thread->start();
135 }
137 s_thread_cnt++;
138 }
139
140 m_tcpSocket->moveToThread(m_thread->qthread());
141 moveToThread(m_thread->qthread());
142}
143
145{
146 LOG(VB_SOCKET, LOG_INFO, LOC() + QString("MythSocket dtor : cb 0x%2")
147 .arg((intptr_t)(m_callback),0,16));
148
149 if (IsConnected())
151
153 {
154 if (m_thread)
155 {
156 m_thread->quit();
157 m_thread->wait();
158 delete m_thread;
159 }
160 }
161 else
162 {
163 QMutexLocker locker(&s_thread_lock);
164 s_thread_cnt--;
165 if (0 == s_thread_cnt)
166 {
167 s_thread->quit();
168 s_thread->wait();
169 delete s_thread;
170 s_thread = nullptr;
171 }
172 }
173 m_thread = nullptr;
174
175 delete m_tcpSocket;
176 m_tcpSocket = nullptr;
177}
178
180{
181 {
182 QMutexLocker locker(&m_lock);
183 m_connected = true;
184 m_socketDescriptor = m_tcpSocket->socketDescriptor();
185 m_peerAddress = m_tcpSocket->peerAddress();
186 m_peerPort = m_tcpSocket->peerPort();
187 }
188
189 m_tcpSocket->setSocketOption(QAbstractSocket::LowDelayOption, QVariant(1));
190 m_tcpSocket->setSocketOption(QAbstractSocket::KeepAliveOption, QVariant(1));
191
192 int reuse_addr_val = 1;
193#ifdef Q_OS_WINDOWS
194 int ret = setsockopt(m_tcpSocket->socketDescriptor(), SOL_SOCKET,
195 SO_REUSEADDR, (char*) &reuse_addr_val,
196 sizeof(reuse_addr_val));
197#else
198 int ret = setsockopt(m_tcpSocket->socketDescriptor(), SOL_SOCKET,
199 SO_REUSEADDR, &reuse_addr_val,
200 sizeof(reuse_addr_val));
201#endif
202 if (ret < 0)
203 {
204 LOG(VB_SOCKET, LOG_INFO, LOC() + "Failed to set SO_REUSEADDR" + ENO);
205 }
206
207 int rcv_buf_val = kSocketReceiveBufferSize;
208#ifdef Q_OS_WINDOWS
209 ret = setsockopt(m_tcpSocket->socketDescriptor(), SOL_SOCKET,
210 SO_RCVBUF, (char*) &rcv_buf_val,
211 sizeof(rcv_buf_val));
212#else
213 ret = setsockopt(m_tcpSocket->socketDescriptor(), SOL_SOCKET,
214 SO_RCVBUF, &rcv_buf_val,
215 sizeof(rcv_buf_val));
216#endif
217 if (ret < 0)
218 {
219 LOG(VB_SOCKET, LOG_INFO, LOC() + "Failed to set SO_RCVBUF" + ENO);
220 }
221
222 if (m_callback)
223 {
224 LOG(VB_SOCKET, LOG_DEBUG, LOC() +
225 "calling m_callback->connected()");
226 m_callback->connected(this);
227 }
228}
229
230void MythSocket::ErrorHandler(QAbstractSocket::SocketError err)
231{
232 // Filter these out, we get them because we call waitForReadyRead with a
233 // small timeout so we can print our own debugging for long timeouts.
234 if (err == QAbstractSocket::SocketTimeoutError)
235 return;
236
237 if (m_callback)
238 {
239 LOG(VB_SOCKET, LOG_DEBUG, LOC() +
240 "calling m_callback->error() err: " + m_tcpSocket->errorString());
241 m_callback->error(this, (int)err);
242 }
243}
244
246{
247 {
248 QMutexLocker locker(&m_lock);
249 m_connected = false;
251 m_peerAddress.clear();
252 m_peerPort = -1;
253 }
254
255 if (m_callback)
256 {
257 LOG(VB_SOCKET, LOG_DEBUG, LOC() +
258 "calling m_callback->connectionClosed()");
260 }
261}
262
264{
265 LOG(VB_SOCKET, LOG_DEBUG, LOC() + "AboutToClose");
266}
267
269{
270 m_dataAvailable.fetchAndStoreOrdered(1);
271 if (m_callback && m_disableReadyReadCallback.testAndSetOrdered(0,0))
272 {
273 emit CallReadyRead();
274 }
275}
276
278{
279 // Because the connection to this is a queued connection the
280 // data may have already been read by the time this is called
281 // so we check that there is still data to read before calling
282 // the callback.
283 if (IsDataAvailable())
284 {
285 LOG(VB_SOCKET, LOG_DEBUG, LOC() +
286 "calling m_callback->readyRead()");
287 m_callback->readyRead(this);
288 }
289}
290
292 const QHostAddress &address, quint16 port)
293{
294 bool ret = false;
295 QMetaObject::invokeMethod(
296 this, "ConnectToHostReal",
297 (QThread::currentThread() != m_thread->qthread()) ?
298 Qt::BlockingQueuedConnection : Qt::DirectConnection,
299 Q_ARG(QHostAddress, address),
300 Q_ARG(quint16, port),
301 Q_ARG(bool*, &ret));
302 return ret;
303}
304
305bool MythSocket::WriteStringList(const QStringList &list)
306{
307 bool ret = false;
308 QMetaObject::invokeMethod(
309 this, "WriteStringListReal",
310 (QThread::currentThread() != m_thread->qthread()) ?
311 Qt::BlockingQueuedConnection : Qt::DirectConnection,
312 Q_ARG(const QStringList*, &list),
313 Q_ARG(bool*, &ret));
314 return ret;
315}
316
317bool MythSocket::ReadStringList(QStringList &list, std::chrono::milliseconds timeoutMS)
318{
319 bool ret = false;
320 QMetaObject::invokeMethod(
321 this, "ReadStringListReal",
322 (QThread::currentThread() != m_thread->qthread()) ?
323 Qt::BlockingQueuedConnection : Qt::DirectConnection,
324 Q_ARG(QStringList*, &list),
325 Q_ARG(std::chrono::milliseconds, timeoutMS),
326 Q_ARG(bool*, &ret));
327 return ret;
328}
329
331 QStringList &strlist, uint min_reply_length, std::chrono::milliseconds timeoutMS)
332{
333 if (m_callback && m_disableReadyReadCallback.testAndSetOrdered(0,0))
334 {
335 // If callbacks are enabled then SendReceiveStringList() will conflict
336 // causing failed reads and socket disconnections - see #11777
337 // SendReceiveStringList() should NOT be used with an event socket, only
338 // the control socket
339 LOG(VB_GENERAL, LOG_EMERG, QString("Programmer Error! "
340 "SendReceiveStringList(%1) used on "
341 "socket with callbacks enabled.")
342 .arg(strlist.isEmpty() ? "empty" : strlist[0]));
343 }
344
345 if (!WriteStringList(strlist))
346 {
347 LOG(VB_GENERAL, LOG_ERR, LOC() + "Failed to send command.");
348 return false;
349 }
350
351 if (!ReadStringList(strlist, timeoutMS))
352 {
353 LOG(VB_GENERAL, LOG_ERR, LOC() + "No response.");
354 return false;
355 }
356
357 if (min_reply_length && ((uint)strlist.size() < min_reply_length))
358 {
359 LOG(VB_GENERAL, LOG_ERR, LOC() + "Response too short.");
360 return false;
361 }
362
363#if 0
364 if (!strlist.empty() && strlist[0] == "BACKEND_MESSAGE")
365 {
366 LOG(VB_GENERAL, LOG_ERR, LOC() + "Got MythEvent on non-event socket");
367 return false;
368 }
369#endif
370
371 return true;
372}
373
378bool MythSocket::ConnectToHost(const QString &host, quint16 port)
379{
380 QHostAddress hadr;
381
382 // attempt direct assignment
383 if (!hadr.setAddress(host))
384 {
385 // attempt internal lookup through MythCoreContext
386 if (!gCoreContext ||
387 !hadr.setAddress(gCoreContext->GetBackendServerIP(host)))
388 {
389 // attempt external lookup from hosts/DNS
390 QHostInfo info = QHostInfo::fromName(host);
391 if (!info.addresses().isEmpty())
392 {
393 hadr = info.addresses().constFirst();
394 }
395 else
396 {
397 LOG(VB_GENERAL, LOG_ERR, LOC() + QString("Unable to lookup: %1")
398 .arg(host));
399 return false;
400 }
401 }
402 }
403
404 return MythSocket::ConnectToHost(hadr, port);
405}
406
407bool MythSocket::Validate(std::chrono::milliseconds timeout, bool error_dialog_desired)
408{
409 if (m_isValidated)
410 return true;
411
412 QStringList strlist(QString("MYTH_PROTO_VERSION %1 %2")
413 .arg(MYTH_PROTO_VERSION,
414 QString::fromUtf8(MYTH_PROTO_TOKEN)));
415
416 WriteStringList(strlist);
417
418 if (!ReadStringList(strlist, timeout) || strlist.empty())
419 {
420 LOG(VB_GENERAL, LOG_ERR, "Protocol version check failure.\n\t\t\t"
421 "The response to MYTH_PROTO_VERSION was empty.\n\t\t\t"
422 "This happens when the backend is too busy to respond,\n\t\t\t"
423 "or has deadlocked due to bugs or hardware failure.");
424 return m_isValidated;
425 }
426
427 if (strlist[0] == "REJECT" && (strlist.size() >= 2))
428 {
429 LOG(VB_GENERAL, LOG_ERR,
430 QString("Protocol version or token mismatch "
431 "(frontend=%1/%2,backend=%3/\?\?)\n")
432 .arg(MYTH_PROTO_VERSION,
433 QString::fromUtf8(MYTH_PROTO_TOKEN),
434 strlist[1]));
435
436 QObject *GUIcontext = gCoreContext->GetGUIContext();
437 if (error_dialog_desired && GUIcontext)
438 {
439 QStringList list(strlist[1]);
440 QCoreApplication::postEvent(
441 GUIcontext, new MythEvent("VERSION_MISMATCH", list));
442 }
443 }
444 else if (strlist[0] == "ACCEPT")
445 {
446 LOG(VB_GENERAL, LOG_NOTICE, QString("Using protocol version %1 %2")
447 .arg(MYTH_PROTO_VERSION, QString::fromUtf8(MYTH_PROTO_TOKEN)));
448 m_isValidated = true;
449 }
450 else
451 {
452 LOG(VB_GENERAL, LOG_ERR,
453 QString("Unexpected response to MYTH_PROTO_VERSION: %1")
454 .arg(strlist[0]));
455 }
456
457 return m_isValidated;
458}
459
460bool MythSocket::Announce(const QStringList &new_announce)
461{
462 if (!m_isValidated)
463 {
464 LOG(VB_GENERAL, LOG_ERR, LOC() +
465 "refusing to announce unvalidated socket");
466 return false;
467 }
468
469 if (m_isAnnounced)
470 {
471 LOG(VB_GENERAL, LOG_ERR, LOC() + "refusing to re-announce socket");
472 return false;
473 }
474
475 WriteStringList(new_announce);
476
477 QStringList tmplist;
479 {
480 LOG(VB_GENERAL, LOG_ERR, LOC() +
481 QString("\n\t\t\tCould not read string list from server %1:%2")
482 .arg(m_tcpSocket->peerAddress().toString())
483 .arg(m_tcpSocket->peerPort()));
484 m_announce.clear();
485 m_isAnnounced = false;
486 }
487 else
488 {
489 m_announce = new_announce;
490 m_isAnnounced = true;
491 }
492
493 return m_isAnnounced;
494}
495
496void MythSocket::SetAnnounce(const QStringList &new_announce)
497{
498 m_announce = new_announce;
499 m_isAnnounced = true;
500}
501
503{
504 if (QThread::currentThread() != m_thread->qthread() &&
506 {
507 LOG(VB_GENERAL, LOG_ERR, LOC() +
508 QString("Programmer error, QEventLoop isn't running and deleting "
509 "MythSocket(0x%1)").arg(reinterpret_cast<intptr_t>(this),0,16));
510 return;
511 }
512 QMetaObject::invokeMethod(
513 this, "DisconnectFromHostReal",
514 (QThread::currentThread() != m_thread->qthread()) ?
515 Qt::BlockingQueuedConnection : Qt::DirectConnection);
516}
517
518int MythSocket::Write(const char *data, int size)
519{
520 int ret = -1;
521 QMetaObject::invokeMethod(
522 this, "WriteReal",
523 (QThread::currentThread() != m_thread->qthread()) ?
524 Qt::BlockingQueuedConnection : Qt::DirectConnection,
525 Q_ARG(const char*, data),
526 Q_ARG(int, size),
527 Q_ARG(int*, &ret));
528 return ret;
529}
530
531int MythSocket::Read(char *data, int size, std::chrono::milliseconds max_wait)
532{
533 int ret = -1;
534 QMetaObject::invokeMethod(
535 this, "ReadReal",
536 (QThread::currentThread() != m_thread->qthread()) ?
537 Qt::BlockingQueuedConnection : Qt::DirectConnection,
538 Q_ARG(char*, data),
539 Q_ARG(int, size),
540 Q_ARG(std::chrono::milliseconds, max_wait),
541 Q_ARG(int*, &ret));
542 return ret;
543}
544
546{
547 QMetaObject::invokeMethod(
548 this, "ResetReal",
549 (QThread::currentThread() != m_thread->qthread()) ?
550 Qt::BlockingQueuedConnection : Qt::DirectConnection);
551}
552
554
556{
557 QMutexLocker locker(&m_lock);
558 return m_connected;
559}
560
562{
563 if (QThread::currentThread() == m_thread->qthread())
564 return m_tcpSocket->bytesAvailable() > 0;
565
566 if (m_dataAvailable.testAndSetOrdered(0,0))
567 return false;
568
569 bool ret = false;
570
571 QMetaObject::invokeMethod(
572 this, "IsDataAvailableReal",
573 Qt::BlockingQueuedConnection,
574 Q_ARG(bool*, &ret));
575
576 return ret;
577}
578
580{
581 QMutexLocker locker(&m_lock);
582 return m_socketDescriptor;
583}
584
585QHostAddress MythSocket::GetPeerAddress(void) const
586{
587 QMutexLocker locker(&m_lock);
588 return m_peerAddress;
589}
590
592{
593 QMutexLocker locker(&m_lock);
594 return m_peerPort;
595}
596
598
600{
601 *ret = (m_tcpSocket->bytesAvailable() > 0);
602 m_dataAvailable.fetchAndStoreOrdered((*ret) ? 1 : 0);
603}
604
605void MythSocket::ConnectToHostReal(const QHostAddress& _addr, quint16 port, bool *ret)
606{
607 if (m_tcpSocket->state() == QAbstractSocket::ConnectedState)
608 {
609 LOG(VB_SOCKET, LOG_ERR, LOC() +
610 "connect() called with already open socket, closing");
611 m_tcpSocket->close();
612 }
613
614 QHostAddress addr = _addr;
615 addr.setScopeId(QString());
616
617 s_loopbackCacheLock.lock();
618 bool usingLoopback = s_loopbackCache.contains(addr.toString());
619 s_loopbackCacheLock.unlock();
620
621 if (usingLoopback)
622 {
623 addr = QHostAddress(s_loopbackCache.value(addr.toString()));
624 }
625 else
626 {
627 QList<QHostAddress> localIPs = QNetworkInterface::allAddresses();
628 for (int i = 0; i < localIPs.count() && !usingLoopback; ++i)
629 {
630 QHostAddress local = localIPs[i];
631 local.setScopeId(QString());
632
633 if (addr == local)
634 {
635 QHostAddress::SpecialAddress loopback = QHostAddress::LocalHost;
636 if (addr.protocol() == QAbstractSocket::IPv6Protocol)
637 loopback = QHostAddress::LocalHostIPv6;
638
639 QMutexLocker locker(&s_loopbackCacheLock);
640 s_loopbackCache[addr.toString()] = loopback;
641 addr = QHostAddress(loopback);
642 usingLoopback = true;
643 }
644 }
645 }
646
647 if (usingLoopback)
648 {
649 LOG(VB_SOCKET, LOG_INFO, LOC() +
650 "IP is local, using loopback address instead");
651 }
652
653 LOG(VB_SOCKET, LOG_INFO, LOC() + QString("attempting connect() to (%1:%2)")
654 .arg(addr.toString()).arg(port));
655
656 bool ok = true;
657
658 // Sort out link-local address scope if applicable
659 if (!usingLoopback)
660 {
661 QString host = addr.toString();
662 if (PortChecker{}.resolveLinkLocal(host, port))
663 addr.setAddress(host);
664 }
665
666 if (ok)
667 {
668 m_tcpSocket->connectToHost(addr, port, QAbstractSocket::ReadWrite);
669 ok = m_tcpSocket->waitForConnected(5000);
670 }
671
672 if (ok)
673 {
674 LOG(VB_SOCKET, LOG_INFO, LOC() + QString("Connected to (%1:%2)")
675 .arg(addr.toString()).arg(port));
676 }
677 else
678 {
679 LOG(VB_GENERAL, LOG_ERR, LOC() +
680 QString("Failed to connect to (%1:%2) %3")
681 .arg(addr.toString()).arg(port)
682 .arg(m_tcpSocket->errorString()));
683 }
684
685 *ret = ok;
686}
687
689{
690 m_tcpSocket->disconnectFromHost();
691}
692
693void MythSocket::WriteStringListReal(const QStringList *list, bool *ret)
694{
695 if (list->empty())
696 {
697 LOG(VB_GENERAL, LOG_ERR, LOC() +
698 "WriteStringList: Error, invalid string list.");
699 *ret = false;
700 return;
701 }
702
703 if (m_tcpSocket->state() != QAbstractSocket::ConnectedState)
704 {
705 LOG(VB_GENERAL, LOG_ERR, LOC() +
706 "WriteStringList: Error, called with unconnected socket.");
707 *ret = false;
708 return;
709 }
710
711 QString str = list->join("[]:[]");
712 if (str.isEmpty())
713 {
714 LOG(VB_GENERAL, LOG_ERR, LOC() +
715 "WriteStringList: Error, joined null string.");
716 *ret = false;
717 return;
718 }
719
720 QByteArray utf8 = str.toUtf8();
721 int size = utf8.length();
722 int written = 0;
723 int written_since_timer_restart = 0;
724
725 QByteArray payload;
726 payload = payload.setNum(size);
727 payload += " ";
728 payload.truncate(8);
729 payload += utf8;
730 size = payload.length();
731
732 if (VERBOSE_LEVEL_CHECK(VB_NETWORK, LOG_INFO))
733 {
734 QString msg = QString("write -> %1 %2")
735 .arg(m_tcpSocket->socketDescriptor(), 2).arg(payload.data());
736
737 if (logLevel < LOG_DEBUG && msg.length() > 128)
738 {
739 msg.truncate(127);
740 msg += "…";
741 }
742 LOG(VB_NETWORK, LOG_INFO, LOC() + msg);
743 }
744
745 MythTimer timer; timer.start();
746 unsigned int errorcount = 0;
747 while (size > 0)
748 {
749 if (m_tcpSocket->state() != QAbstractSocket::ConnectedState)
750 {
751 LOG(VB_GENERAL, LOG_ERR, LOC() +
752 "WriteStringList: Error, socket went unconnected." +
753 QString("\n\t\t\tWe wrote %1 of %2 bytes with %3 errors")
754 .arg(written).arg(written+size).arg(errorcount) +
755 QString("\n\t\t\tstarts with: %1").arg(to_sample(payload)));
756 *ret = false;
757 return;
758 }
759
760 int temp = m_tcpSocket->write(payload.data() + written, size);
761 if (temp > 0)
762 {
763 written += temp;
764 written_since_timer_restart += temp;
765 size -= temp;
766 if ((timer.elapsed() > 500ms) && written_since_timer_restart != 0)
767 {
768 timer.restart();
769 written_since_timer_restart = 0;
770 }
771 }
772 else
773 {
774 errorcount++;
775 if (timer.elapsed() > 1s)
776 {
777 LOG(VB_GENERAL, LOG_ERR, LOC() + "WriteStringList: Error, " +
778 QString("No data written on write (%1 errors)")
779 .arg(errorcount) +
780 QString("\n\t\t\tstarts with: %1")
781 .arg(to_sample(payload)));
782 *ret = false;
783 return;
784 }
785 std::this_thread::sleep_for(1ms);
786 }
787 }
788
789 m_tcpSocket->flush();
790
791 *ret = true;
792}
793
795 QStringList *list, std::chrono::milliseconds timeoutMS, bool *ret)
796{
797 list->clear();
798 *ret = false;
799
800 MythTimer timer;
801 timer.start();
802 std::chrono::milliseconds elapsed { 0ms };
803
804 while (m_tcpSocket->bytesAvailable() < 8)
805 {
806 elapsed = timer.elapsed();
807 if (elapsed >= timeoutMS)
808 {
809 LOG(VB_GENERAL, LOG_ERR, LOC() + "ReadStringList: " +
810 QString("Error, timed out after %1 ms.").arg(timeoutMS.count()));
811 m_tcpSocket->close();
812 m_dataAvailable.fetchAndStoreOrdered(0);
813 return;
814 }
815
816 if (m_tcpSocket->state() != QAbstractSocket::ConnectedState)
817 {
818 LOG(VB_GENERAL, LOG_ERR, LOC() + "ReadStringList: Connection died.");
819 m_dataAvailable.fetchAndStoreOrdered(0);
820 return;
821 }
822
823 m_tcpSocket->waitForReadyRead(50);
824 }
825
826 QByteArray sizestr(8, '\0');
827 if (m_tcpSocket->read(sizestr.data(), 8) < 0)
828 {
829 LOG(VB_GENERAL, LOG_ERR, LOC() +
830 QString("ReadStringList: Error, read return error (%1)")
831 .arg(m_tcpSocket->errorString()));
832 m_tcpSocket->close();
833 m_dataAvailable.fetchAndStoreOrdered(0);
834 return;
835 }
836
837 QString sizes = sizestr;
838 bool ok { false };
839 int btr = sizes.trimmed().toInt(&ok);
840
841 if (btr < 1)
842 {
843 int pending = m_tcpSocket->bytesAvailable();
844 LOG(VB_GENERAL, LOG_ERR, LOC() +
845 QString("Protocol error: %1'%2' is not a valid size "
846 "prefix. %3 bytes pending.")
847 .arg(ok ? "" : "(parse failed) ",
848 sizestr.data(), QString::number(pending)));
849 ResetReal();
850 return;
851 }
852
853 QByteArray utf8(btr + 1, 0);
854
855 qint64 readoffset = 0;
856 std::chrono::milliseconds errmsgtime { 0ms };
857 timer.start();
858
859 while (btr > 0)
860 {
861 if (m_tcpSocket->bytesAvailable() < 1)
862 {
863 if (m_tcpSocket->state() == QAbstractSocket::ConnectedState)
864 {
865 m_tcpSocket->waitForReadyRead(50);
866 }
867 else
868 {
869 LOG(VB_GENERAL, LOG_ERR, LOC() +
870 "ReadStringList: Connection died.");
871 m_dataAvailable.fetchAndStoreOrdered(0);
872 return;
873 }
874 }
875
876 qint64 sret = m_tcpSocket->read(utf8.data() + readoffset, btr);
877 if (sret > 0)
878 {
879 readoffset += sret;
880 btr -= sret;
881 if (btr > 0)
882 {
883 timer.start();
884 }
885 }
886 else if (sret < 0)
887 {
888 LOG(VB_GENERAL, LOG_ERR, LOC() + "ReadStringList: Error, read");
889 m_tcpSocket->close();
890 m_dataAvailable.fetchAndStoreOrdered(0);
891 return;
892 }
893 else if (!m_tcpSocket->isValid())
894 {
895 LOG(VB_GENERAL, LOG_ERR, LOC() +
896 "ReadStringList: Error, socket went unconnected");
897 m_tcpSocket->close();
898 m_dataAvailable.fetchAndStoreOrdered(0);
899 return;
900 }
901 else
902 {
903 elapsed = timer.elapsed();
904 if (elapsed > 10s)
905 {
906 if ((elapsed - errmsgtime) > 10s)
907 {
908 errmsgtime = elapsed;
909 LOG(VB_GENERAL, LOG_ERR, LOC() +
910 QString("ReadStringList: Waiting for data: %1 %2")
911 .arg(readoffset).arg(btr));
912 }
913 }
914
915 if (elapsed > 100s)
916 {
917 LOG(VB_GENERAL, LOG_ERR, LOC() +
918 "Error, ReadStringList timeout (readBlock)");
919 m_dataAvailable.fetchAndStoreOrdered(0);
920 return;
921 }
922 }
923 }
924
925 QString str = QString::fromUtf8(utf8.data());
926
927 if (VERBOSE_LEVEL_CHECK(VB_NETWORK, LOG_INFO))
928 {
929 QByteArray payload;
930 payload = payload.setNum(str.length());
931 payload += " ";
932 payload.truncate(8);
933 payload += utf8.data();
934
935 QString msg = QString("read <- %1 %2")
936 .arg(m_tcpSocket->socketDescriptor(), 2)
937 .arg(payload.data());
938
939 if (logLevel < LOG_DEBUG && msg.length() > 128)
940 {
941 msg.truncate(127);
942 msg += "…";
943 }
944 LOG(VB_NETWORK, LOG_INFO, LOC() + msg);
945 }
946
947 *list = str.split("[]:[]");
948
949 m_dataAvailable.fetchAndStoreOrdered(
950 (m_tcpSocket->bytesAvailable() > 0) ? 1 : 0);
951
952 *ret = true;
953}
954
955void MythSocket::WriteReal(const char *data, int size, int *ret)
956{
957 *ret = m_tcpSocket->write(data, size);
958}
959
960void MythSocket::ReadReal(char *data, int size, std::chrono::milliseconds max_wait_ms, int *ret)
961{
962 MythTimer t; t.start();
963 while ((m_tcpSocket->state() == QAbstractSocket::ConnectedState) &&
964 (m_tcpSocket->bytesAvailable() < size) &&
965 (t.elapsed() < max_wait_ms))
966 {
967 m_tcpSocket->waitForReadyRead(max(2ms, max_wait_ms - t.elapsed()).count());
968 }
969 *ret = m_tcpSocket->read(data, size);
970
971 if (t.elapsed() > 50ms)
972 {
973 LOG(VB_NETWORK, LOG_INFO,
974 QString("ReadReal(?, %1, %2) -> %3 took %4 ms")
975 .arg(size).arg(max_wait_ms.count()).arg(*ret)
976 .arg(t.elapsed().count()));
977 }
978
979 m_dataAvailable.fetchAndStoreOrdered(
980 (m_tcpSocket->bytesAvailable() > 0) ? 1 : 0);
981}
982
984{
985 uint avail {0};
986 std::vector<char> trash;
987
988 m_tcpSocket->waitForReadyRead(30);
989 while ((avail = m_tcpSocket->bytesAvailable()) > 0)
990 {
991 trash.resize(std::max((uint)trash.size(),avail));
992 m_tcpSocket->read(trash.data(), avail);
993
994 LOG(VB_NETWORK, LOG_INFO, LOC() + "Reset() " +
995 QString("%1 bytes available").arg(avail));
996
997 m_tcpSocket->waitForReadyRead(30);
998 }
999
1000 m_dataAvailable.fetchAndStoreOrdered(0);
1001}
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:281
void quit(void)
calls exit(0)
Definition: mthread.cpp:293
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:298
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
Definition: mthread.cpp:231
QObject * GetGUIContext(void)
bool CheckSubnet(const QAbstractSocket *socket)
Check if a socket is connected to an approved peer.
QString GetBackendServerIP(void)
Returns the IP address of the locally defined backend IP.
This class is used as a container for messages.
Definition: mythevent.h:17
virtual void readyRead(MythSocket *)=0
virtual void connected(MythSocket *)=0
virtual void error(MythSocket *, int)
Definition: mythsocket_cb.h:18
virtual void connectionClosed(MythSocket *)=0
void ConnectHandler(void)
Definition: mythsocket.cpp:179
static const int kSocketReceiveBufferSize
Definition: mythsocket.h:123
int m_peerPort
Definition: mythsocket.h:111
QStringList m_announce
Definition: mythsocket.h:121
void WriteStringListReal(const QStringList *list, bool *ret)
Definition: mythsocket.cpp:693
void IsDataAvailableReal(bool *ret) const
Definition: mythsocket.cpp:599
QAtomicInt m_disableReadyReadCallback
Definition: mythsocket.h:114
static QHash< QString, QHostAddress::SpecialAddress > s_loopbackCache
Definition: mythsocket.h:126
static MThread * s_thread
Definition: mythsocket.h:129
void SetAnnounce(const QStringList &new_announce)
Definition: mythsocket.cpp:496
bool SendReceiveStringList(QStringList &list, uint min_reply_length=0, std::chrono::milliseconds timeoutMS=kLongTimeout)
Definition: mythsocket.cpp:330
bool Announce(const QStringList &new_announce)
Definition: mythsocket.cpp:460
bool Validate(std::chrono::milliseconds timeout=kMythSocketLongTimeout, bool error_dialog_desired=false)
Definition: mythsocket.cpp:407
void AboutToCloseHandler(void)
Definition: mythsocket.cpp:263
bool m_isValidated
Definition: mythsocket.h:119
void ResetReal(void)
Definition: mythsocket.cpp:983
bool m_useSharedThread
Definition: mythsocket.h:113
bool ReadStringList(QStringList &list, std::chrono::milliseconds timeoutMS=kShortTimeout)
Definition: mythsocket.cpp:317
static QMutex s_loopbackCacheLock
Definition: mythsocket.h:125
QString LOC()
Definition: mythsocket.h:74
bool IsConnected(void) const
Definition: mythsocket.cpp:555
static int s_thread_cnt
Definition: mythsocket.h:130
bool IsDataAvailable(void)
Definition: mythsocket.cpp:561
qintptr m_socketDescriptor
Definition: mythsocket.h:109
void ReadyReadHandler(void)
Definition: mythsocket.cpp:268
bool m_isAnnounced
Definition: mythsocket.h:120
MythSocketCBs * m_callback
Definition: mythsocket.h:112
void CallReadyReadHandler(void)
Definition: mythsocket.cpp:277
static QMutex s_thread_lock
Definition: mythsocket.h:128
static constexpr std::chrono::milliseconds kShortTimeout
Definition: mythsocket.h:70
~MythSocket() override
Definition: mythsocket.cpp:144
void DisconnectHandler(void)
Definition: mythsocket.cpp:245
void ReadStringListReal(QStringList *list, std::chrono::milliseconds timeoutMS, bool *ret)
Definition: mythsocket.cpp:794
int Read(char *data, int size, std::chrono::milliseconds max_wait)
Definition: mythsocket.cpp:531
MThread * m_thread
Definition: mythsocket.h:107
int GetSocketDescriptor(void) const
Definition: mythsocket.cpp:579
int GetPeerPort(void) const
Definition: mythsocket.cpp:591
void WriteReal(const char *data, int size, int *ret)
Definition: mythsocket.cpp:955
void ReadReal(char *data, int size, std::chrono::milliseconds max_wait_ms, int *ret)
Definition: mythsocket.cpp:960
QHostAddress m_peerAddress
Definition: mythsocket.h:110
bool m_connected
Definition: mythsocket.h:115
QTcpSocket * m_tcpSocket
Definition: mythsocket.h:106
void DisconnectFromHost(void)
Definition: mythsocket.cpp:502
void CallReadyRead(void)
int Write(const char *data, int size)
Definition: mythsocket.cpp:518
bool WriteStringList(const QStringList &list)
Definition: mythsocket.cpp:305
bool ConnectToHost(const QString &hostname, quint16 port)
connect to host
Definition: mythsocket.cpp:378
void Reset(void)
Definition: mythsocket.cpp:545
MythSocket(qintptr socket=-1, MythSocketCBs *cb=nullptr, bool use_shared_thread=false)
Definition: mythsocket.cpp:75
QHostAddress GetPeerAddress(void) const
Definition: mythsocket.cpp:585
QAtomicInt m_dataAvailable
This is used internally as a hint that there might be data available for reading.
Definition: mythsocket.h:118
QMutex m_lock
Definition: mythsocket.h:108
void DisconnectFromHostReal(void)
Definition: mythsocket.cpp:688
void ConnectToHostReal(const QHostAddress &addr, quint16 port, bool *ret)
Definition: mythsocket.cpp:605
void ErrorHandler(QAbstractSocket::SocketError err)
Definition: mythsocket.cpp:230
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:14
std::chrono::milliseconds restart(void)
Returns milliseconds elapsed since last start() or restart() and resets the count.
Definition: mythtimer.cpp:62
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:91
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
Small class to handle TCP port checking and finding link-local context.
Definition: portchecker.h:45
bool resolveLinkLocal(QString &host, int port, std::chrono::milliseconds timeLimit=30s)
Convenience method to resolve link-local address.
General purpose reference counter.
unsigned int uint
Definition: compat.h:60
LogLevel_t logLevel
Definition: logging.cpp:89
static void(* m_callback)(void *, QString &)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
Q_DECLARE_METATYPE(const QStringList *)
static int x5
Definition: mythsocket.cpp:58
static int x0
Definition: mythsocket.cpp:53
static int x1
Definition: mythsocket.cpp:54
static int x6
Definition: mythsocket.cpp:59
int s_dummy_meta_variable_to_suppress_gcc_warning
Definition: mythsocket.cpp:60
static int x2
Definition: mythsocket.cpp:55
static int x4
Definition: mythsocket.cpp:57
static int x3
Definition: mythsocket.cpp:56
static QString to_sample(const QByteArray &payload)
Definition: mythsocket.cpp:63
dictionary info
Definition: azlyrics.py:7