MythTV  master
ssdpcache.cpp
Go to the documentation of this file.
1 // Program Name: ssdpcache.cpp
3 // Created : Jan. 8, 2007
4 //
5 // Purpose :
6 //
7 // Copyright (c) 2007 David Blain <dblain@mythtv.org>
8 //
9 // Licensed under the GPL v2 or later, see COPYING for details
10 //
12 
13 #include "upnp.h"
14 #include "mythevent.h"
15 #include "mythlogging.h"
16 #include "upnptaskcache.h"
17 #include "portchecker.h"
18 
19 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
20  #define QT_ENDL endl
21  #define QT_FLUSH flush
22 #else
23  #define QT_ENDL Qt::endl
24  #define QT_FLUSH Qt::flush
25 #endif
26 
28 
29 int SSDPCacheEntries::g_nAllocated = 0; // Debugging only
30 
33 //
34 // SSDPCacheEntries Implementation
35 //
38 
40 {
41  g_nAllocated++; // Should be atomic increment
42 }
43 
45 {
46  Clear();
47  g_nAllocated--; // Should be atomic decrement
48 }
49 
52 {
53  QMutexLocker locker(&m_mutex);
54 
55  for (auto *const entry : qAsConst(m_mapEntries))
56  {
57  if (entry)
58  entry->DecrRef();
59  }
60 
61  m_mapEntries.clear();
62 }
63 
67 {
68  QMutexLocker locker(&m_mutex);
69 
70  EntryMap::iterator it = m_mapEntries.find(GetNormalizedUSN(sUSN));
71  DeviceLocation *pEntry = (it != m_mapEntries.end()) ? *it : nullptr;
72  if (pEntry)
73  pEntry->IncrRef();
74 
75  return pEntry;
76 }
77 
81 {
82  QMutexLocker locker(&m_mutex);
83  if (m_mapEntries.empty())
84  return nullptr;
85  DeviceLocation *loc = *m_mapEntries.begin();
86  loc->IncrRef();
87  return loc;
88 }
89 
93 {
94  QMutexLocker locker(&m_mutex);
95 
96  for (auto it = m_mapEntries.cbegin(); it != m_mapEntries.cend(); ++it)
97  {
98  (*it)->IncrRef();
99  map.insert(it.key(), *it);
100  }
101 }
102 
104 void SSDPCacheEntries::Insert(const QString &sUSN, DeviceLocation *pEntry)
105 {
106  QMutexLocker locker(&m_mutex);
107 
108  pEntry->IncrRef();
109 
110  // Since insert overwrites anything already there
111  // we need to see if the key already exists and release
112  // it's reference if it does.
113 
114  QString usn = GetNormalizedUSN(sUSN);
115 
116  EntryMap::iterator it = m_mapEntries.find(usn);
117  if ((it != m_mapEntries.end()) && (*it != nullptr))
118  (*it)->DecrRef();
119 
120  m_mapEntries[usn] = pEntry;
121 
122  LOG(VB_UPNP, LOG_INFO, QString("SSDP Cache adding USN: %1 Location %2")
123  .arg(pEntry->m_sUSN, pEntry->m_sLocation));
124 }
125 
127 void SSDPCacheEntries::Remove( const QString &sUSN )
128 {
129  QMutexLocker locker(&m_mutex);
130 
131  QString usn = GetNormalizedUSN(sUSN);
132  EntryMap::iterator it = m_mapEntries.find(usn);
133  if (it != m_mapEntries.end())
134  {
135  if (*it)
136  {
137  LOG(VB_UPNP, LOG_INFO,
138  QString("SSDP Cache removing USN: %1 Location %2")
139  .arg((*it)->m_sUSN, (*it)->m_sLocation));
140  (*it)->DecrRef();
141  }
142 
143  // -=>TODO: Need to somehow call SSDPCache::NotifyRemove
144 
145  m_mapEntries.erase(it);
146  }
147 }
148 
151 {
152  QMutexLocker locker(&m_mutex);
153  uint nCount = 0;
154 
155  EntryMap::iterator it = m_mapEntries.begin();
156  while (it != m_mapEntries.end())
157  {
158  if (*it == nullptr)
159  {
160  it = m_mapEntries.erase(it);
161  }
162  else if ((*it)->m_ttExpires < ttNow)
163  {
164  // Note: locking is not required above since we hold
165  // one reference to each entry and are holding m_mutex.
166  (*it)->DecrRef();
167 
168  // -=>TODO: Need to somehow call SSDPCache::NotifyRemove
169 
170  it = m_mapEntries.erase(it);
171  nCount++;
172  }
173  else
174  {
175  ++it;
176  }
177  }
178 
179  return nCount;
180 }
181 
184  QTextStream &os, uint *pnEntryCount) const
185 {
186  QMutexLocker locker(&m_mutex);
187 
188  for (auto *entry : qAsConst(m_mapEntries))
189  {
190  if (entry == nullptr)
191  continue;
192 
193  // Note: IncrRef,DecrRef not required since SSDPCacheEntries
194  // holds one reference to each entry and we are holding m_mutex.
195  os << "<Service usn='" << entry->m_sUSN
196  << "' expiresInSecs='" << entry->ExpiresInSecs().count()
197  << "' url='" << entry->m_sLocation << "' />" << QT_ENDL;
198 
199  if (pnEntryCount != nullptr)
200  (*pnEntryCount)++;
201  }
202 
203  return os;
204 }
205 
207 void SSDPCacheEntries::Dump(uint &nEntryCount) const
208 {
209  QMutexLocker locker(&m_mutex);
210 
211  for (auto *entry : qAsConst(m_mapEntries))
212  {
213  if (entry == nullptr)
214  continue;
215 
216  // Note: IncrRef,DecrRef not required since SSDPCacheEntries
217  // holds one reference to each entry and we are holding m_mutex.
218  LOG(VB_UPNP, LOG_DEBUG, QString(" * \t\t%1\t | %2\t | %3 ")
219  .arg(entry->m_sUSN) .arg(entry->ExpiresInSecs().count())
220  .arg(entry->m_sLocation));
221 
222  nEntryCount++;
223  }
224 }
225 
228 QString SSDPCacheEntries::GetNormalizedUSN(const QString &sUSN)
229 {
230  int uuid_end_loc = sUSN.indexOf(":",5);
231  if (uuid_end_loc > 0)
232  return sUSN.left(uuid_end_loc).toLower() + sUSN.mid(uuid_end_loc);
233  return sUSN;
234 }
235 
238 //
239 // SSDPCache Implementation
240 //
243 
245 {
246  return g_pSSDPCache ? g_pSSDPCache : (g_pSSDPCache = new SSDPCache());
247 
248 }
249 
251 //
253 
255 {
256  LOG(VB_UPNP, LOG_DEBUG, "SSDPCache - Constructor");
257 
258  // ----------------------------------------------------------------------
259  // Add Task to keep SSDPCache purged of stale entries.
260  // ----------------------------------------------------------------------
261 
262  auto *task = new SSDPCacheTask();
263  TaskQueue::Instance()->AddTask(task);
264  task->DecrRef();
265 }
266 
268 //
270 
272 {
273  // FIXME: Using this causes crashes
274 #if 0
275  LOG(VB_UPNP, LOG_DEBUG, "SSDPCache - Destructor");
276 #endif
277 
278  Clear();
279 }
280 
282 //
284 
286 {
287  QMutexLocker locker(&m_mutex);
288 
289  for (auto *const it : qAsConst(m_cache))
290  {
291  if (it)
292  it->DecrRef();
293  }
294 
295  m_cache.clear();
296 }
297 
300 SSDPCacheEntries *SSDPCache::Find(const QString &sURI)
301 {
302  QMutexLocker locker(&m_mutex);
303 
304  SSDPCacheEntriesMap::iterator it = m_cache.find(sURI);
305  if (it != m_cache.end() && (*it != nullptr))
306  (*it)->IncrRef();
307 
308  return (it != m_cache.end()) ? *it : nullptr;
309 }
310 
313 DeviceLocation *SSDPCache::Find(const QString &sURI, const QString &sUSN)
314 {
315  DeviceLocation *pEntry = nullptr;
316  SSDPCacheEntries *pEntries = Find(sURI);
317 
318  if (pEntries != nullptr)
319  {
320  pEntry = pEntries->Find(sUSN);
321  pEntries->DecrRef();
322  }
323 
324  return pEntry;
325 }
326 
328 //
330 
331 void SSDPCache::Add( const QString &sURI,
332  const QString &sUSN,
333  const QString &sLocation,
334  std::chrono::seconds sExpiresInSecs )
335 {
336  // --------------------------------------------------------------
337  // Calculate when this cache entry should expire.
338  // --------------------------------------------------------------
339 
340  auto ttExpires = nowAsDuration<std::chrono::microseconds>() + sExpiresInSecs;
341 
342  // --------------------------------------------------------------
343  // Get a Pointer to a Entries QDict... (Create if not found)
344  // --------------------------------------------------------------
345 
346  SSDPCacheEntries *pEntries = nullptr;
347  {
348  QMutexLocker locker(&m_mutex);
349  SSDPCacheEntriesMap::iterator it = m_cache.find(sURI);
350  if (it == m_cache.end() || (*it == nullptr))
351  {
352  pEntries = new SSDPCacheEntries();
353  it = m_cache.insert(sURI, pEntries);
354  }
355  pEntries = *it;
356  pEntries->IncrRef();
357  }
358 
359  // --------------------------------------------------------------
360  // See if the Entries Collection contains our USN... (Create if not found)
361  // --------------------------------------------------------------
362 
363  DeviceLocation *pEntry = pEntries->Find(sUSN);
364  if (pEntry == nullptr)
365  {
366  QUrl url = sLocation;
367  QString host = url.host();
368  QString hostport = QString("%1:%2").arg(host).arg(url.port(80));
369  // Check if the port can be reached. If not we won't use it.
370  // Keep a cache of good and bad URLs found so as not to
371  // overwhelm the thread will portchecker requests.
372  // Allow up to 3 atempts before a port is finally treated as bad.
373  if (m_badUrlList.count(hostport) < 3)
374  {
375  bool isGoodUrl = false;
376  if (m_goodUrlList.contains(hostport))
377  isGoodUrl = true;
378  else
379  {
380  PortChecker checker;
381  if (checker.checkPort(host, url.port(80), 5s))
382  {
383  m_goodUrlList.append(hostport);
384  isGoodUrl=true;
385  }
386  else
387  m_badUrlList.append(hostport);
388  }
389  // Only add if the device can be connected
390  if (isGoodUrl)
391  {
392  pEntry = new DeviceLocation(sURI, sUSN, sLocation, ttExpires);
393  pEntries->Insert(sUSN, pEntry);
394  NotifyAdd(sURI, sUSN, sLocation);
395  }
396  }
397  }
398  else
399  {
400  // Only accept locations that have been tested when added.
401  if (pEntry->m_sLocation == sLocation)
402  pEntry->m_ttExpires = ttExpires;
403  }
404 
405  if (pEntry)
406  pEntry->DecrRef();
407  pEntries->DecrRef();
408 }
409 
411 //
413 
414 void SSDPCache::Remove( const QString &sURI, const QString &sUSN )
415 {
416  Lock();
417 
418  // --------------------------------------------------------------
419  // Get a Pointer to a Entries QDict... (Create if not found)
420  // --------------------------------------------------------------
421 
422  SSDPCacheEntriesMap::Iterator it = m_cache.find( sURI );
423 
424  if (it != m_cache.end())
425  {
426  SSDPCacheEntries *pEntries = *it;
427 
428  if (pEntries != nullptr)
429  {
430  pEntries->IncrRef();
431 
432  pEntries->Remove( sUSN );
433 
434  if (pEntries->Count() == 0)
435  {
436  pEntries->DecrRef();
437  m_cache.erase(it);
438  }
439 
440  pEntries->DecrRef();
441  }
442  }
443 
444  Unlock();
445 
446  // -=>TODO:
447  // Should this only by notified if we actually had any entry removed?
448 
449  NotifyRemove( sURI, sUSN );
450 }
451 
453 //
455 
457 {
458  int nCount = 0;
459  QStringList lstKeys;
460 
461  auto ttNow = nowAsDuration<std::chrono::microseconds>();
462 
463  Lock();
464 
465  // ----------------------------------------------------------------------
466  // Iterate through all Type URI's and build list of stale entries keys
467  // ----------------------------------------------------------------------
468 
469  for (auto it = m_cache.begin(); it != m_cache.end(); ++it )
470  {
471  SSDPCacheEntries *pEntries = *it;
472 
473  if (pEntries != nullptr)
474  {
475  pEntries->IncrRef();
476 
477  nCount += pEntries->RemoveStale( ttNow );
478 
479  if (pEntries->Count() == 0)
480  lstKeys.append( it.key() );
481 
482  pEntries->DecrRef();
483  }
484  }
485 
486  nCount = lstKeys.count();
487 
488  // ----------------------------------------------------------------------
489  // Iterate through list of keys and remove them.
490  // (This avoids issues when removing from a QMap while iterating it)
491  // ----------------------------------------------------------------------
492 
493  for (const auto & key : qAsConst(lstKeys))
494  {
495  SSDPCacheEntriesMap::iterator it = m_cache.find( key );
496  if (it == m_cache.end())
497  continue;
498 
499  if (*it)
500  {
501  (*it)->DecrRef();
502  m_cache.erase(it);
503  }
504  }
505 
506  Unlock();
507 
508  return nCount;
509 }
510 
512 //
514 
515 void SSDPCache::NotifyAdd( const QString &sURI,
516  const QString &sUSN,
517  const QString &sLocation )
518 {
519  QStringList values;
520 
521  values.append( sURI );
522  values.append( sUSN );
523  values.append( sLocation );
524 
525  MythEvent me( "SSDP_ADD", values );
526 
527  dispatch( me );
528 }
529 
531 //
533 
534 void SSDPCache::NotifyRemove( const QString &sURI, const QString &sUSN )
535 {
536  QStringList values;
537 
538  values.append( sURI );
539  values.append( sUSN );
540 
541  MythEvent me( "SSDP_REMOVE", values );
542 
543  dispatch( me );
544 }
545 
547 QTextStream &SSDPCache::OutputXML(
548  QTextStream &os, uint *pnDevCount, uint *pnEntryCount) const
549 {
550  QMutexLocker locker(&m_mutex);
551 
552  if (pnDevCount != nullptr)
553  *pnDevCount = 0;
554  if (pnEntryCount != nullptr)
555  *pnEntryCount = 0;
556 
557  for (auto it = m_cache.cbegin(); it != m_cache.cend(); ++it)
558  {
559  if (*it != nullptr)
560  {
561  os << "<Device uri='" << it.key() << "'>" << QT_ENDL;
562 
563  uint tmp = 0;
564 
565  (*it)->OutputXML(os, &tmp);
566 
567  if (pnEntryCount != nullptr)
568  *pnEntryCount += tmp;
569 
570  os << "</Device>" << QT_ENDL;
571 
572  if (pnDevCount != nullptr)
573  (*pnDevCount)++;
574  }
575  }
576  os << QT_FLUSH;
577 
578  return os;
579 }
580 
582 void SSDPCache::Dump(void)
583 {
584  if (!VERBOSE_LEVEL_CHECK(VB_UPNP, LOG_DEBUG))
585  return;
586 
587  QMutexLocker locker(&m_mutex);
588 
589  LOG(VB_UPNP, LOG_DEBUG, "========================================"
590  "=======================================");
591  LOG(VB_UPNP, LOG_DEBUG, QString(" URI (type) - Found: %1 Entries - "
592  "%2 have been Allocated. ")
593  .arg(m_cache.count()).arg(SSDPCacheEntries::g_nAllocated));
594  LOG(VB_UPNP, LOG_DEBUG, " \t\tUSN (unique id)\t\t | Expires"
595  "\t | Location");
596  LOG(VB_UPNP, LOG_DEBUG, "----------------------------------------"
597  "---------------------------------------");
598 
599  uint nCount = 0;
600  for (auto it = m_cache.cbegin(); it != m_cache.cend(); ++it)
601  {
602  if (*it != nullptr)
603  {
604  LOG(VB_UPNP, LOG_DEBUG, it.key());
605  (*it)->Dump(nCount);
606  LOG(VB_UPNP, LOG_DEBUG, " ");
607  }
608  }
609 
610  LOG(VB_UPNP, LOG_DEBUG, "----------------------------------------"
611  "---------------------------------------");
612  LOG(VB_UPNP, LOG_DEBUG,
613  QString(" Found: %1 Entries - %2 have been Allocated. ")
614  .arg(nCount) .arg(DeviceLocation::g_nAllocated));
615  LOG(VB_UPNP, LOG_DEBUG, "========================================"
616  "=======================================" );
617 }
SSDPCache::Clear
void Clear()
Definition: ssdpcache.cpp:285
SSDPCache::m_badUrlList
QStringList m_badUrlList
Definition: ssdpcache.h:88
mythevent.h
SSDPCache::~SSDPCache
~SSDPCache() override
Definition: ssdpcache.cpp:271
DeviceLocation::g_nAllocated
static int g_nAllocated
Definition: upnpdevice.h:213
PortChecker
Small class to handle TCP port checking and finding link-local context.
Definition: portchecker.h:44
SSDPCacheEntries::Remove
void Remove(const QString &sUSN)
Removes a specific entry from the cache.
Definition: ssdpcache.cpp:127
ReferenceCounter::DecrRef
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
Definition: referencecounter.cpp:125
TaskTime
std::chrono::microseconds TaskTime
Definition: upnputil.h:43
SSDPCache::Lock
void Lock()
Definition: ssdpcache.h:116
SSDPCacheEntries::RemoveStale
uint RemoveStale(TaskTime ttNow)
Removes expired cache entries, returning the number removed.
Definition: ssdpcache.cpp:150
SSDPCache
Definition: ssdpcache.h:80
MythEvent
This class is used as a container for messages.
Definition: mythevent.h:16
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
SSDPCacheEntries::GetEntryMap
void GetEntryMap(EntryMap &map)
Returns a copy of the EntryMap.
Definition: ssdpcache.cpp:92
MythObservable::dispatch
void dispatch(const MythEvent &event)
Dispatch an event to all listeners.
Definition: mythobservable.cpp:73
DeviceLocation
Definition: upnpdevice.h:209
SSDPCache::Instance
static SSDPCache * Instance()
Definition: ssdpcache.cpp:244
SSDPCache::g_pSSDPCache
static SSDPCache * g_pSSDPCache
Definition: ssdpcache.h:87
SSDPCache::NotifyRemove
void NotifyRemove(const QString &sURI, const QString &sUSN)
Definition: ssdpcache.cpp:534
SSDPCacheEntries::m_mapEntries
EntryMap m_mapEntries
Definition: ssdpcache.h:66
tmp
static guint32 * tmp
Definition: goom_core.cpp:31
SSDPCache::Add
void Add(const QString &sURI, const QString &sUSN, const QString &sLocation, std::chrono::seconds sExpiresInSecs)
Definition: ssdpcache.cpp:331
SSDPCacheEntries::OutputXML
QTextStream & OutputXML(QTextStream &os, uint *pnEntryCount=nullptr) const
Outputs the XML for this service.
Definition: ssdpcache.cpp:183
upnp.h
SSDPCacheEntries::~SSDPCacheEntries
~SSDPCacheEntries() override
Destructor protected to enforce Release method usage.
Definition: ssdpcache.cpp:44
mythlogging.h
DeviceLocation::m_sUSN
QString m_sUSN
Definition: upnpdevice.h:234
SSDPCacheTask
Definition: upnptaskcache.h:29
SSDPCache::m_cache
SSDPCacheEntriesMap m_cache
Definition: ssdpcache.h:94
QT_FLUSH
#define QT_FLUSH
Definition: ssdpcache.cpp:24
TaskQueue::Instance
static TaskQueue * Instance()
Definition: taskqueue.cpp:58
SSDPCache::Find
SSDPCacheEntries * Find(const QString &sURI)
Finds the SSDPCacheEntries in the cache, returns nullptr when absent.
Definition: ssdpcache.cpp:300
DeviceLocation::m_sLocation
QString m_sLocation
Definition: upnpdevice.h:235
EntryMap
QMap< QString, DeviceLocation * > EntryMap
Key == Unique Service Name (USN)
Definition: ssdpcache.h:28
SSDPCache::Unlock
void Unlock()
Definition: ssdpcache.h:117
SSDPCache::m_goodUrlList
QStringList m_goodUrlList
Definition: ssdpcache.h:89
portchecker.h
sLocation
static const QString sLocation
Definition: mythcontext.cpp:66
SSDPCache::RemoveStale
int RemoveStale()
Definition: ssdpcache.cpp:456
SSDPCacheEntries::Insert
void Insert(const QString &sUSN, DeviceLocation *pEntry)
Inserts a device location into the cache.
Definition: ssdpcache.cpp:104
SSDPCache::Dump
void Dump(void)
Prints this device to the console in a human readable form.
Definition: ssdpcache.cpp:582
SSDPCacheEntries
Definition: ssdpcache.h:34
uint
unsigned int uint
Definition: compat.h:144
SSDPCache::m_mutex
QMutex m_mutex
Definition: ssdpcache.h:93
SSDPCache::Remove
void Remove(const QString &sURI, const QString &sUSN)
Definition: ssdpcache.cpp:414
QT_ENDL
#define QT_ENDL
Definition: ssdpcache.cpp:23
SSDPCacheEntries::SSDPCacheEntries
SSDPCacheEntries()
Definition: ssdpcache.cpp:39
PortChecker::checkPort
bool checkPort(QString &host, int port, std::chrono::milliseconds timeLimit=30s, bool linkLocalOnly=false)
Check if a port is open and sort out the link-local scope.
Definition: portchecker.cpp:73
SSDPCacheEntries::g_nAllocated
static int g_nAllocated
Definition: ssdpcache.h:62
SSDPCache::OutputXML
QTextStream & OutputXML(QTextStream &os, uint *pnDevCount=nullptr, uint *pnEntryCount=nullptr) const
Outputs the XML for this device.
Definition: ssdpcache.cpp:547
SSDPCache::NotifyAdd
void NotifyAdd(const QString &sURI, const QString &sUSN, const QString &sLocation)
Definition: ssdpcache.cpp:515
SSDPCacheEntries::GetNormalizedUSN
static QString GetNormalizedUSN(const QString &sUSN)
Returns a normalized USN, so that capitalization of the uuid is not an issue.
Definition: ssdpcache.cpp:228
VERBOSE_LEVEL_CHECK
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:14
upnptaskcache.h
SSDPCache::SSDPCache
SSDPCache()
Definition: ssdpcache.cpp:254
SSDPCacheEntries::Clear
void Clear(void)
Clears the cache of all entries.
Definition: ssdpcache.cpp:51
TaskQueue::AddTask
void AddTask(std::chrono::milliseconds msec, Task *pTask)
Add a task to run in the future.
Definition: taskqueue.cpp:171
SSDPCacheEntries::Dump
void Dump(uint &nEntryCount) const
Prints this service to the console in human readable form.
Definition: ssdpcache.cpp:207
SSDPCacheEntries::m_mutex
QMutex m_mutex
Definition: ssdpcache.h:65
SSDPCacheEntries::Count
uint Count(void) const
Definition: ssdpcache.h:44
DeviceLocation::m_ttExpires
TaskTime m_ttExpires
Definition: upnpdevice.h:236
ReferenceCounter::IncrRef
virtual int IncrRef(void)
Increments reference count.
Definition: referencecounter.cpp:101
SSDPCacheEntries::Find
DeviceLocation * Find(const QString &sUSN)
Finds the Device in the cache, returns nullptr when absent.
Definition: ssdpcache.cpp:66
ReferenceCounter
General purpose reference counter.
Definition: referencecounter.h:26
SSDPCacheEntries::GetFirst
DeviceLocation * GetFirst(void)
Returns random entry in cache, returns nullptr when list is empty.
Definition: ssdpcache.cpp:80