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 {
00126 m_pNotifyTask->DecrRef();
00127 m_pNotifyTask = NULL;
00128 }
00129
00130 for (int nIdx = 0; nIdx < (int)NumberOfSockets; nIdx++ )
00131 {
00132 if (m_Sockets[ nIdx ] != NULL )
00133 {
00134 delete m_Sockets[ nIdx ];
00135 }
00136 }
00137
00138 LOG(VB_UPNP, LOG_INFO, "SSDP Thread Terminated." );
00139 }
00140
00141 void SSDP::RequestTerminate(void)
00142 {
00143 m_bTermRequested = true;
00144 }
00145
00147
00149
00150 void SSDP::EnableNotifications( int nServicePort )
00151 {
00152 if ( m_pNotifyTask == NULL )
00153 {
00154 m_nServicePort = nServicePort;
00155
00156 LOG(VB_UPNP, LOG_INFO,
00157 "SSDP::EnableNotifications() - creating new task");
00158 m_pNotifyTask = new UPnpNotifyTask( m_nServicePort );
00159
00160
00161
00162
00163
00164 LOG(VB_UPNP, LOG_INFO,
00165 "SSDP::EnableNotifications() - sending NTS_byebye");
00166 m_pNotifyTask->SetNTS( NTS_byebye );
00167 m_pNotifyTask->Execute( NULL );
00168
00169 m_bAnnouncementsEnabled = true;
00170 }
00171
00172
00173
00174
00175
00176 LOG(VB_UPNP, LOG_INFO, "SSDP::EnableNotifications() - sending NTS_alive");
00177
00178 m_pNotifyTask->SetNTS( NTS_alive );
00179
00180 TaskQueue::Instance()->AddTask(m_pNotifyTask);
00181
00182 LOG(VB_UPNP, LOG_INFO,
00183 "SSDP::EnableNotifications() - Task added to UPnP queue");
00184 }
00185
00187
00189
00190 void SSDP::DisableNotifications()
00191 {
00192 m_bAnnouncementsEnabled = false;
00193
00194 if (m_pNotifyTask != NULL)
00195 {
00196
00197
00198 m_pNotifyTask->SetNTS( NTS_byebye );
00199 m_pNotifyTask->Execute( NULL );
00200 }
00201 }
00202
00204
00206 void SSDP::PerformSearch(const QString &sST, uint timeout_secs)
00207 {
00208 timeout_secs = std::max(std::min(timeout_secs, 5U), 1U);
00209 QString rRequest = QString("M-SEARCH * HTTP/1.1\r\n"
00210 "HOST: 239.255.255.250:1900\r\n"
00211 "MAN: \"ssdp:discover\"\r\n"
00212 "MX: %1\r\n"
00213 "ST: %2\r\n"
00214 "\r\n")
00215 .arg(timeout_secs).arg(sST);
00216
00217 LOG(VB_UPNP, LOG_DEBUG, QString("\n\n%1\n").arg(rRequest));
00218
00219 QByteArray sRequest = rRequest.toUtf8();
00220
00221 MSocketDevice *pSocket = m_Sockets[ SocketIdx_Search ];
00222 if ( !pSocket->isValid() )
00223 {
00224 pSocket->setProtocol(MSocketDevice::IPv4);
00225 pSocket->setSocket(pSocket->createNewSocket(), MSocketDevice::Datagram);
00226 }
00227
00228 QHostAddress address;
00229 address.setAddress( SSDP_GROUP );
00230
00231 int nSize = sRequest.size();
00232
00233 if ( pSocket->writeBlock( sRequest.data(),
00234 sRequest.size(), address, SSDP_PORT ) != nSize)
00235 LOG(VB_GENERAL, LOG_INFO,
00236 "SSDP::PerformSearch - did not write entire buffer.");
00237
00238 usleep( random() % 250000 );
00239
00240 if ( pSocket->writeBlock( sRequest.data(),
00241 sRequest.size(), address, SSDP_PORT ) != nSize)
00242 LOG(VB_GENERAL, LOG_INFO,
00243 "SSDP::PerformSearch - did not write entire buffer.");
00244 }
00245
00247
00249
00250 void SSDP::run()
00251 {
00252 RunProlog();
00253
00254 fd_set read_set;
00255 struct timeval timeout;
00256
00257 LOG(VB_UPNP, LOG_INFO, "SSDP::Run - SSDP Thread Started." );
00258
00259
00260
00261
00262
00263 while ( ! m_bTermRequested )
00264 {
00265 int nMaxSocket = 0;
00266
00267 FD_ZERO( &read_set );
00268
00269 for (uint nIdx = 0; nIdx < NumberOfSockets; nIdx++ )
00270 {
00271 if (m_Sockets[nIdx] != NULL && m_Sockets[nIdx]->socket() >= 0)
00272 {
00273 FD_SET( m_Sockets[ nIdx ]->socket(), &read_set );
00274 nMaxSocket = max( m_Sockets[ nIdx ]->socket(), nMaxSocket );
00275
00276 #if 0
00277 if (m_Sockets[ nIdx ]->bytesAvailable() > 0)
00278 {
00279 LOG(VB_GENERAL, LOG_DEBUG,
00280 QString("Found Extra data before select: %1")
00281 .arg(nIdx));
00282 ProcessData( m_Sockets[ nIdx ] );
00283 }
00284 #endif
00285 }
00286 }
00287
00288 timeout.tv_sec = 1;
00289 timeout.tv_usec = 0;
00290
00291 int count;
00292 count = select(nMaxSocket + 1, &read_set, NULL, NULL, &timeout);
00293 for (int nIdx = 0; count && nIdx < (int)NumberOfSockets; nIdx++ )
00294 {
00295 if (m_Sockets[nIdx] != NULL && m_Sockets[nIdx]->socket() >= 0 &&
00296 FD_ISSET(m_Sockets[nIdx]->socket(), &read_set))
00297 {
00298 #if 0
00299 LOG(VB_GENERAL, LOG_DEBUG, QString("FD_ISSET( %1 )").arg(nIdx));
00300 #endif
00301 ProcessData(m_Sockets[nIdx]);
00302 count--;
00303 }
00304 }
00305 }
00306
00307 RunEpilog();
00308 }
00309
00311
00313
00314 void SSDP::ProcessData( MSocketDevice *pSocket )
00315 {
00316 QByteArray buffer;
00317 long nBytes = 0;
00318
00319 while ((nBytes = pSocket->bytesAvailable()) > 0)
00320 {
00321 buffer.resize(nBytes);
00322
00323 long nRead = 0;
00324 do
00325 {
00326 long ret = pSocket->readBlock( buffer.data() + nRead, nBytes - nRead );
00327 if (ret < 0)
00328 {
00329 LOG(VB_GENERAL, LOG_ERR, QString("Socket readBlock error %1")
00330 .arg(pSocket->error()));
00331 buffer.clear();
00332 break;
00333 }
00334
00335 nRead += ret;
00336
00337 if (0 == ret)
00338 {
00339 LOG(VB_SOCKET, LOG_WARNING,
00340 QString("%1 bytes reported available, "
00341 "but only %2 bytes read.")
00342 .arg(nBytes).arg(nRead));
00343 nBytes = nRead;
00344 buffer.resize(nBytes);
00345 break;
00346 }
00347 }
00348 while (nRead < nBytes);
00349
00350 if (buffer.isEmpty())
00351 continue;
00352
00353 QHostAddress peerAddress = pSocket->peerAddress();
00354 quint16 peerPort = pSocket->peerPort ();
00355
00356
00357 QString str = QString(buffer.constData());
00358 QStringList lines = str.split("\r\n", QString::SkipEmptyParts);
00359 QString sRequestLine = lines.size() ? lines[0] : "";
00360
00361 lines.pop_front();
00362
00363
00364
00365
00366
00367 LOG(VB_UPNP, LOG_DEBUG, QString("SSDP::ProcessData - requestLine: %1")
00368 .arg(sRequestLine));
00369
00370 SSDPRequestType eType = ProcessRequestLine( sRequestLine );
00371
00372
00373
00374
00375
00376 QStringMap headers;
00377
00378 for ( QStringList::Iterator it = lines.begin();
00379 it != lines.end(); ++it )
00380 {
00381 QString sLine = *it;
00382 QString sName = sLine.section( ':', 0, 0 ).trimmed();
00383 QString sValue = sLine.section( ':', 1 );
00384
00385 sValue.truncate( sValue.length() );
00386
00387 if ((sName.length() != 0) && (sValue.length() !=0))
00388 headers.insert( sName.toLower(), sValue.trimmed() );
00389 }
00390
00391 #if 0
00392 pSocket->SetDestAddress( peerAddress, peerPort );
00393 #endif
00394
00395
00396
00397
00398
00399 switch( eType )
00400 {
00401 case SSDP_MSearch:
00402 {
00403
00404
00405
00406
00407
00408 if (m_pNotifyTask != NULL)
00409 ProcessSearchRequest( headers, peerAddress, peerPort );
00410
00411 break;
00412 }
00413
00414 case SSDP_MSearchResp:
00415 ProcessSearchResponse( headers);
00416 break;
00417
00418 case SSDP_Notify:
00419 ProcessNotify( headers );
00420 break;
00421
00422 case SSDP_Unknown:
00423 default:
00424 LOG(VB_UPNP, LOG_ERR,
00425 "SSPD::ProcessData - Unknown request Type.");
00426 break;
00427 }
00428 }
00429 }
00430
00432
00434
00435 SSDPRequestType SSDP::ProcessRequestLine( const QString &sLine )
00436 {
00437 QStringList tokens = sLine.split(m_procReqLineExp, QString::SkipEmptyParts);
00438
00439
00440
00441
00442
00443
00444
00445
00446 if ( sLine.startsWith( QString("HTTP/") ))
00447 return SSDP_MSearchResp;
00448 else
00449 {
00450 if (tokens.count() > 0)
00451 {
00452 if (tokens[0] == "M-SEARCH" ) return SSDP_MSearch;
00453 if (tokens[0] == "NOTIFY" ) return SSDP_Notify;
00454 }
00455 }
00456
00457 return SSDP_Unknown;
00458 }
00459
00461
00463
00464 QString SSDP::GetHeaderValue( const QStringMap &headers,
00465 const QString &sKey, const QString &sDefault )
00466 {
00467 QStringMap::const_iterator it = headers.find( sKey.toLower() );
00468
00469 if ( it == headers.end())
00470 return( sDefault );
00471
00472 return *it;
00473 }
00474
00476
00478
00479 bool SSDP::ProcessSearchRequest( const QStringMap &sHeaders,
00480 QHostAddress peerAddress,
00481 quint16 peerPort )
00482 {
00483 QString sMAN = GetHeaderValue( sHeaders, "MAN", "" );
00484 QString sST = GetHeaderValue( sHeaders, "ST" , "" );
00485 QString sMX = GetHeaderValue( sHeaders, "MX" , "" );
00486 int nMX = 0;
00487
00488 LOG(VB_UPNP, LOG_DEBUG, QString("SSDP::ProcessSearchrequest : [%1] MX=%2")
00489 .arg(sST).arg(sMX));
00490
00491
00492
00493
00494
00495 #if 0
00496 if ( pRequest->m_sMethod != "*" ) return false;
00497 if ( pRequest->m_sProtocol != "HTTP" ) return false;
00498 if ( pRequest->m_nMajor != 1 ) return false;
00499 #endif
00500 if ( sMAN != "\"ssdp:discover\"" ) return false;
00501 if ( sST.length() == 0 ) return false;
00502 if ( sMX.length() == 0 ) return false;
00503 if ((nMX = sMX.toInt()) == 0 ) return false;
00504 if ( nMX < 0 ) return false;
00505
00506
00507
00508
00509
00510 nMX = (nMX > 120) ? 120 : nMX;
00511
00512 int nNewMX = (int)(0 + ((unsigned short)random() % nMX)) * 1000;
00513
00514
00515
00516
00517
00518 if ((sST == "ssdp:all") || (sST == "upnp:rootdevice"))
00519 {
00520 UPnpSearchTask *pTask = new UPnpSearchTask( m_nServicePort,
00521 peerAddress, peerPort, sST,
00522 UPnp::g_UPnpDeviceDesc.m_rootDevice.GetUDN());
00523
00524 #if 0
00525
00526
00527 pTask->Execute( NULL );
00528 #endif
00529
00530 TaskQueue::Instance()->AddTask( nNewMX, pTask );
00531
00532 pTask->DecrRef();
00533
00534 return true;
00535 }
00536
00537
00538
00539
00540
00541 QString sUDN = UPnp::g_UPnpDeviceDesc.FindDeviceUDN(
00542 &(UPnp::g_UPnpDeviceDesc.m_rootDevice), sST );
00543
00544 if (sUDN.length() > 0)
00545 {
00546 UPnpSearchTask *pTask = new UPnpSearchTask( m_nServicePort,
00547 peerAddress,
00548 peerPort,
00549 sST,
00550 sUDN );
00551
00552
00553
00554 pTask->Execute( NULL );
00555
00556 TaskQueue::Instance()->AddTask( nNewMX, pTask );
00557
00558 pTask->DecrRef();
00559
00560 return true;
00561 }
00562
00563 return false;
00564 }
00565
00567
00569
00570 bool SSDP::ProcessSearchResponse( const QStringMap &headers )
00571 {
00572 QString sDescURL = GetHeaderValue( headers, "LOCATION" , "" );
00573 QString sST = GetHeaderValue( headers, "ST" , "" );
00574 QString sUSN = GetHeaderValue( headers, "USN" , "" );
00575 QString sCache = GetHeaderValue( headers, "CACHE-CONTROL" , "" );
00576
00577 LOG(VB_UPNP, LOG_DEBUG,
00578 QString( "SSDP::ProcessSearchResponse ...\n"
00579 "DescURL=%1\n"
00580 "ST =%2\n"
00581 "USN =%3\n"
00582 "Cache =%4")
00583 .arg(sDescURL).arg(sST).arg(sUSN).arg(sCache));
00584
00585 int nPos = sCache.indexOf("max-age", 0, Qt::CaseInsensitive);
00586
00587 if (nPos < 0)
00588 return false;
00589
00590 if ((nPos = sCache.indexOf("=", nPos)) < 0)
00591 return false;
00592
00593 int nSecs = sCache.mid( nPos+1 ).toInt();
00594
00595 SSDPCache::Instance()->Add( sST, sUSN, sDescURL, nSecs );
00596
00597 return true;
00598 }
00599
00601
00603
00604 bool SSDP::ProcessNotify( const QStringMap &headers )
00605 {
00606 QString sDescURL = GetHeaderValue( headers, "LOCATION" , "" );
00607 QString sNTS = GetHeaderValue( headers, "NTS" , "" );
00608 QString sNT = GetHeaderValue( headers, "NT" , "" );
00609 QString sUSN = GetHeaderValue( headers, "USN" , "" );
00610 QString sCache = GetHeaderValue( headers, "CACHE-CONTROL" , "" );
00611
00612 LOG(VB_UPNP, LOG_DEBUG,
00613 QString( "SSDP::ProcessNotify ...\n"
00614 "DescURL=%1\n"
00615 "NTS =%2\n"
00616 "NT =%3\n"
00617 "USN =%4\n"
00618 "Cache =%5" )
00619 .arg(sDescURL).arg(sNTS).arg(sNT).arg(sUSN).arg(sCache));
00620
00621 if (sNTS.contains( "ssdp:alive"))
00622 {
00623 int nPos = sCache.indexOf("max-age", 0, Qt::CaseInsensitive);
00624
00625 if (nPos < 0)
00626 return false;
00627
00628 if ((nPos = sCache.indexOf("=", nPos)) < 0)
00629 return false;
00630
00631 int nSecs = sCache.mid( nPos+1 ).toInt();
00632
00633 SSDPCache::Instance()->Add( sNT, sUSN, sDescURL, nSecs );
00634
00635 return true;
00636 }
00637
00638
00639 if ( sNTS.contains( "ssdp:byebye" ) )
00640 {
00641 SSDPCache::Instance()->Remove( sNT, sUSN );
00642
00643 return true;
00644 }
00645
00646 return false;
00647 }
00648
00651
00652
00653
00656
00658
00660
00661 SSDPExtension::SSDPExtension( int nServicePort , const QString sSharePath)
00662 : HttpServerExtension( "SSDP" , sSharePath),
00663 m_nServicePort(nServicePort)
00664 {
00665 m_sUPnpDescPath = UPnp::GetConfiguration()->GetValue( "UPnP/DescXmlPath",
00666 m_sSharePath );
00667 }
00668
00670
00672
00673 SSDPExtension::~SSDPExtension( )
00674 {
00675 }
00676
00678
00680
00681 SSDPMethod SSDPExtension::GetMethod( const QString &sURI )
00682 {
00683 if (sURI == "getDeviceDesc" ) return( SSDPM_GetDeviceDesc );
00684 if (sURI == "getDeviceList" ) return( SSDPM_GetDeviceList );
00685
00686 return( SSDPM_Unknown );
00687 }
00688
00690
00692
00693 QStringList SSDPExtension::GetBasePaths()
00694 {
00695
00696
00697
00698 return QStringList( "/" );
00699 }
00700
00702
00704
00705 bool SSDPExtension::ProcessRequest( HTTPRequest *pRequest )
00706 {
00707 if (pRequest)
00708 {
00709 if ( pRequest->m_sBaseUrl != "/")
00710 return( false );
00711
00712 switch( GetMethod( pRequest->m_sMethod ))
00713 {
00714 case SSDPM_GetDeviceDesc: GetDeviceDesc( pRequest ); return( true );
00715 case SSDPM_GetDeviceList: GetDeviceList( pRequest ); return( true );
00716
00717 default: break;
00718 }
00719 }
00720
00721 return( false );
00722 }
00723
00725
00727
00728 void SSDPExtension::GetDeviceDesc( HTTPRequest *pRequest )
00729 {
00730 pRequest->m_eResponseType = ResponseTypeXML;
00731
00732 QString sUserAgent = pRequest->GetHeaderValue( "User-Agent", "" );
00733
00734 LOG(VB_UPNP, LOG_DEBUG, "SSDPExtension::GetDeviceDesc - " +
00735 QString( "Host=%1 Port=%2 UserAgent=%3" )
00736 .arg(pRequest->GetHostAddress()) .arg(m_nServicePort)
00737 .arg(sUserAgent));
00738
00739 QTextStream stream( &(pRequest->m_response) );
00740
00741 UPnp::g_UPnpDeviceDesc.GetValidXML( pRequest->GetHostAddress(),
00742 m_nServicePort,
00743 stream,
00744 sUserAgent );
00745 }
00746
00748
00750
00751 void SSDPExtension::GetFile( HTTPRequest *pRequest, QString sFileName )
00752 {
00753 pRequest->m_eResponseType = ResponseTypeHTML;
00754 pRequest->m_nResponseStatus = 404;
00755
00756 pRequest->m_sFileName = m_sUPnpDescPath + sFileName;
00757
00758 if (QFile::exists( pRequest->m_sFileName ))
00759 {
00760 LOG(VB_UPNP, LOG_DEBUG,
00761 QString("SSDPExtension::GetFile( %1 ) - Exists")
00762 .arg(pRequest->m_sFileName));
00763
00764 pRequest->m_eResponseType = ResponseTypeFile;
00765 pRequest->m_nResponseStatus = 200;
00766 pRequest->m_mapRespHeaders[ "Cache-Control" ]
00767 = "no-cache=\"Ext\", max-age = 5000";
00768 }
00769 else
00770 {
00771 LOG(VB_UPNP, LOG_ERR,
00772 QString("SSDPExtension::GetFile( %1 ) - Not Found")
00773 .arg(pRequest->m_sFileName));
00774 }
00775
00776 }
00777
00779
00781
00782 void SSDPExtension::GetDeviceList( HTTPRequest *pRequest )
00783 {
00784 LOG(VB_UPNP, LOG_DEBUG, "SSDPExtension::GetDeviceList");
00785
00786 QString sXML;
00787 QTextStream os(&sXML, QIODevice::WriteOnly);
00788
00789 uint nDevCount, nEntryCount;
00790 SSDPCache::Instance()->OutputXML(os, &nDevCount, &nEntryCount);
00791
00792 NameValues list;
00793 list.push_back(
00794 NameValue("DeviceCount", (int)nDevCount));
00795 list.push_back(
00796 NameValue("DevicesAllocated", SSDPCacheEntries::g_nAllocated));
00797 list.push_back(
00798 NameValue("CacheEntriesFound", (int)nEntryCount));
00799 list.push_back(
00800 NameValue("CacheEntriesAllocated", DeviceLocation::g_nAllocated));
00801 list.push_back(
00802 NameValue("DeviceList", sXML));
00803
00804 pRequest->FormatActionResponse(list);
00805
00806 pRequest->m_eResponseType = ResponseTypeXML;
00807 pRequest->m_nResponseStatus = 200;
00808 }