ssdpc.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. /*
  2. * Copyright (C) 2012-2013 by egnite GmbH
  3. *
  4. * All rights reserved.
  5. *
  6. * Redistribution and use in source and binary forms, with or without
  7. * modification, are permitted provided that the following conditions
  8. * are met:
  9. *
  10. * 1. Redistributions of source code must retain the above copyright
  11. * notice, this list of conditions and the following disclaimer.
  12. * 2. Redistributions in binary form must reproduce the above copyright
  13. * notice, this list of conditions and the following disclaimer in the
  14. * documentation and/or other materials provided with the distribution.
  15. * 3. Neither the name of the copyright holders nor the names of
  16. * contributors may be used to endorse or promote products derived
  17. * from this software without specific prior written permission.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  20. * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  21. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  22. * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  23. * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  24. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  25. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
  26. * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
  27. * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  28. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
  29. * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  30. * SUCH DAMAGE.
  31. *
  32. * For additional information see http://www.ethernut.de/
  33. */
  34. #include <arpa/inet.h>
  35. #include <pro/ssdp.h>
  36. #include <string.h>
  37. #include <stdlib.h>
  38. #include <stdio.h>
  39. typedef struct _SSDP_OBSERVER SSDP_OBSERVER;
  40. struct _SSDP_OBSERVER {
  41. SSDP_OBSERVER *sobs_next;
  42. char *sobs_domain;
  43. char *sobs_type;
  44. SSDP_OBSERVER_FUNCTION sobs_cb;
  45. };
  46. static SSDP_DEVICE *ssdp_device_cache;
  47. static SSDP_OBSERVER *ssdp_observer_root;
  48. static int CallObservers(SSDP_SERVICE *ssvc, int_fast8_t removal)
  49. {
  50. int rc;
  51. SSDP_OBSERVER *sobs;
  52. for (rc = 0, sobs = ssdp_observer_root; rc == 0 && sobs; sobs = sobs->sobs_next) {
  53. if (strcmp(ssvc->ssvc_domain, sobs->sobs_domain) == 0 && strcmp(ssvc->ssvc_type, sobs->sobs_type) == 0) {
  54. rc = (*sobs->sobs_cb)(ssvc, removal);
  55. }
  56. }
  57. return rc;
  58. }
  59. static char *SsdpDuplicateHeaderValue(const HTTPU_HEADER *hdr, const char *name)
  60. {
  61. const char *val;
  62. val = HttpuGetHeader(hdr, name);
  63. if (*val) {
  64. return strdup(val);
  65. }
  66. return NULL;
  67. }
  68. static SSDP_DEVICE *CacheDevice(const char *uuid, const char *domain, const char *type, const HTTPU_HEADER *hdr)
  69. {
  70. SSDP_DEVICE *sdev;
  71. for (sdev = ssdp_device_cache; sdev; sdev = sdev->sdev_next) {
  72. if (strcmp(sdev->sdev_uuid, uuid) == 0) {
  73. break;
  74. }
  75. }
  76. if (sdev == NULL) {
  77. sdev = calloc(1, sizeof(*sdev));
  78. if (sdev) {
  79. const char *cp;
  80. sdev->sdev_uuid = strdup(uuid);
  81. sdev->sdev_url_desc = SsdpDuplicateHeaderValue(hdr, "LOCATION");
  82. cp = strchr(HttpuGetHeader(hdr, "CACHE-CONTROL"), '=');
  83. if (cp) {
  84. sdev->sdev_cache = atoi(cp + 1);
  85. }
  86. sdev->sdev_next = ssdp_device_cache;
  87. ssdp_device_cache = sdev;
  88. }
  89. }
  90. if (sdev) {
  91. if (sdev->sdev_domain == NULL && domain) {
  92. sdev->sdev_domain = strdup(domain);
  93. }
  94. if (sdev->sdev_type == NULL && type) {
  95. sdev->sdev_type = strdup(type);
  96. }
  97. }
  98. return sdev;
  99. }
  100. static SSDP_DEVICE *CacheService(const char *uuid, const char *domain, const char *type, const HTTPU_HEADER *hdr)
  101. {
  102. SSDP_DEVICE *sdev;
  103. sdev = CacheDevice(uuid, NULL, NULL, hdr);
  104. if (sdev) {
  105. SSDP_SERVICE *ssvc;
  106. for (ssvc = sdev->sdev_svc; ssvc; ssvc = ssvc->ssvc_next) {
  107. if (strcmp(ssvc->ssvc_domain, domain) == 0 && strcmp(ssvc->ssvc_type, type) == 0) {
  108. break;
  109. }
  110. }
  111. if (ssvc == NULL) {
  112. ssvc = calloc(1, sizeof(*ssvc));
  113. if (ssvc) {
  114. ssvc->ssvc_dev = sdev;
  115. ssvc->ssvc_domain = strdup(domain);
  116. ssvc->ssvc_type = strdup(type);
  117. /* Attach service to device and inform our observers. */
  118. ssvc->ssvc_next = sdev->sdev_svc;
  119. sdev->sdev_svc = ssvc;
  120. CallObservers(ssvc, 0);
  121. }
  122. }
  123. }
  124. return sdev;
  125. }
  126. static void RemoveDevice(const char *uuid)
  127. {
  128. SSDP_DEVICE *sdev;
  129. SSDP_DEVICE **sdev_link = &ssdp_device_cache;
  130. for (sdev = ssdp_device_cache; sdev; sdev = sdev->sdev_next) {
  131. if (strcmp(sdev->sdev_uuid, uuid) == 0) {
  132. break;
  133. }
  134. sdev_link = &sdev->sdev_next;
  135. }
  136. if (sdev) {
  137. SSDP_SERVICE *ssvc = sdev->sdev_svc;
  138. while (ssvc) {
  139. SSDP_SERVICE *svc_next = ssvc->ssvc_next;
  140. CallObservers(ssvc, 1);
  141. free(ssvc->ssvc_domain);
  142. free(ssvc->ssvc_type);
  143. free(ssvc);
  144. ssvc = svc_next;
  145. }
  146. free(sdev->sdev_uuid);
  147. free(sdev->sdev_url_desc);
  148. free(sdev->sdev_domain);
  149. free(sdev->sdev_type);
  150. *sdev_link = sdev->sdev_next;
  151. free(sdev);
  152. }
  153. }
  154. /*!
  155. * \brief Retrieve UUID from HTTPU header.
  156. */
  157. static const char *SsdpUuidFromHeader(const HTTPU_HEADER *hdr)
  158. {
  159. static char *uuid;
  160. const char *cp;
  161. free(uuid);
  162. uuid = NULL;
  163. cp = HttpuGetHeader(hdr, "USN");
  164. if (*cp) {
  165. if (strncmp(cp, ct_uuid_, 5) == 0) {
  166. cp += 5;
  167. }
  168. uuid = strchr(cp, ':');
  169. if (uuid) {
  170. int len = uuid - cp;
  171. uuid = malloc(len + 1);
  172. memcpy(uuid, cp, len);
  173. *(uuid + len) = '\0';
  174. } else {
  175. uuid = strdup(cp);
  176. }
  177. }
  178. return uuid;
  179. }
  180. int SsdpDiscoverDevices(const char *target, int_fast8_t mxwait)
  181. {
  182. int rc = -1;
  183. HTTPU_SESSION *s;
  184. /* Create a new HTTPU session. */
  185. s = HttpuSessionCreate(0);
  186. if (s) {
  187. /* Build and transmit an M-SEARCH header. */
  188. HttpuAddHeader(s, NULL, "M-SEARCH * HTTP/1.1", NULL);
  189. HttpuAddHeader(s, "Host", ct_239_255_255_250, ":1900", NULL);
  190. HttpuAddHeader(s, "MAN", "\"ssdp:discover\"", NULL);
  191. {
  192. char valbuf[4];
  193. sprintf(valbuf, "%d", mxwait);
  194. HttpuAddHeader(s, "MX", valbuf, NULL);
  195. }
  196. HttpuAddHeader(s, "ST", target, NULL);
  197. HttpuSend(s, inet_addr(ct_239_255_255_250), 1900);
  198. /* Receive all responses within maximum wait time. */
  199. while (mxwait) {
  200. rc = HttpuReceive(s, 1000);
  201. if (rc < 0) {
  202. break;
  203. }
  204. else if (rc == 0) {
  205. mxwait--;
  206. } else {
  207. const char *hdr;
  208. /* Get the first header line, which should be
  209. HTTP/1.x 200 OK. */
  210. hdr = HttpuGetHeader(&s->s_rcvhdr, NULL);
  211. if (atoi(hdr + 9) == 200) {
  212. const char *uuid = SsdpUuidFromHeader(&s->s_rcvhdr);
  213. if (uuid) {
  214. const char *cp = HttpuGetHeader(&s->s_rcvhdr, "ST");
  215. if (strcasecmp(cp, ct_upnp_rootdevice) == 0) {
  216. CacheDevice(uuid, NULL, NULL, &s->s_rcvhdr);
  217. }
  218. else if (strncasecmp(cp, "urn:", 4) == 0) {
  219. char *comp[5];
  220. SsdpSplitWords(strdup(cp), ':', comp, 5);
  221. if (strcmp(comp[2], "device") == 0) {
  222. CacheDevice(uuid, comp[1], comp[3], &s->s_rcvhdr);
  223. }
  224. else if (strcmp(comp[2], "service") == 0) {
  225. CacheService(uuid, comp[1], comp[3], &s->s_rcvhdr);
  226. }
  227. free(comp[0]);
  228. }
  229. }
  230. }
  231. rc = 0;
  232. }
  233. }
  234. /* Terminate this session. */
  235. HttpuSessionDestroy(s);
  236. }
  237. return rc;
  238. }
  239. /*!
  240. * \brief Process notification messages from the network.
  241. */
  242. static void NotificationListener(HTTPU_HEADER *hdr)
  243. {
  244. const char *nts;
  245. const char *nt;
  246. const char *uuid;
  247. char *comp[5];
  248. nt = HttpuGetHeader(hdr, "NT");
  249. nts = HttpuGetHeader(hdr, "NTS");
  250. uuid = SsdpUuidFromHeader(hdr);
  251. if (*nt == '\0' || *nts == '\0' || uuid == NULL) {
  252. return;
  253. }
  254. if (strcmp(nts, "ssdp:byebye") == 0) {
  255. RemoveDevice(uuid);
  256. }
  257. else if (strcmp(nts, "ssdp:alive") == 0) {
  258. SsdpSplitWords(strdup(nt), ':', comp, 5);
  259. if (strcmp(comp[0], "urn") == 0 && strcmp(comp[2], "service") == 0) {
  260. SSDP_OBSERVER *sobs;
  261. for (sobs = ssdp_observer_root; sobs; sobs = sobs->sobs_next) {
  262. if (strcmp(comp[1], sobs->sobs_domain) == 0 && strcmp(comp[3], sobs->sobs_type) == 0) {
  263. CacheService(uuid, comp[1], comp[3], hdr);
  264. break;
  265. }
  266. }
  267. }
  268. free(comp[0]);
  269. }
  270. }
  271. int SsdpRegisterServiceObserver(SSDP_OBSERVER_FUNCTION cb, const char *domain, const char *type, int_fast8_t mxwait)
  272. {
  273. int rc = -1;
  274. SSDP_OBSERVER *sobs;
  275. sobs = malloc(sizeof(*sobs));
  276. if (sobs) {
  277. sobs->sobs_domain = strdup(domain);
  278. sobs->sobs_type = strdup(type);
  279. if (sobs->sobs_domain && sobs->sobs_type) {
  280. char *st;
  281. sobs->sobs_cb = cb;
  282. sobs->sobs_next = ssdp_observer_root;
  283. ssdp_observer_root = sobs;
  284. st = malloc(4 + strlen(domain) + 9 + strlen(type) + 3);
  285. if (st) {
  286. sprintf(st, "urn:%s:service:%s:1", domain, type);
  287. rc = SsdpDiscoverDevices(st, mxwait);
  288. free(st);
  289. if (rc == 0) {
  290. rc = SsdpRegisterListener(NotificationListener);
  291. }
  292. }
  293. } else {
  294. free(sobs->sobs_domain);
  295. free(sobs->sobs_type);
  296. free(sobs);
  297. }
  298. }
  299. return rc;
  300. }