aboutsummaryrefslogtreecommitdiff
path: root/Src/replicant/ssdp/SSDPAPI.cpp
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/replicant/ssdp/SSDPAPI.cpp
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz
Initial community commit
Diffstat (limited to 'Src/replicant/ssdp/SSDPAPI.cpp')
-rw-r--r--Src/replicant/ssdp/SSDPAPI.cpp436
1 files changed, 436 insertions, 0 deletions
diff --git a/Src/replicant/ssdp/SSDPAPI.cpp b/Src/replicant/ssdp/SSDPAPI.cpp
new file mode 100644
index 00000000..b3574c83
--- /dev/null
+++ b/Src/replicant/ssdp/SSDPAPI.cpp
@@ -0,0 +1,436 @@
+#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 <time.h>
+#include <stdio.h>
+#ifdef __ANDROID__
+#include <android/log.h>
+#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);
+}