| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- /*
- * Copyright (C) 2012-2013 by egnite GmbH
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the copyright holders nor the names of
- * contributors may be used to endorse or promote products derived
- * from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
- * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- * For additional information see http://www.ethernut.de/
- */
- #include <pro/uri.h>
- #include <pro/uxml.h>
- #include <pro/tcphost.h>
- #include <pro/upnp.h>
- #include <stdlib.h>
- #include <string.h>
- /* Use for local debugging only. Do NOT include into NUTDEBUG.
- #define DEBUG_UPNPC
- */
- #ifdef DEBUG_UPNPC
- #include <stdio.h>
- #endif
- typedef struct _UPNP_OBSERVER UPNP_OBSERVER;
- struct _UPNP_OBSERVER {
- UPNP_OBSERVER *uobs_next;
- char *uobs_domain;
- char *uobs_type;
- SSDP_OBSERVER_FUNCTION uobs_cb;
- };
- static UPNP_OBSERVER *upnp_observer_root;
- /*!
- * \brief Call all our observers.
- *
- * \param ssvc Specifies the service.
- * \param removal 0 if a new service has been added or 1 if a service will
- * be removed.
- *
- * \return 0 on success or -1 on failure.
- */
- static int CallObservers(SSDP_SERVICE *ssvc, int_fast8_t removal)
- {
- int rc;
- UPNP_OBSERVER *uobs;
- for (rc = 0, uobs = upnp_observer_root; rc == 0 && uobs; uobs = uobs->uobs_next) {
- if (strcmp(ssvc->ssvc_domain, uobs->uobs_domain) == 0 && strcmp(ssvc->ssvc_type, uobs->uobs_type) == 0) {
- rc = (*uobs->uobs_cb)(ssvc, removal);
- }
- }
- return rc;
- }
- static const UXML_NODE *GetXmlNode(const UXML_NODE *list, const char *name)
- {
- const UXML_NODE *node;
- for (node = list; node; node = node->xmln_next) {
- if (strcasecmp(node->xmln_name, name) == 0) {
- break;
- }
- }
- return node;
- }
- static const UXML_NODE *GetDeviceChildrenNodes(const UXML_NODE *root)
- {
- root = GetXmlNode(root, "root");
- if (root) {
- root = GetXmlNode(root->xmln_child, "device");
- if (root) {
- root = root->xmln_child;
- }
- }
- return root;
- }
- static char *DuplicateXmlNodeContent(const UXML_NODE *node, const char *name)
- {
- node = GetXmlNode(node, name);
- if (node && node->xmln_content) {
- return strdup(node->xmln_content);
- }
- return NULL;
- }
- static int ParseServiceDeviceInfo(const UXML_NODE *root, SSDP_SERVICE *ssvc)
- {
- int rc = -1;
- const UXML_NODE *node;
- SSDP_DEVICE *sdev = ssvc->ssvc_dev;
- UPNP_DEVICE_INFO *udev = calloc(1, sizeof(*udev));
- root = GetDeviceChildrenNodes(root);
- if (udev) {
- sdev->sdev_info = udev;
- udev->udev_name = DuplicateXmlNodeContent(root, "friendlyName");
- }
- if (sdev->sdev_domain == NULL || sdev->sdev_type == NULL) {
- char *comp[5];
- SsdpSplitWords(DuplicateXmlNodeContent(root, "deviceType"), ':', comp, 5);
- if (sdev->sdev_domain == NULL) {
- sdev->sdev_domain = strdup(comp[1]);
- }
- if (sdev->sdev_type == NULL) {
- sdev->sdev_type = strdup(comp[3]);
- }
- }
- root = GetXmlNode(root, "serviceList");
- if (root) {
- for (root = root->xmln_child; root && root->xmln_child; root = root->xmln_next) {
- char *comp[5];
- const UXML_NODE *snode = root->xmln_child;
- node = GetXmlNode(snode, "serviceType");
- SsdpSplitWords(strdup(node->xmln_content), ':', comp, 5);
- if (strcasecmp(comp[1], ssvc->ssvc_domain) == 0 && strcasecmp(comp[3], ssvc->ssvc_type) == 0) {
- UPNP_SERVICE_INFO *usvc = calloc(1, sizeof(*usvc));
- if (usvc) {
- node = GetXmlNode(snode, "SCPDURL");
- if (node && node->xmln_content) {
- usvc->usvc_url_scpd = strdup(node->xmln_content);
- }
- node = GetXmlNode(snode, "controlURL");
- if (node && node->xmln_content) {
- usvc->usvc_url_ctrl = strdup(node->xmln_content);
- }
- node = GetXmlNode(snode, "eventSubURL");
- if (node && node->xmln_content) {
- usvc->usvc_url_event = strdup(node->xmln_content);
- }
- ssvc->ssvc_info = usvc;
- rc = 0;
- }
- }
- }
- }
- return rc;
- }
- static int ParseServiceInfo(const UXML_NODE *root, SSDP_SERVICE *ssvc)
- {
- int rc = -1;
- const UXML_NODE *node;
- const UXML_NODE *actlist = NULL;
- const UXML_NODE *varlist = NULL;
- root = GetXmlNode(root, "scpd");
- if (root) {
- actlist = GetXmlNode(root->xmln_child, "actionList");
- varlist = GetXmlNode(root->xmln_child, "serviceStateTable");
- }
- if (root == NULL || actlist == NULL || varlist == NULL) {
- return -1;
- }
- /*
- * Read state variables.
- */
- for (varlist = varlist->xmln_child; varlist && varlist->xmln_child; varlist = varlist->xmln_next) {
- UPNP_VARIABLE *var = calloc(1, sizeof(*var));
- if (var) {
- for (node = varlist->xmln_child; node; node = node->xmln_next) {
- if (node->xmln_content) {
- if (strcasecmp(node->xmln_name, "name") == 0) {
- var->ustv_name = strdup(node->xmln_content);
- }
- else if (strcasecmp(node->xmln_name, "dataType") == 0) {
- var->ustv_type = UpnpVarTypeIndex(node->xmln_content);
- }
- else if (strcasecmp(node->xmln_name, "defaultValue") == 0) {
- var->ustv_default = strdup(node->xmln_content);
- }
- else if (strcasecmp(node->xmln_name, "allowedValueList") == 0) {
- }
- }
- }
- if (var->ustv_name && var->ustv_type) {
- UPNP_SERVICE_INFO *usvc = ssvc->ssvc_info;
- var->ustv_next = usvc->usvc_stv;
- usvc->usvc_stv = var;
- } else {
- free(var->ustv_name);
- free(var->ustv_default);
- free(var);
- }
- }
- }
- /*
- * Read actions.
- */
- for (actlist = actlist->xmln_child; actlist && actlist->xmln_child; actlist = actlist->xmln_next) {
- SOAP_PROCEDURE *proc;
- const UXML_NODE *actnode = actlist->xmln_child;
- rc = -1;
- proc = calloc(1, sizeof(*proc));
- if (proc) {
- node = GetXmlNode(actnode, "name");
- if (node && node->xmln_content) {
- const UXML_NODE *argnode = GetXmlNode(actnode, "argumentList");
- proc->proc_name = strdup(node->xmln_content);
- if (argnode) {
- for (argnode = argnode->xmln_child; argnode && argnode->xmln_child; argnode = argnode->xmln_next) {
- SOAP_ARG *arg = calloc(1, sizeof(*arg));
- if (arg) {
- SOAP_ARG **argtyp = NULL;
- for (node = argnode->xmln_child; node; node = node->xmln_next) {
- if (node->xmln_content) {
- if (strcasecmp(node->xmln_name, "name") == 0) {
- arg->arg_name = strdup(node->xmln_content);
- }
- else if (strcasecmp(node->xmln_name, "direction") == 0) {
- if (*node->xmln_content == 'i') {
- argtyp = &proc->proc_argi;
- }
- else if (*node->xmln_content == 'o') {
- argtyp = &proc->proc_argo;
- }
- }
- else if (strcasecmp(node->xmln_name, "relatedStateVariable") == 0) {
- UPNP_VARIABLE *state;
- for (state = ((UPNP_SERVICE_INFO *)ssvc->ssvc_info)->usvc_stv; state; state = state->ustv_next) {
- if (strcasecmp(node->xmln_content, state->ustv_name) == 0) {
- arg->arg_info = state;
- break;
- }
- }
- }
- }
- }
- if (arg->arg_name && argtyp && arg->arg_info) {
- arg->arg_next = *argtyp;
- *argtyp = arg;
- rc = 0;
- } else {
- free(arg->arg_name);
- free(arg);
- }
- }
- }
- }
- }
- if (rc == 0) {
- UPNP_SERVICE_INFO *usvc = ssvc->ssvc_info;
- proc->proc_next = usvc->usvc_proc;
- usvc->usvc_proc = proc;
- }
- }
- }
- return rc;
- }
- static int SendHttpRequest(FILE *stream, const URI_SCHEME *schm)
- {
- fputs("GET ", stream);
- fprintf(stream, "/%s HTTP/1.1\r\n", schm->schm_path);
- fprintf(stream, "Host: %s\r\n", schm->schm_host);
- fputs("Connection: close\r\n\r\n", stream);
- fflush(stream);
- return 0;
- }
- /*!
- * \brief Request XML tree from a specified location.
- *
- * \param url HTTP URL.
- *
- * \return Pointer to the root node or NULL if an error occurred.
- */
- static UXML_NODE *RequestXmlTree(const char *url, uint32_t tmo)
- {
- UXML_NODE *tree = NULL;
- if (strncasecmp(url, "http://", 7) == 0) {
- URI_SCHEME *schm;
- url += 7;
- schm = UriSchemeSplit(url);
- if (schm) {
- TCPSOCKET *sock = NutTcpCreateSocket();
- if (sock) {
- FILE *stream = TcpHostConnectStream(sock, schm->schm_host, schm->schm_portnum, tmo);
- if (stream) {
- SendHttpRequest(stream, schm);
- tree = UxmlParseStream(stream, NULL, NULL);
- fclose(stream);
- }
- NutTcpCloseSocket(sock);
- }
- UriSchemeRelease(schm);
- }
- }
- return tree;
- }
- static int AddServiceDeviceInfo(SSDP_SERVICE *ssvc)
- {
- int rc = -1;
- UXML_NODE *tree;
- tree = RequestXmlTree(ssvc->ssvc_dev->sdev_url_desc, 2000);
- if (tree) {
- rc = ParseServiceDeviceInfo(tree, ssvc);
- UxmlTreeDestroy(tree);
- }
- return rc;
- }
- static int AddServiceInfo(SSDP_SERVICE *ssvc)
- {
- int rc = -1;
- UPNP_SERVICE_INFO *usvc = ssvc->ssvc_info;
- char *location;
- location = malloc(strlen(ssvc->ssvc_dev->sdev_url_desc) + strlen(usvc->usvc_url_scpd));
- if (location) {
- char *cp;
- strcpy(location, ssvc->ssvc_dev->sdev_url_desc);
- cp = strrchr(location, '/');
- if (cp) {
- UXML_NODE *tree;
- if (*usvc->usvc_url_scpd != '/') {
- cp++;
- }
- strcpy(cp, usvc->usvc_url_scpd);
- tree = RequestXmlTree(location, 2000);
- if (tree) {
- rc = ParseServiceInfo(tree, ssvc);
- UxmlTreeDestroy(tree);
- }
- }
- }
- return rc;
- }
- /*!
- * \brief Remove UPnP service information from
- */
- static void RemoveServiceInfo(SSDP_SERVICE *ssvc)
- {
- UPNP_SERVICE_INFO *usvc = ssvc->ssvc_info;
- if (usvc) {
- SOAP_PROCEDURE *proc;
- SOAP_PROCEDURE *proc_next = NULL;
- UPNP_VARIABLE *uvar;
- UPNP_VARIABLE *uvar_next = NULL;
- for (proc = usvc->usvc_proc; proc; proc = proc_next) {
- SOAP_ARG *arg;
- SOAP_ARG *arg_next = NULL;
- proc_next = proc->proc_next;
- for (arg = proc->proc_argi; arg; arg = arg_next) {
- arg_next = arg->arg_next;
- free(arg->arg_name);
- free(arg->arg_val);
- free(arg);
- }
- for (arg = proc->proc_argo; arg; arg = arg_next) {
- arg_next = arg->arg_next;
- free(arg->arg_name);
- free(arg->arg_val);
- free(arg);
- }
- free(proc->proc_name);
- free(proc);
- }
- for (uvar = usvc->usvc_stv; uvar; uvar = uvar_next) {
- uvar_next = uvar->ustv_next;
- free(uvar->ustv_name);
- free(uvar->ustv_default);
- free(uvar);
- }
- free(usvc->usvc_url_scpd);
- free(usvc->usvc_url_ctrl);
- free(usvc->usvc_url_event);
- free(usvc);
- }
- }
- /*!
- * \brief Service observer callback.
- *
- * This function is called by the SSDP receiver thread after a new service
- * has been added to the cache or before a service will be removed from the
- * cache.
- *
- * \param ssvc Specifies the service.
- * \param removal 0 if a new service has been added or 1 if a service will
- * be removed.
- *
- * \return 0 on success or -1 on failure.
- */
- static int ServiceObserver(SSDP_SERVICE *ssvc, int_fast8_t removal)
- {
- int rc = 0;
- if (removal) {
- CallObservers(ssvc, 1);
- RemoveServiceInfo(ssvc);
- } else {
- if (ssvc->ssvc_dev->sdev_info == NULL) {
- rc = AddServiceDeviceInfo(ssvc);
- }
- if (rc == 0) {
- rc = AddServiceInfo(ssvc);
- }
- if (rc == 0) {
- rc = CallObservers(ssvc, 0);
- }
- }
- return rc;
- }
- int UpnpRegisterServiceObserver(SSDP_OBSERVER_FUNCTION cb, const char *domain, const char *type, int_fast8_t mxwait)
- {
- int rc = -1;
- UPNP_OBSERVER *uobs;
- uobs = malloc(sizeof(*uobs));
- if (uobs) {
- uobs->uobs_domain = strdup(domain);
- uobs->uobs_type = strdup(type);
- if (uobs->uobs_domain && uobs->uobs_type) {
- uobs->uobs_cb = cb;
- uobs->uobs_next = upnp_observer_root;
- upnp_observer_root = uobs;
- rc = SsdpRegisterServiceObserver(ServiceObserver, domain, type, mxwait);
- }
- if (rc) {
- free(uobs->uobs_domain);
- free(uobs->uobs_type);
- free(uobs);
- }
- }
- return rc;
- }
- SOAP_PROCEDURE *UpnpServiceProcByName(const SSDP_SERVICE *ssvc, const char *name)
- {
- return SoapProcByName(((UPNP_SERVICE_INFO *) ssvc->ssvc_info)->usvc_proc, name);
- }
- int UpnpServiceProcCall(SSDP_SERVICE *ssvc, SOAP_PROCEDURE *proc, uint32_t tmo)
- {
- int rc = -1;
- char *urn;
- char *url;
- char *uri;
- UPNP_SERVICE_INFO *usvc = ssvc->ssvc_info;
- urn = malloc(strlen(ssvc->ssvc_domain) + strlen(ssvc->ssvc_type) + 12);
- url = malloc(strlen(ssvc->ssvc_dev->sdev_url_desc) + strlen(usvc->usvc_url_ctrl));
- if (urn && url) {
- sprintf(urn, "%s:service:%s:1", ssvc->ssvc_domain, ssvc->ssvc_type);
- strcpy(url, ssvc->ssvc_dev->sdev_url_desc);
- uri = strrchr(url, '/');
- if (uri) {
- if (*usvc->usvc_url_ctrl != '/') {
- uri++;
- }
- strcpy(uri, usvc->usvc_url_ctrl);
- rc = SoapProcCallResource(proc, url, uri, urn, tmo);
- }
- }
- free(urn);
- free(url);
- return rc;
- }
|