ssdptax: discover all URLs

ssdptax was only checking the first URL it found through
SSDP. However most devices provide more than one URL
for different functions, and sometimes the friendlyName
and model information is different for the different URLs.
Check all of them, and print all of the different information
we find.

GSAFSJ1412D0117# /tmp/ssdptax
ssdp 40:16:7e:a3:0c:f0 ASUS WPS Router;ASUSTeK Corporation WPS Router
ssdp 40:16:7e:a3:0c:f0 RT-AC68U;ASUSTeK Computer Inc. Windows Media Connect compatible
ssdp 5c:f6:dc:8e:44:a8 [TV]Samsung LED60;Samsung Electronics UN60F6300
GSAFSJ1412D0117#

Redo the unit test: before, we buried test data in the binary.
This worked but didn't exercise the communication path with minissdpd
nor the curl code to fetch the URLs. Instead supply a test server which
both impersonates minissdpd to supply the list of one SSDP device and also
serves as the web server to fetch XML from that client.

Change-Id: If757cd69c916fe15b2d1778d93e1b5513f61f6d8
diff --git a/cmds/host-test-ssdptax.sh b/cmds/host-test-ssdptax.sh
index d6796dd..3b12fdf 100755
--- a/cmds/host-test-ssdptax.sh
+++ b/cmds/host-test-ssdptax.sh
@@ -6,5 +6,11 @@
 
 SSDP=./host-ssdptax
 
+FIFO="/tmp/ssdptax.test.$$"
+python ./ssdptax-test-server.py "$FIFO" &
+sleep 0.5
+
 WVSTART "ssdptax test"
-WVPASSEQ "$($SSDP -t)" "ssdp 00:01:02:03:04:05 Test Device"
+WVPASSEQ "$($SSDP -t $FIFO)" "ssdp 00:00:00:00:00:00 Test Device;Google Fiber ssdptax"
+
+rm "$FIFO"
diff --git a/cmds/ssdptax-test-server.py b/cmds/ssdptax-test-server.py
new file mode 100644
index 0000000..c0346cb
--- /dev/null
+++ b/cmds/ssdptax-test-server.py
@@ -0,0 +1,47 @@
+#!/usr/bin/python
+"""Fake minissdpd for unit tests.
+
+"""
+
+import BaseHTTPServer
+import socket
+import sys
+
+
+class XmlHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+  def do_GET(self):
+    self.send_response(200)
+    self.send_header('Content-type','text/xml')
+    self.end_headers()
+    self.wfile.write("""<root>
+        <specVersion><major>1</major><minor>0</minor></specVersion>
+        <device><friendlyName>Test Device</friendlyName>
+        <manufacturer>Google Fiber</manufacturer>
+        <modelDescription>Unit Test</modelDescription>
+        <modelName>ssdptax</modelName>
+    </device></root>""")
+
+def main():
+  un = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+  un.bind(sys.argv[1])
+  un.listen(1)
+  conn, _ = un.accept()
+
+  s = BaseHTTPServer.HTTPServer(("", 0), XmlHandler)
+  sn = s.socket.getsockname()
+  port = sn[1]
+  url = 'http://127.0.0.1:%d/foo.xml' % port
+  st = 'server type'
+  uuid = 'uuid goes here'
+  data = [1]
+  data.extend([len(url)] + list(url))
+  data.extend([len(st)] + list(st))
+  data.extend([len(uuid)] + list(uuid))
+
+  _ = conn.recv(8192)
+  conn.sendall(bytearray(data))
+  s.handle_request()
+
+
+if __name__ == '__main__':
+  main()
diff --git a/cmds/ssdptax.cc b/cmds/ssdptax.cc
index 1c99553..2a991cb 100644
--- a/cmds/ssdptax.cc
+++ b/cmds/ssdptax.cc
@@ -40,6 +40,9 @@
 #include <sys/un.h>
 #include <unistd.h>
 
+#include <iostream>
+#include <set>
+
 #include "l2utils.h"
 
 /* Encode length by using 7bit per Byte :
@@ -62,24 +65,33 @@
 #define SOCK_PATH "/var/run/minissdpd.sock"
 
 
-typedef struct {
-  char server[512];
-  char url[512];
-  char friendlyName[64];
+typedef struct ssdp_info {
+  ssdp_info(): srv_type(), url(), friendlyName(), ipaddr(),
+    manufacturer(), model(), failed(0) {}
+  ssdp_info(const ssdp_info& s): srv_type(s.srv_type), url(s.url),
+    friendlyName(s.friendlyName), ipaddr(s.ipaddr),
+    manufacturer(s.manufacturer), model(s.model), failed(s.failed) {}
+  std::string srv_type;
+  std::string url;
+  std::string friendlyName;
+  std::string ipaddr;
+  std::string manufacturer;
+  std::string model;
+
+  std::string buffer;
   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)
+static void strncpy_limited(char *dst, size_t dstlen,
+    const char *src, size_t srclen)
 {
   size_t i;
-  for (i = 0; i < n; ++i) {
+  size_t lim = (srclen >= (dstlen - 1)) ? (dstlen - 2) : srclen;
+
+  for (i = 0; i < lim; ++i) {
     unsigned char s = src[i];
-    if (isspace(s)) {
+    if (isspace(s) || s == ';') {
       dst[i] = ' ';  // deliberately convert newline to space
     } else if (isprint(s)) {
       dst[i] = s;
@@ -87,23 +99,27 @@
       dst[i] = '_';
     }
   }
+  dst[lim] = '\0';
 }
 
 
 /*
- * Send a request to minissdpd. Returns a pointer to a buffer
- * allocated using malloc(). Caller must free() the buffer when done.
+ * Send a request to minissdpd. Returns a std::string containing
+ * minissdpd's response.
  */
