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