Torc  0.1
torcnetworkedcontext.cpp
Go to the documentation of this file.
1 /* Class TorcNetworkedContext
2 *
3 * This file is part of the Torc project.
4 *
5 * Copyright (C) Mark Kendall 2013-18
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20 * USA.
21 */
22 
23 // Torc
24 #include "torcadminthread.h"
25 #include "torclanguage.h"
26 #include "torcnetwork.h"
27 #include "torcbonjour.h"
28 #include "torcevent.h"
29 #include "torcupnp.h"
30 #include "torcwebsocket.h"
31 #include "torcrpcrequest.h"
32 #include "torcnetworkrequest.h"
33 #include "torcwebsocketthread.h"
34 #include "torchttpserver.h"
35 #include "torcnetworkedcontext.h"
36 
38 
45 TorcNetworkService::TorcNetworkService(const QString &Name, const QString &UUID, int Port,
46  bool Secure, const QList<QHostAddress> &Addresses)
47  : QObject(),
48  name(Name),
49  uuid(UUID),
50  port(Port),
51  host(),
52  secure(Secure),
53  uiAddress(QStringLiteral()),
54  startTime(0),
55  priority(-1),
56  apiVersion(QStringLiteral()),
57  connected(false),
58  m_sources(Spontaneous),
59  m_debugString(),
60  m_addresses(Addresses),
61  m_preferredAddressIndex(0),
62  m_abort(0),
63  m_getPeerDetailsRPC(nullptr),
64  m_getPeerDetails(nullptr),
65  m_webSocketThread(nullptr),
66  m_retryScheduled(false),
67  m_retryInterval(10000)
68 {
69  for (int i = 0; i < m_addresses.size(); ++i)
70  {
71  if (m_addresses.at(i).protocol() == QAbstractSocket::IPv4Protocol)
72  m_preferredAddressIndex = i;
73 
74  uiAddress += TorcNetwork::IPAddressToLiteral(m_addresses.at(i), port) + ((i == m_addresses.size() - 1) ? "" : ", ");
75  }
76 
77  if (m_addresses.isEmpty())
78  m_debugString = QStringLiteral("No address found");
79  else
80  m_debugString = TorcNetwork::IPAddressToLiteral(m_addresses[m_preferredAddressIndex], port);
81 
83 }
84 
97 {
98  // cancel any outstanding requests
99  m_abort = 1;
100 
101  if (m_getPeerDetailsRPC && m_webSocketThread)
102  {
103  m_webSocketThread->CancelRequest(m_getPeerDetailsRPC);
104  m_getPeerDetailsRPC->DownRef();
105  m_getPeerDetailsRPC = nullptr;
106  }
107 
108  if (m_getPeerDetails)
109  {
110  TorcNetwork::Cancel(m_getPeerDetails);
111  m_getPeerDetails->DownRef();
112  m_getPeerDetails = nullptr;
113  }
114 
115  // delete websocket
116  if (m_webSocketThread)
117  {
118  m_webSocketThread->quit();
119  m_webSocketThread->wait();
120  delete m_webSocketThread;
121  m_webSocketThread = nullptr;
122  }
123 }
124 
132 bool TorcNetworkService::WeActAsServer(int Priority, qint64 StartTime, const QString &UUID)
133 {
134  if (Priority != gLocalContext->GetPriority())
135  return Priority < gLocalContext->GetPriority();
136  if (StartTime != gLocalContext->GetStartTime())
137  return StartTime > gLocalContext->GetStartTime();
138  if (UUID != gLocalContext->GetUuid())
139  return UUID < gLocalContext->GetUuid();
140  return false; // at least both devices will try and connect...
141 }
142 
151 {
152  // clear retry flag
153  m_retryScheduled = false;
154 
155  // sanity check
156  if (m_addresses.isEmpty())
157  {
158  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("No valid peer addresses"));
159  return;
160  }
161 
162  // NB all connection methods should now provide all necessary peer data. QueryPeerDetails should now be
163  // redundant.
164  if (startTime == 0 || apiVersion.isEmpty() || priority < 0)
165  {
166  QueryPeerDetails();
167  return;
168  }
169 
170  // already connected
171  if (m_webSocketThread)
172  {
173  // notify the parent that the connection is complete
174  if (gNetworkedContext)
175  gNetworkedContext->Connected(this);
176 
177  connected = true;
178  emit ConnectedChanged();
179 
180  return;
181  }
182 
184  {
185  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Not connecting to %1 - we have priority").arg(m_debugString));
186  return;
187  }
188 
189  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Trying to connect to %1").arg(m_debugString));
190 
191  m_webSocketThread = new TorcWebSocketThread(m_addresses.at(m_preferredAddressIndex), port, secure);
192  connect(m_webSocketThread, &TorcWebSocketThread::Finished, this, &TorcNetworkService::Disconnected);
193  connect(m_webSocketThread, &TorcWebSocketThread::ConnectionUpgraded, this, &TorcNetworkService::Connected);
194 
195  m_webSocketThread->start();
196 }
197 
199 {
200  return name;
201 }
202 
204 {
205  return uuid;
206 }
207 
209 {
210  return port;
211 }
212 
214 {
215  return host;
216 }
217 
219 {
220  return uiAddress;
221 }
222 
224 {
225  return startTime;
226 }
227 
229 {
230  return priority;
231 }
232 
234 {
235  return apiVersion;
236 }
237 
239 {
240  return connected;
241 }
242 
244 {
245  TorcWebSocketThread *thread = static_cast<TorcWebSocketThread*>(sender());
246  if (m_webSocketThread && m_webSocketThread == thread)
247  {
248  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Connection established with %1").arg(m_debugString));
249  emit TryConnect();
250  }
251  else
252  {
253  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Unknown WebSocket connected..."));
254  }
255 }
256 
258 {
259  TorcWebSocketThread *thread = static_cast<TorcWebSocketThread*>(sender());
260  if (m_webSocketThread && m_webSocketThread == thread)
261  {
262  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Connection with %1 closed").arg(m_debugString));
263  m_webSocketThread->quit();
264  m_webSocketThread->wait();
265  delete m_webSocketThread;
266  m_webSocketThread = nullptr;
267 
268  // try and reconnect. If this is a discovered service, the socket was probably closed
269  // deliberately and this object is about to be deleted anyway.
270  ScheduleRetry();
271 
272  // notify the parent
273  if (gNetworkedContext)
274  {
275  gNetworkedContext->Disconnected(this);
276 
277  connected = false;
278  emit ConnectedChanged();
279  }
280  }
281  else
282  {
283  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Unknown WebSocket disconnected..."));
284  }
285 }
286 
288 {
289  if (m_getPeerDetails && (Request == m_getPeerDetails))
290  {
291  QJsonDocument jsonresult = QJsonDocument::fromJson(Request->GetBuffer());
292  m_getPeerDetails->DownRef();
293  m_getPeerDetails = nullptr;
294 
295  if (!jsonresult.isNull() && jsonresult.isObject())
296  {
297  QJsonObject object = jsonresult.object();
298 
299  if (object.contains(QStringLiteral("details")))
300  {
301  QJsonObject details = object.value(QStringLiteral("details")).toObject();
302  QJsonValueRef jpriority = details[TORC_PRIORITY];
303  QJsonValueRef jstarttime = details[TORC_STARTTIME];
304  QJsonValueRef jversion = details[TORC_SERVICE_VERSION];
305 
306  if (!jpriority.isNull() && !jstarttime.isNull() && !jversion.isNull())
307  {
308  apiVersion = jversion.toString();
309  priority = (int)jpriority.toDouble();
310  startTime = (qint64)jstarttime.toDouble();
311 
312  emit ApiVersionChanged();
313  emit PriorityChanged();
314  emit StartTimeChanged();
315 
316  emit TryConnect();
317  return;
318  }
319  else
320  {
321  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to retrieve peer information"));
322  }
323  }
324  else
325  {
326  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to find 'details' in peer response"));
327  }
328  }
329  else
330  {
331  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error parsing API return - expecting JSON object"));
332  }
333 
334  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Response:\r\n%1").arg(jsonresult.toJson().constData()));
335 
336  // try again...
337  ScheduleRetry();
338  }
339 }
340 
342 {
343  if (!Request)
344  return;
345 
346  if (Request == m_getPeerDetailsRPC)
347  {
348  bool success = false;
349  QString message;
350  int state = m_getPeerDetailsRPC->GetState();
351 
352  if (state & TorcRPCRequest::TimedOut)
353  {
354  message = QStringLiteral("Timed out");
355  }
356  else if (state & TorcRPCRequest::Cancelled)
357  {
358  message = QStringLiteral("Cancelled");
359  }
360  else if (state & TorcRPCRequest::Errored)
361  {
362  QVariantMap map = m_getPeerDetailsRPC->GetReply().toMap();
363  QVariant error = map.value(QStringLiteral("error"));
364  if (error.type() == QVariant::Map)
365  {
366  QVariantMap errors = error.toMap();
367  message = QStringLiteral("'%1' (%2)").arg(errors.value(QStringLiteral("message")).toString())
368  .arg(errors.value(QStringLiteral("code")).toInt());
369  }
370  }
371  else if (state & TorcRPCRequest::ReplyReceived)
372  {
373  if (m_getPeerDetailsRPC->GetReply().type() == QVariant::Map)
374  {
375  QVariantMap map = m_getPeerDetailsRPC->GetReply().toMap();
376  QVariant vpriority = map.value(TORC_PRIORITY);
377  QVariant vstarttime = map.value(TORC_STARTTIME);
378  QVariant vversion = map.value(TORC_SERVICE_VERSION);
379 
380  if (!vpriority.isNull() && !vstarttime.isNull() && !vversion.isNull())
381  {
382  apiVersion = vversion.toString();
383  priority = (int)vpriority.toDouble();
384  startTime = (qint64)vstarttime.toDouble();
385 
386  emit ApiVersionChanged();
387  emit PriorityChanged();
388  emit StartTimeChanged();
389 
390  success = true;
391  }
392  else
393  {
394  message = QStringLiteral("Incomplete details");
395  }
396  }
397  else
398  {
399  message = QStringLiteral("Unexpected variant type %1").arg(m_getPeerDetailsRPC->GetReply().type());
400  }
401  }
402 
403  if (!success)
404  {
405  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Call to '%1' failed (%2)").arg(m_getPeerDetailsRPC->GetMethod(), message));
406  ScheduleRetry();
407  }
408 
409  m_getPeerDetailsRPC->DownRef();
410  m_getPeerDetailsRPC = nullptr;
411 
412  if (success)
413  {
414  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Reply received"));
415  emit TryConnect();
416  }
417  }
418  else
419  {
420  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Unknown request ready"));
421  }
422 }
423 
424 void TorcNetworkService::ScheduleRetry(void)
425 {
426  if (!m_retryScheduled)
427  {
428  QTimer::singleShot(m_retryInterval, Qt::VeryCoarseTimer, this, &TorcNetworkService::Connect);
429  m_retryScheduled = true;
430  }
431 }
432 
433 void TorcNetworkService::QueryPeerDetails(void)
434 {
435  // this is a private method only called from Connect. No need to validate m_addresses or current details.
436 
437  if (!m_webSocketThread)
438  {
439  if (m_getPeerDetails)
440  {
441  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Already running GetDetails HTTP request"));
442  return;
443  }
444 
445  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Querying peer details over HTTP"));
446 
447  QUrl url;
448  url.setScheme(secure ? QStringLiteral("https") : QStringLiteral("http"));
449  url.setPort(port);
450  url.setHost(m_addresses.value(m_preferredAddressIndex).toString());
451  url.setPath(QStringLiteral("/services/GetDetails"));
452  QNetworkRequest networkrequest(url);
453  networkrequest.setRawHeader(QByteArrayLiteral("Accept"), QByteArrayLiteral("application/json"));
454 
455  m_getPeerDetails = new TorcNetworkRequest(networkrequest, QNetworkAccessManager::GetOperation, 0, &m_abort);
456  TorcNetwork::GetAsynchronous(m_getPeerDetails, this);
457 
458  return;
459  }
460 
461  // perform JSON-RPC call over socket
462  if (m_getPeerDetailsRPC)
463  {
464  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Already running GetDetails RPC"));
465  return;
466  }
467 
468  m_getPeerDetailsRPC = new TorcRPCRequest(QStringLiteral("/services/GetDetails"), this);
469  RemoteRequest(m_getPeerDetailsRPC);
470 }
471 
473 {
474  if (!Thread)
475  return;
476 
477  // guard against incorrect use
478  if (m_webSocketThread)
479  {
480  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Already have websocket - closing new socket"));
481  Thread->quit();
482  return;
483  }
484 
485  // create the socket
486  m_webSocketThread = Thread;
487  connect(m_webSocketThread, &TorcWebSocketThread::Finished, this, &TorcNetworkService::Disconnected);
488 }
489 
491 {
492  if (!Request)
493  return;
494 
495  if (m_webSocketThread)
496  {
497  m_webSocketThread->RemoteRequest(Request);
498  }
499  else
500  {
501  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Cannot fulfill remote request - not connected"));
502  Request->DownRef();
503  }
504 }
505 
507 {
508  if (!Request)
509  return;
510 
511  if (m_webSocketThread)
512  m_webSocketThread->CancelRequest(Request);
513  else
514  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Cannot cancel request - not connected"));
515 }
516 
518 {
519  QVariantMap result;
520  result.insert(TORC_NAME, name);
521  result.insert(TORC_UUID, uuid);
522  result.insert(TORC_PORT, port);
523  result.insert(QStringLiteral("uiAddress"), uiAddress);
524  result.insert(TORC_ADDRESS, m_addresses.isEmpty() ? QStringLiteral("INValid") : TorcNetwork::IPAddressToLiteral(m_addresses[m_preferredAddressIndex], 0));
525  result.insert(TORC_BONJOUR_HOST, host);
526  if (secure)
527  result.insert(TORC_SECURE, TORC_YES);
528  return result;
529 }
530 
531 QList<QHostAddress> TorcNetworkService::GetAddresses(void)
532 {
533  return m_addresses;
534 }
535 
536 void TorcNetworkService::SetHost(const QString &Host)
537 {
538  host = Host;
539  m_debugString = host + ":" + QString::number(port);
540 }
541 
542 void TorcNetworkService::SetStartTime(qint64 StartTime)
543 {
544  startTime = StartTime;
545 }
546 
548 {
549  priority = Priority;
550 }
551 
552 void TorcNetworkService::SetAPIVersion(const QString &Version)
553 {
554  apiVersion = Version;
555 }
556 
558 {
559  m_sources |= Source;
560 }
561 
562 TorcNetworkService::ServiceSources TorcNetworkService::GetSources(void)
563 {
564  return m_sources;
565 }
566 
568 {
569  m_sources &= !Source;
570 }
571 
572 #define BLACKLIST QStringLiteral("submit,revert")
573 
590  : QObject(),
591  TorcHTTPService(this, QStringLiteral("peers"), QStringLiteral("peers"), TorcNetworkedContext::staticMetaObject, BLACKLIST),
592  m_discoveredServices(),
593  m_serviceList(),
594  peers()
595 {
596  // listen for events
597  gLocalContext->AddObserver(this);
598 
599  // connect signals
603 
604  // always create the global instance
606 
607  // NB all searches are triggered from TorcHTTPServer
608 }
609 
611 {
612  // stoplistening
614 
615  if (!m_discoveredServices.isEmpty())
616  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Removing %1 discovered peers").arg(m_discoveredServices.size()));
617  while (!m_discoveredServices.isEmpty())
618  delete m_discoveredServices.takeLast();
619 }
620 
622 {
623  return tr("Peers");
624 }
625 
627 {
628  QVariantList result;
629 
630  QReadLocker locker(&m_httpServiceLock);
631  foreach (TorcNetworkService* service, m_discoveredServices)
632  result.append(service->ToMap());
633 
634  return result;
635 }
636 
638 {
639  if (!Peer)
640  return;
641 
642  QString name = Peer->GetName();
643  QString uuid = Peer->GetUuid();
644  emit PeerConnected(name, uuid);
645 }
646 
648 {
649  if (!Peer)
650  return;
651 
652  QString name = Peer->GetName();
653  QString uuid = Peer->GetUuid();
654  emit PeerDisconnected(name, uuid);
655 
656  // we need to delete the service if it was not discovered (i.e. a disconnection means it went away
657  // and we will receive no other notifications).
658  Remove(uuid);
659 }
660 
661 bool TorcNetworkedContext::event(QEvent *Event)
662 {
663  if (Event->type() == TorcEvent::TorcEventType)
664  {
665  TorcEvent *event = static_cast<TorcEvent*>(Event);
666  if (event && (event->GetEvent() == Torc::ServiceDiscovered || event->GetEvent() == Torc::ServiceWentAway))
667  {
668  if (event->Data().contains(TORC_BONJOUR_TXT))
669  {
670  // txtrecords is Bonjour specific
671  QMap<QByteArray,QByteArray> records = TorcBonjour::TxtRecordToMap(event->Data().value(TORC_BONJOUR_TXT).toByteArray());
672 
673  if (records.contains(TORC_UUID_B))
674  {
675  QByteArray uuid = records.value(TORC_UUID_B);
676 
677  if (event->GetEvent() == Torc::ServiceDiscovered)
678  {
679  if (uuid == gLocalContext->GetUuid().toLatin1())
680  {
681  // this is our own external Bonjour host name
682  TorcNetwork::AddHostName(event->Data().value(QByteArrayLiteral("host")).toString());
683  }
684  else if (m_serviceList.contains(uuid))
685  {
686  // register this as an additional source
687  QWriteLocker locker(&m_httpServiceLock);
688  for (int i = 0; i < m_discoveredServices.size(); ++i)
689  if (m_discoveredServices.at(i)->GetUuid() == uuid)
690  m_discoveredServices.at(i)->SetSource(TorcNetworkService::Bonjour);
691  }
692  else
693  {
694  QStringList addresses = event->Data().value(TORC_BONJOUR_ADDRESSES).toStringList();
695 
696  QList<QHostAddress> hosts;
697  foreach (const QString &address, addresses)
698  {
699  // filter out link local addresses for external peers that have network problems...
700  // but allow loopback addresses for other Torc instances running on the same device...
701  QHostAddress hostaddress(address);
702  if (!TorcNetwork::IsExternal(hostaddress) && !hostaddress.isLoopback())
703  {
704  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Ignoring peer address '%1' - address is unreachable").arg(address));
705  }
706  else
707  {
708  hosts.append(QHostAddress(address));
709  }
710  }
711 
712  QString host = event->Data().value(TORC_BONJOUR_HOST).toString();
713 
714  if (hosts.isEmpty())
715  {
716  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Ignoring peer '%1' - no useable network addresses.").arg(host));
717  }
718  else
719  {
720  QString name = event->Data().value(TORC_BONJOUR_NAME).toString();
721  QByteArray txtrecords = event->Data().value(TORC_BONJOUR_TXT).toByteArray();
722  QMap<QByteArray,QByteArray> map = TorcBonjour::TxtRecordToMap(txtrecords);
723  QString version = QString(map.value(TORC_APIVERSION_B));
724  qint64 starttime = map.value(TORC_STARTTIME_B).toULongLong();
725  int priority = map.value(TORC_PRIORITY_B).toInt();
726  bool secure = map.contains(TORC_SECURE_B);
727 
728  // create the new peer
729  TorcNetworkService *service = new TorcNetworkService(name, uuid, event->Data().value(TORC_BONJOUR_PORT).toInt(),
730  secure, hosts);
731  service->SetAPIVersion(version);
732  service->SetPriority(priority);
733  service->SetStartTime(starttime);
734  service->SetHost(host);
736 
737  // and insert into the list model
738  Add(service);
739 
740  // try and connect - the txt records should have given us everything we need to know
741  emit service->TryConnect();
742  }
743  }
744  }
745  else if (event->GetEvent() == Torc::ServiceWentAway)
746  {
747  if (uuid == gLocalContext->GetUuid().toLatin1())
748  TorcNetwork::RemoveHostName(event->Data().value(TORC_BONJOUR_HOST).toString());
749  else
750  Remove(uuid, TorcNetworkService::Bonjour);
751  }
752  }
753  }
754  else if (event->Data().contains(TORC_USN))
755  {
756  // USN == Unique Service Name (UPnP)
757  QString uuid = TorcUPNP::UUIDFromUSN(event->Data().value(TORC_USN).toString());
758 
759  if (event->GetEvent() == Torc::ServiceWentAway)
760  {
761  Remove(uuid, TorcNetworkService::UPnP);
762  }
763  else if (event->GetEvent() == Torc::ServiceDiscovered)
764  {
765  if (uuid == gLocalContext->GetUuid().toLatin1())
766  {
767  // this is us!
768  }
769  else if (m_serviceList.contains(uuid))
770  {
771  // register this as an additional source
772  QWriteLocker locker(&m_httpServiceLock);
773  for (int i = 0; i < m_discoveredServices.size(); ++i)
774  if (m_discoveredServices.at(i)->GetUuid() == uuid)
775  m_discoveredServices.at(i)->SetSource(TorcNetworkService::UPnP);
776  }
777  else
778  {
779  // need name, uuid, port, hosts, apiversion, priority, starttime, host?
780  QUrl location(event->Data().value(TORC_ADDRESS).toString());
781  QString name = event->Data().value(TORC_NAME).toString();
782  bool secure = event->Data().contains(TORC_SECURE);
783  QList<QHostAddress> hosts;
784  hosts << QHostAddress(location.host());
785  TorcNetworkService *service = new TorcNetworkService(name, uuid, location.port(), secure, hosts);
786  service->SetAPIVersion(event->Data().value(TORC_APIVERSION).toString());
787  service->SetPriority(event->Data().value(TORC_PRIORITY).toInt());
788  service->SetStartTime(event->Data().value(TORC_STARTTIME).toULongLong());
789  service->SetHost(location.host());
790  service->SetSource(TorcNetworkService::UPnP);
791  Add(service);
792  emit service->TryConnect();
793  }
794  }
795  }
796  }
797  }
798 
799  return QObject::event(Event);
800 }
801 
807 void TorcNetworkedContext::PeerConnected(TorcWebSocketThread* Thread, const QVariantMap & Data)
808 {
809  if (!Thread)
810  return;
811 
812  if (!gNetworkedContext)
813  {
814  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Upgrade request but no TorcNetworkedContext singleton"));
815  return;
816  }
817 
818  // and create the WebSocket in the correct thread
819  emit gNetworkedContext->NewPeer(Thread, Data);
820 }
821 
823 void TorcNetworkedContext::RemoteRequest(const QString &UUID, TorcRPCRequest *Request)
824 {
825  if (!Request || UUID.isEmpty())
826  return;
827 
828  if (!gNetworkedContext)
829  {
830  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("RemoteRequest but no TorcNetworkedContext singleton"));
831  return;
832  }
833 
834  emit gNetworkedContext->NewRequest(UUID, Request);
835 }
836 
843 void TorcNetworkedContext::CancelRequest(const QString &UUID, TorcRPCRequest *Request, int Wait /* = 1000ms*/)
844 {
845  if (!Request || UUID.isEmpty())
846  return;
847 
848  if (!gNetworkedContext)
849  {
850  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("CancelRequest but no TorcNetworkedContext singleton"));
851  return;
852  }
853 
854  if (Request && !Request->IsNotification())
855  {
857  emit gNetworkedContext->RequestCancelled(UUID, Request);
858 
859  if (Wait > 0)
860  {
861  int count = 0;
862  while (Request->IsShared() && (count++ < Wait))
863  QThread::msleep(1);
864 
865  if (Request->IsShared())
866  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Request is still shared after cancellation"));
867  }
868  }
869 }
870 
871 void TorcNetworkedContext::HandleNewRequest(const QString &UUID, TorcRPCRequest *Request)
872 {
873  if (!UUID.isEmpty() && m_serviceList.contains(UUID))
874  {
875  QReadLocker locker(&m_httpServiceLock);
876  for (int i = 0; i < m_discoveredServices.size(); ++i)
877  {
878  if (m_discoveredServices.value(i)->GetUuid() == UUID)
879  {
880  m_discoveredServices.value(i)->RemoteRequest(Request);
881  return;
882  }
883  }
884  }
885 
886  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Connection identified by '%1' unknown").arg(UUID));
887 }
888 
890 {
891  if (!UUID.isEmpty() && m_serviceList.contains(UUID))
892  {
893  QReadLocker locker(&m_httpServiceLock);
894  for (int i = 0; i < m_discoveredServices.size(); ++i)
895  {
896  if (m_discoveredServices.value(i)->GetUuid() == UUID)
897  {
898  m_discoveredServices.value(i)->CancelRequest(Request);
899  return;
900  }
901  }
902  }
903 
904  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Connection identified by '%1' unknown").arg(UUID));
905 }
906 
908 {
910 }
911 
912 void TorcNetworkedContext::HandleNewPeer(TorcWebSocketThread *Thread, const QVariantMap &Data)
913 {
914  if (!Thread)
915  return;
916 
917  QString UUID = Data.value(TORC_UUID).toString();
918  QString name = Data.value(TORC_NAME).toString();
919  int port = Data.value(TORC_PORT).toInt();
920  QString apiversion = Data.value(TORC_APIVERSION).toString();
921  int priority = Data.value(TORC_PRIORITY).toInt();
922  qint64 starttime = Data.value(TORC_STARTTIME).toULongLong();
923  QHostAddress address(Data.value(TORC_ADDRESS).toString());
924 
925  if (UUID.isEmpty())
926  {
927  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Received WebSocket for peer without UUID (%1) - closing").arg(name));
928  Thread->quit();
929  return;
930  }
931 
932  if (m_serviceList.contains(UUID))
933  {
934  QReadLocker locker(&m_httpServiceLock);
935  for (int i = 0; i < m_discoveredServices.size(); ++i)
936  {
937  if (m_discoveredServices[i]->GetUuid() == UUID && !TorcNetworkService::WeActAsServer(priority, starttime, UUID))
938  {
939  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Received unexpected WebSocket from peer '%1' (%2) - closing")
940  .arg(m_discoveredServices.value(i)->GetName(), UUID));
941  Thread->quit();
942  return;
943  }
944  }
945  }
946 
948  if (Thread == thread)
949  {
950  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Received WebSocket for new peer '%1' (%2)").arg(name, UUID));
951  QList<QHostAddress> addresses;
952  addresses << address;
953  TorcNetworkService *service = new TorcNetworkService(name, UUID, port, Data.contains(TORC_SECURE), addresses);
954  service->SetWebSocketThread(thread);
955  service->SetAPIVersion(apiversion);
956  service->SetPriority(priority);
957  service->SetStartTime(starttime);
958  Add(service);
959  return;
960  }
961 
962  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to take ownership of WebSocket from %1").arg(TorcNetwork::IPAddressToLiteral(address, port)));
963 }
964 
965 void TorcNetworkedContext::Add(TorcNetworkService *Peer)
966 {
967  if (Peer && !m_serviceList.contains(Peer->GetUuid()))
968  {
969  {
970  QWriteLocker locker(&m_httpServiceLock);
971  m_discoveredServices.append(Peer);
972  }
973 
974  m_serviceList.append(Peer->GetUuid());
975  emit PeersChanged();
976  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("New Torc peer '%1' %2").arg(Peer->GetName(), Peer->GetUuid()));
977  }
978 }
979 
980 void TorcNetworkedContext::Remove(const QString &UUID, TorcNetworkService::ServiceSource Source)
981 {
982  if (m_serviceList.contains(UUID))
983  {
984  {
985  QWriteLocker locker(&m_httpServiceLock);
986  for (int i = 0; i < m_discoveredServices.size(); ++i)
987  {
988  if (m_discoveredServices.at(i)->GetUuid() == UUID)
989  {
990  // remove the source first - this acts as a form of reference counting
991  m_discoveredServices.at(i)->RemoveSource(Source);
992 
993  // don't delete if the service is still advertised by other means
994  if (m_discoveredServices.at(i)->GetSources() > TorcNetworkService::Spontaneous)
995  return;
996 
997  // remove the item
998  m_discoveredServices.takeAt(i)->deleteLater();
999  break;
1000  }
1001  }
1002  }
1003 
1004  (void)m_serviceList.removeAll(UUID);
1005  emit PeersChanged();
1006  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Torc peer %1 went away").arg(UUID));
1007  }
1008 }
1009 
1011 {
1012  public:
1014  : TorcAdminObject(TORC_ADMIN_HIGH_PRIORITY + 1 /* start after network and before http server */)
1015  {
1016  qRegisterMetaType<TorcRPCRequest*>();
1017  }
1018 
1020  {
1021  Destroy();
1022  }
1023 
1024  void GetStrings(QVariantMap &Strings)
1025  {
1026  Strings.insert(QStringLiteral("NoPeers"), QCoreApplication::translate("TorcNetworkedContext", "No other Torc devices detected"));
1027  // this is here to capture the plural translation
1028  (void)QCoreApplication::translate("TorcNetworkedContext", "%n other Torc device(s) discovered", nullptr, 2);
1029  }
1030 
1031  void Create(void)
1032  {
1033  Destroy();
1034 
1035  gNetworkedContext = new TorcNetworkedContext();
1036  }
1037 
1038  void Destroy(void)
1039  {
1040  delete gNetworkedContext;
1041  gNetworkedContext = nullptr;
1042  }
1043 
void SetSource(ServiceSource Source)
#define TORC_PRIORITY
Definition: torcupnp.h:19
void HandleNewPeer(TorcWebSocketThread *Thread, const QVariantMap &Data)
static bool WeActAsServer(int Priority, qint64 StartTime, const QString &UUID)
Determine whether we (the local device) should be the server for peer to peer communications.
void SetAPIVersion(const QString &Version)
void SubscriberDeleted(QObject *Subscriber)
void RemoteRequest(TorcRPCRequest *Request)
TorcNetworkService(const QString &Name, const QString &UUID, int Port, bool Secure, const QList< QHostAddress > &Addresses)
TorcLocalContext * gLocalContext
void RemoveSource(ServiceSource Source)
static QString UUIDFromUSN(const QString &USN)
Definition: torcupnp.cpp:60
#define TORC_PRIORITY_B
Definition: torcupnp.h:28
#define TORC_BONJOUR_TXT
Definition: torcbonjour.h:17
void ConnectionUpgraded(void)
#define TORC_APIVERSION_B
Definition: torcupnp.h:27
void SetHost(const QString &Host)
QList< QHostAddress > GetAddresses(void)
A factory class for automatically running objects outside of the main loop.
A wrapper around QNetworkRequest.
static bool GetAsynchronous(TorcNetworkRequest *Request, QObject *Parent)
Queue an asynchronous HTTP request.
Definition: torcnetwork.cpp:94
friend class TorcNetworkedContextObject
#define TORC_STARTTIME
Definition: torcupnp.h:20
~TorcNetworkService()
Destroy this service.
QString GetUuid(void) const
static QString IPAddressToLiteral(const QHostAddress &Address, int Port, bool UseLocalhost=true)
Convert an IP address to a string literal.
#define TORC_USN
Definition: torcupnp.h:23
TorcNetworkedContext * gNetworkedContext
virtual bool DownRef(void)
void NewRequest(const QString &UUID, TorcRPCRequest *Request)
void HandleNewRequest(const QString &UUID, TorcRPCRequest *Request)
QByteArray & GetBuffer(void)
void AddState(int State)
Progress the state for this request.
#define TORC_APIVERSION
Definition: torcupnp.h:18
#define TORC_PORT
Definition: torcupnp.h:17
#define TORC_ADMIN_HIGH_PRIORITY
#define TORC_BONJOUR_ADDRESSES
Definition: torcbonjour.h:16
void Connect(void)
Establish a WebSocket connection to the peer if necessary.
void HandleCancelRequest(const QString &UUID, TorcRPCRequest *Request)
A class encapsulating a Remote Procedure Call.
QVariantList GetPeers(void)
static void AddHostName(const QString &Host)
Register a known host name for this application.
#define TORC_BONJOUR_NAME
Definition: torcbonjour.h:18
#define TORC_YES
Definition: torcupnp.h:24
void CancelRequest(TorcRPCRequest *Request)
static void CancelRequest(const QString &UUID, TorcRPCRequest *Request, int Wait=1000)
Cancel Request associated with the connection identified by UUID.
void Connected(TorcNetworkService *Peer)
Encapsulates information on a discovered Torc peer.
static void PeerConnected(TorcWebSocketThread *Thread, const QVariantMap &Data)
Respond to a valid WebSocket upgrade request and schedule creation of a WebSocket on the give QTcpSoc...
QString GetMethod(void) const
void PriorityChanged(void)
void NewPeer(TorcWebSocketThread *Socket, const QVariantMap &Data)
#define TORC_SECURE
Definition: torcupnp.h:22
bool event(QEvent *Event)
void SetPriority(int Priority)
void SetStartTime(qint64 StartTime)
void PeerDisconnected(QString &Name, QString &UUID)
#define TORC_ADDRESS
Definition: torcupnp.h:21
static Type TorcEventType
Register TorcEventType with QEvent.
Definition: torcevent.h:19
#define TORC_UUID_B
Definition: torcupnp.h:26
int GetState(void) const
static QMap< QByteArray, QByteArray > TxtRecordToMap(const QByteArray &TxtRecord)
Extracts a QMap from a properly formatted Bonjour Txt record.
void HandleSubscriberDeleted(QObject *Subscriber)
static void RemoteRequest(const QString &UUID, TorcRPCRequest *Request)
Pass Request to the remote connection identified by UUID.
QReadWriteLock m_httpServiceLock
#define TORC_SERVICE_VERSION
void Finished(void)
bool IsNotification(void) const
#define TORC_BONJOUR_HOST
Definition: torcbonjour.h:15
static bool IsExternal(const QHostAddress &Address)
Returns true if the address is accessible from other devices.
void StartTimeChanged(void)
void ConnectedChanged(void)
void PeersChanged(void)
static TorcBonjour * Instance(void)
Returns the global TorcBonjour singleton.
void RequestReady(TorcNetworkRequest *Request)
const QVariant & GetReply(void) const
void GetStrings(QVariantMap &Strings)
A general purpose event object.
Definition: torcevent.h:9
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
void SetWebSocketThread(TorcWebSocketThread *Thread)
void Disconnected(TorcNetworkService *Peer)
void CancelRequest(TorcRPCRequest *Request)
void AddObserver(QObject *Observer)
brief Register the given object to receive events.
#define BLACKLIST
A factory class to register translatable strings for use with external interfaces/applications.
Definition: torclanguage.h:66
#define TORC_UUID
Definition: torcupnp.h:15
#define TORC_BONJOUR_PORT
Definition: torcbonjour.h:20
ServiceSources GetSources(void)
static void Cancel(TorcNetworkRequest *Request)
Definition: torcnetwork.cpp:73
void ApiVersionChanged(void)
Wraps a TorcQThread around a TorcWebsocket.
qint64 GetStartTime(void)
void RemoveObserver(QObject *Observer)
brief Deregister the given object.
void TryConnect(void)
#define TORC_SECURE_B
Definition: torcupnp.h:30
static TorcWebSocketThread * TakeSocket(TorcWebSocketThread *Socket)
static void RemoveHostName(const QString &Host)
Remove a host name from the known list of host names.
void RequestCancelled(const QString &UUID, TorcRPCRequest *Request)
#define TORC_NAME
Definition: torcupnp.h:16
void RemoteRequest(TorcRPCRequest *Request)
#define TORC_STARTTIME_B
Definition: torcupnp.h:29
A class to discover and connect to other Torc applications.