--- /dev/null
+++ b/minissdpd/ssdpd.c
@@ -0,0 +1,626 @@
+/*
+ * Copyright (C) 2022 iopsys Software Solutions AB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ *	Author: Amin Ben Romdhane <amin.benromdhane@iopsys.eu>
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <net/if.h>
+#include <syslog.h>
+
+#include <curl/curl.h>
+#include <libubox/uloop.h>
+#include <libubox/blobmsg_json.h>
+#include <libubox/list.h>
+#include <libubus.h>
+#include <mxml.h>
+
+#include "codelength.h"
+
+struct UPNPDev {
+	struct list_head list;
+	char *descURL;
+	char *st;
+	char *usn;
+};
+
+struct desc_list_elt {
+	struct list_head list;
+	char *url;
+	char *desc_path;
+	bool is_device_desc;
+};
+
+#define UPNP_DESC_PATH "/etc/upnp/description"
+#define UPNP_DISCOVER_TIMEOUT (30 * 1000)
+
+#ifndef MIN
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif /* MIN */
+
+/* macros used to read from unix socket */
+#define READ_BYTE_BUFFER(c) \
+	if ((int)bufferindex >= n) { \
+		n = read(s, buffer, sizeof(buffer)); \
+		if (n <= 0) break; \
+		bufferindex = 0; \
+	} \
+	c = buffer[bufferindex++];
+
+#define READ_COPY_BUFFER(dst, len) \
+	for (l = len, p = (unsigned char *)dst; l > 0; ) { \
+		unsigned int lcopy; \
+		if ((int)bufferindex >= n) { \
+			n = read(s, buffer, sizeof(buffer)); \
+			if ( n<= 0) break; \
+			bufferindex = 0; \
+		} \
+		lcopy = MIN(l, (n - bufferindex)); \
+		memcpy(p, buffer + bufferindex, lcopy); \
+		l -= lcopy; \
+		p += lcopy; \
+		bufferindex += lcopy; \
+	}
+
+LIST_HEAD(dev_list);
+LIST_HEAD(desc_list);
+
+char *ssdp_sockpath = NULL;
+
+static void upnp_discover_devices(struct uloop_timeout *timeout);
+static struct uloop_timeout upnpdiscover_timer = { .cb = upnp_discover_devices };
+
+static void add_dev_to_dev_list(char *descURL, char *st, char *usn)
+{
+	struct UPNPDev *dev = NULL;
+
+	dev = calloc(1, sizeof(struct UPNPDev));
+	list_add_tail(&dev->list, &dev_list);
+
+	dev->descURL = descURL;
+	dev->st = st;
+	dev->usn = usn;
+}
+
+void free_all_dev_list(void)
+{
+	struct UPNPDev *dev = NULL;
+
+	while (dev_list.next != &dev_list) {
+		dev = list_entry(dev_list.next, struct UPNPDev, list);
+		free(dev->descURL);
+		free(dev->st);
+		free(dev->usn);
+		free(dev);
+		list_del(&dev->list);
+	}
+}
+
+static int connectToMiniSSDPD(void)
+{
+	int s = 0;
+	struct sockaddr_un addr;
+
+	s = socket(AF_UNIX, SOCK_STREAM, 0);
+	if(s < 0)
+		return -1;
+
+	char *ssdp_s = ssdp_sockpath ? ssdp_sockpath : "/var/run/minissdpd.sock";
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sun_family = AF_UNIX;
+
+	strncpy(addr.sun_path, ssdp_s, sizeof(addr.sun_path));
+
+	if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
+		close(s);
+		return -1;
+	}
+
+	return s;
+}
+
+static int disconnectFromMiniSSDPD(int s)
+{
+	if (close(s) < 0)
+		return -1;
+	return 0;
+}
+
+static int requestDevicesFromMiniSSDPD(int s)
+{
+	unsigned char buffer[256];
+	unsigned char *p = NULL;
+	unsigned int stsize = 0, l = 0;
+	char *devtype = "ssdp:all";
+
+	buffer[0] = 3;	/* request type 3 : everything */
+	stsize = strlen(devtype);
+
+	p = buffer + 1;
+	l = stsize;	CODELENGTH(l, p);
+	if (p + stsize > buffer + sizeof(buffer))
+		return -1;
+
+	memcpy(p, devtype, stsize);
+	p += stsize;
+	if (write(s, buffer, p - buffer) < 0)
+		return -1;
+
+	return 0;
+}
+
+static int receiveDevicesFromMiniSSDPD(int s)
+{
+	unsigned char buffer[256];
+	ssize_t n;
+	unsigned char *p;
+	unsigned int bufferindex;
+	unsigned int i, ndev;
+	unsigned int urlsize, stsize, usnsize, l;
+	char *url, *st, *usn;
+
+	n = read(s, buffer, sizeof(buffer));
+	if (n <= 0)
+		return -1;
+
+	ndev = buffer[0];
+	bufferindex = 1;
+	for (i = 0; i < ndev; i++) {
+		DECODELENGTH_READ(urlsize, READ_BYTE_BUFFER);
+		if (n <= 0)
+			return -1;
+
+		url = (char *)malloc(urlsize);
+		if (url == NULL)
+			return -1;
+
+		READ_COPY_BUFFER(url, urlsize);
+		if (n <= 0)
+			return -1;
+
+		DECODELENGTH_READ(stsize, READ_BYTE_BUFFER);
+		if (n <= 0)
+			goto free_url_and_return;
+
+		st = (char *)malloc(stsize);
+		if (st == NULL)
+			goto free_url_and_return;
+
+		READ_COPY_BUFFER(st, stsize);
+		if (n <= 0)
+			goto free_url_and_st_and_return;
+
+		DECODELENGTH_READ(usnsize, READ_BYTE_BUFFER);
+		if (n <= 0)
+			goto free_url_and_st_and_return;
+
+		usn = (char *)malloc(usnsize);
+		if (usn == NULL)
+			goto free_url_and_st_and_return;
+
+		READ_COPY_BUFFER(usn, usnsize);
+		if (n <= 0)
+			goto free_url_and_st_and_usn_and_return;
+
+		add_dev_to_dev_list(url, st, usn);
+	}
+
+	return 0;
+
+free_url_and_st_and_usn_and_return:
+	free(usn);
+free_url_and_st_and_return:
+	free(st);
+free_url_and_return:
+	free(url);
+	return -1;
+}
+
+static int getDevicesFromMiniSSDPD(void)
+{
+	int s = 0;
+	int res = 0;
+
+	s = connectToMiniSSDPD();
+	if (s < 0)
+		return -1;
+
+	res = requestDevicesFromMiniSSDPD(s);
+	if (res < 0)
+		goto close_socket_and_return;
+
+	res = receiveDevicesFromMiniSSDPD(s);
+
+close_socket_and_return:
+	disconnectFromMiniSSDPD(s);
+
+	return res;
+}
+
+static void download_file(char *file_path, const char *url)
+{
+	CURL *curl = curl_easy_init();
+	if (curl) {
+		curl_easy_setopt(curl, CURLOPT_URL, url);
+		curl_easy_setopt(curl, CURLOPT_TIMEOUT, 500);
+
+		FILE *fp = fopen(file_path, "wb");
+		if (fp) {
+			curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
+			curl_easy_perform(curl);
+			fclose(fp);
+		}
+
+		curl_easy_cleanup(curl);
+	}
+}
+
+static bool is_desc_exist(const char *desc_url)
+{
+	struct desc_list_elt *desc_elt = NULL;
+
+	if (!desc_url)
+		return false;
+
+	list_for_each_entry(desc_elt, &desc_list, list) {
+		if (strcmp(desc_elt->url, desc_url) == 0)
+			return true;
+	}
+
+	return false;
+}
+
+static void get_desc_name(const char *desc_url, char *str, size_t len)
+{
+	if (!desc_url || !str || len == 0)
+		return;
+
+	char *p = strstr(desc_url, "://");
+
+	snprintf(str, len, "%s", p ? p + 3 : desc_url);
+
+	for (int i = 0; str[i]; i++) {
+		if (str[i] == '/')
+			str[i] = '_';
+	}
+}
+
+static void add_desc_to_desc_list(const char *desc_path, const char *url, int is_device_desc)
+{
+	struct desc_list_elt *desc_elt;
+
+	desc_elt = calloc(1, sizeof(struct desc_list_elt));
+	list_add_tail(&desc_elt->list, &desc_list);
+
+	desc_elt->desc_path = strdup(desc_path);
+	desc_elt->url = strdup(url);
+	desc_elt->is_device_desc = is_device_desc;
+}
+
+static void free_all_desc_list(void)
+{
+	struct desc_list_elt *desc_elt = NULL;
+
+	while (desc_list.next != &desc_list) {
+		desc_elt = list_entry(desc_list.next, struct desc_list_elt, list);
+		free(desc_elt->desc_path);
+		free(desc_elt->url);
+		free(desc_elt);
+		list_del(&desc_elt->list);
+	}
+}
+
+static void __upnp_discover_devices(void)
+{
+	struct UPNPDev *dev = NULL;
+	char desc_name[128] = {0};
+	char file_path[256] = {0};
+	int res = 0, is_device_desc = 0;
+
+	/*
+	 * Discover devices
+	 */
+	if (!list_empty(&dev_list))
+		free_all_dev_list();
+
+	res = getDevicesFromMiniSSDPD();
+	if (res)
+		goto end;
+
+	/*
+	 * Download description files
+	 */
+	list_for_each_entry_reverse(dev, &dev_list, list) {
+
+		if (is_desc_exist(dev->descURL))
+			continue;
+
+		get_desc_name(dev->descURL, desc_name, sizeof(desc_name));
+		snprintf(file_path, sizeof(file_path), "%s/%s", UPNP_DESC_PATH, desc_name);
+		is_device_desc = (dev->usn && strstr(dev->usn, ":service:")) ? 0 : 1;
+
+		// Download Description
+		download_file(file_path, dev->descURL);
+
+		// Add description to descriptions list
+		add_desc_to_desc_list(file_path, dev->descURL, is_device_desc);
+	}
+
+end:
+	uloop_timeout_set(&upnpdiscover_timer, UPNP_DISCOVER_TIMEOUT);
+}
+
+static int upnp_discovery_res(struct ubus_context *ctx, struct ubus_object *obj __attribute__((unused)),
+		struct ubus_request_data *req, const char *method __attribute__((unused)), struct blob_attr *msg __attribute__((unused)))
+{
+	struct blob_buf bb = {0};
+	struct UPNPDev *dev = NULL;
+
+	memset(&bb,0,sizeof(struct blob_buf));
+	blob_buf_init(&bb, 0);
+
+	void *devices_array = blobmsg_open_array(&bb, "devices");
+	list_for_each_entry_reverse(dev, &dev_list, list) {
+		// Parse Root device and devices
+		if ((dev->st && strstr(dev->st, ":rootdevice") != NULL) || (dev->usn && strstr(dev->usn, ":device:") != NULL)) {
+			void *device_obj = blobmsg_open_table(&bb, NULL);
+			blobmsg_add_string(&bb, "descurl", dev->descURL);
+			blobmsg_add_string(&bb, "st", dev->st);
+			blobmsg_add_string(&bb, "usn", dev->usn);
+			blobmsg_add_string(&bb, "is_root_device", dev->st && strstr(dev->st, ":rootdevice") ? "1" : "0");
+			blobmsg_close_table(&bb, device_obj);
+		}
+	}
+	blobmsg_close_array(&bb, devices_array);
+
+	void *services_array = blobmsg_open_array(&bb, "services");
+	list_for_each_entry_reverse(dev, &dev_list, list) {
+		// Parse Services
+		if (dev->usn && strstr(dev->usn, ":service:") != NULL) {
+			void *service_obj = blobmsg_open_table(&bb, NULL);
+			blobmsg_add_string(&bb, "descurl", dev->descURL);
+			blobmsg_add_string(&bb, "st", dev->st);
+			blobmsg_add_string(&bb, "usn", dev->usn);
+			blobmsg_close_table(&bb, service_obj);
+		}
+	}
+	blobmsg_close_array(&bb, services_array);
+
+	ubus_send_reply(ctx, req, bb.head);
+	blob_buf_free(&bb);
+	return 0;
+}
+
+static void fill_device_instances(struct blob_buf *bb, mxml_node_t *device)
+{
+	void *device_obj = NULL;
+	mxml_node_t *b = device;
+	char buf[64] = {0};
+	bool new_device_discovery = false;
+
+	while (b) {
+
+		if (mxmlGetType(b) != MXML_ELEMENT) {
+			b = mxmlWalkNext(b, device, MXML_DESCEND);
+			continue;
+		}
+
+		const char *elm_name = mxmlGetElement(b);
+		const char *elm_val = mxmlGetOpaque(b);
+
+		if (elm_name && strcmp(elm_name, "device") == 0) {
+
+			if (new_device_discovery && device_obj)
+				blobmsg_close_table(bb, device_obj);
+
+			device_obj = blobmsg_open_table(bb, NULL);
+			blobmsg_add_string(bb, "parent_dev", buf);
+			new_device_discovery = true;
+		}
+
+		if (elm_name && strcmp(elm_name, "deviceType") == 0 && new_device_discovery)
+			blobmsg_add_string(bb, "deviceType", elm_val ? elm_val : "");
+
+		if (elm_name && strcmp(elm_name, "friendlyName") == 0 && new_device_discovery)
+			blobmsg_add_string(bb, "friendlyName", elm_val ? elm_val : "");
+
+		if (elm_name && strcmp(elm_name, "manufacturer") == 0 && new_device_discovery)
+			blobmsg_add_string(bb, "manufacturer", elm_val ? elm_val : "");
+
+		if (elm_name && strcmp(elm_name, "manufacturerURL") == 0 && new_device_discovery)
+			blobmsg_add_string(bb, "manufacturerURL", elm_val ? elm_val : "");
+
+		if (elm_name && strcmp(elm_name, "modelDescription") == 0 && new_device_discovery)
+			blobmsg_add_string(bb, "modelDescription", elm_val ? elm_val : "");
+
+		if (elm_name && strcmp(elm_name, "modelName") == 0 && new_device_discovery)
+			blobmsg_add_string(bb, "modelName", elm_val ? elm_val : "");
+
+		if (elm_name && strcmp(elm_name, "modelNumber") == 0 && new_device_discovery)
+			blobmsg_add_string(bb, "modelNumber", elm_val ? elm_val : "");
+
+		if (elm_name && strcmp(elm_name, "modelURL") == 0 && new_device_discovery)
+			blobmsg_add_string(bb, "modelURL", elm_val ? elm_val : "");
+
+		if (elm_name && strcmp(elm_name, "serialNumber") == 0 && new_device_discovery)
+			blobmsg_add_string(bb, "serialNumber", elm_val ? elm_val : "");
+
+		if (elm_name && strcmp(elm_name, "UDN") == 0 && new_device_discovery) {
+			snprintf(buf, sizeof(buf), "%s", elm_val ? elm_val : "");
+			blobmsg_add_string(bb, "UDN", buf);
+		}
+
+		if (elm_name && strcmp(elm_name, "UPC") == 0 && new_device_discovery)
+			blobmsg_add_string(bb, "UPC", elm_val ? elm_val : "");
+
+		b = mxmlWalkNext(b, device, MXML_DESCEND);
+	}
+
+	if (new_device_discovery && device_obj)
+		blobmsg_close_table(bb, device_obj);
+}
+
+static void fill_service_element(struct blob_buf *bb, mxml_node_t *service)
+{
+	mxml_node_t *b = service;
+	void *service_obj = NULL;
+	char buf[64] = {0};
+	bool new_srv_discovery = false;
+
+	while (b) {
+
+		if (mxmlGetType(b) != MXML_ELEMENT) {
+			b = mxmlWalkNext(b, service, MXML_DESCEND);
+			continue;
+		}
+
+		const char *elm_name = mxmlGetElement(b);
+		const char *elm_val = mxmlGetOpaque(b);
+
+		if (elm_name && strcmp(elm_name, "UDN") == 0)
+			snprintf(buf, sizeof(buf), "%s", elm_val ? elm_val : "");
+
+
+		if (elm_name && strcmp(elm_name, "service") == 0) {
+
+			if (new_srv_discovery && service_obj)
+				blobmsg_close_table(bb, service_obj);
+
+			service_obj = blobmsg_open_table(bb, NULL);
+			blobmsg_add_string(bb, "parent_dev", buf);
+			new_srv_discovery = true;
+		}
+
+		if (elm_name && strcmp(elm_name, "serviceType") == 0 && new_srv_discovery)
+			blobmsg_add_string(bb, "serviceType", elm_val ? elm_val : "");
+
+		if (elm_name && strcmp(elm_name, "serviceId") == 0 && new_srv_discovery)
+			blobmsg_add_string(bb, "serviceId", elm_val ? elm_val : "");
+
+		if (elm_name && strcmp(elm_name, "SCPDURL") == 0 && new_srv_discovery)
+			blobmsg_add_string(bb, "SCPDURL", elm_val ? elm_val : "");
+
+		if (elm_name && strcmp(elm_name, "controlURL") == 0 && new_srv_discovery)
+			blobmsg_add_string(bb, "controlURL", elm_val ? elm_val : "");
+
+		if (elm_name && strcmp(elm_name, "eventSubURL") == 0 && new_srv_discovery)
+			blobmsg_add_string(bb, "eventSubURL", elm_val ? elm_val : "");
+
+		b = mxmlWalkNext(b, service, MXML_DESCEND);
+	}
+
+	if (new_srv_discovery && service_obj)
+		blobmsg_close_table(bb, service_obj);
+}
+
+static int upnp_description_res(struct ubus_context *ctx, struct ubus_object *obj __attribute__((unused)),
+		struct ubus_request_data *req, const char *method __attribute__((unused)), struct blob_attr *msg __attribute__((unused)))
+{
+	struct desc_list_elt *desc_elt = NULL;
+	struct blob_buf bb = {0};
+
+	memset(&bb,0,sizeof(struct blob_buf));
+	blob_buf_init(&bb, 0);
+
+	void *desc_array = blobmsg_open_array(&bb, "descriptions");
+	list_for_each_entry(desc_elt, &desc_list, list) {
+		void *device_obj = blobmsg_open_table(&bb, NULL);
+		blobmsg_add_string(&bb, "desc_url", desc_elt->url);
+		blobmsg_add_u32(&bb, "is_device_desc", desc_elt->is_device_desc);
+		blobmsg_close_table(&bb, device_obj);
+
+	}
+	blobmsg_close_array(&bb, desc_array);
+
+	list_for_each_entry(desc_elt, &desc_list, list) {
+
+		FILE *fp = fopen(desc_elt->desc_path, "r");
+		if (!fp)
+			continue;
+
+		mxml_node_t *tree = mxmlLoadFile(NULL, fp, MXML_OPAQUE_CALLBACK);
+		fclose(fp);
+
+		if (tree) {
+			void *devices_array = blobmsg_open_array(&bb, "devices");
+	 		mxml_node_t *device = mxmlFindElement(tree, tree, "device", NULL, NULL, MXML_DESCEND);
+			fill_device_instances(&bb, device);
+			blobmsg_close_array(&bb, devices_array);
+
+			void *services_array = blobmsg_open_array(&bb, "services");
+	 		mxml_node_t *service = mxmlFindElement(tree, tree, "device", NULL, NULL, MXML_DESCEND);
+			fill_service_element(&bb, service);
+			blobmsg_close_array(&bb, services_array);
+
+			mxmlDelete(tree);
+		}
+	}
+
+	ubus_send_reply(ctx, req, bb.head);
+	blob_buf_free(&bb);
+	return 0;
+}
+
+static struct ubus_method upnp_methods[] = {
+	UBUS_METHOD_NOARG("discovery", upnp_discovery_res),
+	UBUS_METHOD_NOARG("description", upnp_description_res),
+};
+
+static struct ubus_object_type upnp_type = UBUS_OBJECT_TYPE("upnp", upnp_methods);
+
+static void upnp_discover_devices(struct uloop_timeout *timeout __attribute__((unused)))
+{
+	__upnp_discover_devices();
+}
+
+static struct ubus_object upnp_object = {
+	.name = "upnp",
+	.type = &upnp_type,
+	.methods = upnp_methods,
+	.n_methods = ARRAY_SIZE(upnp_methods),
+};
+
+void upnp_thread_discover_devices(void)
+{
+	struct ubus_context *ctx = NULL;
+	const char *ubus_socket = NULL;
+	int ret = 0;
+
+	uloop_init();
+
+	ctx = ubus_connect(ubus_socket);
+	if (!ctx) {
+		syslog(LOG_ERR, "Failed to connect to ubus\n");
+		return;
+	}
+
+	ubus_add_uloop(ctx);
+
+	__upnp_discover_devices();
+
+	ret = ubus_add_object(ctx, &upnp_object);
+	if (ret) {
+		syslog(LOG_ERR, "Failed to add 'upnp' ubus object: %s\n", ubus_strerror(ret));
+		goto end;
+	}
+
+	uloop_run();
+
+end:
+	free_all_desc_list();
+	free_all_dev_list();
+	uloop_done();
+	ubus_free(ctx);
+}
