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