00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00012
00013 #include <algorithm>
00014
00015 #include "upnp.h"
00016 #include "mythlogging.h"
00017
00018 #include "upnptasksearch.h"
00019 #include "upnptaskcache.h"
00020
00021 #include "mmulticastsocketdevice.h"
00022 #include "mbroadcastsocketdevice.h"
00023
00024 #include <QRegExp>
00025 #include <QStringList>
00026
00027 #include <stdlib.h>
00028
00031
00032
00033
00036
00037
00038
00039 static QMutex g_pSSDPCreationLock;
00040 SSDP* SSDP::g_pSSDP = NULL;
00041
00043
00045
00046 SSDP* SSDP::Instance()
00047 {
00048 QMutexLocker locker(&g_pSSDPCreationLock);
00049 return g_pSSDP ? g_pSSDP : (g_pSSDP = new SSDP());
00050 }
00051
00053
00055
00056 void SSDP::Shutdown()
00057 {
00058 QMutexLocker locker(&g_pSSDPCreationLock);
00059 delete g_pSSDP;
00060 g_pSSDP = NULL;
00061 }
00062
00064
00066
00067 SSDP::SSDP() :
00068 MThread ("SSDP" ),
00069 m_procReqLineExp ("[ \r\n][ \r\n]*"),
00070 m_nPort ( SSDP_PORT ),
00071 m_nSearchPort ( SSDP_SEARCHPORT ),
00072 m_nServicePort ( 0 ),
00073 m_pNotifyTask ( NULL ),
00074 m_bAnnouncementsEnabled( false ),
00075 m_bTermRequested ( false ),
00076 m_lock ( QMutex::NonRecursive )
00077 {
00078 LOG(VB_UPNP, LOG_NOTICE, "Starting up SSDP Thread..." );
00079
00080 Configuration *pConfig = UPnp::GetConfiguration();
00081
00082 m_nPort = pConfig->GetValue("UPnP/SSDP/Port" , SSDP_PORT );
00083 m_nSearchPort = pConfig->GetValue("UPnP/SSDP/SearchPort", SSDP_SEARCHPORT);
00084
00085 m_Sockets[ SocketIdx_Search ] =
00086 new MMulticastSocketDevice();
00087 m_Sockets[ SocketIdx_Multicast ] =
00088 new MMulticastSocketDevice(SSDP_GROUP, m_nPort);
00089 m_Sockets[ SocketIdx_Broadcast ] =
00090 new MBroadcastSocketDevice("255.255.255.255", m_nPort);
00091
00092 m_Sockets[ SocketIdx_Search ]->setBlocking( false );
00093 m_Sockets[ SocketIdx_Multicast ]->setBlocking( false );
00094 m_Sockets[ SocketIdx_Broadcast ]->setBlocking( false );
00095
00096
00097 QHostAddress ip4addr( QHostAddress::Any );
00098
00099 m_Sockets[ SocketIdx_Search ]->bind( ip4addr , m_nSearchPort );
00100 m_Sockets[ SocketIdx_Search ]->bind( QHostAddress::Any, m_nSearchPort );
00101
00102
00103
00104
00105
00106 start();
00107
00108 LOG(VB_UPNP, LOG_INFO, "SSDP Thread Starting soon" );
00109 }
00110
00112
00114
00115 SSDP::~SSDP()
00116 {
00117 LOG(VB_UPNP, LOG_NOTICE, "Shutting Down SSDP Thread..." );
00118
00119 DisableNotifications();
00120
00121 m_bTermRequested = true;
00122 wait();
00123
00124 if (m_pNotifyTask != NULL)
00125 m_pNotifyTask->Release();
00126
00127 for (int nIdx = 0; nIdx < (int)NumberOfSockets; nIdx++ )
00128 {
00129 if (m_Sockets[ nIdx ] != NULL )
00130 {
00131 delete m_Sockets[ nIdx ];
00132 }
00133 }
00134
00135 LOG(VB_UPNP, LOG_INFO, "SSDP Thread Terminated." );
00136 }
00137
00138 void SSDP::RequestTerminate(void)
00139 {
00140 m_bTermRequested = true;
00141 }
00142
00144
00146
00147 void SSDP::EnableNotifications( int nServicePort )
00148 {
00149 if ( m_pNotifyTask == NULL )
00150 {
00151 m_nServicePort = nServicePort;
00152
00153 LOG(VB_UPNP, LOG_INFO,
00154 "SSDP::EnableNotifications() - creating new task");
00155 m_pNotifyTask = new UPnpNotifyTask( m_nServicePort );
00156
00157
00158
00159
00160
00161 m_pNotifyTask->AddRef();
00162
00163
00164
00165
00166
00167 LOG(VB_UPNP, LOG_INFO,
00168 "SSDP::EnableNotifications() - sending NTS_byebye");
00169 m_pNotifyTask->SetNTS( NTS_byebye );
00170 m_pNotifyTask->Execute( NULL );
00171
00172 m_bAnnouncementsEnabled = true;
00173 }
00174
00175
00176
00177
00178
00179 LOG(VB_UPNP, LOG_INFO, "SSDP::EnableNotifications() - sending NTS_alive");
00180
00181 m_pNotifyTask->SetNTS( NTS_alive );
00182
00183 TaskQueue::Instance()->AddTask(m_pNotifyTask);
00184
00185 LOG(VB_UPNP, LOG_INFO,
00186 "SSDP::EnableNotifications() - Task added to UPnP queue");
00187 }
00188
00190
00192
00193 void SSDP::DisableNotifications()
00194 {
00195 m_bAnnouncementsEnabled = false;
00196
00197 if (m_pNotifyTask != NULL)
00198 {
00199
00200
00201 m_pNotifyTask->SetNTS( NTS_byebye );
00202 m_pNotifyTask->Execute( NULL );
00203 }
00204 }
00205
00207
00209 void SSDP::PerformSearch(const QString &sST, uint timeout_secs)
00210 {
00211 timeout_secs = std::max(std::min(timeout_secs, 5U), 1U);
00212 QString rRequest = QString("M-SEARCH * HTTP/1.1\r\n"
00213 "HOST: 239.255.255.250:1900\r\n"
00214 "MAN: \"ssdp:discover\"\r\n"
00215 "MX: %1\r\n"
00216 "ST: %2\r\n"
00217 "\r\n")
00218 .arg(timeout_secs).arg(sST);
00219
00220 LOG(VB_UPNP, LOG_DEBUG, QString("\n\n%1\n").arg(rRequest));
00221
00222 QByteArray sRequest = rRequest.toUtf8();
00223
00224 MSocketDevice *pSocket = m_Sockets[ SocketIdx_Search ];
00225 if ( !pSocket->isValid() )
00226 {
00227 pSocket->setProtocol(MSocketDevice::IPv4);
00228 pSocket->setSocket(pSocket->createNewSocket(), MSocketDevice::Datagram);
00229 }
00230
00231 QHostAddress address;
00232 address.setAddress( SSDP_GROUP );
00233
00234 int nSize = sRequest.size();
00235
00236 if ( pSocket->writeBlock( sRequest.data(),
00237 sRequest.size(), address, SSDP_PORT ) != nSize)
00238 LOG(VB_GENERAL, LOG_INFO,
00239 "SSDP::PerformSearch - did not write entire buffer.");
00240
00241 usleep( random() % 250000 );
00242
00243 if ( pSocket->writeBlock( sRequest.data(),
00244 sRequest.size(), address, SSDP_PORT ) != nSize)
00245 LOG(VB_GENERAL, LOG_INFO,
00246 "SSDP::PerformSearch - did not write entire buffer.");
00247 }
00248
00250
00252
00253 void SSDP::run()
00254 {
00255 RunProlog();
00256
00257 fd_set read_set;
00258 struct timeval timeout;
00259
00260 LOG(VB_UPNP, LOG_INFO, "SSDP::Run - SSDP Thread Started." );
00261
00262
00263
00264
00265
00266 while ( ! m_bTermRequested )
00267 {
00268 int nMaxSocket = 0;
00269
00270 FD_ZERO( &read_set );
00271
00272 for (uint nIdx = 0; nIdx < NumberOfSockets; nIdx++ )
00273 {
00274 if (m_Sockets[nIdx] != NULL && m_Sockets[nIdx]->socket() >= 0)
00275 {
00276 FD_SET( m_Sockets[ nIdx ]->socket(), &read_set );
00277 nMaxSocket = max( m_Sockets[ nIdx ]->socket(), nMaxSocket );
00278
00279 #if 0
00280 if (m_Sockets[ nIdx ]->bytesAvailable() > 0)
00281 {
00282 LOG(VB_GENERAL, LOG_DEBUG,
00283 QString("Found Extra data before select: %1")
00284 .arg(nIdx));
00285 ProcessData( m_Sockets[ nIdx ] );
00286 }
00287 #endif
00288 }
00289 }
00290
00291 timeout.tv_sec = 1;
00292 timeout.tv_usec = 0;
00293
00294 int count;
00295 count = select(nMaxSocket + 1, &read_set, NULL, NULL, &timeout);
00296 for (int nIdx = 0; count && nIdx < (int)NumberOfSockets; nIdx++ )
00297 {
00298 if (m_Sockets[nIdx] != NULL && m_Sockets[nIdx]->socket() >= 0 &&
00299 FD_ISSET(m_Sockets[nIdx]->socket(), &read_set))
00300 {
00301 #if 0
00302 LOG(VB_GENERAL, LOG_DEBUG, QString("FD_ISSET( %1 )").arg(nIdx));
00303 #endif
00304 ProcessData(m_Sockets[nIdx]);
00305 count--;
00306 }
00307 }
00308 }
00309
00310 RunEpilog();
00311 }
00312
00314
00316
00317 void SSDP::ProcessData( MSocketDevice *pSocket )
00318 {
00319 long nBytes = 0;
00320 long nRead = 0;
00321
00322 while ((nBytes = pSocket->bytesAvailable()) > 0)
00323 {
00324 QByteArray buffer;
00325 buffer.resize(nBytes);
00326
00327 nRead = pSocket->readBlock( buffer.data(), nBytes );
00328
00329 QHostAddress peerAddress = pSocket->peerAddress();
00330 quint16 peerPort = pSocket->peerPort ();
00331
00332
00333 QString str = QString(buffer.constData());
00334 QStringList lines = str.split("\r\n", QString::SkipEmptyParts);
00335 QString sRequestLine = lines.size() ? lines[0] : "";
00336
00337 lines.pop_front();
00338
00339
00340
00341
00342
00343 LOG(VB_UPNP, LOG_DEBUG, QString("SSDP::ProcessData - requestLine: %1")
00344 .arg(sRequestLine));
00345
00346 SSDPRequestType eType = ProcessRequestLine( sRequestLine );
00347
00348
00349
00350
00351
00352 QStringMap headers;
00353
00354 for ( QStringList::Iterator it = lines.begin();
00355 it != lines.end(); ++it )
00356 {
00357 QString sLine = *it;
00358 QString sName = sLine.section( ':', 0, 0 ).trimmed();
00359 QString sValue = sLine.section( ':', 1 );
00360
00361 sValue.truncate( sValue.length() );
00362
00363 if ((sName.length() != 0) && (sValue.length() !=0))
00364 headers.insert( sName.toLower(), sValue.trimmed() );
00365 }
00366
00367 #if 0
00368 pSocket->SetDestAddress( peerAddress, peerPort );
00369 #endif
00370
00371
00372
00373
00374
00375 switch( eType )
00376 {
00377 case SSDP_MSearch:
00378 {
00379
00380
00381
00382
00383
00384 if (m_pNotifyTask != NULL)
00385 ProcessSearchRequest( headers, peerAddress, peerPort );
00386
00387 break;
00388 }
00389
00390 case SSDP_MSearchResp:
00391 ProcessSearchResponse( headers);
00392 break;
00393
00394 case SSDP_Notify:
00395 ProcessNotify( headers );
00396 break;
00397
00398 case SSDP_Unknown:
00399 default:
00400 LOG(VB_UPNP, LOG_ERR,
00401 "SSPD::ProcessData - Unknown request Type.");
00402 break;
00403 }
00404 }
00405 }
00406
00408
00410
00411 SSDPRequestType SSDP::ProcessRequestLine( const QString &sLine )
00412 {
00413 QStringList tokens = sLine.split(m_procReqLineExp, QString::SkipEmptyParts);
00414
00415
00416
00417
00418
00419
00420
00421
00422 if ( sLine.startsWith( QString("HTTP/") ))
00423 return SSDP_MSearchResp;
00424 else
00425 {
00426 if (tokens.count() > 0)
00427 {
00428 if (tokens[0] == "M-SEARCH" ) return SSDP_MSearch;
00429 if (tokens[0] == "NOTIFY" ) return SSDP_Notify;
00430 }
00431 }
00432
00433 return SSDP_Unknown;
00434 }
00435
00437
00439
00440 QString SSDP::GetHeaderValue( const QStringMap &headers,
00441 const QString &sKey, const QString &sDefault )
00442 {
00443 QStringMap::const_iterator it = headers.find( sKey.toLower() );
00444
00445 if ( it == headers.end())
00446 return( sDefault );
00447
00448 return *it;
00449 }
00450
00452
00454
00455 bool SSDP::ProcessSearchRequest( const QStringMap &sHeaders,
00456 QHostAddress peerAddress,
00457 quint16 peerPort )
00458 {
00459 QString sMAN = GetHeaderValue( sHeaders, "MAN", "" );
00460 QString sST = GetHeaderValue( sHeaders, "ST" , "" );
00461 QString sMX = GetHeaderValue( sHeaders, "MX" , "" );
00462 int nMX = 0;
00463
00464 LOG(VB_UPNP, LOG_DEBUG, QString("SSDP::ProcessSearchrequest : [%1] MX=%2")
00465 .arg(sST).arg(sMX));
00466
00467
00468
00469
00470
00471 #if 0
00472 if ( pRequest->m_sMethod != "*" ) return false;
00473 if ( pRequest->m_sProtocol != "HTTP" ) return false;
00474 if ( pRequest->m_nMajor != 1 ) return false;
00475 #endif
00476 if ( sMAN != "\"ssdp:discover\"" ) return false;
00477 if ( sST.length() == 0 ) return false;
00478 if ( sMX.length() == 0 ) return false;
00479 if ((nMX = sMX.toInt()) == 0 ) return false;
00480 if ( nMX < 0 ) return false;
00481
00482
00483
00484
00485
00486 nMX = (nMX > 120) ? 120 : nMX;
00487
00488 int nNewMX = (int)(0 + ((unsigned short)random() % nMX)) * 1000;
00489
00490
00491
00492
00493
00494 if ((sST == "ssdp:all") || (sST == "upnp:rootdevice"))
00495 {
00496 UPnpSearchTask *pTask = new UPnpSearchTask( m_nServicePort,
00497 peerAddress, peerPort, sST,
00498 UPnp::g_UPnpDeviceDesc.m_rootDevice.GetUDN());
00499
00500 #if 0
00501
00502
00503 pTask->Execute( NULL );
00504 #endif
00505
00506 TaskQueue::Instance()->AddTask( nNewMX, pTask );
00507
00508 return true;
00509 }
00510
00511
00512
00513
00514
00515 QString sUDN = UPnp::g_UPnpDeviceDesc.FindDeviceUDN(
00516 &(UPnp::g_UPnpDeviceDesc.m_rootDevice), sST );
00517
00518 if (sUDN.length() > 0)
00519 {
00520 UPnpSearchTask *pTask = new UPnpSearchTask( m_nServicePort,
00521 peerAddress,
00522 peerPort,
00523 sST,
00524 sUDN );
00525
00526
00527
00528 pTask->Execute( NULL );
00529
00530 TaskQueue::Instance()->AddTask( nNewMX, pTask );
00531
00532 return true;
00533 }
00534
00535 return false;
00536 }
00537
00539
00541
00542 bool SSDP::ProcessSearchResponse( const QStringMap &headers )
00543 {
00544 QString sDescURL = GetHeaderValue( headers, "LOCATION" , "" );
00545 QString sST = GetHeaderValue( headers, "ST" , "" );
00546 QString sUSN = GetHeaderValue( headers, "USN" , "" );
00547 QString sCache = GetHeaderValue( headers, "CACHE-CONTROL" , "" );
00548
00549 LOG(VB_UPNP, LOG_DEBUG,
00550 QString( "SSDP::ProcessSearchResponse ...\n"
00551 "DescURL=%1\n"
00552 "ST =%2\n"
00553 "USN =%3\n"
00554 "Cache =%4")
00555 .arg(sDescURL).arg(sST).arg(sUSN).arg(sCache));
00556
00557 int nPos = sCache.indexOf("max-age", 0, Qt::CaseInsensitive);
00558
00559 if (nPos < 0)
00560 return false;
00561
00562 if ((nPos = sCache.indexOf("=", nPos)) < 0)
00563 return false;
00564
00565 int nSecs = sCache.mid( nPos+1 ).toInt();
00566
00567 SSDPCache::Instance()->Add( sST, sUSN, sDescURL, nSecs );
00568
00569 return true;
00570 }
00571
00573
00575
00576 bool SSDP::ProcessNotify( const QStringMap &headers )
00577 {
00578 QString sDescURL = GetHeaderValue( headers, "LOCATION" , "" );
00579 QString sNTS = GetHeaderValue( headers, "NTS" , "" );
00580 QString sNT = GetHeaderValue( headers, "NT" , "" );
00581 QString sUSN = GetHeaderValue( headers, "USN" , "" );
00582 QString sCache = GetHeaderValue( headers, "CACHE-CONTROL" , "" );
00583
00584 LOG(VB_UPNP, LOG_DEBUG,
00585 QString( "SSDP::ProcessNotify ...\n"
00586 "DescURL=%1\n"
00587 "NTS =%2\n"
00588 "NT =%3\n"
00589 "USN =%4\n"
00590 "Cache =%5" )
00591 .arg(sDescURL).arg(sNTS).arg(sNT).arg(sUSN).arg(sCache));
00592
00593 if (sNTS.contains( "ssdp:alive"))
00594 {
00595 int nPos = sCache.indexOf("max-age", 0, Qt::CaseInsensitive);
00596
00597 if (nPos < 0)
00598 return false;
00599
00600 if ((nPos = sCache.indexOf("=", nPos)) < 0)
00601 return false;
00602
00603 int nSecs = sCache.mid( nPos+1 ).toInt();
00604
00605 SSDPCache::Instance()->Add( sNT, sUSN, sDescURL, nSecs );
00606
00607 return true;
00608 }
00609
00610
00611 if ( sNTS.contains( "ssdp:byebye" ) )
00612 {
00613 SSDPCache::Instance()->Remove( sNT, sUSN );
00614
00615 return true;
00616 }
00617
00618 return false;
00619 }
00620
00623
00624
00625
00628
00630
00632
00633 SSDPExtension::SSDPExtension( int nServicePort , const QString sSharePath)
00634 : HttpServerExtension( "SSDP" , sSharePath),
00635 m_nServicePort(nServicePort)
00636 {
00637 m_sUPnpDescPath = UPnp::GetConfiguration()->GetValue( "UPnP/DescXmlPath",
00638 m_sSharePath );
00639 }
00640
00642
00644
00645 SSDPExtension::~SSDPExtension( )
00646 {
00647 }
00648
00650
00652
00653 SSDPMethod SSDPExtension::GetMethod( const QString &sURI )
00654 {
00655 if (sURI == "getDeviceDesc" ) return( SSDPM_GetDeviceDesc );
00656 if (sURI == "getDeviceList" ) return( SSDPM_GetDeviceList );
00657
00658 return( SSDPM_Unknown );
00659 }
00660
00662
00664
00665 QStringList SSDPExtension::GetBasePaths()
00666 {
00667
00668
00669
00670 return QStringList( "/" );
00671 }
00672
00674
00676
00677 bool SSDPExtension::ProcessRequest( HTTPRequest *pRequest )
00678 {
00679 if (pRequest)
00680 {
00681 if ( pRequest->m_sBaseUrl != "/")
00682 return( false );
00683
00684 switch( GetMethod( pRequest->m_sMethod ))
00685 {
00686 case SSDPM_GetDeviceDesc: GetDeviceDesc( pRequest ); return( true );
00687 case SSDPM_GetDeviceList: GetDeviceList( pRequest ); return( true );
00688
00689 default: break;
00690 }
00691 }
00692
00693 return( false );
00694 }
00695
00697
00699
00700 void SSDPExtension::GetDeviceDesc( HTTPRequest *pRequest )
00701 {
00702 pRequest->m_eResponseType = ResponseTypeXML;
00703
00704 QString sUserAgent = pRequest->GetHeaderValue( "User-Agent", "" );
00705
00706 LOG(VB_UPNP, LOG_DEBUG, "SSDPExtension::GetDeviceDesc - " +
00707 QString( "Host=%1 Port=%2 UserAgent=%3" )
00708 .arg(pRequest->GetHostAddress()) .arg(m_nServicePort)
00709 .arg(sUserAgent));
00710
00711 QTextStream stream( &(pRequest->m_response) );
00712
00713 UPnp::g_UPnpDeviceDesc.GetValidXML( pRequest->GetHostAddress(),
00714 m_nServicePort,
00715 stream,
00716 sUserAgent );
00717 }
00718
00720
00722
00723 void SSDPExtension::GetFile( HTTPRequest *pRequest, QString sFileName )
00724 {
00725 pRequest->m_eResponseType = ResponseTypeHTML;
00726 pRequest->m_nResponseStatus = 404;
00727
00728 pRequest->m_sFileName = m_sUPnpDescPath + sFileName;
00729
00730 if (QFile::exists( pRequest->m_sFileName ))
00731 {
00732 LOG(VB_UPNP, LOG_DEBUG,
00733 QString("SSDPExtension::GetFile( %1 ) - Exists")
00734 .arg(pRequest->m_sFileName));
00735
00736 pRequest->m_eResponseType = ResponseTypeFile;
00737 pRequest->m_nResponseStatus = 200;
00738 pRequest->m_mapRespHeaders[ "Cache-Control" ]
00739 = "no-cache=\"Ext\", max-age = 5000";
00740 }
00741 else
00742 {
00743 LOG(VB_UPNP, LOG_ERR,
00744 QString("SSDPExtension::GetFile( %1 ) - Not Found")
00745 .arg(pRequest->m_sFileName));
00746 }
00747
00748 }
00749
00751
00753
00754 void SSDPExtension::GetDeviceList( HTTPRequest *pRequest )
00755 {
00756 LOG(VB_UPNP, LOG_DEBUG, "SSDPExtension::GetDeviceList");
00757
00758 QString sXML;
00759 QTextStream os(&sXML, QIODevice::WriteOnly);
00760
00761 uint nDevCount, nEntryCount;
00762 SSDPCache::Instance()->OutputXML(os, &nDevCount, &nEntryCount);
00763
00764 NameValues list;
00765 list.push_back(
00766 NameValue("DeviceCount", (int)nDevCount));
00767 list.push_back(
00768 NameValue("DevicesAllocated", SSDPCacheEntries::g_nAllocated));
00769 list.push_back(
00770 NameValue("CacheEntriesFound", (int)nEntryCount));
00771 list.push_back(
00772 NameValue("CacheEntriesAllocated", DeviceLocation::g_nAllocated));
00773 list.push_back(
00774 NameValue("DeviceList", sXML));
00775
00776 pRequest->FormatActionResponse(list);
00777
00778 pRequest->m_eResponseType = ResponseTypeXML;
00779 pRequest->m_nResponseStatus = 200;
00780 }