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