-char *request_from_ssdpd(int reqtype, const char *device)
+std::string request_from_ssdpd(const char *sock_path,
+    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;
+  char *buffer;
   ssize_t len;
+  size_t siz = 256 * 1024;
+  char *p;
   int device_len = (int)strlen(device);
   fd_set readfds;
   struct timeval tv;
+  std::string rc;
 
   if (s < 0) {
     perror("socket AF_UNIX failed");
@@ -111,7 +127,7 @@
   }
   memset(&addr, 0, sizeof(addr));
   addr.sun_family = AF_UNIX;
-  strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path));
+  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);
@@ -149,14 +165,32 @@
   }
 
   close(s);
-  return(buffer);
+  rc = std::string(buffer, len);
+  free(buffer);
+  return(rc);
 }
 
 
-static void print_responses(const std::string &ipaddr,
-    const ssdp_info_t *info, L2Map *l2map)
+/*
+ * Combine the manufacturer and model. If the manufacturer name
+ * is already present in the model string, don't duplicate it.
+ */
+const std::string unfriendly_name(const std::string &manufacturer,
+    const std::string &model)
 {
-  const char *mac;
+  if (strcasestr(model.c_str(), manufacturer.c_str()) != NULL) {
+    return model;
+  }
+
+  return manufacturer + " " + model;
+}
+
+
+std::string format_response(const ssdp_info_t *info, L2Map *l2map)
+{
+  std::string mac;
+  std::string ipaddr;
+  std::string result;
 
   if (info->failed) {
     /*
@@ -168,80 +202,62 @@
      * and that is misleading. We only report information about devices which
      * are active right now.
      */
+    return result;
+  }
+
+  mac = get_l2addr_for_ip(info->ipaddr);
+  if (info->friendlyName.length() > 0) {
+    result = "ssdp " + mac + " " + info->friendlyName + ";" +
+      unfriendly_name(info->manufacturer, info->model);
+  } else {
+    result = "ssdp " + mac + " Unknown;" + info->srv_type;
+  }
+
+  return result;
+}
+
+
+void parse_minissdpd_response(std::string &response,
+    std::string &url, std::string &srv_type)
+{
+  size_t slen;
+  const char *p;
+  const char *end = response.c_str() + response.length();
+  char urlbuf[256];
+  char srv_type_buf[256];
+
+  memset(urlbuf, 0, sizeof(urlbuf));
+  memset(srv_type_buf, 0, sizeof(srv_type_buf));
+
+  p = response.c_str();
+  DECODELENGTH(slen, p);
+  if ((p + slen) > end) {
+    fprintf(stderr, "Unable to parse SSDP response\n");
     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';
+  strncpy_limited(urlbuf, sizeof(urlbuf), p, slen);
   p += slen;
 
   DECODELENGTH(slen, p);
-  copylen = (slen >= value_len) ? value_len - 1 : slen;
-  memcpy_printable(value, p, copylen);
-  value[copylen] = '\0';
+  if ((p + slen) > end) {
+    fprintf(stderr, "Unable to parse SSDP response\n");
+    return;
+  }
+  strncpy_limited(srv_type_buf, sizeof(srv_type_buf), p, slen);
   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);
+  DECODELENGTH(slen, p);
+  if ((p + slen) > end) {
+    fprintf(stderr, "Unable to parse SSDP response\n");
+    return;
   }
-  if (inet_pton(AF_INET, ip, &in)) {
-    inet_ntop(AF_INET, &in, key, key_len);
-  }
+  /* Skip over the UUID without processing it. */
+  p += slen;
 
-  return p;
-}
+  url = urlbuf;
+  srv_type = srv_type_buf;
 
-
-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;
+  response.erase(0, (p - response.c_str()));
 }
 
 
@@ -256,7 +272,7 @@
   start = strcasestr(ptr, openlabel) + strlen(openlabel);
   end = strcasestr(ptr, closelabel);
 
