19#include <QHostAddress>
22#include <QMutexLocker>
23#include <QNetworkDatagram>
24#include <QRegularExpression>
80 LOG(VB_UPNP, LOG_NOTICE,
"SSDP instance created." );
89 LOG(VB_UPNP, LOG_NOTICE,
"Destroying SSDP instance." );
98 LOG(VB_UPNP, LOG_INFO,
"SSDP instance destroyed." );
111 LOG(VB_UPNP, LOG_INFO,
112 "SSDP::EnableNotifications() - creating new task");
119 LOG(VB_UPNP, LOG_INFO,
120 "SSDP::EnableNotifications() - sending NTS_byebye");
129 LOG(VB_UPNP, LOG_INFO,
"SSDP::EnableNotifications() - sending NTS_alive");
135 LOG(VB_UPNP, LOG_INFO,
136 "SSDP::EnableNotifications() - Task added to UPnP queue");
162 QString rRequest = QString(
"M-SEARCH * HTTP/1.1\r\n"
163 "HOST: 239.255.255.250:1900\r\n"
164 "MAN: \"ssdp:discover\"\r\n"
168 .arg(
timeout.count()).arg(sST);
170 LOG(VB_UPNP, LOG_DEBUG, QString(
"\n\n%1\n").arg(rRequest));
172 QByteArray sRequest = rRequest.toUtf8();
173 int nSize = sRequest.size();
177 LOG(VB_GENERAL, LOG_INFO,
"SSDP::PerformSearch - did not write entire buffer.");
180 std::this_thread::sleep_for(std::chrono::milliseconds(
MythRandom(0, 250)));
184 LOG(VB_GENERAL, LOG_INFO,
"SSDP::PerformSearch - did not write entire buffer.");
190 static const QRegularExpression k_whitespace {
"\\s+"};
191 QStringList tokens = sLine.split(k_whitespace, Qt::SkipEmptyParts);
200 if ( sLine.startsWith( QString(
"HTTP/") ))
203 if (tokens.count() > 0)
213 const QString &sKey,
const QString &sDefault )
215 QMap<QString, QString>::const_iterator it =
headers.find(sKey.toLower());
224 const QHostAddress& peerAddress,
229 if (servicePort == 0)
237 std::chrono::seconds nMX = 0s;
239 LOG(VB_UPNP, LOG_DEBUG, QString(
"SSDP::ProcessSearchrequest : [%1] MX=%2")
247 if ( pRequest->m_sMethod !=
"*" )
return false;
248 if ( pRequest->m_sProtocol !=
"HTTP" )
return false;
249 if ( pRequest->m_nMajor != 1 )
return false;
251 if ( sMAN !=
"\"ssdp:discover\"" )
return false;
252 if ( sST.length() == 0 )
return false;
253 if ( sMX.length() == 0 )
return false;
254 nMX = std::chrono::seconds(sMX.toInt());
255 if ( nMX <= 0s )
return false;
263 auto nNewMX = std::chrono::milliseconds(
MythRandom(0, (duration_cast<std::chrono::milliseconds>(nMX)).count()));
269 if ((sST ==
"ssdp:all") || (sST ==
"upnp:rootdevice"))
272 peerAddress, peerPort, sST,
278 pTask->Execute(
nullptr );
295 if (sUDN.length() > 0)
298 peerPort, sST, sUDN );
302 pTask->Execute(
nullptr );
321 LOG(VB_UPNP, LOG_DEBUG,
322 QString(
"SSDP::ProcessSearchResponse ...\n"
327 .arg(sDescURL, sST, sUSN, sCache));
329 int nPos = sCache.indexOf(
"max-age", 0, Qt::CaseInsensitive);
334 nPos = sCache.indexOf(
"=", nPos);
338 auto nSecs = std::chrono::seconds(sCache.mid( nPos+1 ).toInt());
353 LOG(VB_UPNP, LOG_DEBUG,
354 QString(
"SSDP::ProcessNotify ...\n"
360 .arg(sDescURL, sNTS, sNT, sUSN, sCache));
362 if (sNTS.contains(
"ssdp:alive"))
364 int nPos = sCache.indexOf(
"max-age", 0, Qt::CaseInsensitive);
369 nPos = sCache.indexOf(
"=", nPos);
373 auto nSecs = std::chrono::seconds(sCache.mid( nPos+1 ).toInt());
381 if ( sNTS.contains(
"ssdp:byebye" ) )
394 m_socket.bind(QHostAddress::AnyIPv4,
m_port, QUdpSocket::ShareAddress);
402 while (
m_socket.hasPendingDatagrams())
404 QNetworkDatagram datagram =
m_socket.receiveDatagram();
405 QString str = QString::fromUtf8(datagram.data());
406 QStringList lines = str.split(
"\r\n", Qt::SkipEmptyParts);
407 QString sRequestLine = !lines.empty() ? lines[0] :
"";
409 if (!lines.isEmpty())
413 LOG(VB_UPNP, LOG_DEBUG, QString(
"SSDP::ProcessData - requestLine: %1")
418 QMap<QString, QString>
headers;
419 for (
const auto& sLine : std::as_const(lines))
421 QString sName = sLine.section(
':', 0, 0).trimmed();
422 QString sValue = sLine.section(
':', 1);
424 sValue.truncate(sValue.length());
426 if ((sName.length() != 0) && (sValue.length() != 0))
428 headers.insert(sName.toLower(), sValue.trimmed());
453 LOG(VB_UPNP, LOG_ERR,
"SSPD::ProcessData - Unknown request Type.");
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()
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