2018-12-30 22:07:53 +01:00
|
|
|
#include <ssdp/SSDPDiscover.h>
|
|
|
|
|
2020-06-28 23:05:32 +02:00
|
|
|
// Qt includes
|
2018-12-30 22:07:53 +01:00
|
|
|
#include <QUdpSocket>
|
|
|
|
#include <QUrl>
|
|
|
|
|
|
|
|
static const QHostAddress SSDP_ADDR("239.255.255.250");
|
|
|
|
static const quint16 SSDP_PORT(1900);
|
|
|
|
|
|
|
|
// as per upnp spec 1.1, section 1.2.2.
|
|
|
|
// TODO: Make IP and port below another #define and replace message below
|
|
|
|
static const QString UPNP_DISCOVER_MESSAGE = "M-SEARCH * HTTP/1.1\r\n"
|
|
|
|
"HOST: 239.255.255.250:1900\r\n"
|
|
|
|
"MAN: \"ssdp:discover\"\r\n"
|
2020-06-28 23:19:06 +02:00
|
|
|
"MX: 1\r\n"
|
2018-12-30 22:07:53 +01:00
|
|
|
"ST: %1\r\n"
|
|
|
|
"\r\n";
|
|
|
|
|
|
|
|
SSDPDiscover::SSDPDiscover(QObject* parent)
|
|
|
|
: QObject(parent)
|
|
|
|
, _log(Logger::getInstance("SSDPDISCOVER"))
|
|
|
|
, _udpSocket(new QUdpSocket(this))
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void SSDPDiscover::searchForService(const QString& st)
|
|
|
|
{
|
|
|
|
_searchTarget = st;
|
|
|
|
_usnList.clear();
|
|
|
|
// setup socket
|
|
|
|
connect(_udpSocket, &QUdpSocket::readyRead, this, &SSDPDiscover::readPendingDatagrams, Qt::UniqueConnection);
|
|
|
|
|
|
|
|
sendSearch(st);
|
|
|
|
}
|
|
|
|
|
|
|
|
const QString SSDPDiscover::getFirstService(const searchType& type, const QString& st, const int& timeout_ms)
|
|
|
|
{
|
2019-09-14 22:54:41 +02:00
|
|
|
Debug(_log, "Search for Service [%s]", QSTRING_CSTR(st));
|
2018-12-30 22:07:53 +01:00
|
|
|
_searchTarget = st;
|
|
|
|
|
|
|
|
// search
|
2020-06-28 23:05:32 +02:00
|
|
|
sendSearch(_searchTarget);
|
2018-12-30 22:07:53 +01:00
|
|
|
|
2020-04-05 19:41:02 +02:00
|
|
|
if ( _udpSocket->waitForReadyRead(timeout_ms) )
|
2018-12-30 22:07:53 +01:00
|
|
|
{
|
2020-04-05 19:41:02 +02:00
|
|
|
while (_udpSocket->waitForReadyRead(500))
|
|
|
|
{
|
|
|
|
QByteArray datagram;
|
|
|
|
while (_udpSocket->hasPendingDatagrams())
|
|
|
|
{
|
|
|
|
datagram.resize(_udpSocket->pendingDatagramSize());
|
|
|
|
QHostAddress sender;
|
|
|
|
quint16 senderPort;
|
2019-04-08 23:13:11 +02:00
|
|
|
|
2020-04-05 19:41:02 +02:00
|
|
|
_udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
|
2018-12-30 22:07:53 +01:00
|
|
|
|
2020-04-05 19:41:02 +02:00
|
|
|
QString data(datagram);
|
2018-12-30 22:07:53 +01:00
|
|
|
|
2020-06-28 23:05:32 +02:00
|
|
|
//Debug(_log, "_data: [%s]", QSTRING_CSTR(data));
|
2018-12-30 22:07:53 +01:00
|
|
|
|
2020-04-05 19:41:02 +02:00
|
|
|
QMap<QString,QString> headers;
|
|
|
|
QString address;
|
|
|
|
// parse request
|
2020-06-28 23:05:32 +02:00
|
|
|
|
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
|
|
|
QStringList entries = data.split("\n", Qt::SkipEmptyParts);
|
|
|
|
#else
|
|
|
|
QStringList entries = data.split("\n", QString::SkipEmptyParts);
|
|
|
|
#endif
|
|
|
|
|
2020-04-05 19:41:02 +02:00
|
|
|
for(auto entry : entries)
|
|
|
|
{
|
|
|
|
// http header parse skip
|
|
|
|
if(entry.contains("HTTP/1.1"))
|
|
|
|
continue;
|
2018-12-30 22:07:53 +01:00
|
|
|
|
2020-04-05 19:41:02 +02:00
|
|
|
// split into key:vale, be aware that value field may contain also a ":"
|
|
|
|
entry = entry.simplified();
|
|
|
|
int pos = entry.indexOf(":");
|
|
|
|
if(pos == -1)
|
|
|
|
continue;
|
2018-12-30 22:07:53 +01:00
|
|
|
|
2020-04-05 19:41:02 +02:00
|
|
|
headers[entry.left(pos).trimmed().toLower()] = entry.mid(pos+1).trimmed();
|
|
|
|
}
|
2018-12-30 22:07:53 +01:00
|
|
|
|
2020-04-05 19:41:02 +02:00
|
|
|
// verify ssdp spec
|
|
|
|
if(!headers.contains("st"))
|
2018-12-30 22:07:53 +01:00
|
|
|
continue;
|
2020-04-05 19:41:02 +02:00
|
|
|
|
|
|
|
// usn duplicates
|
|
|
|
if (_usnList.contains(headers.value("usn")))
|
2019-08-17 09:44:57 +02:00
|
|
|
continue;
|
2020-04-05 19:41:02 +02:00
|
|
|
|
|
|
|
if (headers.value("st") == _searchTarget)
|
2019-08-17 09:44:57 +02:00
|
|
|
{
|
2020-04-05 19:41:02 +02:00
|
|
|
_usnList << headers.value("usn");
|
|
|
|
QUrl url(headers.value("location"));
|
|
|
|
//Debug(_log, "Received msearch response from '%s:%d'. Search target: %s",QSTRING_CSTR(sender.toString()), senderPort, QSTRING_CSTR(headers.value("st")));
|
2020-06-28 23:05:32 +02:00
|
|
|
if(type == searchType::STY_WEBSERVER)
|
2020-04-05 19:41:02 +02:00
|
|
|
{
|
|
|
|
Debug(_log, "Found service [%s] at: %s:%d", QSTRING_CSTR(st), QSTRING_CSTR(url.host()), url.port());
|
|
|
|
|
|
|
|
return url.host()+":"+QString::number(url.port());
|
|
|
|
}
|
2020-06-28 23:05:32 +02:00
|
|
|
else if(type == searchType::STY_FLATBUFSERVER)
|
2020-04-05 19:41:02 +02:00
|
|
|
{
|
|
|
|
const QString fbsport = headers.value("hyperion-fbs-port");
|
|
|
|
if(fbsport.isEmpty())
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Debug(_log, "Found service [%s] at: %s:%s", QSTRING_CSTR(st), QSTRING_CSTR(url.host()), QSTRING_CSTR(fbsport));
|
|
|
|
return url.host()+":"+fbsport;
|
|
|
|
}
|
|
|
|
}
|
2020-06-28 23:05:32 +02:00
|
|
|
else if(type == searchType::STY_JSONSERVER)
|
2020-04-05 19:41:02 +02:00
|
|
|
{
|
|
|
|
const QString jssport = headers.value("hyperion-jss-port");
|
|
|
|
if(jssport.isEmpty())
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Debug(_log, "Found service at: %s:%s", QSTRING_CSTR(url.host()), QSTRING_CSTR(jssport));
|
|
|
|
return url.host()+":"+jssport;
|
|
|
|
}
|
|
|
|
}
|
2019-08-17 09:44:57 +02:00
|
|
|
}
|
|
|
|
}
|
2018-12-30 22:07:53 +01:00
|
|
|
}
|
|
|
|
}
|
2019-09-14 22:54:41 +02:00
|
|
|
Debug(_log,"Search timeout, service [%s] not found", QSTRING_CSTR(st) );
|
2018-12-30 22:07:53 +01:00
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SSDPDiscover::readPendingDatagrams()
|
|
|
|
{
|
|
|
|
while (_udpSocket->hasPendingDatagrams()) {
|
|
|
|
|
|
|
|
QByteArray datagram;
|
|
|
|
datagram.resize(_udpSocket->pendingDatagramSize());
|
|
|
|
QHostAddress sender;
|
|
|
|
quint16 senderPort;
|
|
|
|
|
|
|
|
_udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
|
|
|
|
|
|
|
|
QString data(datagram);
|
|
|
|
QMap<QString,QString> headers;
|
|
|
|
// parse request
|
2020-06-28 23:05:32 +02:00
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
|
|
|
QStringList entries = data.split("\n", Qt::SkipEmptyParts);
|
|
|
|
#else
|
|
|
|
QStringList entries = data.split("\n", QString::SkipEmptyParts);
|
|
|
|
#endif
|
2018-12-30 22:07:53 +01:00
|
|
|
for(auto entry : entries)
|
|
|
|
{
|
|
|
|
// http header parse skip
|
|
|
|
if(entry.contains("HTTP/1.1"))
|
|
|
|
continue;
|
|
|
|
|
2020-06-28 23:19:06 +02:00
|
|
|
// split into key:value, be aware that value field may contain also a ":"
|
2018-12-30 22:07:53 +01:00
|
|
|
entry = entry.simplified();
|
|
|
|
int pos = entry.indexOf(":");
|
|
|
|
if(pos == -1)
|
|
|
|
continue;
|
|
|
|
|
2020-06-28 23:19:06 +02:00
|
|
|
const QString key = entry.left(pos).trimmed().toLower();
|
|
|
|
const QString value = entry.mid(pos + 1).trimmed();
|
|
|
|
headers[key] = value;
|
2018-12-30 22:07:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// verify ssdp spec
|
|
|
|
if(!headers.contains("st"))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// usn duplicates
|
|
|
|
if (_usnList.contains(headers.value("usn")))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (headers.value("st") == _searchTarget)
|
|
|
|
{
|
|
|
|
_usnList << headers.value("usn");
|
2019-09-14 22:54:41 +02:00
|
|
|
//Debug(_log, "Received msearch response from '%s:%d'. Search target: %s",QSTRING_CSTR(sender.toString()), senderPort, QSTRING_CSTR(headers.value("st")));
|
2018-12-30 22:07:53 +01:00
|
|
|
QUrl url(headers.value("location"));
|
2020-06-28 23:19:06 +02:00
|
|
|
emit newService(url.host() + ":" + QString::number(url.port()));
|
2018-12-30 22:07:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SSDPDiscover::sendSearch(const QString& st)
|
|
|
|
{
|
|
|
|
const QString msg = UPNP_DISCOVER_MESSAGE.arg(st);
|
|
|
|
|
2020-06-28 23:19:06 +02:00
|
|
|
_udpSocket->writeDatagram(msg.toUtf8(), QHostAddress(SSDP_ADDR), SSDP_PORT);
|
2018-12-30 22:07:53 +01:00
|
|
|
}
|