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