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