19#include <QHostAddress>
22#include <QMutexLocker>
23#include <QNetworkDatagram>
24#include <QRegularExpression>
82 LOG(VB_UPNP, LOG_NOTICE,
"SSDP instance created." );
91 LOG(VB_UPNP, LOG_NOTICE,
"Destroying SSDP instance." );
103 LOG(VB_UPNP, LOG_INFO,
"SSDP instance destroyed." );
116 LOG(VB_UPNP, LOG_INFO,
117 "SSDP::EnableNotifications() - creating new task");
124 LOG(VB_UPNP, LOG_INFO,
125 "SSDP::EnableNotifications() - sending NTS_byebye");
134 LOG(VB_UPNP, LOG_INFO,
"SSDP::EnableNotifications() - sending NTS_alive");
140 LOG(VB_UPNP, LOG_INFO,
141 "SSDP::EnableNotifications() - Task added to UPnP queue");
167 QString rRequest = QString(
"M-SEARCH * HTTP/1.1\r\n"
168 "HOST: 239.255.255.250:1900\r\n"
169 "MAN: \"ssdp:discover\"\r\n"
173 .arg(
timeout.count()).arg(sST);
175 LOG(VB_UPNP, LOG_DEBUG, QString(
"Sending SSDP search datagram\n%1").arg(rRequest));
177 QByteArray sRequest = rRequest.toUtf8();
178 int nSize = sRequest.size();
182 LOG(VB_GENERAL, LOG_INFO,
"SSDP::PerformSearch - did not write entire buffer.");
185 std::this_thread::sleep_for(std::chrono::milliseconds(
MythRandom(0, 250)));
189 LOG(VB_GENERAL, LOG_INFO,
"SSDP::PerformSearch - did not write entire buffer.");
195 static const QRegularExpression k_whitespace {
"\\s+"};
196 QStringList tokens = sLine.split(k_whitespace, Qt::SkipEmptyParts);
205 if ( sLine.startsWith( QString(
"HTTP/") ))
208 if (tokens.count() > 0)
218 const QString &sKey,
const QString &sDefault )
220 QMap<QString, QString>::const_iterator it =
headers.find(sKey.toLower());
229 const QHostAddress& peerAddress,
234 if (servicePort == 0)
242 std::chrono::seconds nMX = 0s;
244 LOG(VB_UPNP, LOG_DEBUG, QString(
"SSDP::ProcessSearchrequest : [%1] MX=%2")
252 if ( pRequest->m_sMethod !=
"*" )
return false;
253 if ( pRequest->m_sProtocol !=
"HTTP" )
return false;
254 if ( pRequest->m_nMajor != 1 )
return false;
256 if ( sMAN !=
"\"ssdp:discover\"" )
return false;
257 if ( sST.length() == 0 )
return false;
258 if ( sMX.length() == 0 )
return false;
259 nMX = std::chrono::seconds(sMX.toInt());
260 if ( nMX <= 0s )
return false;
268 auto nNewMX = std::chrono::milliseconds(
MythRandom(0, (duration_cast<std::chrono::milliseconds>(nMX)).count()));
274 if ((sST ==
"ssdp:all") || (sST ==
"upnp:rootdevice"))
277 peerAddress, peerPort, sST,
283 pTask->Execute(
nullptr );
300 if (sUDN.length() > 0)
303 peerPort, sST, sUDN );
307 pTask->Execute(
nullptr );
326 LOG(VB_UPNP, LOG_DEBUG,
327 QString(
"SSDP::ProcessSearchResponse ...\n"
332 .arg(sDescURL, sST, sUSN, sCache));
334 int nPos = sCache.indexOf(
"max-age", 0, Qt::CaseInsensitive);
339 nPos = sCache.indexOf(
"=", nPos);
343 auto nSecs = std::chrono::seconds(sCache.mid( nPos+1 ).toInt());
358 LOG(VB_UPNP, LOG_DEBUG,
359 QString(
"SSDP::ProcessNotify ...\n"
365 .arg(sDescURL, sNTS, sNT, sUSN, sCache));
367 if (sNTS.contains(
"ssdp:alive"))
369 int nPos = sCache.indexOf(
"max-age", 0, Qt::CaseInsensitive);
374 nPos = sCache.indexOf(
"=", nPos);
378 auto nSecs = std::chrono::seconds(sCache.mid( nPos+1 ).toInt());
386 if ( sNTS.contains(
"ssdp:byebye" ) )
399 m_socket.bind(QHostAddress::AnyIPv4,
m_port, QUdpSocket::ShareAddress);
409 QNetworkDatagram datagram =
m_socket.receiveDatagram();
410 QString str = QString::fromUtf8(datagram.data());
411 QStringList lines = str.split(
"\r\n", Qt::SkipEmptyParts);
412 QString sRequestLine = !lines.empty() ? lines[0] :
"";
414 if (!lines.isEmpty())
418 LOG(VB_UPNP, LOG_DEBUG, QString(
"SSDP::ProcessData - requestLine: %1")
423 QMap<QString, QString>
headers;
424 for (
const auto& sLine : std::as_const(lines))
426 QString sName = sLine.section(
':', 0, 0).trimmed();
427 QString sValue = sLine.section(
':', 1);
429 sValue.truncate(sValue.length());
431 if ((sName.length() != 0) && (sValue.length() != 0))
433 headers.insert(sName.toLower(), sValue.trimmed());
458 LOG(VB_UPNP, LOG_ERR,
"SSPD::ProcessData - Unknown request Type.");
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
void quit(void)
calls exit(0)
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
virtual int DecrRef(void)
Decrements reference count and deletes on 0.
static SSDPCache * Instance()
void Add(const QString &sURI, const QString &sUSN, const QString &sLocation, std::chrono::seconds sExpiresInSecs)
void Remove(const QString &sURI, const QString &sUSN)
void processPendingDatagrams()
void setIsRunning(bool isRunning)
const QHostAddress m_groupAddress
void performSearch(const QString &sST, std::chrono::seconds timeout=2s)
void EnableNotifications(int nServicePort)
void DisableNotifications()
int getNotificationPort() const
void PerformSearch(const QString &sST, std::chrono::seconds timeout=2s)
Send a SSDP discover multicast datagram.
class UPnpNotifyTask * m_pNotifyTask
void AddTask(std::chrono::milliseconds msec, Task *pTask)
Add a task to run in the future.
static TaskQueue * Instance()
QString FindDeviceUDN(UPnpDevice *pDevice, QString sST)
void SetNTS(UPnpNotifyNTS nts)
void Execute(TaskQueue *pQueue) override
static UPnpDeviceDesc g_UPnpDeviceDesc
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Convenience inline random number generator functions.
uint32_t MythRandom()
generate 32 random bits
static eu8 clamp(eu8 value, eu8 low, eu8 high)
static bool ProcessNotify(const QMap< QString, QString > &headers)
static SSDPRequestType ProcessRequestLine(const QString &sLine)
static bool ProcessSearchResponse(const QMap< QString, QString > &headers)
static QMutex g_pSSDPCreationLock
static bool ProcessSearchRequest(const QMap< QString, QString > &sHeaders, const QHostAddress &peerAddress, quint16 peerPort, int servicePort)
static QString GetHeaderValue(const QMap< QString, QString > &headers, const QString &sKey, const QString &sDefault)
static constexpr uint16_t SSDP_PORT
static constexpr const char * SSDP_GROUP