3#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
4#include <QtSystemDetection>
7#include <QNetworkInterface>
8#include <QCoreApplication>
9#include <QWaitCondition>
10#include <QSharedPointer>
23#include <sys/socket.h>
33#include "mythversion.h"
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 >();
67 for (
uint i = 0; (i<60) && (i<(
uint)payload.length()); i++)
69 sample += QChar(payload[i]).isPrint() ?
70 QChar(payload[i]) : QChar(
'?');
72 sample += (payload.length() > 60) ?
"..." :
"";
79 m_tcpSocket(new QTcpSocket()),
81 m_useSharedThread(use_shared_thread)
83 LOG(VB_SOCKET, LOG_INFO,
LOC() + QString(
"MythSocket(%1, 0x%2) ctor")
84 .arg(socket).arg((intptr_t)(cb),0,16));
89 socket, QAbstractSocket::ConnectedState,
90 QAbstractSocket::ReadWrite);
107 Qt::DirectConnection);
108 connect(
m_tcpSocket, &QAbstractSocket::errorOccurred,
110 Qt::DirectConnection);
113 connect(
m_tcpSocket, &QAbstractSocket::disconnected,
115 Qt::DirectConnection);
118 Qt::DirectConnection);
122 Qt::QueuedConnection);
124 if (!use_shared_thread)
147 LOG(VB_SOCKET, LOG_INFO,
LOC() + QString(
"MythSocket dtor : cb 0x%2")
183 QMutexLocker locker(&
m_lock);
190 m_tcpSocket->setSocketOption(QAbstractSocket::LowDelayOption, QVariant(1));
191 m_tcpSocket->setSocketOption(QAbstractSocket::KeepAliveOption, QVariant(1));
193 int reuse_addr_val = 1;
195 int ret = setsockopt(
m_tcpSocket->socketDescriptor(), SOL_SOCKET,
196 SO_REUSEADDR, (
char*) &reuse_addr_val,
197 sizeof(reuse_addr_val));
199 int ret = setsockopt(
m_tcpSocket->socketDescriptor(), SOL_SOCKET,
200 SO_REUSEADDR, &reuse_addr_val,
201 sizeof(reuse_addr_val));
205 LOG(VB_SOCKET, LOG_INFO,
LOC() +
"Failed to set SO_REUSEADDR" +
ENO);
210 ret = setsockopt(
m_tcpSocket->socketDescriptor(), SOL_SOCKET,
211 SO_RCVBUF, (
char*) &rcv_buf_val,
212 sizeof(rcv_buf_val));
214 ret = setsockopt(
m_tcpSocket->socketDescriptor(), SOL_SOCKET,
215 SO_RCVBUF, &rcv_buf_val,
216 sizeof(rcv_buf_val));
220 LOG(VB_SOCKET, LOG_INFO,
LOC() +
"Failed to set SO_RCVBUF" +
ENO);
225 LOG(VB_SOCKET, LOG_DEBUG,
LOC() +
226 "calling m_callback->connected()");
235 if (err == QAbstractSocket::SocketTimeoutError)
240 LOG(VB_SOCKET, LOG_DEBUG,
LOC() +
241 "calling m_callback->error() err: " +
m_tcpSocket->errorString());
249 QMutexLocker locker(&
m_lock);
258 LOG(VB_SOCKET, LOG_DEBUG,
LOC() +
259 "calling m_callback->connectionClosed()");
266 LOG(VB_SOCKET, LOG_DEBUG,
LOC() +
"AboutToClose");
286 LOG(VB_SOCKET, LOG_DEBUG,
LOC() +
287 "calling m_callback->readyRead()");
293 const QHostAddress &address, quint16 port)
296 QMetaObject::invokeMethod(
297 this,
"ConnectToHostReal",
299 Qt::BlockingQueuedConnection : Qt::DirectConnection,
300 Q_ARG(QHostAddress, address),
301 Q_ARG(quint16, port),
309 QMetaObject::invokeMethod(
310 this,
"WriteStringListReal",
312 Qt::BlockingQueuedConnection : Qt::DirectConnection,
313 Q_ARG(
const QStringList*, &list),
321 QMetaObject::invokeMethod(
322 this,
"ReadStringListReal",
324 Qt::BlockingQueuedConnection : Qt::DirectConnection,
325 Q_ARG(QStringList*, &list),
326 Q_ARG(std::chrono::milliseconds, timeoutMS),
332 QStringList &strlist,
uint min_reply_length, std::chrono::milliseconds timeoutMS)
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]));
348 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"Failed to send command.");
354 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"No response.");
358 if (min_reply_length && ((
uint)strlist.size() < min_reply_length))
360 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"Response too short.");
365 if (!strlist.empty() && strlist[0] ==
"BACKEND_MESSAGE")
367 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"Got MythEvent on non-event socket");
384 if (!hadr.setAddress(host))
391 QHostInfo
info = QHostInfo::fromName(host);
392 if (!
info.addresses().isEmpty())
394 hadr =
info.addresses().constFirst();
398 LOG(VB_GENERAL, LOG_ERR,
LOC() + QString(
"Unable to lookup: %1")
413 QStringList strlist(QString(
"MYTH_PROTO_VERSION %1 %2")
414 .arg(MYTH_PROTO_VERSION,
415 QString::fromUtf8(MYTH_PROTO_TOKEN)));
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.");
428 if (strlist[0] ==
"REJECT" && (strlist.size() >= 2))
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),
438 if (error_dialog_desired && GUIcontext)
440 QStringList list(strlist[1]);
441 QCoreApplication::postEvent(
442 GUIcontext,
new MythEvent(
"VERSION_MISMATCH", list));
445 else if (strlist[0] ==
"ACCEPT")
447 LOG(VB_GENERAL, LOG_NOTICE, QString(
"Using protocol version %1 %2")
448 .arg(MYTH_PROTO_VERSION, QString::fromUtf8(MYTH_PROTO_TOKEN)));
453 LOG(VB_GENERAL, LOG_ERR,
454 QString(
"Unexpected response to MYTH_PROTO_VERSION: %1")
465 LOG(VB_GENERAL, LOG_ERR,
LOC() +
466 "refusing to announce unvalidated socket");
472 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"refusing to re-announce socket");
481 LOG(VB_GENERAL, LOG_ERR,
LOC() +
482 QString(
"\n\t\t\tCould not read string list from server %1:%2")
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));
513 QMetaObject::invokeMethod(
514 this,
"DisconnectFromHostReal",
516 Qt::BlockingQueuedConnection : Qt::DirectConnection);
522 QMetaObject::invokeMethod(
525 Qt::BlockingQueuedConnection : Qt::DirectConnection,
526 Q_ARG(
const char*, data),
535 QMetaObject::invokeMethod(
538 Qt::BlockingQueuedConnection : Qt::DirectConnection,
541 Q_ARG(std::chrono::milliseconds, max_wait),
548 QMetaObject::invokeMethod(
551 Qt::BlockingQueuedConnection : Qt::DirectConnection);
558 QMutexLocker locker(&
m_lock);
572 QMetaObject::invokeMethod(
573 this,
"IsDataAvailableReal",
574 Qt::BlockingQueuedConnection,
582 QMutexLocker locker(&
m_lock);
588 QMutexLocker locker(&
m_lock);
594 QMutexLocker locker(&
m_lock);
608 if (
m_tcpSocket->state() == QAbstractSocket::ConnectedState)
610 LOG(VB_SOCKET, LOG_ERR,
LOC() +
611 "connect() called with already open socket, closing");
615 QHostAddress addr = _addr;
616 addr.setScopeId(QString());
628 QList<QHostAddress> localIPs = QNetworkInterface::allAddresses();
629 for (
int i = 0; i < localIPs.count() && !usingLoopback; ++i)
631 QHostAddress local = localIPs[i];
632 local.setScopeId(QString());
636 QHostAddress::SpecialAddress loopback = QHostAddress::LocalHost;
637 if (addr.protocol() == QAbstractSocket::IPv6Protocol)
638 loopback = QHostAddress::LocalHostIPv6;
642 addr = QHostAddress(loopback);
643 usingLoopback =
true;
650 LOG(VB_SOCKET, LOG_INFO,
LOC() +
651 "IP is local, using loopback address instead");
654 LOG(VB_SOCKET, LOG_INFO,
LOC() + QString(
"attempting connect() to (%1:%2)")
655 .arg(addr.toString()).arg(port));
662 QString host = addr.toString();
664 addr.setAddress(host);
669 m_tcpSocket->connectToHost(addr, port, QAbstractSocket::ReadWrite);
675 LOG(VB_SOCKET, LOG_INFO,
LOC() + QString(
"Connected to (%1:%2)")
676 .arg(addr.toString()).arg(port));
680 LOG(VB_GENERAL, LOG_ERR,
LOC() +
681 QString(
"Failed to connect to (%1:%2) %3")
682 .arg(addr.toString()).arg(port)
698 LOG(VB_GENERAL, LOG_ERR,
LOC() +
699 "WriteStringList: Error, invalid string list.");
704 if (
m_tcpSocket->state() != QAbstractSocket::ConnectedState)
706 LOG(VB_GENERAL, LOG_ERR,
LOC() +
707 "WriteStringList: Error, called with unconnected socket.");
712 QString str = list->join(
"[]:[]");
715 LOG(VB_GENERAL, LOG_ERR,
LOC() +
716 "WriteStringList: Error, joined null string.");
721 QByteArray utf8 = str.toUtf8();
722 int size = utf8.length();
724 int written_since_timer_restart = 0;
727 payload = payload.setNum(size);
731 size = payload.length();
735 QString msg = QString(
"write -> %1 %2")
736 .arg(
m_tcpSocket->socketDescriptor(), 2).arg(payload.data());
738 if (
logLevel < LOG_DEBUG && msg.length() > 128)
743 LOG(VB_NETWORK, LOG_INFO,
LOC() + msg);
747 unsigned int errorcount = 0;
750 if (
m_tcpSocket->state() != QAbstractSocket::ConnectedState)
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)));
761 int temp =
m_tcpSocket->write(payload.data() + written, size);
765 written_since_timer_restart += temp;
767 if ((timer.
elapsed() > 500ms) && written_since_timer_restart != 0)
770 written_since_timer_restart = 0;
778 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"WriteStringList: Error, " +
779 QString(
"No data written on write (%1 errors)")
781 QString(
"\n\t\t\tstarts with: %1")
786 std::this_thread::sleep_for(1ms);
796 QStringList *list, std::chrono::milliseconds timeoutMS,
bool *ret)
803 std::chrono::milliseconds elapsed { 0ms };
808 if (elapsed >= timeoutMS)
810 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"ReadStringList: " +
811 QString(
"Error, timed out after %1 ms.").arg(timeoutMS.count()));
817 if (
m_tcpSocket->state() != QAbstractSocket::ConnectedState)
819 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"ReadStringList: Connection died.");
827 QByteArray sizestr(8,
'\0');
830 LOG(VB_GENERAL, LOG_ERR,
LOC() +
831 QString(
"ReadStringList: Error, read return error (%1)")
838 QString sizes = sizestr;
840 int btr = sizes.trimmed().toInt(&ok);
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)));
854 QByteArray utf8(btr + 1, 0);
856 qint64 readoffset = 0;
857 std::chrono::milliseconds errmsgtime { 0ms };
864 if (
m_tcpSocket->state() == QAbstractSocket::ConnectedState)
870 LOG(VB_GENERAL, LOG_ERR,
LOC() +
871 "ReadStringList: Connection died.");
877 qint64 sret =
m_tcpSocket->read(utf8.data() + readoffset, btr);
889 LOG(VB_GENERAL, LOG_ERR,
LOC() +
"ReadStringList: Error, read");
896 LOG(VB_GENERAL, LOG_ERR,
LOC() +
897 "ReadStringList: Error, socket went unconnected");
907 if ((elapsed - errmsgtime) > 10s)
909 errmsgtime = elapsed;
910 LOG(VB_GENERAL, LOG_ERR,
LOC() +
911 QString(
"ReadStringList: Waiting for data: %1 %2")
912 .arg(readoffset).arg(btr));
918 LOG(VB_GENERAL, LOG_ERR,
LOC() +
919 "Error, ReadStringList timeout (readBlock)");
926 QString str = QString::fromUtf8(utf8.data());
931 payload = payload.setNum(str.length());
934 payload += utf8.data();
936 QString msg = QString(
"read <- %1 %2")
938 .arg(payload.data());
940 if (
logLevel < LOG_DEBUG && msg.length() > 128)
945 LOG(VB_NETWORK, LOG_INFO,
LOC() + msg);
948 *list = str.split(
"[]:[]");
964 while ((
m_tcpSocket->state() == QAbstractSocket::ConnectedState) &&
966 (
t.elapsed() < max_wait_ms))
968 m_tcpSocket->waitForReadyRead(max(2ms, max_wait_ms -
t.elapsed()).count());
972 if (
t.elapsed() > 50ms)
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()));
987 std::vector<char> trash;
990 while ((avail =
m_tcpSocket->bytesAvailable()) > 0)
992 trash.resize(std::max((
uint)trash.size(),avail));
995 LOG(VB_NETWORK, LOG_INFO,
LOC() +
"Reset() " +
996 QString(
"%1 bytes available").arg(avail));
1004#include "moc_mythsocket.cpp"
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)