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