-  if ((end - start) > 0) {
+  if (start < end) {
     *len = end - start;
     return start;
   }
@@ -266,9 +282,7 @@
 
 
 /*
- * libcurl calls this function back with the result of the HTTP GET.
- *
- * Expected value is an XML blob of
+ * Expected value in buffer is an XML blob of
  * http://upnp.org/specs/basic/UPnP-basic-Basic-v1-Device.pdf
  *
  * Like this (a Samsung TV):
@@ -285,22 +299,35 @@
  *    <modelURL>http://www.samsung.com/sec</modelURL>
  * ... etc, etc ...
  */
-size_t callback(const char *ptr, size_t size, size_t nmemb, void *userdata)
+void extract_fields_from_buffer(ssdp_info_t *info)
 {
-  ssdp_info_t *info = (ssdp_info_t *)userdata;
+  const char *ptr = info->buffer.c_str();
   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);
+  if (p && len > 0) {
+    info->friendlyName = std::string(p, len);
   }
 
+  p = findXmlField(ptr, "manufacturer", &len);
+  if (p && len > 0) {
+    info->manufacturer = std::string(p, len);
+  }
+
+  p = findXmlField(ptr, "modelName", &len);
+  if (p && len > 0) {
+    info->model = std::string(p, len);
+  }
+}
+
+
+size_t callback(const char *ptr, size_t size, size_t nmemb, void *userdata)
+{
+  ssdp_info_t *info = (ssdp_info_t *)userdata;
+  info->buffer.append(ptr, size * nmemb);
   return size * nmemb;
 }
 
@@ -308,44 +335,51 @@
 /*
  * SSDP returned an endpoint URL, use curl to GET its contents.
  */
-void fetch_device_info(const char *url, ssdp_info_t *ssdp)
+void fetch_device_info(const std::string &url, ssdp_info_t *info)
 {
   CURL *curl = curl_easy_init();
-  int rc;
+  char *ip;
 
   if (!curl) {
     fprintf(stderr, "curl_easy_init failed\n");
     return;
   }
-  curl_easy_setopt(curl, CURLOPT_URL, url);
+  curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
   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_WRITEDATA, info);
+  curl_easy_setopt(curl, CURLOPT_USERAGENT, "ssdptaxonomy/1.0");
   curl_easy_setopt(curl, CURLOPT_TIMEOUT, 1L);
-  if ((rc = curl_easy_perform(curl)) != CURLE_OK) {
-    ssdp->failed = 1;
+  curl_easy_setopt(curl, CURLOPT_FAILONERROR, true);
+  if (curl_easy_perform(curl) == CURLE_OK) {
+    extract_fields_from_buffer(info);
+  } else {
+    info->failed = 1;
   }
+  if (curl_easy_getinfo(curl, CURLINFO_PRIMARY_IP, &ip) == CURLE_OK) {
+    info->ipaddr = ip;
+  }
+
+  info->buffer.clear();
   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");
+  printf("usage: %s [-t /path/to/fifo]\n", progname);
+  printf("\t-t\ttest mode, use a fake path instead of minissdpd.\n");
   exit(1);
 }
 
 
 int main(int argc, char **argv)
 {
-  char *buffer;
-  const char *p;
+  std::string buffer;
   typedef std::tr1::unordered_map<std::string, ssdp_info_t*> ResponsesMap;
   ResponsesMap responses;
   L2Map l2map;
   int c, num;
-  int testmode = 0;
+  const char *sock_path = SOCK_PATH;
 
   setlinebuf(stdout);
   alarm(30);
@@ -355,241 +389,48 @@
     exit(1);
   }
 
-  while ((c = getopt(argc, argv, "t")) != -1) {
+  while ((c = getopt(argc, argv, "t:")) != -1) {
     switch(c) {
-      case 't': testmode = 1; break;
+      case 't': sock_path = optarg; 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();
-  }
+  buffer = request_from_ssdpd(sock_path, 3, "ssdp:all");
+  num = buffer.c_str()[0];
+  buffer.erase(0, 1);
+  while ((num-- > 0) && buffer.length() > 0) {
+    ssdp_info_t *info = new ssdp_info_t;
 
-  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)));
+    parse_minissdpd_response(buffer, info->url, info->srv_type);
+    if (info->url.length() && responses.find(info->url) == responses.end()) {
+      fetch_device_info(info->url, info);
+      responses[info->url] = info;
+    } else {
+      delete info;
     }
   }
-  free(buffer);
 
-  if (!testmode) {
-    get_l2_map(&l2map);
-  } else {
-    get_test_l2_map(&l2map);
+  get_l2_map(&l2map);
+
+  typedef std::set<std::string> ResultsSet;
+  ResultsSet results;
+  for (ResponsesMap::const_iterator ii = responses.begin();
+      ii != responses.end(); ++ii) {
+    std::string r = format_response(ii->second, &l2map);
+    if (r.length() > 0) {
+      results.insert(r);
+    }
   }
 
-  for(ResponsesMap::const_iterator ii = responses.begin();
-      ii != responses.end(); ++ii) {
-    print_responses(ii->first, ii->second, &l2map);
+  /* Many devices advertise multiple URLs with the same
+   * model information in all of them. Suppress duplicate
+   * output using the set. */
+  for (ResultsSet::const_iterator ii = results.begin();
+      ii != results.end(); ++ii) {
+    std::cout << *ii << std::endl;
   }
 
   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");
-}