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).arg(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).arg((*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()
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())
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  long sExpiresInSecs )
335 {
336  // --------------------------------------------------------------
337  // Calculate when this cache entry should expire.
338  // --------------------------------------------------------------
339 
340  TaskTime ttExpires;
341  gettimeofday ( (&ttExpires), nullptr );
342  AddSecondsToTaskTime( ttExpires, sExpiresInSecs );
343 
344  // --------------------------------------------------------------
345  // Get a Pointer to a Entries QDict... (Create if not found)
346  // --------------------------------------------------------------
347 
348  SSDPCacheEntries *pEntries = nullptr;
349  {
350  QMutexLocker locker(&m_mutex);
351  SSDPCacheEntriesMap::iterator it = m_cache.find(sURI);
352  if (it == m_cache.end() || (*it == nullptr))
353  {
354  pEntries = new SSDPCacheEntries();
355  it = m_cache.insert(sURI, pEntries);
356  }
357  pEntries = *it;
358  pEntries->IncrRef();
359  }
360 
361  // --------------------------------------------------------------
362  // See if the Entries Collection contains our USN... (Create if not found)
363  // --------------------------------------------------------------
364 
365  DeviceLocation *pEntry = pEntries->Find(sUSN);
366  if (pEntry == nullptr)
367  {
368  QUrl url = sLocation;
369  QString host = url.host();
370  QString hostport = QString("%1:%2").arg(host).arg(url.port(80));
371  // Check if the port can be reached. If not we won't use it.
372  // Keep a cache of good and bad URLs found so as not to
373  // overwhelm the thread will portchecker requests.
374  // Allow up to 3 atempts before a port is finally treated as bad.
375  if (m_badUrlList.count(hostport) < 3)
376  {
377  bool isGoodUrl = false;
378  if (m_goodUrlList.contains(hostport))
379  isGoodUrl = true;
380  else
381  {
382  PortChecker checker;
383  if (checker.checkPort(host, url.port(80), 5000))
384  {
385  m_goodUrlList.append(hostport);
386  isGoodUrl=true;
387  }
388  else
389  m_badUrlList.append(hostport);
390  }
391  // Only add if the device can be connected
392  if (isGoodUrl)
393  {
394  pEntry = new DeviceLocation(sURI, sUSN, sLocation, ttExpires);
395  pEntries->Insert(sUSN, pEntry);
396  NotifyAdd(sURI, sUSN, sLocation);
397  }
398  }
399  }
400  else
401  {
402  // Only accept locations that have been tested when added.
403  if (pEntry->m_sLocation == sLocation)
404  pEntry->m_ttExpires = ttExpires;
405  }
406 
407  if (pEntry)
408  pEntry->DecrRef();
409  pEntries->DecrRef();
410 }
411 
413 //
415 
416 void SSDPCache::Remove( const QString &sURI, const QString &sUSN )
417 {
418  Lock();
419 
420  // --------------------------------------------------------------
421  // Get a Pointer to a Entries QDict... (Create if not found)
422  // --------------------------------------------------------------
423 
424  SSDPCacheEntriesMap::Iterator it = m_cache.find( sURI );
425 
426  if (it != m_cache.end())
427  {
428  SSDPCacheEntries *pEntries = *it;
429 
430  if (pEntries != nullptr)
431  {
432  pEntries->IncrRef();
433 
434  pEntries->Remove( sUSN );
435 
436  if (pEntries->Count() == 0)
437  {
438  pEntries->DecrRef();
439  m_cache.erase(it);
440  }
441 
442  pEntries->DecrRef();
443  }
444  }
445 
446  Unlock();
447 
448  // -=>TODO:
449  // Should this only by notified if we actually had any entry removed?
450 
451  NotifyRemove( sURI, sUSN );
452 }
453 
455 //
457 
459 {
460  int nCount = 0;
461  TaskTime ttNow;
462  QStringList lstKeys;
463 
464  gettimeofday( (&ttNow), nullptr );
465 
466  Lock();
467 
468  // ----------------------------------------------------------------------
469  // Iterate through all Type URI's and build list of stale entries keys
470  // ----------------------------------------------------------------------
471 
472  for (auto it = m_cache.begin(); it != m_cache.end(); ++it )
473  {
474  SSDPCacheEntries *pEntries = *it;
475 
476  if (pEntries != nullptr)
477  {
478  pEntries->IncrRef();
479 
480  nCount += pEntries->RemoveStale( ttNow );
481 
482  if (pEntries->Count() == 0)
483  lstKeys.append( it.key() );
484 
485  pEntries->DecrRef();
486  }
487  }
488 
489  nCount = lstKeys.count();
490 
491  // ----------------------------------------------------------------------
492  // Iterate through list of keys and remove them.
493  // (This avoids issues when removing from a QMap while iterating it)
494  // ----------------------------------------------------------------------
495 
496  for (const auto & key : qAsConst(lstKeys))
497  {
498  SSDPCacheEntriesMap::iterator it = m_cache.find( key );
499  if (it == m_cache.end())
500  continue;
501 
502  if (*it)
503  {
504  (*it)->DecrRef();
505  m_cache.erase(it);
506  }
507  }
508 
509  Unlock();
510 
511  return nCount;
512 }
513 
515 //
517 
518 void SSDPCache::NotifyAdd( const QString &sURI,
519  const QString &sUSN,
520  const QString &sLocation )
521 {
522  QStringList values;
523 
524  values.append( sURI );
525  values.append( sUSN );
526  values.append( sLocation );
527 
528  MythEvent me( "SSDP_ADD", values );
529 
530  dispatch( me );
531 }
532 
534 //
536 
537 void SSDPCache::NotifyRemove( const QString &sURI, const QString &sUSN )
538 {
539  QStringList values;
540 
541  values.append( sURI );
542  values.append( sUSN );
543 
544  MythEvent me( "SSDP_REMOVE", values );
545 
546  dispatch( me );
547 }
548 
550 QTextStream &SSDPCache::OutputXML(
551  QTextStream &os, uint *pnDevCount, uint *pnEntryCount) const
552 {
553  QMutexLocker locker(&m_mutex);
554 
555  if (pnDevCount != nullptr)
556  *pnDevCount = 0;
557  if (pnEntryCount != nullptr)
558  *pnEntryCount = 0;
559 
560  for (auto it = m_cache.cbegin(); it != m_cache.cend(); ++it)
561  {
562  if (*it != nullptr)
563  {
564  os << "<Device uri='" << it.key() << "'>" << QT_ENDL;
565 
566  uint tmp = 0;
567 
568  (*it)->OutputXML(os, &tmp);
569 
570  if (pnEntryCount != nullptr)
571  *pnEntryCount += tmp;
572 
573  os << "</Device>" << QT_ENDL;
574 
575  if (pnDevCount != nullptr)
576  (*pnDevCount)++;
577  }
578  }
579  os << QT_FLUSH;
580 
581  return os;
582 }
583 
585 void SSDPCache::Dump(void)
586 {
587  if (!VERBOSE_LEVEL_CHECK(VB_UPNP, LOG_DEBUG))
588  return;
589 
590  QMutexLocker locker(&m_mutex);
591 
592  LOG(VB_UPNP, LOG_DEBUG, "========================================"
593  "=======================================");
594  LOG(VB_UPNP, LOG_DEBUG, QString(" URI (type) - Found: %1 Entries - "
595  "%2 have been Allocated. ")
597  LOG(VB_UPNP, LOG_DEBUG, " \t\tUSN (unique id)\t\t | Expires"
598  "\t | Location");
599  LOG(VB_UPNP, LOG_DEBUG, "----------------------------------------"
600  "---------------------------------------");
601 
602  uint nCount = 0;
603  for (auto it = m_cache.cbegin(); it != m_cache.cend(); ++it)
604  {
605  if (*it != nullptr)
606  {
607  LOG(VB_UPNP, LOG_DEBUG, it.key());
608  (*it)->Dump(nCount);
609  LOG(VB_UPNP, LOG_DEBUG, " ");
610  }
611  }
612 
613  LOG(VB_UPNP, LOG_DEBUG, "----------------------------------------"
614  "---------------------------------------");
615  LOG(VB_UPNP, LOG_DEBUG,
616  QString(" Found: %1 Entries - %2 have been Allocated. ")
618  LOG(VB_UPNP, LOG_DEBUG, "========================================"
619  "=======================================" );
620 }
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:211
PortChecker
Small class to handle TCP port checking and finding link-local context.
Definition: portchecker.h:43
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
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
arg
arg(title).arg(filename).arg(doDelete))
TaskQueue::AddTask
void AddTask(long msec, Task *pTask)
Definition: taskqueue.cpp:170
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
TaskTime
struct timeval TaskTime
Definition: httpserver.h:45
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:207
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:537
SSDPCacheEntries::m_mapEntries
EntryMap m_mapEntries
Definition: ssdpcache.h:66
PortChecker::checkPort
bool checkPort(QString &host, int port, int timeLimit=30000, bool linkLocalOnly=false)
Check if a port is open and sort out the link-local scope.
Definition: portchecker.cpp:73
tmp
static guint32 * tmp
Definition: goom_core.cpp:31
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:232
SSDPCacheTask
Definition: upnptaskcache.h:29
SSDPCache::m_cache
SSDPCacheEntriesMap m_cache
Definition: ssdpcache.h:94
SSDPCache::Add
void Add(const QString &sURI, const QString &sUSN, const QString &sLocation, long sExpiresInSecs)
Definition: ssdpcache.cpp:331
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:233
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:458
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:585
SSDPCacheEntries
Definition: ssdpcache.h:34
uint
unsigned int uint
Definition: compat.h:141
SSDPCache::m_mutex
QMutex m_mutex
Definition: ssdpcache.h:93
SSDPCache::Remove
void Remove(const QString &sURI, const QString &sUSN)
Definition: ssdpcache.cpp:416
QT_ENDL
#define QT_ENDL
Definition: ssdpcache.cpp:23
SSDPCacheEntries::SSDPCacheEntries
SSDPCacheEntries()
Definition: ssdpcache.cpp:39
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:550
SSDPCache::NotifyAdd
void NotifyAdd(const QString &sURI, const QString &sUSN, const QString &sLocation)
Definition: ssdpcache.cpp:518
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
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:234
AddSecondsToTaskTime
void AddSecondsToTaskTime(TaskTime &t, long nSecs)
Definition: upnputil.cpp:119
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