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