1
0

SSDPAPI.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. #include "api__ssdp.h"
  2. #include "SSDPAPI.h"
  3. #include "foundation/error.h"
  4. #include "jnetlib/jnetlib.h"
  5. #include "nx/nxsleep.h"
  6. #include "nswasabi/AutoCharNX.h"
  7. #include "nswasabi/ReferenceCounted.h"
  8. #include <time.h>
  9. #include <stdio.h>
  10. #ifdef __ANDROID__
  11. #include <android/log.h>
  12. #endif
  13. #ifdef _WIN32
  14. #define snprintf _snprintf
  15. #endif
  16. SSDPAPI::SSDPAPI()
  17. {
  18. listener=0;
  19. user_agent=0;
  20. }
  21. SSDPAPI::~SSDPAPI()
  22. {
  23. jnl_udp_release(listener);
  24. for (ServiceList::iterator itr=services.begin();itr!=services.end();itr++)
  25. {
  26. NXURIRelease(itr->location);
  27. NXStringRelease(itr->type);
  28. NXStringRelease(itr->usn);
  29. }
  30. for (ServiceList::iterator itr=found_services.begin();itr!=found_services.end();itr++)
  31. {
  32. NXURIRelease(itr->location);
  33. NXStringRelease(itr->type);
  34. NXStringRelease(itr->usn);
  35. }
  36. }
  37. int SSDPAPI::Initialize()
  38. {
  39. user_agent=WASABI2_API_APP->GetUserAgent();
  40. NXThreadCreate(&ssdp_thread, SSDPThread, this);
  41. return NErr_Success;
  42. }
  43. int SSDPAPI::SSDP_RegisterService(nx_uri_t location, nx_string_t type, nx_string_t usn)
  44. {
  45. nu::AutoLock auto_lock(services_lock);
  46. Service service = {0};
  47. service.location = NXURIRetain(location);
  48. service.type = NXStringRetain(type);
  49. service.usn = NXStringRetain(usn);
  50. services.push_back(service);
  51. return NErr_Success;
  52. }
  53. int SSDPAPI::SSDP_RegisterCallback(cb_ssdp *callback)
  54. {
  55. if (!callback)
  56. {
  57. return NErr_BadParameter;
  58. }
  59. threadloop_node_t *node = thread_loop.GetAPC();
  60. if (!node)
  61. {
  62. return NErr_OutOfMemory;
  63. }
  64. node->func = APC_RegisterCallback;
  65. node->param1 = this;
  66. node->param2 = callback;
  67. callback->Retain();
  68. thread_loop.Schedule(node);
  69. return NErr_Success;
  70. }
  71. int SSDPAPI::SSDP_UnregisterCallback(cb_ssdp *callback)
  72. {
  73. if (!callback)
  74. {
  75. return NErr_BadParameter;
  76. }
  77. threadloop_node_t *node = thread_loop.GetAPC();
  78. if (!node)
  79. {
  80. return NErr_OutOfMemory;
  81. }
  82. node->func = APC_UnregisterCallback;
  83. node->param1 = this;
  84. node->param2 = callback;
  85. // since we don't actually know if the callback is registered until the other thread runs
  86. // we can't guarantee that we have a reference. so we'll add one!
  87. callback->Retain();
  88. thread_loop.Schedule(node);
  89. return NErr_Success;
  90. }
  91. int SSDPAPI::SSDP_Search(nx_string_t type)
  92. {
  93. if (!type)
  94. {
  95. return NErr_BadParameter;
  96. }
  97. threadloop_node_t *node = thread_loop.GetAPC();
  98. if (!node)
  99. {
  100. return NErr_OutOfMemory;
  101. }
  102. node->func = APC_Search;
  103. node->param1 = this;
  104. node->param2 = NXStringRetain(type);
  105. thread_loop.Schedule(node);
  106. return NErr_Success;
  107. }
  108. int SSDPAPI::FindUSN(ServiceList &service_list, const char *usn, Service *&service)
  109. {
  110. for (ServiceList::iterator itr = service_list.begin(); itr != service_list.end(); itr++)
  111. {
  112. if (!NXStringKeywordCompareWithCString(itr->usn, usn))
  113. {
  114. service = &(* itr);
  115. return NErr_Success;
  116. }
  117. }
  118. size_t num_services = service_list.size();
  119. Service new_service = {0};
  120. NXStringCreateWithUTF8(&new_service.usn, usn);
  121. service_list.push_back(new_service);
  122. service = &service_list.at(num_services);
  123. return NErr_Empty;
  124. }
  125. nx_thread_return_t SSDPAPI::SSDPThread(nx_thread_parameter_t parameter)
  126. {
  127. return ((SSDPAPI *)parameter)->Internal_Run();
  128. }
  129. void SSDPAPI::Internal_RegisterCallback(cb_ssdp *callback)
  130. {
  131. callbacks.push_back(callback);
  132. nu::AutoLock auto_lock(services_lock);
  133. for (ServiceList::iterator itr=found_services.begin();itr!=found_services.end();itr++)
  134. {
  135. callback->OnServiceConnected(itr->location, itr->type, itr->usn);
  136. }
  137. }
  138. void SSDPAPI::Internal_UnregisterCallback(cb_ssdp *callback)
  139. {
  140. // TODO: verify that our list actually contains the callback?
  141. //callbacks.eraseObject(callback);
  142. auto it = std::find(callbacks.begin(), callbacks.end(), callback);
  143. if (it != callbacks.end())
  144. {
  145. callbacks.erase(it);
  146. }
  147. callback->Release(); // release the reference retained on the other thread
  148. callback->Release(); // release _our_ reference
  149. }
  150. nx_thread_return_t SSDPAPI::Internal_Run()
  151. {
  152. addrinfo *addr = 0;
  153. jnl_dns_resolve_now("239.255.255.250", 1900, &addr, SOCK_DGRAM);
  154. int ret = jnl_udp_create_multicast_listener(&listener, "239.255.255.250", 1900);
  155. if (ret != NErr_Success || !addr)
  156. return (nx_thread_return_t)ret;
  157. jnl_httpu_request_t httpu;
  158. ret = jnl_httpu_request_create(&httpu);
  159. if (ret != NErr_Success)
  160. {
  161. return (nx_thread_return_t)ret;
  162. }
  163. time_t last_notify = 0;
  164. for (;;)
  165. {
  166. time_t now = time(0);
  167. if ((now - last_notify) > 15)
  168. {
  169. Internal_Notify(0, addr->ai_addr, (socklen_t)addr->ai_addrlen);
  170. if (ret != NErr_Success)
  171. {
  172. // TODO: ?
  173. }
  174. last_notify = time(0);
  175. #if 0
  176. #if defined(_DEBUG) && defined(_WIN32)
  177. FILE *f = fopen("c:/services.txt", "wb");
  178. if (f)
  179. {
  180. for (ServiceList::iterator itr=found_services.begin();itr!=found_services.end();itr++)
  181. {
  182. fprintf(f, "-----\r\n");
  183. fprintf(f, "ST: %s\r\n", AutoCharPrintfUTF8(itr->type));
  184. fprintf(f, "Location: %s\r\n", AutoCharPrintfUTF8(itr->location));
  185. fprintf(f, "USN: %s\r\n", AutoCharPrintfUTF8(itr->usn));
  186. }
  187. fclose(f);
  188. }
  189. #elif defined(__ANDROID__)
  190. for (ServiceList::iterator itr=found_services.begin();itr!=found_services.end();itr++)
  191. {
  192. __android_log_print(ANDROID_LOG_INFO, "libreplicant", "-----\r\n");
  193. __android_log_print(ANDROID_LOG_INFO, "libreplicant", "ST: %s\r\n", AutoCharPrintfUTF8(itr->type));
  194. __android_log_print(ANDROID_LOG_INFO, "libreplicant", "Location: %s\r\n", AutoCharPrintfUTF8(itr->location));
  195. __android_log_print(ANDROID_LOG_INFO, "libreplicant", "USN: %s\r\n", AutoCharPrintfUTF8(itr->usn));
  196. }
  197. #endif
  198. #endif
  199. }
  200. thread_loop.Step(100);
  201. for (;;)
  202. {
  203. size_t bytes_received=0;
  204. jnl_udp_run(listener, 0, 8192, 0, &bytes_received);
  205. if (bytes_received)
  206. {
  207. jnl_httpu_request_process(httpu, listener);
  208. const char *method = jnl_httpu_request_get_method(httpu);
  209. if (method)
  210. {
  211. if (!strcmp(method, "NOTIFY"))
  212. {
  213. unsigned int max_age=0;
  214. const char *cache_control = jnl_httpu_request_get_header(httpu, "cache-control");
  215. if (cache_control)
  216. {
  217. if (strncasecmp(cache_control, "max-age=", 8) == 0)
  218. {
  219. max_age = strtoul(cache_control+8, 0, 10);
  220. }
  221. }
  222. const char *location = jnl_httpu_request_get_header(httpu, "location");
  223. const char *nt = jnl_httpu_request_get_header(httpu, "nt");
  224. const char *nts = jnl_httpu_request_get_header(httpu, "nts");
  225. const char *usn = jnl_httpu_request_get_header(httpu, "usn");
  226. // a hack to work with older android wifi sync protocol
  227. if (!usn)
  228. usn = jnl_httpu_request_get_header(httpu, "id");
  229. if (usn && nt && location && nts && !strcmp(nts, "ssdp:alive"))
  230. {
  231. Service *service = 0;
  232. int ret = FindUSN(found_services, usn, service);
  233. if (ret == NErr_Success) // found an existing one
  234. {
  235. // update the last seen time and expiration date
  236. // TODO: should we double check that the location didn't change?
  237. bool changed=false;
  238. ReferenceCountedNXString old_location;
  239. if (service->location)
  240. NXURIGetNXString(&old_location, service->location);
  241. if (!service->location || NXStringKeywordCompareWithCString(old_location, location))
  242. {
  243. NXURIRelease(service->location);
  244. NXURICreateWithUTF8(&service->location, location);
  245. changed=true;
  246. }
  247. service->last_seen = time(0);
  248. if (max_age)
  249. service->expiry = service->last_seen + max_age;
  250. else
  251. service->expiry = (time_t)-1;
  252. if (changed)
  253. {
  254. // notify clients
  255. for (CallbackList::iterator itr=callbacks.begin();itr!=callbacks.end();itr++)
  256. {
  257. (*itr)->OnServiceDisconnected(service->usn);
  258. (*itr)->OnServiceConnected(service->location, service->type, service->usn);
  259. }
  260. }
  261. }
  262. else if (ret == NErr_Empty) // new one
  263. {
  264. NXURICreateWithUTF8(&service->location, location);
  265. NXStringCreateWithUTF8(&service->type, nt);
  266. service->last_seen = time(0);
  267. if (max_age)
  268. service->expiry = service->last_seen + max_age;
  269. else
  270. service->expiry = (time_t)-1;
  271. // notify clients
  272. for (CallbackList::iterator itr=callbacks.begin();itr!=callbacks.end();itr++)
  273. {
  274. (*itr)->OnServiceConnected(service->location, service->type, service->usn);
  275. }
  276. }
  277. }
  278. }
  279. else if (!strcmp(method, "M-SEARCH"))
  280. {
  281. sockaddr *addr; socklen_t addr_length;
  282. jnl_udp_get_address(listener, &addr, &addr_length);
  283. const char *st = jnl_httpu_request_get_header(httpu, "st");
  284. Internal_Notify(st, addr, addr_length);
  285. }
  286. }
  287. }
  288. else
  289. {
  290. break;
  291. }
  292. }
  293. // check for expirations
  294. again:
  295. nu::AutoLock auto_lock(services_lock);
  296. for (ServiceList::iterator itr=found_services.begin();itr!=found_services.end();itr++)
  297. {
  298. if (itr->expiry < now)
  299. {
  300. for (CallbackList::iterator itr2=callbacks.begin();itr2!=callbacks.end();itr2++)
  301. {
  302. (*itr2)->OnServiceDisconnected(itr->usn);
  303. }
  304. NXURIRelease(itr->location);
  305. NXStringRelease(itr->usn);
  306. NXStringRelease(itr->type);
  307. found_services.erase(itr);
  308. goto again;
  309. }
  310. }
  311. }
  312. return 0;
  313. }
  314. static char notify_request[] = "NOTIFY * HTTP/1.1\r\n";
  315. static char notify_host[] = "HOST: 239.255.255.250:1900\r\n";
  316. static char notify_cache_control[] = "CACHE-CONTROL:max-age=600\r\n";
  317. static char notify_nts[] = "NTS:ssdp:alive\r\n";
  318. static char search_request[] = "NOTIFY * HTTP/1.1\r\n";
  319. static char search_man[] = "MAN:\"ssdp:discover\"\r\n";
  320. ns_error_t SSDPAPI::Internal_Notify(const char *st, sockaddr *addr, socklen_t addr_length)
  321. {
  322. jnl_udp_set_peer_address(listener, addr, addr_length);
  323. nu::AutoLock auto_lock(services_lock);
  324. for (ServiceList::iterator itr=services.begin();itr!=services.end();itr++)
  325. {
  326. if (st && NXStringKeywordCompareWithCString(itr->type, st))
  327. continue;
  328. jnl_udp_send(listener, notify_request, strlen(notify_request));
  329. jnl_udp_send(listener, notify_host, strlen(notify_host));
  330. jnl_udp_send(listener, notify_cache_control, strlen(notify_cache_control));
  331. jnl_udp_send(listener, notify_nts, strlen(notify_nts));
  332. char header[512] = {0};
  333. snprintf(header, 511, "LOCATION:%s\r\n", AutoCharPrintfUTF8(itr->location));
  334. header[511]=0;
  335. jnl_udp_send(listener, header, strlen(header));
  336. if (itr->usn)
  337. {
  338. snprintf(header, 511, "USN:%s\r\n", AutoCharPrintfUTF8(itr->usn));
  339. header[511]=0;
  340. jnl_udp_send(listener, header, strlen(header));
  341. }
  342. if (itr->type)
  343. {
  344. snprintf(header, 511, "NT:%s\r\n", AutoCharPrintfUTF8(itr->type));
  345. header[511]=0;
  346. jnl_udp_send(listener, header, strlen(header));
  347. }
  348. if (user_agent)
  349. {
  350. snprintf(header, 511, "SERVER:%s\r\n",user_agent);
  351. header[511]=0;
  352. jnl_udp_send(listener, header, strlen(header));
  353. }
  354. jnl_udp_send(listener, "\r\n", 2);
  355. size_t bytes_sent=0;
  356. do
  357. {
  358. jnl_udp_run(listener, 8192, 0, &bytes_sent, 0); // TODO: error check
  359. if (bytes_sent == 0)
  360. NXSleep(100);
  361. } while (bytes_sent == 0);
  362. }
  363. return NErr_Success;
  364. }
  365. void SSDPAPI::Internal_Search(nx_string_t st)
  366. {
  367. addrinfo *addr;
  368. jnl_dns_resolve_now("239.255.255.250", 1900, &addr, SOCK_DGRAM);
  369. jnl_udp_set_peer_address(listener, addr->ai_addr, (socklen_t)addr->ai_addrlen);
  370. jnl_udp_send(listener, notify_request, strlen(notify_request));
  371. jnl_udp_send(listener, search_request, strlen(search_request));
  372. jnl_udp_send(listener, notify_host, strlen(notify_host));
  373. jnl_udp_send(listener, search_man, strlen(search_man));
  374. char header[512] = {0};
  375. sprintf(header, "ST:%s\r\n", AutoCharPrintfUTF8(st));
  376. header[511]=0;
  377. jnl_udp_send(listener, header, strlen(header));
  378. size_t bytes_sent=0;
  379. do
  380. {
  381. jnl_udp_run(listener, 8192, 0, &bytes_sent, 0); // TODO: error check
  382. if (bytes_sent == 0)
  383. NXSleep(100);
  384. } while (bytes_sent == 0);
  385. }