SSDPDiscover.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. #include <ssdp/SSDPDiscover.h>
  2. #include <utils/Logger.h>
  3. #include <utils/QStringUtils.h>
  4. // Qt includes
  5. #include <QUdpSocket>
  6. #include <QUrl>
  7. #include <QRegularExpression>
  8. #include <QJsonArray>
  9. #include <QJsonObject>
  10. #include <QHostInfo>
  11. // Constants
  12. namespace {
  13. // as per upnp spec 1.1, section 1.2.2.
  14. const QString UPNP_DISCOVER_MESSAGE = "M-SEARCH * HTTP/1.1\r\n"
  15. "HOST: %1:%2\r\n"
  16. "MAN: \"ssdp:discover\"\r\n"
  17. "MX: %3\r\n"
  18. "ST: %4\r\n"
  19. "\r\n";
  20. } //End of constants
  21. SSDPDiscover::SSDPDiscover(QObject* parent)
  22. : QObject(parent)
  23. , _log(Logger::getInstance("SSDPDISCOVER"))
  24. , _udpSocket(new QUdpSocket(this))
  25. , _ssdpAddr(DEFAULT_SEARCH_ADDRESS)
  26. , _ssdpPort(DEFAULT_SEARCH_PORT)
  27. , _ssdpMaxWaitResponseTime(1)
  28. , _ssdpTimeout(DEFAULT_SSDP_TIMEOUT.count())
  29. ,_filter(DEFAULT_FILTER)
  30. ,_filterHeader(DEFAULT_FILTER_HEADER)
  31. ,_regExFilter(_filter)
  32. ,_skipDupKeys(false)
  33. {
  34. }
  35. void SSDPDiscover::searchForService(const QString& st)
  36. {
  37. _searchTarget = st;
  38. _usnList.clear();
  39. // setup socket
  40. connect(_udpSocket, &QUdpSocket::readyRead, this, &SSDPDiscover::readPendingDatagrams, Qt::UniqueConnection);
  41. sendSearch(st);
  42. }
  43. QString SSDPDiscover::getFirstService(const searchType& type, const QString& st, int timeout_ms)
  44. {
  45. _searchTarget = st;
  46. _services.clear();
  47. Debug(_log, "Search for Service [%s], address [%s], port [%d]", QSTRING_CSTR(_searchTarget), QSTRING_CSTR(_ssdpAddr.toString()), _ssdpPort);
  48. // search
  49. sendSearch(_searchTarget);
  50. if ( _udpSocket->waitForReadyRead(timeout_ms) )
  51. {
  52. while (_udpSocket->waitForReadyRead(500))
  53. {
  54. QByteArray datagram;
  55. while (_udpSocket->hasPendingDatagrams())
  56. {
  57. datagram.resize(_udpSocket->pendingDatagramSize());
  58. QHostAddress sender;
  59. quint16 senderPort;
  60. _udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
  61. QString data(datagram);
  62. QMap<QString,QString> headers;
  63. QString address;
  64. // parse request
  65. QStringList entries = QStringUtils::split(data,"\n", QStringUtils::SplitBehavior::SkipEmptyParts);
  66. for(auto entry : entries)
  67. {
  68. // http header parse skip
  69. if(entry.contains("HTTP/1.1"))
  70. continue;
  71. // split into key:vale, be aware that value field may contain also a ":"
  72. entry = entry.simplified();
  73. int pos = entry.indexOf(":");
  74. if(pos == -1)
  75. continue;
  76. headers[entry.left(pos).trimmed().toLower()] = entry.mid(pos+1).trimmed();
  77. }
  78. // verify ssdp spec
  79. if(!headers.contains("st"))
  80. continue;
  81. // usn duplicates
  82. if (_usnList.contains(headers.value("usn")))
  83. continue;
  84. if (headers.value("st") == _searchTarget)
  85. {
  86. _usnList << headers.value("usn");
  87. QUrl url(headers.value("location"));
  88. if(type == searchType::STY_WEBSERVER)
  89. {
  90. Debug(_log, "Found service [%s] at: %s:%d", QSTRING_CSTR(st), QSTRING_CSTR(url.host()), url.port());
  91. return url.host()+":"+QString::number(url.port());
  92. }
  93. else if(type == searchType::STY_FLATBUFSERVER)
  94. {
  95. const QString fbsport = headers.value("hyperion-fbs-port");
  96. if(fbsport.isEmpty())
  97. {
  98. continue;
  99. }
  100. else
  101. {
  102. Debug(_log, "Found service [%s] at: %s:%s", QSTRING_CSTR(st), QSTRING_CSTR(url.host()), QSTRING_CSTR(fbsport));
  103. return url.host()+":"+fbsport;
  104. }
  105. }
  106. else if(type == searchType::STY_JSONSERVER)
  107. {
  108. const QString jssport = headers.value("hyperion-jss-port");
  109. if(jssport.isEmpty())
  110. {
  111. continue;
  112. }
  113. else
  114. {
  115. Debug(_log, "Found service at: %s:%s", QSTRING_CSTR(url.host()), QSTRING_CSTR(jssport));
  116. return url.host()+":"+jssport;
  117. }
  118. }
  119. }
  120. }
  121. }
  122. }
  123. Debug(_log,"Search timeout, service [%s] not found", QSTRING_CSTR(st) );
  124. return QString();
  125. }
  126. void SSDPDiscover::readPendingDatagrams()
  127. {
  128. while (_udpSocket->hasPendingDatagrams()) {
  129. QByteArray datagram;
  130. datagram.resize(_udpSocket->pendingDatagramSize());
  131. QHostAddress sender;
  132. quint16 senderPort;
  133. _udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
  134. QString data(datagram);
  135. QMap<QString,QString> headers;
  136. // parse request
  137. QStringList entries = QStringUtils::split(data,"\n", QStringUtils::SplitBehavior::SkipEmptyParts);
  138. for(auto entry : entries)
  139. {
  140. // http header parse skip
  141. if(entry.contains("HTTP/1.1"))
  142. continue;
  143. // split into key:value, be aware that value field may contain also a ":"
  144. entry = entry.simplified();
  145. int pos = entry.indexOf(":");
  146. if(pos == -1)
  147. continue;
  148. const QString key = entry.left(pos).trimmed().toLower();
  149. const QString value = entry.mid(pos + 1).trimmed();
  150. headers[key] = value;
  151. }
  152. // verify ssdp spec
  153. if(!headers.contains("st"))
  154. continue;
  155. // usn duplicates
  156. if (_usnList.contains(headers.value("usn")))
  157. continue;
  158. if (headers.value("st") == _searchTarget)
  159. {
  160. _usnList << headers.value("usn");
  161. QUrl url(headers.value("location"));
  162. emit newService(url.host() + ":" + QString::number(url.port()));
  163. }
  164. }
  165. }
  166. int SSDPDiscover::discoverServices(const QString& searchTarget, const QString& key)
  167. {
  168. _searchTarget = searchTarget;
  169. int rc = -1;
  170. Debug(_log, "Search for Service [%s], address [%s], port [%d]", QSTRING_CSTR(_searchTarget), QSTRING_CSTR(_ssdpAddr.toString()), _ssdpPort);
  171. _services.clear();
  172. // search
  173. sendSearch(_searchTarget);
  174. if ( _udpSocket->waitForReadyRead( _ssdpTimeout ) )
  175. {
  176. while (_udpSocket->waitForReadyRead(500))
  177. {
  178. QByteArray datagram;
  179. while (_udpSocket->hasPendingDatagrams())
  180. {
  181. datagram.resize(_udpSocket->pendingDatagramSize());
  182. QHostAddress sender;
  183. quint16 senderPort;
  184. _udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
  185. QString data(datagram);
  186. QMap<QString,QString> headers;
  187. // parse request
  188. QStringList entries = QStringUtils::split(data,"\n", QStringUtils::SplitBehavior::SkipEmptyParts);
  189. for(auto entry : entries)
  190. {
  191. // http header parse skip
  192. if(entry.contains("HTTP/1.1"))
  193. continue;
  194. // split into key:vale, be aware that value field may contain also a ":"
  195. entry = entry.simplified();
  196. int pos = entry.indexOf(":");
  197. if(pos == -1)
  198. continue;
  199. headers[entry.left(pos).trimmed().toUpper()] = entry.mid(pos+1).trimmed();
  200. }
  201. QRegularExpressionMatch match = _regExFilter.match(headers[_filterHeader]);
  202. if ( match.hasMatch() )
  203. {
  204. Debug(_log,"Found target [%s], plus record [%s] matches [%s:%s]", QSTRING_CSTR(_searchTarget), QSTRING_CSTR(headers[_filterHeader]), QSTRING_CSTR(_filterHeader), QSTRING_CSTR(_filter) );
  205. QString mapKey = headers[key];
  206. SSDPService service;
  207. service.cacheControl = headers["CACHE-CONTROL"];
  208. service.location = QUrl (headers["LOCATION"]);
  209. service.server = headers["SERVER"];
  210. service.searchTarget = headers["ST"];
  211. service.uniqueServiceName = headers["USN"];
  212. headers.remove("CACHE-CONTROL");
  213. headers.remove("LOCATION");
  214. headers.remove("SERVER");
  215. headers.remove("ST");
  216. headers.remove("USN");
  217. service.otherHeaders = headers;
  218. if ( _skipDupKeys )
  219. {
  220. _services.replace(mapKey, service);
  221. }
  222. else
  223. {
  224. _services.insert(mapKey, service);
  225. }
  226. }
  227. }
  228. }
  229. }
  230. _udpSocket->close();
  231. if ( _services.empty() )
  232. {
  233. Debug(_log,"Search target [%s], no record(s) matching [%s:%s]", QSTRING_CSTR(_searchTarget), QSTRING_CSTR(_filterHeader), QSTRING_CSTR(_filter) );
  234. rc = 0;
  235. }
  236. else
  237. {
  238. rc = _services.size();
  239. Debug(_log," [%d] service record(s) found", rc );
  240. }
  241. return rc;
  242. }
  243. QJsonArray SSDPDiscover::getServicesDiscoveredJson() const
  244. {
  245. QJsonArray result;
  246. QMultiMap<QString, SSDPService>::const_iterator i;
  247. for (i = _services.begin(); i != _services.end(); ++i)
  248. {
  249. QJsonObject obj;
  250. obj.insert("id", i.key());
  251. obj.insert("cache-control", i.value().cacheControl);
  252. obj.insert("location", i.value().location.toString());
  253. obj.insert("server", i.value().server);
  254. obj.insert("st", i.value().searchTarget);
  255. obj.insert("usn", i.value().uniqueServiceName);
  256. QUrl url (i.value().location);
  257. QString ipAddress = url.host();
  258. obj.insert("ip", ipAddress);
  259. obj.insert("port", url.port());
  260. QHostInfo hostInfo = QHostInfo::fromName(url.host());
  261. if (hostInfo.error() == QHostInfo::NoError)
  262. {
  263. QString hostname = hostInfo.hostName();
  264. if (!QHostInfo::localDomainName().isEmpty())
  265. {
  266. obj.insert("hostname", hostname.remove("." + QHostInfo::localDomainName()));
  267. obj.insert("domain", QHostInfo::localDomainName());
  268. }
  269. else
  270. {
  271. if (hostname.startsWith(ipAddress))
  272. {
  273. obj.insert("hostname", ipAddress);
  274. QString domain = hostname.remove(ipAddress);
  275. if (domain.at(0) == '.')
  276. {
  277. domain.remove(0, 1);
  278. }
  279. obj.insert("domain", domain);
  280. }
  281. else
  282. {
  283. int domainPos = hostname.indexOf('.');
  284. obj.insert("hostname", hostname.left(domainPos));
  285. obj.insert("domain", hostname.mid(domainPos + 1));
  286. }
  287. }
  288. }
  289. QJsonObject objOther;
  290. QMap <QString,QString>::const_iterator o;
  291. for (o = i.value().otherHeaders.begin(); o != i.value().otherHeaders.end(); ++o)
  292. {
  293. objOther.insert(o.key().toLower(), o.value());
  294. }
  295. obj.insert("other", objOther);
  296. result << obj;
  297. }
  298. return result;
  299. }
  300. void SSDPDiscover::sendSearch(const QString& st)
  301. {
  302. const QString msg = QString(UPNP_DISCOVER_MESSAGE).arg(_ssdpAddr.toString()).arg(_ssdpPort).arg(_ssdpMaxWaitResponseTime).arg(st);
  303. _udpSocket->writeDatagram(msg.toUtf8(), _ssdpAddr, _ssdpPort);
  304. }
  305. bool SSDPDiscover::setSearchFilter ( const QString& filter, const QString& filterHeader)
  306. {
  307. bool rc = true;
  308. QRegularExpression regEx( filter );
  309. if (!regEx.isValid()) {
  310. QString errorString = regEx.errorString();
  311. int errorOffset = regEx.patternErrorOffset();
  312. Error(_log,"Filtering regular expression [%s] error [%d]:[%s]", QSTRING_CSTR(filter), errorOffset, QSTRING_CSTR(errorString) );
  313. rc = false;
  314. }
  315. else
  316. {
  317. _filter = filter;
  318. _filterHeader=filterHeader.toUpper();
  319. _regExFilter = regEx;
  320. }
  321. return rc;
  322. }