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 
33 const int MythSocket::kSocketReceiveBufferSize = 128 * 1024;
34 
36 QHash<QString, QHostAddress::SpecialAddress> MythSocket::s_loopbackCache;
37 
39 MThread *MythSocket::s_thread = nullptr;
41 
42 Q_DECLARE_METATYPE ( const QStringList * );
43 Q_DECLARE_METATYPE ( QStringList * );
44 Q_DECLARE_METATYPE ( const char * );
45 Q_DECLARE_METATYPE ( char * );
46 Q_DECLARE_METATYPE ( bool * );
47 Q_DECLARE_METATYPE ( int * );
48 Q_DECLARE_METATYPE ( QHostAddress );
49 static int x0 = qRegisterMetaType< const QStringList * >();
50 static int x1 = qRegisterMetaType< QStringList * >();
51 static int x2 = qRegisterMetaType< const char * >();
52 static int x3 = qRegisterMetaType< char * >();
53 static int x4 = qRegisterMetaType< bool * >();
54 static int x5 = qRegisterMetaType< int * >();
55 static int x6 = qRegisterMetaType< QHostAddress >();
57  x0 + x1 + x2 + x3 + x4 + x5 + x6;
58 
59 static 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  }
132  m_thread = s_thread;
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 
148  if (!m_useSharedThread)
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 
226 void 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;
246  m_socketDescriptor = -1;
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 
301 bool 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 
313 bool 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 
374 bool 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 
403 bool 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 
456 bool 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 
492 void 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 
514 int 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 
527 int 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 
551 bool MythSocket::IsConnected(void) const
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 
581 QHostAddress MythSocket::GetPeerAddress(void) const
582 {
583  QMutexLocker locker(&m_lock);
584  return m_peerAddress;
585 }
586 
587 int MythSocket::GetPeerPort(void) const
588 {
589  QMutexLocker locker(&m_lock);
590  return m_peerPort;
591 }
592 
594 
595 void MythSocket::IsDataAvailableReal(bool *ret) const
596 {
597  *ret = (m_tcpSocket->bytesAvailable() > 0);
598  m_dataAvailable.fetchAndStoreOrdered((*ret) ? 1 : 0);
599 }
600 
601 void 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 
689 void 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 
951 void MythSocket::WriteReal(const char *data, int size, int *ret)
952 {
953  *ret = m_tcpSocket->write(data, size);
954 }
955 
956 void 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  std::vector<char> trash;
982 
983  m_tcpSocket->waitForReadyRead(30);
984  do
985  {
986  uint avail = m_tcpSocket->bytesAvailable();
987  if (avail)
988  {
989  trash.resize(std::max((uint)trash.size(),avail));
990  m_tcpSocket->read(trash.data(), avail);
991  }
992 
993  LOG(VB_NETWORK, LOG_INFO, LOC() + "Reset() " +
994  QString("%1 bytes available").arg(avail));
995 
996  m_tcpSocket->waitForReadyRead(30);
997  }
998  while (m_tcpSocket->bytesAvailable() > 0);
999 
1000  m_dataAvailable.fetchAndStoreOrdered(0);
1001 }
MythSocket::m_socketDescriptor
qintptr m_socketDescriptor
Definition: mythsocket.h:109
MythSocket::ConnectToHostReal
void ConnectToHostReal(const QHostAddress &addr, quint16 port, bool *ret)
Definition: mythsocket.cpp:601
MythSocket::m_connected
bool m_connected
Definition: mythsocket.h:115
MythTimer::elapsed
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:91
mythevent.h
MythSocket::m_dataAvailable
QAtomicInt m_dataAvailable
This is used internally as a hint that there might be data available for reading.
Definition: mythsocket.h:118
MThread::start
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:283
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:73
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:102
MThread::quit
void quit(void)
calls exit(0)
Definition: mthread.cpp:295
MythSocket::m_disableReadyReadCallback
QAtomicInt m_disableReadyReadCallback
Definition: mythsocket.h:114
MythTimer
A QElapsedTimer based timer to replace use of QTime as a timer.
Definition: mythtimer.h:13
MythSocket::m_tcpSocket
QTcpSocket * m_tcpSocket
Definition: mythsocket.h:106
MThread::wait
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:300
MythSocket::ReadReal
void ReadReal(char *data, int size, std::chrono::milliseconds max_wait_ms, int *ret)
Definition: mythsocket.cpp:956
logLevel
LogLevel_t logLevel
Definition: logging.cpp:85
x2
static int x2
Definition: mythsocket.cpp:51
MythSocket::s_loopbackCacheLock
static QMutex s_loopbackCacheLock
Definition: mythsocket.h:125
MythSocket::~MythSocket
~MythSocket() override
Definition: mythsocket.cpp:140
MythSocket::Reset
void Reset(void)
Definition: mythsocket.cpp:541
MythSocket::m_peerPort
int m_peerPort
Definition: mythsocket.h:111
MythEvent
This class is used as a container for messages.
Definition: mythevent.h:16
VERBOSE_LEVEL_CHECK
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
x0
static int x0
Definition: mythsocket.cpp:49
MythSocket::ConnectHandler
void ConnectHandler(void)
Definition: mythsocket.cpp:175
MythSocket::DisconnectFromHost
void DisconnectFromHost(void)
Definition: mythsocket.cpp:498
x3
static int x3
Definition: mythsocket.cpp:52
MythSocket::DisconnectHandler
void DisconnectHandler(void)
Definition: mythsocket.cpp:241
MythSocket::kSocketReceiveBufferSize
static const int kSocketReceiveBufferSize
Definition: mythsocket.h:123
MythTimer::start
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythSocket::LOC
QString LOC()
Definition: mythsocket.h:74
MythSocket::Announce
bool Announce(const QStringList &new_announce)
Definition: mythsocket.cpp:456
MythSocket::s_thread
static MThread * s_thread
Definition: mythsocket.h:129
MythSocket::ConnectToHost
bool ConnectToHost(const QString &hostname, quint16 port)
connect to host
Definition: mythsocket.cpp:374
MythSocket::m_useSharedThread
bool m_useSharedThread
Definition: mythsocket.h:113
m_callback
static void(* m_callback)(void *, QString &)
Definition: mytharchive.cpp:252
MythSocket::IsConnected
bool IsConnected(void) const
Definition: mythsocket.cpp:551
s_dummy_meta_variable_to_suppress_gcc_warning
int s_dummy_meta_variable_to_suppress_gcc_warning
Definition: mythsocket.cpp:56
x6
static int x6
Definition: mythsocket.cpp:55
x4
static int x4
Definition: mythsocket.cpp:53
MythSocket::s_loopbackCache
static QHash< QString, QHostAddress::SpecialAddress > s_loopbackCache
Definition: mythsocket.h:126
MythSocket::m_thread
MThread * m_thread
Definition: mythsocket.h:107
MythSocketCBs::readyRead
virtual void readyRead(MythSocket *)=0
mythlogging.h
MythSocket::DisconnectFromHostReal
void DisconnectFromHostReal(void)
Definition: mythsocket.cpp:684
MythCoreContext::GetGUIContext
QObject * GetGUIContext(void)
Definition: mythcorecontext.cpp:1748
MythSocket::WriteStringList
bool WriteStringList(const QStringList &list)
Definition: mythsocket.cpp:301
MythSocket::s_thread_lock
static QMutex s_thread_lock
Definition: mythsocket.h:128
hardwareprofile.i18n.t
t
Definition: i18n.py:36
MythCoreContext::GetBackendServerIP
QString GetBackendServerIP(void)
Returns the IP address of the locally defined backend IP.
Definition: mythcorecontext.cpp:1004
MythSocket::m_callback
MythSocketCBs * m_callback
Definition: mythsocket.h:112
MythSocket::s_thread_cnt
static int s_thread_cnt
Definition: mythsocket.h:130
x1
static int x1
Definition: mythsocket.cpp:50
MythSocket::CallReadyRead
void CallReadyRead(void)
MythTimer::restart
std::chrono::milliseconds restart(void)
Returns milliseconds elapsed since last start() or restart() and resets the count.
Definition: mythtimer.cpp:62
MThread::qthread
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
MythSocket::CallReadyReadHandler
void CallReadyReadHandler(void)
Definition: mythsocket.cpp:273
MythSocketCBs
Definition: mythsocket_cb.h:13
portchecker.h
MythSocket::kShortTimeout
static constexpr std::chrono::milliseconds kShortTimeout
Definition: mythsocket.h:70
PortChecker::resolveLinkLocal
static bool resolveLinkLocal(QString &host, int port, std::chrono::milliseconds timeLimit=30s)
Convenience method to resolve link-local address.
Definition: portchecker.cpp:226
MythSocket::ReadyReadHandler
void ReadyReadHandler(void)
Definition: mythsocket.cpp:264
uint
unsigned int uint
Definition: compat.h:81
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
MythSocket::Read
int Read(char *data, int size, std::chrono::milliseconds max_wait)
Definition: mythsocket.cpp:527
x5
static int x5
Definition: mythsocket.cpp:54
to_sample
static QString to_sample(const QByteArray &payload)
Definition: mythsocket.cpp:59
MythSocket::WriteStringListReal
void WriteStringListReal(const QStringList *list, bool *ret)
Definition: mythsocket.cpp:689
MythSocket::MythSocket
MythSocket(qintptr socket=-1, MythSocketCBs *cb=nullptr, bool use_shared_thread=false)
Definition: mythsocket.cpp:71
MythSocket::m_announce
QStringList m_announce
Definition: mythsocket.h:121
MythSocket::Write
int Write(const char *data, int size)
Definition: mythsocket.cpp:514
MythSocketCBs::connected
virtual void connected(MythSocket *)=0
MythSocket::ResetReal
void ResetReal(void)
Definition: mythsocket.cpp:979
MythSocket::AboutToCloseHandler
void AboutToCloseHandler(void)
Definition: mythsocket.cpp:259
mythcorecontext.h
MythSocketCBs::connectionClosed
virtual void connectionClosed(MythSocket *)=0
MythSocket::IsDataAvailableReal
void IsDataAvailableReal(bool *ret) const
Definition: mythsocket.cpp:595
MythSocket::Validate
bool Validate(std::chrono::milliseconds timeout=kMythSocketLongTimeout, bool error_dialog_desired=false)
Definition: mythsocket.cpp:403
MythSocket::IsDataAvailable
bool IsDataAvailable(void)
Definition: mythsocket.cpp:557
MythSocketCBs::error
virtual void error(MythSocket *, int)
Definition: mythsocket_cb.h:18
MythSocket::GetPeerAddress
QHostAddress GetPeerAddress(void) const
Definition: mythsocket.cpp:581
MythSocket::GetSocketDescriptor
int GetSocketDescriptor(void) const
Definition: mythsocket.cpp:575
MThread
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:48
Q_DECLARE_METATYPE
Q_DECLARE_METATYPE(const QStringList *)
MythSocket::SetAnnounce
void SetAnnounce(const QStringList &new_announce)
Definition: mythsocket.cpp:492
MythSocket::m_peerAddress
QHostAddress m_peerAddress
Definition: mythsocket.h:110
MythSocket::GetPeerPort
int GetPeerPort(void) const
Definition: mythsocket.cpp:587
mythtimer.h
MythSocket::m_isValidated
bool m_isValidated
Definition: mythsocket.h:119
MythSocket::ReadStringListReal
void ReadStringListReal(QStringList *list, std::chrono::milliseconds timeoutMS, bool *ret)
Definition: mythsocket.cpp:790
MythCoreContext::IsExiting
bool IsExiting(void)
Definition: mythcorecontext.cpp:2117
MythSocket::m_lock
QMutex m_lock
Definition: mythsocket.h:108
MythCoreContext::CheckSubnet
bool CheckSubnet(const QAbstractSocket *socket)
Check if a socket is connected to an approved peer.
Definition: mythcorecontext.cpp:1268
mythsocket.h
MythSocket::ReadStringList
bool ReadStringList(QStringList &list, std::chrono::milliseconds timeoutMS=kShortTimeout)
Definition: mythsocket.cpp:313
MythSocket::WriteReal
void WriteReal(const char *data, int size, int *ret)
Definition: mythsocket.cpp:951
MythSocket::ErrorHandler
void ErrorHandler(QAbstractSocket::SocketError err)
Definition: mythsocket.cpp:226
MythSocket::m_isAnnounced
bool m_isAnnounced
Definition: mythsocket.h:120
ReferenceCounter
General purpose reference counter.
Definition: referencecounter.h:26
MythSocket::SendReceiveStringList
bool SendReceiveStringList(QStringList &list, uint min_reply_length=0, std::chrono::milliseconds timeoutMS=kLongTimeout)
Definition: mythsocket.cpp:326