SSDPServer.cpp 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. #include <ssdp/SSDPServer.h>
  2. // utils
  3. #include <utils/SysInfo.h>
  4. #include <utils/QStringUtils.h>
  5. // Hyperion
  6. #include <HyperionConfig.h>
  7. #include <QUdpSocket>
  8. #include <QDateTime>
  9. static const QHostAddress SSDP_ADDR("239.255.255.250");
  10. static const quint16 SSDP_PORT(1900);
  11. static const QString SSDP_MAX_AGE("1800");
  12. // as per upnp spec 1.1, section 1.2.2.
  13. // - BOOTID.UPNP.ORG
  14. // - CONFIGID.UPNP.ORG
  15. // - SEARCHPORT.UPNP.ORG (optional)
  16. // TODO: Make IP and port below another #define and replace message below
  17. static const QString UPNP_ALIVE_MESSAGE = "NOTIFY * HTTP/1.1\r\n"
  18. "HOST: 239.255.255.250:1900\r\n"
  19. "CACHE-CONTROL: max-age=%1\r\n"
  20. "LOCATION: %2\r\n"
  21. "NT: %3\r\n"
  22. "NTS: ssdp:alive\r\n"
  23. "SERVER: %4\r\n"
  24. "USN: uuid:%5\r\n"
  25. #if defined(ENABLE_FLATBUF_SERVER)
  26. "HYPERION-FBS-PORT: %6\r\n"
  27. #endif
  28. "HYPERION-JSS-PORT: %7\r\n"
  29. "HYPERION-NAME: %8\r\n"
  30. "\r\n";
  31. // Implement ssdp:update as per spec 1.1, section 1.2.4
  32. // and use the below define to build the message, where
  33. // SEARCHPORT.UPNP.ORG are optional.
  34. // TODO: Make IP and port below another #define and replace message below
  35. static const QString UPNP_UPDATE_MESSAGE = "NOTIFY * HTTP/1.1\r\n"
  36. "HOST: 239.255.255.250:1900\r\n"
  37. "LOCATION: %1\r\n"
  38. "NT: %2\r\n"
  39. "NTS: ssdp:update\r\n"
  40. "USN: uuid:%3\r\n"
  41. /* "CONFIGID.UPNP.ORG: %4\r\n"
  42. UPNP spec = 1.1 "NEXTBOOTID.UPNP.ORG: %5\r\n"
  43. "SEARCHPORT.UPNP.ORG: %6\r\n"
  44. */ "\r\n";
  45. // TODO: Add this two fields commented below in the BYEBYE MESSAGE
  46. // as per upnp spec 1.1, section 1.2.2 and 1.2.3.
  47. // - BOOTID.UPNP.ORG
  48. // - CONFIGID.UPNP.ORG
  49. // TODO: Make IP and port below another #define and replace message below
  50. static const QString UPNP_BYEBYE_MESSAGE = "NOTIFY * HTTP/1.1\r\n"
  51. "HOST: 239.255.255.250:1900\r\n"
  52. "NT: %1\r\n"
  53. "NTS: ssdp:byebye\r\n"
  54. "USN: uuid:%2\r\n"
  55. "\r\n";
  56. // TODO: Add this three fields commented below in the MSEARCH_RESPONSE
  57. // as per upnp spec 1.1, section 1.3.3.
  58. // - BOOTID.UPNP.ORG
  59. // - CONFIGID.UPNP.ORG
  60. // - SEARCHPORT.UPNP.ORG (optional)
  61. static const QString UPNP_MSEARCH_RESPONSE = "HTTP/1.1 200 OK\r\n"
  62. "CACHE-CONTROL: max-age = %1\r\n"
  63. "DATE: %2\r\n"
  64. "EXT: \r\n"
  65. "LOCATION: %3\r\n"
  66. "SERVER: %4\r\n"
  67. "ST: %5\r\n"
  68. "USN: uuid:%6\r\n"
  69. #if defined(ENABLE_FLATBUF_SERVER)
  70. "HYPERION-FBS-PORT: %7\r\n"
  71. #endif
  72. "HYPERION-JSS-PORT: %8\r\n"
  73. "HYPERION-NAME: %9\r\n"
  74. "\r\n";
  75. SSDPServer::SSDPServer(QObject * parent)
  76. : QObject(parent)
  77. , _log(Logger::getInstance("SSDP"))
  78. , _udpSocket(nullptr)
  79. , _running(false)
  80. {
  81. }
  82. SSDPServer::~SSDPServer()
  83. {
  84. stop();
  85. }
  86. void SSDPServer::initServer()
  87. {
  88. _udpSocket = new QUdpSocket(this);
  89. // get system info
  90. SysInfo::HyperionSysInfo data = SysInfo::get();
  91. // create SERVER String
  92. _serverHeader = QString("%1/%2 UPnP/1.0 Hyperion/%3")
  93. .arg(data.prettyName, data.productVersion, HYPERION_VERSION);
  94. connect(_udpSocket, &QUdpSocket::readyRead, this, &SSDPServer::readPendingDatagrams);
  95. }
  96. bool SSDPServer::start()
  97. {
  98. if(!_running && _udpSocket->bind(QHostAddress::AnyIPv4, SSDP_PORT, QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress))
  99. {
  100. _udpSocket->joinMulticastGroup(SSDP_ADDR);
  101. _running = true;
  102. return true;
  103. }
  104. return false;
  105. }
  106. void SSDPServer::stop()
  107. {
  108. if(_running)
  109. {
  110. _udpSocket->close();
  111. _running = false;
  112. }
  113. }
  114. void SSDPServer::readPendingDatagrams()
  115. {
  116. while (_udpSocket->hasPendingDatagrams()) {
  117. QByteArray datagram;
  118. datagram.resize(_udpSocket->pendingDatagramSize());
  119. QHostAddress sender;
  120. quint16 senderPort;
  121. _udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
  122. QString data(datagram);
  123. QMap<QString,QString> headers;
  124. // parse request
  125. QStringList entries = QStringUtils::split(data,"\n", QStringUtils::SplitBehavior::SkipEmptyParts);
  126. for(auto entry : entries)
  127. {
  128. // http header parse skip
  129. if(entry.contains("HTTP/1.1"))
  130. continue;
  131. // split into key:vale, be aware that value field may contain also a ":"
  132. entry = entry.simplified();
  133. int pos = entry.indexOf(":");
  134. if(pos == -1)
  135. continue;
  136. headers[entry.left(pos).trimmed().toLower()] = entry.mid(pos+1).trimmed();
  137. }
  138. // verify ssdp spec
  139. if(!headers.contains("man"))
  140. continue;
  141. if (headers.value("man") == "\"ssdp:discover\"")
  142. {
  143. emit msearchRequestReceived(headers.value("st"), headers.value("mx"), sender.toString(), senderPort);
  144. }
  145. }
  146. }
  147. void SSDPServer::sendMSearchResponse(const QString& st, const QString& senderIp, quint16 senderPort)
  148. {
  149. QString message = UPNP_MSEARCH_RESPONSE.arg(SSDP_MAX_AGE
  150. , QDateTime::currentDateTimeUtc().toString("ddd, dd MMM yyyy HH:mm:ss GMT")
  151. , _descAddress
  152. , _serverHeader
  153. , st
  154. , _uuid
  155. #if defined(ENABLE_FLATBUF_SERVER)
  156. , _fbsPort
  157. #endif
  158. , _jssPort
  159. , _name );
  160. _udpSocket->writeDatagram(message.toUtf8(), QHostAddress(senderIp), senderPort);
  161. }
  162. void SSDPServer::sendByeBye(const QString& st)
  163. {
  164. QString message = UPNP_BYEBYE_MESSAGE.arg(st, _uuid+"::"+st );
  165. // we repeat 3 times
  166. quint8 rep = 0;
  167. while(rep++ < 3) {
  168. _udpSocket->writeDatagram(message.toUtf8(), QHostAddress(SSDP_ADDR), SSDP_PORT);
  169. }
  170. }
  171. void SSDPServer::sendAlive(const QString& st)
  172. {
  173. const QString tempUSN = (st == "upnp:rootdevice ") ? _uuid+"::"+st : _uuid;
  174. QString message = UPNP_ALIVE_MESSAGE.arg(SSDP_MAX_AGE
  175. , _descAddress
  176. , st
  177. , _serverHeader
  178. , tempUSN
  179. #if defined(ENABLE_FLATBUF_SERVER)
  180. , _fbsPort
  181. #endif
  182. , _jssPort
  183. , _name );
  184. // we repeat 3 times
  185. quint8 rep = 0;
  186. while(rep++ < 3) {
  187. _udpSocket->writeDatagram(message.toUtf8(), QHostAddress(SSDP_ADDR), SSDP_PORT);
  188. }
  189. }
  190. void SSDPServer::sendUpdate(const QString& st)
  191. {
  192. QString message = UPNP_UPDATE_MESSAGE.arg(_descAddress
  193. , st
  194. , _uuid+"::"+st );
  195. _udpSocket->writeDatagram(message.toUtf8(), QHostAddress(SSDP_ADDR), SSDP_PORT);
  196. }