3#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
4#include <QtSystemDetection>
6#include <QNetworkInterface>
7#include <QCoreApplication>
8#include <QWaitCondition>
9#include <QSharedPointer>
22#include <sys/socket.h>
32#include "mythversion.h"
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 >();
66 for (
uint i = 0; (i<60) && (i<(
uint)payload.length()); i++)
68 sample += QChar(payload[i]).isPrint() ?
69 QChar(payload[i]) : QChar(
'?');
71 sample += (payload.length() > 60) ?
"..." :
"";
78 m_tcpSocket(new QTcpSocket()),
80 m_useSharedThread(use_shared_thread)
82 LOG(VB_SOCKET, LOG_INFO,
LOC() + QString(
"MythSocket(%1, 0x%2) ctor")
83 .arg(socket).arg((intptr_t)(cb),0,16));
88 socket, QAbstractSocket::ConnectedState,
89 QAbstractSocket::ReadWrite);
106 Qt::DirectConnection);
107 connect(
m_tcpSocket, &QAbstractSocket::errorOccurred,
109 Qt::DirectConnection);
112 connect(
m_tcpSocket, &QAbstractSocket::disconnected,
114 Qt::DirectConnection);
117 Qt::DirectConnection);
121 Qt::QueuedConnection);
123 if (!use_shared_thread)
146 LOG(VB_SOCKET, LOG_INFO,
LOC() + QString(
"MythSocket dtor : cb 0x%2")
182 QMutexLocker locker(&
m_lock);
189 m_tcpSocket->setSocketOption(QAbstractSocket::LowDelayOption, QVariant(1));
190 m_tcpSocket->setSocketOption(QAbstractSocket::KeepAliveOption, QVariant(1));
192 int reuse_addr_val = 1;
194 int ret = setsockopt(
m_tcpSocket->socketDescriptor(), SOL_SOCKET,
195 SO_REUSEADDR, (
char*) &reuse_addr_val,
196 sizeof(reuse_addr_val));
198 int ret = setsockopt(
m_tcpSocket->socketDescriptor(), SOL_SOCKET,
199 SO_REUSEADDR, &reuse_addr_val,
200 sizeof(reuse_addr_val));
204 LOG(VB_SOCKET, LOG_INFO,
LOC() +
"Failed to set SO_REUSEADDR" +
ENO);
209 ret = setsockopt(
m_tcpSocket->socketDescriptor(), SOL_SOCKET,
210 SO_RCVBUF, (
char*) &rcv_buf_val,
211 sizeof(rcv_buf_val));
213 ret = setsockopt(
m_tcpSocket->socketDescriptor(), SOL_SOCKET,
214 SO_RCVBUF, &rcv_buf_val,
215 sizeof(rcv_buf_val));
219 LOG(VB_SOCKET, LOG_INFO,
LOC() +
"Failed to set SO_RCVBUF" +
ENO);
224 LOG(VB_SOCKET, LOG_DEBUG,
LOC() +
225 "calling m_callback->connected()");
234 if (err == QAbstractSocket::SocketTimeoutError)
239 LOG(VB_SOCKET, LOG_DEBUG,
LOC() +
240 "calling m_callback->error() err: " +
m_tcpSocket->errorString());
248 QMutexLocker locker(&
m_lock);
257 LOG(VB_SOCKET, LOG_DEBUG,
LOC() +
258 "calling m_callback->connectionClosed()");
265 LOG(VB_SOCKET, LOG_DEBUG,
LOC() +
"AboutToClose");
285 LOG(VB_SOCKET, LOG_DEBUG,
LOC() +
286 "calling m_callback->readyRead()");
292 const QHostAddress &address, quint16 port)
295 QMetaObject::invokeMethod(
296 this,
"ConnectToHostReal",
298 Qt::BlockingQueuedConnection : Qt::DirectConnection,
299 Q_ARG(QHostAddress, address),
300 Q_ARG(quint16, port),
308 QMetaObject::invokeMethod(
309 this,
"WriteStringListReal",
311 Qt::BlockingQueuedConnection : Qt::DirectConnection,
312 Q_ARG(
const QStringList*, &list),
320 QMetaObject::invokeMethod(
321 this,
"ReadStringListReal",
323 Qt::BlockingQueuedConnection : Qt::DirectConnection,
324 Q_ARG(QStringList*, &list),
325 Q_ARG(std::chrono::milliseconds, timeoutMS),
331 QStringList &strlist,
uint min_reply_length, std::chrono::milliseconds timeoutMS)
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]));
347 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"Failed to send command.");
353 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"No response.");
357 if (min_reply_length && ((
uint)strlist.size() < min_reply_length))
359 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"Response too short.");
364 if (!strlist.empty() && strlist[0] ==
"BACKEND_MESSAGE")
366 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"Got MythEvent on non-event socket");
383 if (!hadr.setAddress(host))
390 QHostInfo
info = QHostInfo::fromName(host);
391 if (!
info.addresses().isEmpty())
393 hadr =
info.addresses().constFirst();
397 LOG(VB_GENERAL, LOG_ERR,
LOC() + QString(
"Unable to lookup: %1")
412 QStringList strlist(QString(
"MYTH_PROTO_VERSION %1 %2")
413 .arg(MYTH_PROTO_VERSION,
414 QString::fromUtf8(MYTH_PROTO_TOKEN)));
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.");
427 if (strlist[0] ==
"REJECT" && (strlist.size() >= 2))
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),
437 if (error_dialog_desired && GUIcontext)
439 QStringList list(strlist[1]);
440 QCoreApplication::postEvent(
441 GUIcontext,
new MythEvent(
"VERSION_MISMATCH", list));
444 else if (strlist[0] ==
"ACCEPT")
446 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Using protocol version %1 %2")
447 .arg(MYTH_PROTO_VERSION, QString::fromUtf8(MYTH_PROTO_TOKEN)));
452 LOG(VB_GENERAL, LOG_ERR,
453 QString(
"Unexpected response to MYTH_PROTO_VERSION: %1")
464 LOG(VB_GENERAL, LOG_ERR,
LOC() +
465 "refusing to announce unvalidated socket");
471 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"refusing to re-announce socket");
480 LOG(VB_GENERAL, LOG_ERR,
LOC() +
481 QString(
"\n\t\t\tCould not read string list from server %1:%2")
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));
512 QMetaObject::invokeMethod(
513 this,
"DisconnectFromHostReal",
515 Qt::BlockingQueuedConnection : Qt::DirectConnection);
521 QMetaObject::invokeMethod(
524 Qt::BlockingQueuedConnection : Qt::DirectConnection,
525 Q_ARG(
const char*, data),
534 QMetaObject::invokeMethod(
537 Qt::BlockingQueuedConnection : Qt::DirectConnection,
540 Q_ARG(std::chrono::milliseconds, max_wait),
547 QMetaObject::invokeMethod(
550 Qt::BlockingQueuedConnection : Qt::DirectConnection);
557 QMutexLocker locker(&
m_lock);
571 QMetaObject::invokeMethod(
572 this,
"IsDataAvailableReal",
573 Qt::BlockingQueuedConnection,
581 QMutexLocker locker(&
m_lock);
587 QMutexLocker locker(&
m_lock);
593 QMutexLocker locker(&
m_lock);
607 if (
m_tcpSocket->state() == QAbstractSocket::ConnectedState)
609 LOG(VB_SOCKET, LOG_ERR,
LOC() +
610 "connect() called with already open socket, closing");
614 QHostAddress addr = _addr;
615 addr.setScopeId(QString());
627 QList<QHostAddress> localIPs = QNetworkInterface::allAddresses();
628 for (
int i = 0; i < localIPs.count() && !usingLoopback; ++i)
630 QHostAddress local = localIPs[i];
631 local.setScopeId(QString());
635 QHostAddress::SpecialAddress loopback = QHostAddress::LocalHost;
636 if (addr.protocol() == QAbstractSocket::IPv6Protocol)
637 loopback = QHostAddress::LocalHostIPv6;
641 addr = QHostAddress(loopback);
642 usingLoopback =
true;
649 LOG(VB_SOCKET, LOG_INFO,
LOC() +
650 "IP is local, using loopback address instead");
653 LOG(VB_SOCKET, LOG_INFO,
LOC() + QString(
"attempting connect() to (%1:%2)")
654 .arg(addr.toString()).arg(port));
661 QString host = addr.toString();
663 addr.setAddress(host);
668 m_tcpSocket->connectToHost(addr, port, QAbstractSocket::ReadWrite);
674 LOG(VB_SOCKET, LOG_INFO,
LOC() + QString(
"Connected to (%1:%2)")
675 .arg(addr.toString()).arg(port));
679 LOG(VB_GENERAL, LOG_ERR,
LOC() +
680 QString(
"Failed to connect to (%1:%2) %3")
681 .arg(addr.toString()).arg(port)
697 LOG(VB_GENERAL, LOG_ERR,
LOC() +
698 "WriteStringList: Error, invalid string list.");
703 if (
m_tcpSocket->state() != QAbstractSocket::ConnectedState)
705 LOG(VB_GENERAL, LOG_ERR,
LOC() +
706 "WriteStringList: Error, called with unconnected socket.");
711 QString str = list->join(
"[]:[]");
714 LOG(VB_GENERAL, LOG_ERR,
LOC() +
715 "WriteStringList: Error, joined null string.");
720 QByteArray utf8 = str.toUtf8();
721 int size = utf8.length();
723 int written_since_timer_restart = 0;
726 payload = payload.setNum(size);
730 size = payload.length();
734 QString msg = QString(
"write -> %1 %2")
735 .arg(
m_tcpSocket->socketDescriptor(), 2).arg(payload.data());
737 if (
logLevel < LOG_DEBUG && msg.length() > 128)
742 LOG(VB_NETWORK, LOG_INFO,
LOC() + msg);
746 unsigned int errorcount = 0;
749 if (
m_tcpSocket->state() != QAbstractSocket::ConnectedState)
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)));
760 int temp =
m_tcpSocket->write(payload.data() + written, size);
764 written_since_timer_restart += temp;
766 if ((timer.
elapsed() > 500ms) && written_since_timer_restart != 0)
769 written_since_timer_restart = 0;
777 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"WriteStringList: Error, " +
778 QString(
"No data written on write (%1 errors)")
780 QString(
"\n\t\t\tstarts with: %1")
785 std::this_thread::sleep_for(1ms);
795 QStringList *list, std::chrono::milliseconds timeoutMS,
bool *ret)
802 std::chrono::milliseconds elapsed { 0ms };
807 if (elapsed >= timeoutMS)
809 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"ReadStringList: " +
810 QString(
"Error, timed out after %1 ms.").arg(timeoutMS.count()));
816 if (
m_tcpSocket->state() != QAbstractSocket::ConnectedState)
818 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"ReadStringList: Connection died.");
826 QByteArray sizestr(8,
'\0');
829 LOG(VB_GENERAL, LOG_ERR,
LOC() +
830 QString(
"ReadStringList: Error, read return error (%1)")
837 QString sizes = sizestr;
839 int btr = sizes.trimmed().toInt(&ok);
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)));
853 QByteArray utf8(btr + 1, 0);
855 qint64 readoffset = 0;
856 std::chrono::milliseconds errmsgtime { 0ms };
863 if (
m_tcpSocket->state() == QAbstractSocket::ConnectedState)
869 LOG(VB_GENERAL, LOG_ERR,
LOC() +
870 "ReadStringList: Connection died.");
876 qint64 sret =
m_tcpSocket->read(utf8.data() + readoffset, btr);
888 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"ReadStringList: Error, read");
895 LOG(VB_GENERAL, LOG_ERR,
LOC() +
896 "ReadStringList: Error, socket went unconnected");
906 if ((elapsed - errmsgtime) > 10s)
908 errmsgtime = elapsed;
909 LOG(VB_GENERAL, LOG_ERR,
LOC() +
910 QString(
"ReadStringList: Waiting for data: %1 %2")
911 .arg(readoffset).arg(btr));
917 LOG(VB_GENERAL, LOG_ERR,
LOC() +
918 "Error, ReadStringList timeout (readBlock)");
925 QString str = QString::fromUtf8(utf8.data());
930 payload = payload.setNum(str.length());
933 payload += utf8.data();
935 QString msg = QString(
"read <- %1 %2")
937 .arg(payload.data());
939 if (
logLevel < LOG_DEBUG && msg.length() > 128)
944 LOG(VB_NETWORK, LOG_INFO,
LOC() + msg);
947 *list = str.split(
"[]:[]");
963 while ((
m_tcpSocket->state() == QAbstractSocket::ConnectedState) &&
965 (
t.elapsed() < max_wait_ms))
967 m_tcpSocket->waitForReadyRead(max(2ms, max_wait_ms -
t.elapsed()).count());
971 if (
t.elapsed() > 50ms)
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()));
986 std::vector<char> trash;
989 while ((avail =
m_tcpSocket->bytesAvailable()) > 0)
991 trash.resize(std::max((
uint)trash.size(),avail));
994 LOG(VB_NETWORK, LOG_INFO,
LOC() +
"Reset() " +
995 QString(
"%1 bytes available").arg(avail));
This is a wrapper around QThread that does several additional things.
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
void quit(void)
calls exit(0)
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
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.
virtual void readyRead(MythSocket *)=0
virtual void connected(MythSocket *)=0
virtual void error(MythSocket *, int)
virtual void connectionClosed(MythSocket *)=0
void ConnectHandler(void)
static const int kSocketReceiveBufferSize
void WriteStringListReal(const QStringList *list, bool *ret)
void IsDataAvailableReal(bool *ret) const
QAtomicInt m_disableReadyReadCallback
static QHash< QString, QHostAddress::SpecialAddress > s_loopbackCache
static MThread * s_thread
void SetAnnounce(const QStringList &new_announce)
bool SendReceiveStringList(QStringList &list, uint min_reply_length=0, std::chrono::milliseconds timeoutMS=kLongTimeout)
bool Announce(const QStringList &new_announce)
bool Validate(std::chrono::milliseconds timeout=kMythSocketLongTimeout, bool error_dialog_desired=false)
void AboutToCloseHandler(void)
bool ReadStringList(QStringList &list, std::chrono::milliseconds timeoutMS=kShortTimeout)
static QMutex s_loopbackCacheLock
bool IsConnected(void) const
bool IsDataAvailable(void)
qintptr m_socketDescriptor
void ReadyReadHandler(void)
MythSocketCBs * m_callback
void CallReadyReadHandler(void)
static QMutex s_thread_lock
static constexpr std::chrono::milliseconds kShortTimeout
void DisconnectHandler(void)
void ReadStringListReal(QStringList *list, std::chrono::milliseconds timeoutMS, bool *ret)
int Read(char *data, int size, std::chrono::milliseconds max_wait)
int GetSocketDescriptor(void) const
int GetPeerPort(void) const
void WriteReal(const char *data, int size, int *ret)
void ReadReal(char *data, int size, std::chrono::milliseconds max_wait_ms, int *ret)
QHostAddress m_peerAddress
void DisconnectFromHost(void)
int Write(const char *data, int size)
bool WriteStringList(const QStringList &list)
bool ConnectToHost(const QString &hostname, quint16 port)
connect to host
MythSocket(qintptr socket=-1, MythSocketCBs *cb=nullptr, bool use_shared_thread=false)
QHostAddress GetPeerAddress(void) const
QAtomicInt m_dataAvailable
This is used internally as a hint that there might be data available for reading.
void DisconnectFromHostReal(void)
void ConnectToHostReal(const QHostAddress &addr, quint16 port, bool *ret)
void ErrorHandler(QAbstractSocket::SocketError err)
A QElapsedTimer based timer to replace use of QTime as a timer.
std::chrono::milliseconds restart(void)
Returns milliseconds elapsed since last start() or restart() and resets the count.
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
void start(void)
starts measuring elapsed time.
Small class to handle TCP port checking and finding link-local context.
bool resolveLinkLocal(QString &host, int port, std::chrono::milliseconds timeLimit=30s)
Convenience method to resolve link-local address.
General purpose reference counter.
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)
#define ENO
This can be appended to the LOG args with "+".
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Q_DECLARE_METATYPE(const QStringList *)
int s_dummy_meta_variable_to_suppress_gcc_warning
static QString to_sample(const QByteArray &payload)