MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
ssdp.cpp
Go to the documentation of this file.
1 
2 // Program Name: ssdp.cpp
3 // Created : Oct. 1, 2005
4 //
5 // Purpose : SSDP Discovery Service Implmenetation
6 //
7 // Copyright (c) 2005 David Blain <dblain@mythtv.org>
8 //
9 // Licensed under the GPL v2 or later, see COPYING for details
10 //
12 
13 #include <algorithm>
14 
15 #include "upnp.h"
16 #include "mythlogging.h"
17 
18 #include "upnptasksearch.h"
19 #include "upnptaskcache.h"
20 
21 #include "mmulticastsocketdevice.h"
22 #include "mbroadcastsocketdevice.h"
23 
24 #include <QRegExp>
25 #include <QStringList>
26 
27 #include <stdlib.h>
28 
31 //
32 // SSDP Class Implementation
33 //
36 
37 // We're creating this class immediately so it will always be available.
38 
39 static QMutex g_pSSDPCreationLock;
40 SSDP* SSDP::g_pSSDP = NULL;
41 
43 //
45 
47 {
48  QMutexLocker locker(&g_pSSDPCreationLock);
49  return g_pSSDP ? g_pSSDP : (g_pSSDP = new SSDP());
50 }
51 
53 //
55 
57 {
58  QMutexLocker locker(&g_pSSDPCreationLock);
59  delete g_pSSDP;
60  g_pSSDP = NULL;
61 }
62 
64 //
66 
68  MThread ("SSDP" ),
69  m_procReqLineExp ("[ \r\n][ \r\n]*"),
70  m_nPort ( SSDP_PORT ),
71  m_nSearchPort ( SSDP_SEARCHPORT ),
72  m_nServicePort ( 0 ),
73  m_pNotifyTask ( NULL ),
74  m_bAnnouncementsEnabled( false ),
75  m_bTermRequested ( false ),
76  m_lock ( QMutex::NonRecursive )
77 {
78  LOG(VB_UPNP, LOG_NOTICE, "Starting up SSDP Thread..." );
79 
81 
82  m_nPort = pConfig->GetValue("UPnP/SSDP/Port" , SSDP_PORT );
83  m_nSearchPort = pConfig->GetValue("UPnP/SSDP/SearchPort", SSDP_SEARCHPORT);
84 
85  m_Sockets[ SocketIdx_Search ] =
87  m_Sockets[ SocketIdx_Multicast ] =
88  new MMulticastSocketDevice(SSDP_GROUP, m_nPort);
89  m_Sockets[ SocketIdx_Broadcast ] =
90  new MBroadcastSocketDevice("255.255.255.255", m_nPort);
91 
92  m_Sockets[ SocketIdx_Search ]->setBlocking( false );
93  m_Sockets[ SocketIdx_Multicast ]->setBlocking( false );
94  m_Sockets[ SocketIdx_Broadcast ]->setBlocking( false );
95 
96  // Setup SearchSocket
97  QHostAddress ip4addr( QHostAddress::Any );
98 
99  m_Sockets[ SocketIdx_Search ]->bind( ip4addr , m_nSearchPort );
100  m_Sockets[ SocketIdx_Search ]->bind( QHostAddress::Any, m_nSearchPort );
101 
102  // ----------------------------------------------------------------------
103  // Create the SSDP (Upnp Discovery) Thread.
104  // ----------------------------------------------------------------------
105 
106  start();
107 
108  LOG(VB_UPNP, LOG_INFO, "SSDP Thread Starting soon" );
109 }
110 
112 //
114 
116 {
117  LOG(VB_UPNP, LOG_NOTICE, "Shutting Down SSDP Thread..." );
118 
120 
121  m_bTermRequested = true;
122  wait();
123 
124  if (m_pNotifyTask != NULL)
125  {
127  m_pNotifyTask = NULL;
128  }
129 
130  for (int nIdx = 0; nIdx < (int)NumberOfSockets; nIdx++ )
131  {
132  if (m_Sockets[ nIdx ] != NULL )
133  {
134  delete m_Sockets[ nIdx ];
135  }
136  }
137 
138  LOG(VB_UPNP, LOG_INFO, "SSDP Thread Terminated." );
139 }
140 
142 {
143  m_bTermRequested = true;
144 }
145 
147 //
149 
150 void SSDP::EnableNotifications( int nServicePort )
151 {
152  if ( m_pNotifyTask == NULL )
153  {
154  m_nServicePort = nServicePort;
155 
156  LOG(VB_UPNP, LOG_INFO,
157  "SSDP::EnableNotifications() - creating new task");
159 
160  // ------------------------------------------------------------------
161  // First Send out Notification that we are leaving the network.
162  // ------------------------------------------------------------------
163 
164  LOG(VB_UPNP, LOG_INFO,
165  "SSDP::EnableNotifications() - sending NTS_byebye");
167  m_pNotifyTask->Execute( NULL );
168 
170  }
171 
172  // ------------------------------------------------------------------
173  // Add Announcement Task to the Queue
174  // ------------------------------------------------------------------
175 
176  LOG(VB_UPNP, LOG_INFO, "SSDP::EnableNotifications() - sending NTS_alive");
177 
179 
181 
182  LOG(VB_UPNP, LOG_INFO,
183  "SSDP::EnableNotifications() - Task added to UPnP queue");
184 }
185 
187 //
189 
191 {
192  m_bAnnouncementsEnabled = false;
193 
194  if (m_pNotifyTask != NULL)
195  {
196  // Send Announcement that we are leaving.
197 
199  m_pNotifyTask->Execute( NULL );
200  }
201 }
202 
204 //
206 void SSDP::PerformSearch(const QString &sST, uint timeout_secs)
207 {
208  timeout_secs = std::max(std::min(timeout_secs, 5U), 1U);
209  QString rRequest = QString("M-SEARCH * HTTP/1.1\r\n"
210  "HOST: 239.255.255.250:1900\r\n"
211  "MAN: \"ssdp:discover\"\r\n"
212  "MX: %1\r\n"
213  "ST: %2\r\n"
214  "\r\n")
215  .arg(timeout_secs).arg(sST);
216 
217  LOG(VB_UPNP, LOG_DEBUG, QString("\n\n%1\n").arg(rRequest));
218 
219  QByteArray sRequest = rRequest.toUtf8();
220 
221  MSocketDevice *pSocket = m_Sockets[ SocketIdx_Search ];
222  if ( !pSocket->isValid() )
223  {
225  pSocket->setSocket(pSocket->createNewSocket(), MSocketDevice::Datagram);
226  }
227 
228  QHostAddress address;
229  address.setAddress( SSDP_GROUP );
230 
231  int nSize = sRequest.size();
232 
233  if ( pSocket->writeBlock( sRequest.data(),
234  sRequest.size(), address, SSDP_PORT ) != nSize)
235  LOG(VB_GENERAL, LOG_INFO,
236  "SSDP::PerformSearch - did not write entire buffer.");
237 
238  usleep( random() % 250000 );
239 
240  if ( pSocket->writeBlock( sRequest.data(),
241  sRequest.size(), address, SSDP_PORT ) != nSize)
242  LOG(VB_GENERAL, LOG_INFO,
243  "SSDP::PerformSearch - did not write entire buffer.");
244 }
245 
247 //
249 
250 void SSDP::run()
251 {
252  RunProlog();
253 
254  fd_set read_set;
255  struct timeval timeout;
256 
257  LOG(VB_UPNP, LOG_INFO, "SSDP::Run - SSDP Thread Started." );
258 
259  // ----------------------------------------------------------------------
260  // Listen for new Requests
261  // ----------------------------------------------------------------------
262 
263  while ( ! m_bTermRequested )
264  {
265  int nMaxSocket = 0;
266 
267  FD_ZERO( &read_set );
268 
269  for (uint nIdx = 0; nIdx < NumberOfSockets; nIdx++ )
270  {
271  if (m_Sockets[nIdx] != NULL && m_Sockets[nIdx]->socket() >= 0)
272  {
273  FD_SET( m_Sockets[ nIdx ]->socket(), &read_set );
274  nMaxSocket = max( m_Sockets[ nIdx ]->socket(), nMaxSocket );
275 
276 #if 0
277  if (m_Sockets[ nIdx ]->bytesAvailable() > 0)
278  {
279  LOG(VB_GENERAL, LOG_DEBUG,
280  QString("Found Extra data before select: %1")
281  .arg(nIdx));
282  ProcessData( m_Sockets[ nIdx ] );
283  }
284 #endif
285  }
286  }
287 
288  timeout.tv_sec = 1;
289  timeout.tv_usec = 0;
290 
291  int count;
292  count = select(nMaxSocket + 1, &read_set, NULL, NULL, &timeout);
293  for (int nIdx = 0; count && nIdx < (int)NumberOfSockets; nIdx++ )
294  {
295  if (m_Sockets[nIdx] != NULL && m_Sockets[nIdx]->socket() >= 0 &&
296  FD_ISSET(m_Sockets[nIdx]->socket(), &read_set))
297  {
298 #if 0
299  LOG(VB_GENERAL, LOG_DEBUG, QString("FD_ISSET( %1 )").arg(nIdx));
300 #endif
301  ProcessData(m_Sockets[nIdx]);
302  count--;
303  }
304  }
305  }
306 
307  RunEpilog();
308 }
309 
311 //
313 
315 {
316  QByteArray buffer;
317  long nBytes = 0;
318 
319  while ((nBytes = pSocket->bytesAvailable()) > 0)
320  {
321  buffer.resize(nBytes);
322 
323  long nRead = 0;
324  do
325  {
326  long ret = pSocket->readBlock( buffer.data() + nRead, nBytes - nRead );
327  if (ret < 0)
328  {
329  LOG(VB_GENERAL, LOG_ERR, QString("Socket readBlock error %1")
330  .arg(pSocket->error()));
331  buffer.clear();
332  break;
333  }
334 
335  nRead += ret;
336 
337  if (0 == ret)
338  {
339  LOG(VB_SOCKET, LOG_WARNING,
340  QString("%1 bytes reported available, "
341  "but only %2 bytes read.")
342  .arg(nBytes).arg(nRead));
343  nBytes = nRead;
344  buffer.resize(nBytes);
345  break;
346  }
347  }
348  while (nRead < nBytes);
349 
350  if (buffer.isEmpty())
351  continue;
352 
353  QHostAddress peerAddress = pSocket->peerAddress();
354  quint16 peerPort = pSocket->peerPort ();
355 
356  // ------------------------------------------------------------------
357  QString str = QString(buffer.constData());
358  QStringList lines = str.split("\r\n", QString::SkipEmptyParts);
359  QString sRequestLine = lines.size() ? lines[0] : "";
360 
361  lines.pop_front();
362 
363  // ------------------------------------------------------------------
364  // Parse request Type
365  // ------------------------------------------------------------------
366 
367  LOG(VB_UPNP, LOG_DEBUG, QString("SSDP::ProcessData - requestLine: %1")
368  .arg(sRequestLine));
369 
370  SSDPRequestType eType = ProcessRequestLine( sRequestLine );
371 
372  // ------------------------------------------------------------------
373  // Read Headers into map
374  // ------------------------------------------------------------------
375 
376  QStringMap headers;
377 
378  for ( QStringList::Iterator it = lines.begin();
379  it != lines.end(); ++it )
380  {
381  QString sLine = *it;
382  QString sName = sLine.section( ':', 0, 0 ).trimmed();
383  QString sValue = sLine.section( ':', 1 );
384 
385  sValue.truncate( sValue.length() ); //-2
386 
387  if ((sName.length() != 0) && (sValue.length() !=0))
388  headers.insert( sName.toLower(), sValue.trimmed() );
389  }
390 
391 #if 0
392  pSocket->SetDestAddress( peerAddress, peerPort );
393 #endif
394 
395  // --------------------------------------------------------------
396  // See if this is a valid request
397  // --------------------------------------------------------------
398 
399  switch( eType )
400  {
401  case SSDP_MSearch:
402  {
403  // ----------------------------------------------------------
404  // If we haven't enabled notifications yet, then we don't
405  // want to answer search requests.
406  // ----------------------------------------------------------
407 
408  if (m_pNotifyTask != NULL)
409  ProcessSearchRequest( headers, peerAddress, peerPort );
410 
411  break;
412  }
413 
414  case SSDP_MSearchResp:
415  ProcessSearchResponse( headers);
416  break;
417 
418  case SSDP_Notify:
419  ProcessNotify( headers );
420  break;
421 
422  case SSDP_Unknown:
423  default:
424  LOG(VB_UPNP, LOG_ERR,
425  "SSPD::ProcessData - Unknown request Type.");
426  break;
427  }
428  }
429 }
430 
432 //
434 
436 {
437  QStringList tokens = sLine.split(m_procReqLineExp, QString::SkipEmptyParts);
438 
439  // ----------------------------------------------------------------------
440  // if this is actually a response, then sLine's format will be:
441  // HTTP/m.n <response code> <response text>
442  // otherwise:
443  // <method> <Resource URI> HTTP/m.n
444  // ----------------------------------------------------------------------
445 
446  if ( sLine.startsWith( QString("HTTP/") ))
447  return SSDP_MSearchResp;
448  else
449  {
450  if (tokens.count() > 0)
451  {
452  if (tokens[0] == "M-SEARCH" ) return SSDP_MSearch;
453  if (tokens[0] == "NOTIFY" ) return SSDP_Notify;
454  }
455  }
456 
457  return SSDP_Unknown;
458 }
459 
461 //
463 
464 QString SSDP::GetHeaderValue( const QStringMap &headers,
465  const QString &sKey, const QString &sDefault )
466 {
467  QStringMap::const_iterator it = headers.find( sKey.toLower() );
468 
469  if ( it == headers.end())
470  return( sDefault );
471 
472  return *it;
473 }
474 
476 //
478 
479 bool SSDP::ProcessSearchRequest( const QStringMap &sHeaders,
480  QHostAddress peerAddress,
481  quint16 peerPort )
482 {
483  QString sMAN = GetHeaderValue( sHeaders, "MAN", "" );
484  QString sST = GetHeaderValue( sHeaders, "ST" , "" );
485  QString sMX = GetHeaderValue( sHeaders, "MX" , "" );
486  int nMX = 0;
487 
488  LOG(VB_UPNP, LOG_DEBUG, QString("SSDP::ProcessSearchrequest : [%1] MX=%2")
489  .arg(sST).arg(sMX));
490 
491  // ----------------------------------------------------------------------
492  // Validate Header Values...
493  // ----------------------------------------------------------------------
494 
495 #if 0
496  if ( pRequest->m_sMethod != "*" ) return false;
497  if ( pRequest->m_sProtocol != "HTTP" ) return false;
498  if ( pRequest->m_nMajor != 1 ) return false;
499 #endif
500  if ( sMAN != "\"ssdp:discover\"" ) return false;
501  if ( sST.length() == 0 ) return false;
502  if ( sMX.length() == 0 ) return false;
503  if ((nMX = sMX.toInt()) == 0 ) return false;
504  if ( nMX < 0 ) return false;
505 
506  // ----------------------------------------------------------------------
507  // Adjust timeout to be a random interval between 0 and MX (max of 120)
508  // ----------------------------------------------------------------------
509 
510  nMX = (nMX > 120) ? 120 : nMX;
511 
512  int nNewMX = (int)(0 + ((unsigned short)random() % nMX)) * 1000;
513 
514  // ----------------------------------------------------------------------
515  // See what they are looking for...
516  // ----------------------------------------------------------------------
517 
518  if ((sST == "ssdp:all") || (sST == "upnp:rootdevice"))
519  {
521  peerAddress, peerPort, sST,
522  UPnp::g_UPnpDeviceDesc.m_rootDevice.GetUDN());
523 
524 #if 0
525  // Excute task now for fastest response, queue for time-delayed response
526  // -=>TODO: To be trully uPnp compliant, this Execute should be removed.
527  pTask->Execute( NULL );
528 #endif
529 
530  TaskQueue::Instance()->AddTask( nNewMX, pTask );
531 
532  pTask->DecrRef();
533 
534  return true;
535  }
536 
537  // ----------------------------------------------------------------------
538  // Look for a specific device/service
539  // ----------------------------------------------------------------------
540 
541  QString sUDN = UPnp::g_UPnpDeviceDesc.FindDeviceUDN(
542  &(UPnp::g_UPnpDeviceDesc.m_rootDevice), sST );
543 
544  if (sUDN.length() > 0)
545  {
547  peerAddress,
548  peerPort,
549  sST,
550  sUDN );
551 
552  // Excute task now for fastest response, queue for time-delayed response
553  // -=>TODO: To be trully uPnp compliant, this Execute should be removed.
554  pTask->Execute( NULL );
555 
556  TaskQueue::Instance()->AddTask( nNewMX, pTask );
557 
558  pTask->DecrRef();
559 
560  return true;
561  }
562 
563  return false;
564 }
565 
567 //
569 
571 {
572  QString sDescURL = GetHeaderValue( headers, "LOCATION" , "" );
573  QString sST = GetHeaderValue( headers, "ST" , "" );
574  QString sUSN = GetHeaderValue( headers, "USN" , "" );
575  QString sCache = GetHeaderValue( headers, "CACHE-CONTROL" , "" );
576 
577  LOG(VB_UPNP, LOG_DEBUG,
578  QString( "SSDP::ProcessSearchResponse ...\n"
579  "DescURL=%1\n"
580  "ST =%2\n"
581  "USN =%3\n"
582  "Cache =%4")
583  .arg(sDescURL).arg(sST).arg(sUSN).arg(sCache));
584 
585  int nPos = sCache.indexOf("max-age", 0, Qt::CaseInsensitive);
586 
587  if (nPos < 0)
588  return false;
589 
590  if ((nPos = sCache.indexOf("=", nPos)) < 0)
591  return false;
592 
593  int nSecs = sCache.mid( nPos+1 ).toInt();
594 
595  SSDPCache::Instance()->Add( sST, sUSN, sDescURL, nSecs );
596 
597  return true;
598 }
599 
601 //
603 
604 bool SSDP::ProcessNotify( const QStringMap &headers )
605 {
606  QString sDescURL = GetHeaderValue( headers, "LOCATION" , "" );
607  QString sNTS = GetHeaderValue( headers, "NTS" , "" );
608  QString sNT = GetHeaderValue( headers, "NT" , "" );
609  QString sUSN = GetHeaderValue( headers, "USN" , "" );
610  QString sCache = GetHeaderValue( headers, "CACHE-CONTROL" , "" );
611 
612  LOG(VB_UPNP, LOG_DEBUG,
613  QString( "SSDP::ProcessNotify ...\n"
614  "DescURL=%1\n"
615  "NTS =%2\n"
616  "NT =%3\n"
617  "USN =%4\n"
618  "Cache =%5" )
619  .arg(sDescURL).arg(sNTS).arg(sNT).arg(sUSN).arg(sCache));
620 
621  if (sNTS.contains( "ssdp:alive"))
622  {
623  int nPos = sCache.indexOf("max-age", 0, Qt::CaseInsensitive);
624 
625  if (nPos < 0)
626  return false;
627 
628  if ((nPos = sCache.indexOf("=", nPos)) < 0)
629  return false;
630 
631  int nSecs = sCache.mid( nPos+1 ).toInt();
632 
633  SSDPCache::Instance()->Add( sNT, sUSN, sDescURL, nSecs );
634 
635  return true;
636  }
637 
638 
639  if ( sNTS.contains( "ssdp:byebye" ) )
640  {
641  SSDPCache::Instance()->Remove( sNT, sUSN );
642 
643  return true;
644  }
645 
646  return false;
647 }
648 
651 //
652 // SSDPExtension Implementation
653 //
656 
658 //
660 
661 SSDPExtension::SSDPExtension( int nServicePort , const QString sSharePath)
662  : HttpServerExtension( "SSDP" , sSharePath),
663  m_nServicePort(nServicePort)
664 {
665  m_sUPnpDescPath = UPnp::GetConfiguration()->GetValue( "UPnP/DescXmlPath",
666  m_sSharePath );
667 }
668 
670 //
672 
674 {
675 }
676 
678 //
680 
681 SSDPMethod SSDPExtension::GetMethod( const QString &sURI )
682 {
683  if (sURI == "getDeviceDesc" ) return( SSDPM_GetDeviceDesc );
684  if (sURI == "getDeviceList" ) return( SSDPM_GetDeviceList );
685 
686  return( SSDPM_Unknown );
687 }
688 
690 //
692 
694 {
695  // -=>TODO: This is very inefficient... should look into making
696  // it a unique path.
697 
698  return QStringList( "/" );
699 }
700 
702 //
704 
706 {
707  if (pRequest)
708  {
709  if ( pRequest->m_sBaseUrl != "/")
710  return( false );
711 
712  switch( GetMethod( pRequest->m_sMethod ))
713  {
714  case SSDPM_GetDeviceDesc: GetDeviceDesc( pRequest ); return( true );
715  case SSDPM_GetDeviceList: GetDeviceList( pRequest ); return( true );
716 
717  default: break;
718  }
719  }
720 
721  return( false );
722 }
723 
725 //
727 
729 {
730  pRequest->m_eResponseType = ResponseTypeXML;
731 
732  QString sUserAgent = pRequest->GetHeaderValue( "User-Agent", "" );
733 
734  LOG(VB_UPNP, LOG_DEBUG, "SSDPExtension::GetDeviceDesc - " +
735  QString( "Host=%1 Port=%2 UserAgent=%3" )
736  .arg(pRequest->GetHostAddress()) .arg(m_nServicePort)
737  .arg(sUserAgent));
738 
739  QTextStream stream( &(pRequest->m_response) );
740 
743  stream,
744  sUserAgent );
745 }
746 
748 //
750 
751 void SSDPExtension::GetFile( HTTPRequest *pRequest, QString sFileName )
752 {
753  pRequest->m_eResponseType = ResponseTypeHTML;
754  pRequest->m_nResponseStatus = 404;
755 
756  pRequest->m_sFileName = m_sUPnpDescPath + sFileName;
757 
758  if (QFile::exists( pRequest->m_sFileName ))
759  {
760  LOG(VB_UPNP, LOG_DEBUG,
761  QString("SSDPExtension::GetFile( %1 ) - Exists")
762  .arg(pRequest->m_sFileName));
763 
764  pRequest->m_eResponseType = ResponseTypeFile;
765  pRequest->m_nResponseStatus = 200;
766  pRequest->m_mapRespHeaders[ "Cache-Control" ]
767  = "no-cache=\"Ext\", max-age = 5000";
768  }
769  else
770  {
771  LOG(VB_UPNP, LOG_ERR,
772  QString("SSDPExtension::GetFile( %1 ) - Not Found")
773  .arg(pRequest->m_sFileName));
774  }
775 
776 }
777 
779 //
781 
783 {
784  LOG(VB_UPNP, LOG_DEBUG, "SSDPExtension::GetDeviceList");
785 
786  QString sXML;
787  QTextStream os(&sXML, QIODevice::WriteOnly);
788 
789  uint nDevCount, nEntryCount;
790  SSDPCache::Instance()->OutputXML(os, &nDevCount, &nEntryCount);
791 
792  NameValues list;
793  list.push_back(
794  NameValue("DeviceCount", (int)nDevCount));
795  list.push_back(
796  NameValue("DevicesAllocated", SSDPCacheEntries::g_nAllocated));
797  list.push_back(
798  NameValue("CacheEntriesFound", (int)nEntryCount));
799  list.push_back(
800  NameValue("CacheEntriesAllocated", DeviceLocation::g_nAllocated));
801  list.push_back(
802  NameValue("DeviceList", sXML));
803 
804  pRequest->FormatActionResponse(list);
805 
806  pRequest->m_eResponseType = ResponseTypeXML;
807  pRequest->m_nResponseStatus = 200;
808 }