#include "api__ssdp.h" #include "SSDPAPI.h" #include "foundation/error.h" #include "jnetlib/jnetlib.h" #include "nx/nxsleep.h" #include "nswasabi/AutoCharNX.h" #include "nswasabi/ReferenceCounted.h" #include #include #ifdef __ANDROID__ #include #endif #ifdef _WIN32 #define snprintf _snprintf #endif SSDPAPI::SSDPAPI() { listener=0; user_agent=0; } SSDPAPI::~SSDPAPI() { jnl_udp_release(listener); for (ServiceList::iterator itr=services.begin();itr!=services.end();itr++) { NXURIRelease(itr->location); NXStringRelease(itr->type); NXStringRelease(itr->usn); } for (ServiceList::iterator itr=found_services.begin();itr!=found_services.end();itr++) { NXURIRelease(itr->location); NXStringRelease(itr->type); NXStringRelease(itr->usn); } } int SSDPAPI::Initialize() { user_agent=WASABI2_API_APP->GetUserAgent(); NXThreadCreate(&ssdp_thread, SSDPThread, this); return NErr_Success; } int SSDPAPI::SSDP_RegisterService(nx_uri_t location, nx_string_t type, nx_string_t usn) { nu::AutoLock auto_lock(services_lock); Service service = {0}; service.location = NXURIRetain(location); service.type = NXStringRetain(type); service.usn = NXStringRetain(usn); services.push_back(service); return NErr_Success; } int SSDPAPI::SSDP_RegisterCallback(cb_ssdp *callback) { if (!callback) { return NErr_BadParameter; } threadloop_node_t *node = thread_loop.GetAPC(); if (!node) { return NErr_OutOfMemory; } node->func = APC_RegisterCallback; node->param1 = this; node->param2 = callback; callback->Retain(); thread_loop.Schedule(node); return NErr_Success; } int SSDPAPI::SSDP_UnregisterCallback(cb_ssdp *callback) { if (!callback) { return NErr_BadParameter; } threadloop_node_t *node = thread_loop.GetAPC(); if (!node) { return NErr_OutOfMemory; } node->func = APC_UnregisterCallback; node->param1 = this; node->param2 = callback; // since we don't actually know if the callback is registered until the other thread runs // we can't guarantee that we have a reference. so we'll add one! callback->Retain(); thread_loop.Schedule(node); return NErr_Success; } int SSDPAPI::SSDP_Search(nx_string_t type) { if (!type) { return NErr_BadParameter; } threadloop_node_t *node = thread_loop.GetAPC(); if (!node) { return NErr_OutOfMemory; } node->func = APC_Search; node->param1 = this; node->param2 = NXStringRetain(type); thread_loop.Schedule(node); return NErr_Success; } int SSDPAPI::FindUSN(ServiceList &service_list, const char *usn, Service *&service) { for (ServiceList::iterator itr = service_list.begin(); itr != service_list.end(); itr++) { if (!NXStringKeywordCompareWithCString(itr->usn, usn)) { service = &(* itr); return NErr_Success; } } size_t num_services = service_list.size(); Service new_service = {0}; NXStringCreateWithUTF8(&new_service.usn, usn); service_list.push_back(new_service); service = &service_list.at(num_services); return NErr_Empty; } nx_thread_return_t SSDPAPI::SSDPThread(nx_thread_parameter_t parameter) { return ((SSDPAPI *)parameter)->Internal_Run(); } void SSDPAPI::Internal_RegisterCallback(cb_ssdp *callback) { callbacks.push_back(callback); nu::AutoLock auto_lock(services_lock); for (ServiceList::iterator itr=found_services.begin();itr!=found_services.end();itr++) { callback->OnServiceConnected(itr->location, itr->type, itr->usn); } } void SSDPAPI::Internal_UnregisterCallback(cb_ssdp *callback) { // TODO: verify that our list actually contains the callback? //callbacks.eraseObject(callback); auto it = std::find(callbacks.begin(), callbacks.end(), callback); if (it != callbacks.end()) { callbacks.erase(it); } callback->Release(); // release the reference retained on the other thread callback->Release(); // release _our_ reference } nx_thread_return_t SSDPAPI::Internal_Run() { addrinfo *addr = 0; jnl_dns_resolve_now("239.255.255.250", 1900, &addr, SOCK_DGRAM); int ret = jnl_udp_create_multicast_listener(&listener, "239.255.255.250", 1900); if (ret != NErr_Success || !addr) return (nx_thread_return_t)ret; jnl_httpu_request_t httpu; ret = jnl_httpu_request_create(&httpu); if (ret != NErr_Success) { return (nx_thread_return_t)ret; } time_t last_notify = 0; for (;;) { time_t now = time(0); if ((now - last_notify) > 15) { Internal_Notify(0, addr->ai_addr, (socklen_t)addr->ai_addrlen); if (ret != NErr_Success) { // TODO: ? } last_notify = time(0); #if 0 #if defined(_DEBUG) && defined(_WIN32) FILE *f = fopen("c:/services.txt", "wb"); if (f) { for (ServiceList::iterator itr=found_services.begin();itr!=found_services.end();itr++) { fprintf(f, "-----\r\n"); fprintf(f, "ST: %s\r\n", AutoCharPrintfUTF8(itr->type)); fprintf(f, "Location: %s\r\n", AutoCharPrintfUTF8(itr->location)); fprintf(f, "USN: %s\r\n", AutoCharPrintfUTF8(itr->usn)); } fclose(f); } #elif defined(__ANDROID__) for (ServiceList::iterator itr=found_services.begin();itr!=found_services.end();itr++) { __android_log_print(ANDROID_LOG_INFO, "libreplicant", "-----\r\n"); __android_log_print(ANDROID_LOG_INFO, "libreplicant", "ST: %s\r\n", AutoCharPrintfUTF8(itr->type)); __android_log_print(ANDROID_LOG_INFO, "libreplicant", "Location: %s\r\n", AutoCharPrintfUTF8(itr->location)); __android_log_print(ANDROID_LOG_INFO, "libreplicant", "USN: %s\r\n", AutoCharPrintfUTF8(itr->usn)); } #endif #endif } thread_loop.Step(100); for (;;) { size_t bytes_received=0; jnl_udp_run(listener, 0, 8192, 0, &bytes_received); if (bytes_received) { jnl_httpu_request_process(httpu, listener); const char *method = jnl_httpu_request_get_method(httpu); if (method) { if (!strcmp(method, "NOTIFY")) { unsigned int max_age=0; const char *cache_control = jnl_httpu_request_get_header(httpu, "cache-control"); if (cache_control) { if (strncasecmp(cache_control, "max-age=", 8) == 0) { max_age = strtoul(cache_control+8, 0, 10); } } const char *location = jnl_httpu_request_get_header(httpu, "location"); const char *nt = jnl_httpu_request_get_header(httpu, "nt"); const char *nts = jnl_httpu_request_get_header(httpu, "nts"); const char *usn = jnl_httpu_request_get_header(httpu, "usn"); // a hack to work with older android wifi sync protocol if (!usn) usn = jnl_httpu_request_get_header(httpu, "id"); if (usn && nt && location && nts && !strcmp(nts, "ssdp:alive")) { Service *service = 0; int ret = FindUSN(found_services, usn, service); if (ret == NErr_Success) // found an existing one { // update the last seen time and expiration date // TODO: should we double check that the location didn't change? bool changed=false; ReferenceCountedNXString old_location; if (service->location) NXURIGetNXString(&old_location, service->location); if (!service->location || NXStringKeywordCompareWithCString(old_location, location)) { NXURIRelease(service->location); NXURICreateWithUTF8(&service->location, location); changed=true; } service->last_seen = time(0); if (max_age) service->expiry = service->last_seen + max_age; else service->expiry = (time_t)-1; if (changed) { // notify clients for (CallbackList::iterator itr=callbacks.begin();itr!=callbacks.end();itr++) { (*itr)->OnServiceDisconnected(service->usn); (*itr)->OnServiceConnected(service->location, service->type, service->usn); } } } else if (ret == NErr_Empty) // new one { NXURICreateWithUTF8(&service->location, location); NXStringCreateWithUTF8(&service->type, nt); service->last_seen = time(0); if (max_age) service->expiry = service->last_seen + max_age; else service->expiry = (time_t)-1; // notify clients for (CallbackList::iterator itr=callbacks.begin();itr!=callbacks.end();itr++) { (*itr)->OnServiceConnected(service->location, service->type, service->usn); } } } } else if (!strcmp(method, "M-SEARCH")) { sockaddr *addr; socklen_t addr_length; jnl_udp_get_address(listener, &addr, &addr_length); const char *st = jnl_httpu_request_get_header(httpu, "st"); Internal_Notify(st, addr, addr_length); } } } else { break; } } // check for expirations again: nu::AutoLock auto_lock(services_lock); for (ServiceList::iterator itr=found_services.begin();itr!=found_services.end();itr++) { if (itr->expiry < now) { for (CallbackList::iterator itr2=callbacks.begin();itr2!=callbacks.end();itr2++) { (*itr2)->OnServiceDisconnected(itr->usn); } NXURIRelease(itr->location); NXStringRelease(itr->usn); NXStringRelease(itr->type); found_services.erase(itr); goto again; } } } return 0; } static char notify_request[] = "NOTIFY * HTTP/1.1\r\n"; static char notify_host[] = "HOST: 239.255.255.250:1900\r\n"; static char notify_cache_control[] = "CACHE-CONTROL:max-age=600\r\n"; static char notify_nts[] = "NTS:ssdp:alive\r\n"; static char search_request[] = "NOTIFY * HTTP/1.1\r\n"; static char search_man[] = "MAN:\"ssdp:discover\"\r\n"; ns_error_t SSDPAPI::Internal_Notify(const char *st, sockaddr *addr, socklen_t addr_length) { jnl_udp_set_peer_address(listener, addr, addr_length); nu::AutoLock auto_lock(services_lock); for (ServiceList::iterator itr=services.begin();itr!=services.end();itr++) { if (st && NXStringKeywordCompareWithCString(itr->type, st)) continue; jnl_udp_send(listener, notify_request, strlen(notify_request)); jnl_udp_send(listener, notify_host, strlen(notify_host)); jnl_udp_send(listener, notify_cache_control, strlen(notify_cache_control)); jnl_udp_send(listener, notify_nts, strlen(notify_nts)); char header[512] = {0}; snprintf(header, 511, "LOCATION:%s\r\n", AutoCharPrintfUTF8(itr->location)); header[511]=0; jnl_udp_send(listener, header, strlen(header)); if (itr->usn) { snprintf(header, 511, "USN:%s\r\n", AutoCharPrintfUTF8(itr->usn)); header[511]=0; jnl_udp_send(listener, header, strlen(header)); } if (itr->type) { snprintf(header, 511, "NT:%s\r\n", AutoCharPrintfUTF8(itr->type)); header[511]=0; jnl_udp_send(listener, header, strlen(header)); } if (user_agent) { snprintf(header, 511, "SERVER:%s\r\n",user_agent); header[511]=0; jnl_udp_send(listener, header, strlen(header)); } jnl_udp_send(listener, "\r\n", 2); size_t bytes_sent=0; do { jnl_udp_run(listener, 8192, 0, &bytes_sent, 0); // TODO: error check if (bytes_sent == 0) NXSleep(100); } while (bytes_sent == 0); } return NErr_Success; } void SSDPAPI::Internal_Search(nx_string_t st) { addrinfo *addr; jnl_dns_resolve_now("239.255.255.250", 1900, &addr, SOCK_DGRAM); jnl_udp_set_peer_address(listener, addr->ai_addr, (socklen_t)addr->ai_addrlen); jnl_udp_send(listener, notify_request, strlen(notify_request)); jnl_udp_send(listener, search_request, strlen(search_request)); jnl_udp_send(listener, notify_host, strlen(notify_host)); jnl_udp_send(listener, search_man, strlen(search_man)); char header[512] = {0}; sprintf(header, "ST:%s\r\n", AutoCharPrintfUTF8(st)); header[511]=0; jnl_udp_send(listener, header, strlen(header)); size_t bytes_sent=0; do { jnl_udp_run(listener, 8192, 0, &bytes_sent, 0); // TODO: error check if (bytes_sent == 0) NXSleep(100); } while (bytes_sent == 0); }