ssdp_poll: reimplement for taxonomy as ssdptax.
SSDP can provide much better information about devices
than we currently use it for. Now that catawampus no
longer uses ssdp_poll, turn it into a source of taxonomy
information for client devices.
Rename it as 'ssdptax' to reflect its new purpose.
Also:
+ add an opensource copyright header, we
neglected to do that when this file was first
released (in 2014).
+ be more cautious about the strings returned
from SSDP. Only allow alphanumerics & space,
turn everything else into an underscore.
Change-Id: I9cfa9a00b7cc4e2ade4f54ad8ca31f799a5d22ec
diff --git a/cmds/Makefile b/cmds/Makefile
index 4487b75..ab3cb0b 100644
--- a/cmds/Makefile
+++ b/cmds/Makefile
@@ -58,7 +58,8 @@
ARCH_TARGETS=\
ifeq ($(BUILD_SSDP),y)
-TARGETS += ssdp_poll
+TARGETS += ssdptax
+HOST_TEST_TARGETS += host-test-ssdptax.sh
endif
ifeq ($(BUILD_DNSSD),y)
@@ -191,7 +192,10 @@
http_bouncer: LIBS+=-lcurl $(RT)
http_bouncer: http_bouncer.o
host-utils_test: host-utils_test.o host-utils.o
-ssdp_poll: ssdp_poll.o
+ssdptax: ssdptax.o l2utils.o
+ssdptax: LIBS += -lcurl -lnl-3 -lstdc++ -lm
+host-ssdptax: host-ssdptax.o host-l2utils.o
+host-ssdptax: LIBS += $(HOST_LIBS) -lcurl -lnl-3 -lstdc++ -lm
statpitcher.o: device_stats.pb.o
statpitcher: LIBS+=-L$(DESTDIR)$(PREFIX)/usr/lib -lprotobuf-lite -lpthread -lstdc++
statpitcher: device_stats.pb.o statpitcher.o
diff --git a/cmds/host-test-ssdptax.sh b/cmds/host-test-ssdptax.sh
new file mode 100755
index 0000000..d6796dd
--- /dev/null
+++ b/cmds/host-test-ssdptax.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#
+# Copyright 2016 Google Inc. All Rights Reserved.
+
+. ./wvtest/wvtest.sh
+
+SSDP=./host-ssdptax
+
+WVSTART "ssdptax test"
+WVPASSEQ "$($SSDP -t)" "ssdp 00:01:02:03:04:05 Test Device"
diff --git a/cmds/l2utils.cc b/cmds/l2utils.cc
new file mode 100644
index 0000000..d0549cb
--- /dev/null
+++ b/cmds/l2utils.cc
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2016 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <arpa/inet.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <netlink/msg.h>
+#include <netlink/netlink.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "l2utils.h"
+
+void get_l2_map(L2Map *l2map)
+{
+ int s;
+ struct {
+ struct nlmsghdr hdr;
+ struct ndmsg msg;
+ } nlreq;
+ struct sockaddr_nl addr;
+ struct msghdr msg;
+ static uint8_t l2buf[256 * 1024];
+ struct iovec iov = {.iov_base = l2buf, .iov_len = sizeof(l2buf)};
+ struct nlmsghdr *nh;
+ struct ndmsg *ndm;
+ struct nlattr *tb[NDA_MAX+1];
+ int len;
+ int af[] = {AF_INET, AF_INET6};
+ unsigned int i;
+
+ for (i = 0; i < (sizeof(af) / sizeof(af[0])); ++i) {
+ if ((s = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
+ perror("socket AF_NETLINK");
+ exit(1);
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.nl_family = AF_NETLINK;
+ addr.nl_pid = getpid();
+ addr.nl_groups = 0;
+
+ if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+ perror("bind AF_NETLINK");
+ exit(1);
+ }
+
+ memset(&nlreq, 0, sizeof(nlreq));
+ nlreq.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(nlreq.msg));
+ nlreq.hdr.nlmsg_type = RTM_GETNEIGH;
+ nlreq.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ nlreq.msg.ndm_family = af[i];
+
+ if (send(s, &nlreq, nlreq.hdr.nlmsg_len, 0) < 0) {
+ perror("send AF_NETLINK");
+ exit(1);
+ }
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_controllen = 0;
+ msg.msg_control = NULL;
+ msg.msg_flags = 0;
+
+ if ((len = recvmsg(s, &msg, 0)) <= 0) {
+ perror("recvmsg AL_NETLINK");
+ exit(1);
+ }
+
+ if (msg.msg_flags & MSG_TRUNC) {
+ fprintf(stderr, "recvmsg AL_NETLINK MSG_TRUNC\n");
+ exit(1);
+ }
+
+ memset(tb, 0, sizeof(tb));
+ nh = (struct nlmsghdr *)l2buf;
+ while (nlmsg_ok(nh, len)) {
+ ndm = (struct ndmsg *)nlmsg_data(nh);
+ if (nlmsg_parse(nh, sizeof(*ndm), tb, NDA_MAX, NULL)) {
+ fprintf(stderr, "nlmsg_parse failed\n");
+ exit(1);
+ }
+
+ if (tb[NDA_DST] && tb[NDA_LLADDR] &&
+ !(ndm->ndm_state & (NUD_INCOMPLETE | NUD_FAILED)) &&
+ (ndm->ndm_family == AF_INET || ndm->ndm_family == AF_INET6)) {
+ char mac[18];
+ char ipaddr[INET6_ADDRSTRLEN];
+ uint8_t *p;
+
+ p = (uint8_t *)nla_data(tb[NDA_LLADDR]);
+ snprintf(mac, sizeof(mac), "%02x:%02x:%02x:%02x:%02x:%02x",
+ p[0], p[1], p[2], p[3], p[4], p[5]);
+
+ p = (uint8_t *)nla_data(tb[NDA_DST]);
+ inet_ntop(ndm->ndm_family, p, ipaddr, sizeof(ipaddr));
+
+ (*l2map)[std::string(ipaddr)] = std::string(mac);
+ }
+
+ nh = nlmsg_next(nh, &len);
+ }
+
+ close(s);
+ }
+}
diff --git a/cmds/l2utils.h b/cmds/l2utils.h
new file mode 100644
index 0000000..6b2385a
--- /dev/null
+++ b/cmds/l2utils.h
@@ -0,0 +1,10 @@
+#include <string>
+#include <tr1/unordered_map>
+
+#ifndef L2UTILS_H
+#define L2UTILS_H
+
+typedef std::tr1::unordered_map<std::string, std::string> L2Map;
+extern void get_l2_map(L2Map *l2map);
+
+#endif // L2UTILS_H
diff --git a/cmds/ssdp_poll.c b/cmds/ssdp_poll.c
deleted file mode 100644
index ef24b94..0000000
--- a/cmds/ssdp_poll.c
+++ /dev/null
@@ -1,127 +0,0 @@
-/* ssdp_poll
- *
- * A client implementing the API described in
- * http://miniupnp.free.fr/minissdpd.html
- *
- * Requests the list of all known SSDP nodes and the
- * services they export, and prints it to stdout in
- * a format which is simple to parse.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/un.h>
-#include <unistd.h>
-
-/* Encode length by using 7bit per Byte :
- * Most significant bit of each byte specifies that the
- * following byte is part of the code */
-#define DECODELENGTH(n, p) { \
- n = 0; \
- do { n = (n << 7) | (*p & 0x7f); } \
- while (*(p++)&0x80); \
-}
-
-#define CODELENGTH(n, p) { \
- if(n>=0x10000000) *(p++) = (n >> 28) | 0x80; \
- if(n>=0x200000) *(p++) = (n >> 21) | 0x80; \
- if(n>=0x4000) *(p++) = (n >> 14) | 0x80; \
- if(n>=0x80) *(p++) = (n >> 7) | 0x80; \
- *(p++) = n & 0x7f; \
-}
-
-#define SOCK_PATH "/var/run/minissdpd.sock"
-
-int connect_to_ssdpd()
-{
- struct sockaddr_un addr;
- int s;
-
- s = socket(AF_UNIX, SOCK_STREAM, 0);
- if(s < 0) {
- perror("socket AF_UNIX failed");
- exit(1);
- }
- memset(&addr, 0, sizeof(addr));
- addr.sun_family = AF_UNIX;
- strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path));
- if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
- perror("connect to minisspd failed");
- exit(1);
- }
-
- return s;
-}
-
-int main()
-{
- unsigned char *buffer;
- unsigned char *p;
- const char *device = "ssdp:all";
- int device_len = (int)strlen(device);
- int socket = connect_to_ssdpd();
- size_t siz = 65536;
- ssize_t len;
- fd_set readfds;
- struct timeval tv;
-
- if ((buffer = (unsigned char *)malloc(siz)) == NULL) {
- fprintf(stderr, "malloc(%zu) failed\n", siz);
- exit(1);
- }
- memset(buffer, 0, siz);
-
- buffer[0] = 5; /* request type : request all device server IDs */
- p = buffer + 1;
- CODELENGTH(device_len, p);
- memcpy(p, device, device_len);
- p += device_len;
- if (write(socket, buffer, p - buffer) < 0) {
- perror("write to minissdpd failed");
- exit(1);
- }
-
- FD_ZERO(&readfds);
- FD_SET(socket, &readfds);
- memset(&tv, 0, sizeof(tv));
- tv.tv_sec = 2;
-
- if (select(socket + 1, &readfds, NULL, NULL, &tv) < 1) {
- fprintf(stderr, "select failed\n");
- exit(1);
- }
-
- if ((len = read(socket, buffer, siz)) < 0) {
- perror("read from minissdpd failed");
- exit(1);
- }
-
- int num = buffer[0];
- p = buffer + 1;
- while (num-- > 0) {
- size_t copylen, slen;
- char url[256];
- char server[512];
-
- DECODELENGTH(slen, p);
- copylen = (slen >= sizeof(url)) ? sizeof(url) - 1 : slen;
- memcpy(url, p, copylen);
- url[copylen] = '\0';
- p += slen;
-
- DECODELENGTH(slen, p);
- copylen = (slen >= sizeof(server)) ? sizeof(server) - 1 : slen;
- memcpy(server, p, copylen);
- server[copylen] = '\0';
- p += slen;
-
- printf("%s|%s\n", url, server);
- }
-
- free(buffer);
- exit(0);
-}
diff --git a/cmds/ssdptax.cc b/cmds/ssdptax.cc
new file mode 100644
index 0000000..d218c0a
--- /dev/null
+++ b/cmds/ssdptax.cc
@@ -0,0 +1,593 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * ssdptax (SSDP Taxonomy)
+ *
+ * A client implementing the API described in
+ * http://miniupnp.free.fr/minissdpd.html
+ *
+ * Requests the list of all known SSDP nodes, requests
+ * device info from them, and tries to figure out what
+ * they are.
+ */
+
+#include <arpa/inet.h>
+#include <asm/types.h>
+#include <ctype.h>
+#include <curl/curl.h>
+#include <getopt.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "l2utils.h"
+
+/* Encode length by using 7bit per Byte :
+ * Most significant bit of each byte specifies that the
+ * following byte is part of the code */
+#define DECODELENGTH(n, p) { \
+ n = 0; \
+ do { n = (n << 7) | (*p & 0x7f); } \
+ while (*(p++)&0x80); \
+}
+
+#define CODELENGTH(n, p) { \
+ if(n>=0x10000000) *(p++) = (n >> 28) | 0x80; \
+ if(n>=0x200000) *(p++) = (n >> 21) | 0x80; \
+ if(n>=0x4000) *(p++) = (n >> 14) | 0x80; \
+ if(n>=0x80) *(p++) = (n >> 7) | 0x80; \
+ *(p++) = n & 0x7f; \
+}
+
+#define SOCK_PATH "/var/run/minissdpd.sock"
+
+
+typedef struct {
+ char server[512];
+ char url[512];
+ char friendlyName[64];
+ int failed;
+} ssdp_info_t;
+
+
+/* Unit test support */
+char *get_test_ssdp_data();
+void get_test_l2_map(L2Map *l2map);
+
+static void memcpy_printable(char *dst, const char *src, size_t n)
+{
+ size_t i;
+ for (i = 0; i < n; ++i) {
+ unsigned char s = src[i];
+ if (isspace(s)) {
+ dst[i] = ' '; // deliberately convert newline to space
+ } else if (isprint(s)) {
+ dst[i] = s;
+ } else {
+ dst[i] = '_';
+ }
+ }
+}
+
+
+/*
+ * Send a request to minissdpd. Returns a pointer to a buffer
+ * allocated using malloc(). Caller must free() the buffer when done.
+ */
+char *request_from_ssdpd(int reqtype, const char *device)
+{
+ int s = socket(AF_UNIX, SOCK_STREAM, 0);
+ struct sockaddr_un addr;
+ size_t siz = 256 * 1024;
+ char *buffer, *p;
+ ssize_t len;
+ int device_len = (int)strlen(device);
+ fd_set readfds;
+ struct timeval tv;
+
+ if (s < 0) {
+ perror("socket AF_UNIX failed");
+ exit(1);
+ }
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path));
+ if(connect(s, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
+ perror("connect to minisspd failed");
+ exit(1);
+ }
+
+ if ((buffer = (char *)malloc(siz)) == NULL) {
+ fprintf(stderr, "malloc(%zu) failed\n", siz);
+ exit(1);
+ }
+ memset(buffer, 0, siz);
+
+ buffer[0] = reqtype;
+ p = buffer + 1;
+ CODELENGTH(device_len, p);
+ memcpy(p, device, device_len);
+ p += device_len;
+ if (write(s, buffer, p - buffer) < 0) {
+ perror("write to minissdpd failed");
+ exit(1);
+ }
+
+ FD_ZERO(&readfds);
+ FD_SET(s, &readfds);
+ memset(&tv, 0, sizeof(tv));
+ tv.tv_sec = 2;
+
+ if (select(s + 1, &readfds, NULL, NULL, &tv) < 1) {
+ fprintf(stderr, "select failed\n");
+ exit(1);
+ }
+
+ if ((len = read(s, buffer, siz)) < 0) {
+ perror("read from minissdpd failed");
+ exit(1);
+ }
+
+ close(s);
+ return(buffer);
+}
+
+
+static void print_responses(const std::string &ipaddr,
+ const ssdp_info_t *info, L2Map *l2map)
+{
+ const char *mac;
+
+ if (info->failed) {
+ /*
+ * We could not fetch information from this client. That often means that
+ * the device was powered off recently. minissdpd still remembers that
+ * it is there, but we cannot contact it.
+ *
+ * Don't print anything for these, as we'd end up calling them "Unknown"
+ * and that is misleading. We only report information about devices which
+ * are active right now.
+ */
+ return;
+ }
+
+ L2Map::const_iterator ii = l2map->find(ipaddr);
+ if (ii != l2map->end()) {
+ mac = ii->second.c_str();
+ } else {
+ mac = "00:00:00:00:00:00";
+ }
+
+ /* taxonomy information to stdout */
+ if (strlen(info->friendlyName)) {
+ printf("ssdp %s %s\n", mac, info->friendlyName);
+ } else {
+ printf("ssdp %s Unknown;%s\n", mac, info->server);
+ }
+}
+
+
+const char *parse_minissdpd_response(const char *response,
+ char *key, size_t key_len,
+ char *url, size_t url_len,
+ char *value, size_t value_len)
+{
+ const char *p = response;
+ size_t copylen, slen;
+ int prefix = 0;
+ struct in6_addr in6;
+ struct in_addr in;
+ char ip[INET6_ADDRSTRLEN];
+
+ key[0] = url[0] = value[0] = '\0';
+
+ DECODELENGTH(slen, p);
+ copylen = (slen >= url_len) ? url_len - 1 : slen;
+ memcpy_printable(url, p, copylen);
+ url[copylen] = '\0';
+ p += slen;
+
+ DECODELENGTH(slen, p);
+ copylen = (slen >= value_len) ? value_len - 1 : slen;
+ memcpy_printable(value, p, copylen);
+ value[copylen] = '\0';
+ p += slen;
+
+ if (strncasecmp(url, "https://[", 9) == 0) prefix = 9;
+ if (strncasecmp(url, "http://[", 8) == 0) prefix = 8;
+ if (strncasecmp(url, "https://", 8) == 0) prefix = 8;
+ if (strncasecmp(url, "http://", 7) == 0) prefix = 7;
+ strncpy(ip, url + prefix, sizeof(ip));
+ strtok(ip, ":/@");
+
+ if (inet_pton(AF_INET6, ip, &in6)) {
+ inet_ntop(AF_INET6, &in6, key, key_len);
+ }
+ if (inet_pton(AF_INET, ip, &in)) {
+ inet_ntop(AF_INET, &in, key, key_len);
+ }
+
+ return p;
+}
+
+
+ssdp_info_t *dupinfo(ssdp_info_t *info)
+{
+ ssdp_info_t *i = (ssdp_info_t *)malloc(sizeof(*info));
+
+ if (i == NULL) {
+ perror("malloc");
+ exit(1);
+ }
+
+ memcpy(i, info, sizeof(*i));
+ return i;
+}
+
+
+const char *findXmlField(const char *ptr, const char *label, ssize_t *len)
+{
+ char openlabel[64], closelabel[64];
+ const char *start, *end;
+
+ snprintf(openlabel, sizeof(openlabel), "<%s>", label);
+ snprintf(closelabel, sizeof(closelabel), "</%s>", label);
+
+ start = strcasestr(ptr, openlabel) + strlen(openlabel);
+ end = strcasestr(ptr, closelabel);
+
+ if ((end - start) > 0) {
+ *len = end - start;
+ return start;
+ }
+
+ return NULL;
+}
+
+
+/*
+ * libcurl calls this function back with the result of the HTTP GET.
+ *
+ * Expected value is an XML blob of
+ * http://upnp.org/specs/basic/UPnP-basic-Basic-v1-Device.pdf
+ *
+ * Like this (a Samsung TV):
+ * <?xml version="1.0"?>
+ * <root xmlns='urn:schemas-upnp-org:device-1-0' ...
+ * <device>
+ * <deviceType>urn:dial-multiscreen-org:device:dialreceiver:1</deviceType>
+ * <friendlyName>[TV]Samsung LED60</friendlyName>
+ * <manufacturer>Samsung Electronics</manufacturer>
+ * <manufacturerURL>http://www.samsung.com/sec</manufacturerURL>
+ * <modelDescription>Samsung TV NS</modelDescription>
+ * <modelName>UN60F6300</modelName>
+ * <modelNumber>1.0</modelNumber>
+ * <modelURL>http://www.samsung.com/sec</modelURL>
+ * ... etc, etc ...
+ */
+size_t callback(const char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+ ssdp_info_t *info = (ssdp_info_t *)userdata;
+ const char *p;
+ ssize_t len;
+ ssize_t max = (ssize_t)sizeof(info->friendlyName);
+
+ if ((p = findXmlField(ptr, "friendlyName", &len)) == NULL) {
+ p = findXmlField(ptr, "modelDescription", &len);
+ }
+
+ if (p && (len > 0) && (len < max)) {
+ /* the len < max check ensures there will be a NUL byte at the end */
+ memcpy(info->friendlyName, p, len);
+ }
+
+ return size * nmemb;
+}
+
+
+/*
+ * SSDP returned an endpoint URL, use curl to GET its contents.
+ */
+void fetch_device_info(const char *url, ssdp_info_t *ssdp)
+{
+ CURL *curl = curl_easy_init();
+ int rc;
+
+ if (!curl) {
+ fprintf(stderr, "curl_easy_init failed\n");
+ return;
+ }
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ curl_easy_setopt(curl, CURLOPT_PATH_AS_IS, 1L);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &callback);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, ssdp);
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, "ssdptax/1.0");
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, 1L);
+ if ((rc = curl_easy_perform(curl)) != CURLE_OK) {
+ ssdp->failed = 1;
+ }
+ curl_easy_cleanup(curl);
+}
+
+
+void usage(char *progname) {
+ printf("usage: %s [-t]\n", progname);
+ printf("\t-t\ttest mode, run a test with fake SSDP data.\n");
+ exit(1);
+}
+
+
+int main(int argc, char **argv)
+{
+ char *buffer;
+ const char *p;
+ typedef std::tr1::unordered_map<std::string, ssdp_info_t*> ResponsesMap;
+ ResponsesMap responses;
+ L2Map l2map;
+ int c, num;
+ int testmode = 0;
+
+ setlinebuf(stdout);
+ if (curl_global_init(CURL_GLOBAL_NOTHING)) {
+ fprintf(stderr, "curl_global_init failed\n");
+ exit(1);
+ }
+
+ while ((c = getopt(argc, argv, "t")) != -1) {
+ switch(c) {
+ case 't': testmode = 1; break;
+ default: usage(argv[0]); break;
+ }
+ }
+
+ if (!testmode) {
+ /* 5 == request all device server IDs */
+ buffer = request_from_ssdpd(5, "ssdp:all");
+ } else {
+ buffer = get_test_ssdp_data();
+ }
+
+ num = buffer[0];
+ p = buffer + 1;
+ while ((num-- > 0) && (p < (buffer + sizeof(buffer)))) {
+ char key[INET6_ADDRSTRLEN];
+ ssdp_info_t info;
+
+ memset(&info, 0, sizeof(info));
+ p = parse_minissdpd_response(p, key, sizeof(key),
+ info.url, sizeof(info.url), info.server, sizeof(info.server));
+ if (strlen(key) && responses.find(std::string(key)) == responses.end()) {
+ if (!testmode) {
+ fetch_device_info(info.url, &info);
+ } else {
+ snprintf(info.friendlyName, sizeof(info.friendlyName), "Test Device");
+ }
+ responses.insert(std::make_pair<std::string,
+ ssdp_info_t*>(std::string(key), dupinfo(&info)));
+ }
+ }
+ free(buffer);
+
+ if (!testmode) {
+ get_l2_map(&l2map);
+ } else {
+ get_test_l2_map(&l2map);
+ }
+
+ for(ResponsesMap::const_iterator ii = responses.begin();
+ ii != responses.end(); ++ii) {
+ print_responses(ii->first, ii->second, &l2map);
+ }
+
+ curl_global_cleanup();
+ exit(0);
+}
+
+
+/*
+ * data for a unit test, response from a single SSDP
+ * client.
+ */
+uint8_t test_ssdp_data[] = {
+ 0x12, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38,
+ 0x2e, 0x34, 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a,
+ 0x37, 0x36, 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70,
+ 0x5f, 0x32, 0x39, 0x5f, 0x23, 0x53, 0x48, 0x50,
+ 0x2c, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x2f, 0x31,
+ 0x2e, 0x30, 0x2c, 0x20, 0x53, 0x61, 0x6d, 0x73,
+ 0x75, 0x6e, 0x67, 0x20, 0x55, 0x50, 0x6e, 0x50,
+ 0x20, 0x53, 0x44, 0x4b, 0x2f, 0x31, 0x2e, 0x30,
+ 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e,
+ 0x34, 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a, 0x37,
+ 0x36, 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70, 0x5f,
+ 0x32, 0x39, 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c,
+ 0x20, 0x55, 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e,
+ 0x30, 0x2c, 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75,
+ 0x6e, 0x67, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20,
+ 0x53, 0x44, 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x22,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31,
+ 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34,
+ 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36,
+ 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x32,
+ 0x39, 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c, 0x20,
+ 0x55, 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e, 0x30,
+ 0x2c, 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75, 0x6e,
+ 0x67, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20, 0x53,
+ 0x44, 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x22, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39,
+ 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34, 0x32,
+ 0x2e, 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36, 0x37,
+ 0x36, 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x32, 0x39,
+ 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55,
+ 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c,
+ 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67,
+ 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44,
+ 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x22, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+ 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34, 0x32, 0x2e,
+ 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36, 0x37, 0x36,
+ 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x31, 0x39, 0x5f,
+ 0x23, 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55, 0x50,
+ 0x6e, 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c, 0x20,
+ 0x53, 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67, 0x20,
+ 0x55, 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44, 0x4b,
+ 0x2f, 0x31, 0x2e, 0x30, 0x22, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e,
+ 0x31, 0x36, 0x38, 0x2e, 0x34, 0x32, 0x2e, 0x31,
+ 0x32, 0x35, 0x3a, 0x37, 0x36, 0x37, 0x36, 0x2f,
+ 0x73, 0x6d, 0x70, 0x5f, 0x31, 0x39, 0x5f, 0x23,
+ 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55, 0x50, 0x6e,
+ 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x53,
+ 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67, 0x20, 0x55,
+ 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44, 0x4b, 0x2f,
+ 0x31, 0x2e, 0x30, 0x22, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x31,
+ 0x36, 0x38, 0x2e, 0x34, 0x32, 0x2e, 0x31, 0x32,
+ 0x35, 0x3a, 0x37, 0x36, 0x37, 0x36, 0x2f, 0x73,
+ 0x6d, 0x70, 0x5f, 0x31, 0x39, 0x5f, 0x23, 0x53,
+ 0x48, 0x50, 0x2c, 0x20, 0x55, 0x50, 0x6e, 0x50,
+ 0x2f, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x53, 0x61,
+ 0x6d, 0x73, 0x75, 0x6e, 0x67, 0x20, 0x55, 0x50,
+ 0x6e, 0x50, 0x20, 0x53, 0x44, 0x4b, 0x2f, 0x31,
+ 0x2e, 0x30, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a,
+ 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36,
+ 0x38, 0x2e, 0x34, 0x32, 0x2e, 0x31, 0x32, 0x35,
+ 0x3a, 0x37, 0x36, 0x37, 0x36, 0x2f, 0x73, 0x6d,
+ 0x70, 0x5f, 0x31, 0x39, 0x5f, 0x23, 0x53, 0x48,
+ 0x50, 0x2c, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x2f,
+ 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x53, 0x61, 0x6d,
+ 0x73, 0x75, 0x6e, 0x67, 0x20, 0x55, 0x50, 0x6e,
+ 0x50, 0x20, 0x53, 0x44, 0x4b, 0x2f, 0x31, 0x2e,
+ 0x30, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38,
+ 0x2e, 0x34, 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a,
+ 0x37, 0x36, 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70,
+ 0x5f, 0x31, 0x39, 0x5f, 0x23, 0x53, 0x48, 0x50,
+ 0x2c, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x2f, 0x31,
+ 0x2e, 0x30, 0x2c, 0x20, 0x53, 0x61, 0x6d, 0x73,
+ 0x75, 0x6e, 0x67, 0x20, 0x55, 0x50, 0x6e, 0x50,
+ 0x20, 0x53, 0x44, 0x4b, 0x2f, 0x31, 0x2e, 0x30,
+ 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f,
+ 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e,
+ 0x34, 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a, 0x37,
+ 0x36, 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70, 0x5f,
+ 0x31, 0x39, 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c,
+ 0x20, 0x55, 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e,
+ 0x30, 0x2c, 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75,
+ 0x6e, 0x67, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20,
+ 0x53, 0x44, 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x22,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31,
+ 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34,
+ 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36,
+ 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x31,
+ 0x31, 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c, 0x20,
+ 0x55, 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e, 0x30,
+ 0x2c, 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75, 0x6e,
+ 0x67, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20, 0x53,
+ 0x44, 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x22, 0x68,
+ 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39,
+ 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34, 0x32,
+ 0x2e, 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36, 0x37,
+ 0x36, 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x31, 0x31,
+ 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55,
+ 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c,
+ 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67,
+ 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44,
+ 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x22, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+ 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34, 0x32, 0x2e,
+ 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36, 0x37, 0x36,
+ 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x31, 0x31, 0x5f,
+ 0x23, 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55, 0x50,
+ 0x6e, 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c, 0x20,
+ 0x53, 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67, 0x20,
+ 0x55, 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44, 0x4b,
+ 0x2f, 0x31, 0x2e, 0x30, 0x22, 0x68, 0x74, 0x74,
+ 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e,
+ 0x31, 0x36, 0x38, 0x2e, 0x34, 0x32, 0x2e, 0x31,
+ 0x32, 0x35, 0x3a, 0x37, 0x36, 0x37, 0x36, 0x2f,
+ 0x73, 0x6d, 0x70, 0x5f, 0x31, 0x31, 0x5f, 0x23,
+ 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55, 0x50, 0x6e,
+ 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x53,
+ 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67, 0x20, 0x55,
+ 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44, 0x4b, 0x2f,
+ 0x31, 0x2e, 0x30, 0x21, 0x68, 0x74, 0x74, 0x70,
+ 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x31,
+ 0x36, 0x38, 0x2e, 0x34, 0x32, 0x2e, 0x31, 0x32,
+ 0x35, 0x3a, 0x37, 0x36, 0x37, 0x36, 0x2f, 0x73,
+ 0x6d, 0x70, 0x5f, 0x32, 0x5f, 0x23, 0x53, 0x48,
+ 0x50, 0x2c, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x2f,
+ 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x53, 0x61, 0x6d,
+ 0x73, 0x75, 0x6e, 0x67, 0x20, 0x55, 0x50, 0x6e,
+ 0x50, 0x20, 0x53, 0x44, 0x4b, 0x2f, 0x31, 0x2e,
+ 0x30, 0x21, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f,
+ 0x2f, 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38,
+ 0x2e, 0x34, 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a,
+ 0x37, 0x36, 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70,
+ 0x5f, 0x32, 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c,
+ 0x20, 0x55, 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e,
+ 0x30, 0x2c, 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75,
+ 0x6e, 0x67, 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20,
+ 0x53, 0x44, 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x21,
+ 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31,
+ 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34,
+ 0x32, 0x2e, 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36,
+ 0x37, 0x36, 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x32,
+ 0x5f, 0x23, 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55,
+ 0x50, 0x6e, 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c,
+ 0x20, 0x53, 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67,
+ 0x20, 0x55, 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44,
+ 0x4b, 0x2f, 0x31, 0x2e, 0x30, 0x21, 0x68, 0x74,
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x31, 0x39, 0x32,
+ 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x34, 0x32, 0x2e,
+ 0x31, 0x32, 0x35, 0x3a, 0x37, 0x36, 0x37, 0x36,
+ 0x2f, 0x73, 0x6d, 0x70, 0x5f, 0x32, 0x5f, 0x23,
+ 0x53, 0x48, 0x50, 0x2c, 0x20, 0x55, 0x50, 0x6e,
+ 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x2c, 0x20, 0x53,
+ 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67, 0x20, 0x55,
+ 0x50, 0x6e, 0x50, 0x20, 0x53, 0x44, 0x4b, 0x2f,
+ 0x31, 0x2e, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+
+char *get_test_ssdp_data()
+{
+ size_t len = sizeof(test_ssdp_data);
+ char *buffer = (char *)malloc(len);
+
+ if (buffer == NULL) {
+ perror("malloc failed");
+ exit(1);
+ }
+
+ memcpy(buffer, test_ssdp_data, len);
+ return buffer;
+}
+
+
+void get_test_l2_map(L2Map *l2map)
+{
+ (*l2map)[std::string("192.168.42.125")] = std::string("00:01:02:03:04:05");
+}