implement sending discovery requests
authorJorn Baayen <jorn@openedhand.com>
Thu, 20 Apr 2006 13:38:01 +0000 (13:38 +0000)
committerJorn Baayen <jorn@openedhand.com>
Thu, 20 Apr 2006 13:38:01 +0000 (13:38 +0000)
git-svn-id: https://svn.o-hand.com/repos/gupnp/gssdp@125 d8cb91d7-bff9-0310-92b9-80b65e4482b2

libgssdp/Makefile.am
libgssdp/gssdp-device-private.h
libgssdp/gssdp-error.c [new file with mode: 0644]
libgssdp/gssdp-error.h [new file with mode: 0644]
libgssdp/gssdp-marshal.list
libgssdp/gssdp-protocol.h [new file with mode: 0644]
libgssdp/gssdp-root-device-private.h
libgssdp/gssdp-root-device.c
libgssdp/gssdp-root-device.h
libgssdp/gssdp.h
tests/gssdp-test.c

index d32eb41..e58c083 100644 (file)
@@ -6,6 +6,7 @@ lib_LTLIBRARIES = libgssdp-1.0.la
 
 libgssdpinc_HEADERS =  gssdp-device.h           \
                        gssdp-discoverable.h     \
+                       gssdp-error.h            \
                        gssdp-root-device.h      \
                        gssdp-service.h          \
                        gssdp.h
@@ -21,6 +22,8 @@ BUILT_SOURCES = gssdp-marshal.c gssdp-marshal.h
 libgssdp_1_0_la_SOURCES = gssdp-device.c               \
                          gssdp-device-private.h        \
                          gssdp-discoverable.c          \
+                         gssdp-error.c                 \
+                         gssdp-protocol.h              \
                          gssdp-root-device.c           \
                          gssdp-root-device-private.h   \
                          gssdp-service.c               \
index 6a79656..0c0996d 100644 (file)
@@ -22,7 +22,7 @@
 #include "gssdp-device.h"
 
 #ifndef __GSSDP_DEVICE_PRIVATE_H__
-#define __GSSDP_DEVICE_PRIVANE_H__
+#define __GSSDP_DEVICE_PRIVATE_H__
 
 G_BEGIN_DECLS
 
diff --git a/libgssdp/gssdp-error.c b/libgssdp/gssdp-error.c
new file mode 100644 (file)
index 0000000..6747275
--- /dev/null
@@ -0,0 +1,33 @@
+/* 
+ * (C) 2006 OpenedHand Ltd.
+ *
+ * Author: Jorn Baayen <jorn@openedhand.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "gssdp-error.h"
+
+GQuark
+gssdp_error_quark (void)
+{
+        static GQuark quark = 0;
+
+        if (!quark)
+                quark = g_quark_from_static_string ("gssdp-error-quark");
+
+        return quark;
+}
diff --git a/libgssdp/gssdp-error.h b/libgssdp/gssdp-error.h
new file mode 100644 (file)
index 0000000..216a9de
--- /dev/null
@@ -0,0 +1,36 @@
+/* 
+ * (C) 2006 OpenedHand Ltd.
+ *
+ * Author: Jorn Baayen <jorn@openedhand.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GSSDP_ERROR_H__
+#define __GSSDP_ERROR_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+GQuark
+gssdp_error_quark (void) G_GNUC_CONST;
+
+#define GSSDP_ERROR_QUARK (gssdp_error_quark ())
+
+G_END_DECLS
+
+#endif /* __GSSDP_ERROR_H__ */
index 68d59ac..ff9f19e 100644 (file)
@@ -1,2 +1,3 @@
 VOID:STRING,STRING,STRING
 VOID:STRING,STRING
+VOID:POINTER
diff --git a/libgssdp/gssdp-protocol.h b/libgssdp/gssdp-protocol.h
new file mode 100644 (file)
index 0000000..afa77ab
--- /dev/null
@@ -0,0 +1,40 @@
+/* 
+ * (C) 2006 OpenedHand Ltd.
+ *
+ * Author: Jorn Baayen <jorn@openedhand.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GSSDP_PROTOCOL_H__
+#define __GSSDP_PROTOCOL_H__
+
+G_BEGIN_DECLS
+
+#define SSDP_ADDR "239.255.255.250"
+#define SSDP_PORT 1900
+#define SSDP_PORT_STR "1900"
+
+#define SSDP_DISCOVERY_REQUEST                 \
+   "M-SEARCH * HTTP/1.1\r\n"                   \
+   "Host: " SSDP_ADDR ":" SSDP_PORT_STR "\r\n" \
+   "Man: \"ssdp:discover\"\r\n"                \
+   "ST: %s\r\n"                                \
+   "MX: %d\r\n\r\n"
+
+G_END_DECLS
+
+#endif /* __GSSDP_PROTOCOL_H__ */
index 9aaff04..b54d29d 100644 (file)
@@ -22,7 +22,7 @@
 #include "gssdp-root-device.h"
 
 #ifndef __GSSDP_ROOT_DEVICE_PRIVATE_H__
-#define __GSSDP_ROOT_DEVICE_PRIVANE_H__
+#define __GSSDP_ROOT_DEVICE_PRIVATE_H__
 
 G_BEGIN_DECLS
 
index 3e72729..54e0304 100644 (file)
  */
 
 #include <config.h>
-#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
 #include <sys/utsname.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
 
 #include "gssdp-root-device-private.h"
 #include "gssdp-marshal.h"
+#include "gssdp-protocol.h"
+#include "gssdp-error.h"
+
+/* An MX of 3 seconds by default */
+#define DEFAULT_MX 3
 
 /* Hack around G_DEFINE_TYPE hardcoding the type function name */
 #define gssdp_root_device_get_type gssdp_root_device_type
@@ -36,23 +48,31 @@ G_DEFINE_TYPE (GSSDPRootDevice,
 #undef gssdp_root_device_get_type
 
 struct _GSSDPRootDevicePrivate {
-        char  *location;
+        char   *location;
 
-        char  *server_id;
+        char   *server_id;
         
-        GList *devices;
+        GList  *devices;
+
+        gushort mx;
+
+        int     socket_fd;
+
+        guint   socket_source_id;
 };
 
 enum {
         PROP_0,
         PROP_LOCATION,
         PROP_SERVER_ID,
-        PROP_DEVICES
+        PROP_DEVICES,
+        PROP_MX
 };
 
 enum {
         DISCOVERABLE_AVAILABLE,
         DISCOVERABLE_UNAVAILABLE,
+        ERROR,
         LAST_SIGNAL
 };
 
@@ -62,6 +82,35 @@ static guint signals[LAST_SIGNAL];
 static void
 gssdp_root_device_set_location (GSSDPRootDevice *root_device,
                                 const char      *location);
+static void
+emit_error                     (GSSDPRootDevice *root_device,
+                                int              error_number);
+
+/* SocketSource */
+typedef struct {
+        GSource source;
+
+        GPollFD poll_fd;
+
+        GSSDPRootDevice *root_device;
+} SocketSource;
+
+static gboolean
+socket_source_prepare  (GSource    *source,
+                        int        *timeout);
+static gboolean
+socket_source_check    (GSource    *source);
+static gboolean
+socket_source_dispatch (GSource    *source,
+                        GSourceFunc callback,
+                        gpointer    user_data);
+
+GSourceFuncs socket_source_funcs = {
+        socket_source_prepare,
+        socket_source_check,
+        socket_source_dispatch,
+        NULL
+};
 
 static void
 gssdp_root_device_init (GSSDPRootDevice *root_device)
@@ -80,6 +129,59 @@ gssdp_root_device_init (GSSDPRootDevice *root_device)
                                                         sysinfo.sysname,
                                                         sysinfo.version,
                                                         VERSION);
+
+        /* Default MX value */
+        root_device->priv->mx = DEFAULT_MX;
+
+        /* Set up socket */
+        root_device->priv->socket_fd = socket (AF_INET,
+                                               SOCK_DGRAM,
+                                               IPPROTO_UDP);
+        if (root_device->priv->socket_fd != -1) {
+                int on, res;
+                GSource *source;
+                SocketSource *socket_source;
+
+                /* Enable broadcasting */
+                on = 1;
+                
+                res = setsockopt (root_device->priv->socket_fd, 
+                                  SOL_SOCKET,
+                                  SO_BROADCAST,
+                                  &on,
+                                  sizeof (on));
+                if (res == -1)
+                        emit_error (root_device, errno);
+
+                /* Create a GSource monitoring the socket */
+                source = g_source_new (&socket_source_funcs,
+                                       sizeof (SocketSource));
+
+                socket_source = (SocketSource *) source;
+                
+                socket_source->poll_fd.fd     = root_device->priv->socket_fd;
+                socket_source->poll_fd.events = G_IO_IN | G_IO_ERR;
+
+                g_source_add_poll (source, &socket_source->poll_fd);
+
+                socket_source->root_device = root_device;
+
+                root_device->priv->socket_source_id = g_source_attach (source,
+                                                                       NULL);
+        } else 
+                emit_error (root_device, errno);
+}
+
+/**
+ * Default error handler
+ **/
+static void
+gssdp_root_device_error (GSSDPRootDevice *device,
+                         GError          *error)
+{
+        g_critical ("Error %d: %s",
+                    error->code,
+                    error->message);
 }
 
 static void
@@ -109,6 +211,11 @@ gssdp_root_device_get_property (GObject    *object,
                          (gpointer)
                           gssdp_root_device_get_devices (root_device));
                 break;
+        case PROP_MX:
+                g_value_set_uint
+                        (value,
+                         gssdp_root_device_get_mx (root_device));
+                break;
         default:
                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
                 break;
@@ -134,6 +241,10 @@ gssdp_root_device_set_property (GObject      *object,
                 gssdp_root_device_set_server_id (root_device,
                                                  g_value_get_string (value));
                 break;
+        case PROP_MX:
+                gssdp_root_device_set_mx (root_device,
+                                          g_value_get_uint (value));
+                break;
         default:
                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
                 break;
@@ -144,12 +255,87 @@ static void
 gssdp_root_device_notify (GObject    *object,
                           GParamSpec *param_spec)
 {
+        GSSDPRootDevice *root_device;
+
+        root_device = GSSDP_ROOT_DEVICE (object);
+        
         if (strcmp (param_spec->name, "available") == 0) {
+                gboolean available;
+                struct sockaddr_in addr;
+                struct ip_mreq mreq;
+                int res, optname;
+
+                available = gssdp_discoverable_get_available
+                                (GSSDP_DISCOVERABLE (root_device));
+
+                /* Bind or unbind to SSDP port */
+                memset (&addr, 0, sizeof (addr));
+                
+                addr.sin_family      = AF_INET;
+                addr.sin_addr.s_addr = htonl (INADDR_ANY);
+
+                if (available) 
+                        addr.sin_port = htons (SSDP_PORT);
+                else 
+                        addr.sin_port = htons (0); /* FIXME is this OK ? */
+
+                res = bind (root_device->priv->socket_fd,
+                            (struct sockaddr *) &addr,
+                            sizeof (addr));
+                if (res == -1)
+                        emit_error (root_device, errno);
+                
+                /* Add or drop multicast membership */
+                mreq.imr_multiaddr.s_addr = inet_addr (SSDP_ADDR);
+                mreq.imr_interface.s_addr = htonl (INADDR_ANY);
+
+                if (available)
+                        optname = IP_ADD_MEMBERSHIP;
+                else
+                        optname = IP_DROP_MEMBERSHIP;
+
+                res = setsockopt (root_device->priv->socket_fd,
+                                  IPPROTO_IP,
+                                  optname,
+                                  &mreq,
+                                  sizeof (mreq));
+                if (res == -1)
+                        emit_error (root_device, errno);
+
                 /* XXX */
         }
 }
 
 static void
+gssdp_root_device_dispose (GObject *object)
+{
+        GSSDPRootDevice *root_device;
+        GObjectClass *object_class;
+
+        root_device = GSSDP_ROOT_DEVICE (object);
+
+        /* Get rid of the SocketSource */
+        if (root_device->priv->socket_source_id) {
+                g_source_remove (root_device->priv->socket_source_id);
+                root_device->priv->socket_source_id = 0;
+        }
+
+        /* Close the socket, if it is open */
+        if (root_device->priv->socket_fd != -1) {
+                int res;
+                
+                res = close (root_device->priv->socket_fd);
+                if (res == 0)
+                        root_device->priv->socket_fd = -1;
+                else
+                        emit_error (root_device, errno);
+        }
+
+        object_class = G_OBJECT_CLASS (gssdp_root_device_parent_class);
+        object_class->dispose (object);
+}
+
+static void
 gssdp_root_device_finalize (GObject *object)
 {
         GSSDPRootDevice *root_device;
@@ -169,10 +355,13 @@ gssdp_root_device_class_init (GSSDPRootDeviceClass *klass)
 {
         GObjectClass *object_class;
 
+        klass->error               = gssdp_root_device_error;
+
        object_class = G_OBJECT_CLASS (klass);
 
        object_class->set_property = gssdp_root_device_set_property;
        object_class->get_property = gssdp_root_device_get_property;
+       object_class->dispose      = gssdp_root_device_dispose;
        object_class->finalize     = gssdp_root_device_finalize;
        object_class->notify       = gssdp_root_device_notify;
 
@@ -213,6 +402,21 @@ gssdp_root_device_class_init (GSSDPRootDeviceClass *klass)
                           G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
                           G_PARAM_STATIC_BLURB));
 
+        g_object_class_install_property
+                (object_class,
+                 PROP_MX,
+                 g_param_spec_uint
+                         ("mx",
+                          "MX",
+                          "Maximum number of seconds in which to request "
+                          "other parties to respond.",
+                          1,
+                          G_MAXUSHORT,
+                          DEFAULT_MX,
+                          G_PARAM_READWRITE |
+                          G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK |
+                          G_PARAM_STATIC_BLURB));
+
         signals[DISCOVERABLE_AVAILABLE] =
                 g_signal_new ("discoverable-available",
                               GSSDP_TYPE_ROOT_DEVICE,
@@ -239,6 +443,18 @@ gssdp_root_device_class_init (GSSDPRootDeviceClass *klass)
                               2,
                               G_TYPE_STRING,
                               G_TYPE_STRING);
+
+        signals[ERROR] =
+                g_signal_new ("error",
+                              GSSDP_TYPE_ROOT_DEVICE,
+                              G_SIGNAL_RUN_LAST,
+                              G_STRUCT_OFFSET (GSSDPRootDeviceClass,
+                                               error),
+                              NULL, NULL,
+                              gssdp_marshal_VOID__POINTER,
+                              G_TYPE_NONE,
+                              1,
+                              G_TYPE_POINTER);
 }
 
 /**
@@ -326,6 +542,40 @@ gssdp_root_device_get_server_id (GSSDPRootDevice *root_device)
 }
 
 /**
+ * gssdp_root_device_set_mx
+ * @root_device: A #GSSDPRootDevice
+ * @mx: The to be used MX value
+ *
+ * Sets the used MX value of @root_device to @mx.
+ **/
+void
+gssdp_root_device_set_mx (GSSDPRootDevice *root_device,
+                          gushort          mx)
+{
+        g_return_if_fail (GSSDP_IS_ROOT_DEVICE (root_device));
+
+        if (root_device->priv->mx != mx) {
+                root_device->priv->mx = mx;
+                
+                g_object_notify (G_OBJECT (root_device), "mx");
+        }
+}
+
+/**
+ * gssdp_root_device_get_mx
+ * @root_device: A #GSSDPRootDevice
+ *
+ * Return value: The used MX value.
+ **/
+gushort
+gssdp_root_device_get_mx (GSSDPRootDevice *root_device)
+{
+        g_return_val_if_fail (GSSDP_IS_ROOT_DEVICE (root_device), 0);
+
+        return root_device->priv->mx;
+}
+
+/**
  * Adds @device to the list of devices contained in @root_device
  **/
 void
@@ -384,8 +634,115 @@ void
 gssdp_root_device_discover (GSSDPRootDevice *root_device,
                             const char      *target)
 {
+        char *request;
+        struct sockaddr_in sin;
+        int res;
+
         g_return_if_fail (GSSDP_IS_ROOT_DEVICE (root_device));
         g_return_if_fail (target != NULL);
 
-        /* XXX */
+        request = g_strdup_printf (SSDP_DISCOVERY_REQUEST,
+                                   target,
+                                   root_device->priv->mx);
+
+        memset (&sin, 0, sizeof (sin));
+
+        sin.sin_family      = AF_INET;
+        sin.sin_port        = htons (SSDP_PORT);
+        sin.sin_addr.s_addr = inet_addr (SSDP_ADDR);
+
+        res = sendto (root_device->priv->socket_fd,
+                      request,
+                      strlen (request),
+                      0,
+                      (struct sockaddr *) &sin,
+                      sizeof (struct sockaddr_in));
+
+        if (res == -1)
+                emit_error (root_device, errno);
+
+        g_free (request);
+}
+
+/**
+ * Emits @error_number using the 'error' signal
+ **/
+static void
+emit_error (GSSDPRootDevice *root_device,
+            int              error_number)
+{
+        GError *error;
+
+        error = g_error_new (GSSDP_ERROR_QUARK,
+                             error_number,
+                             strerror (error_number));
+
+        g_signal_emit (root_device,
+                       signals[ERROR],
+                       0,
+                       error);
+
+        g_error_free (error);
+}
+
+/**
+ * SocketSource implementation
+ **/
+static gboolean
+socket_source_prepare (GSource *source,
+                       int     *timeout)
+{
+        return FALSE;
+}
+
+static gboolean
+socket_source_check (GSource *source)
+{
+        SocketSource *socket_source;
+
+        socket_source = (SocketSource *) source;
+
+        return socket_source->poll_fd.revents & (G_IO_IN | G_IO_ERR);
+}
+
+static gboolean
+socket_source_dispatch (GSource    *source,
+                        GSourceFunc callback,
+                        gpointer    user_data)
+{
+        SocketSource *socket_source;
+
+        socket_source = (SocketSource *) source;
+
+        if (socket_source->poll_fd.revents & G_IO_IN) {
+                /* Ready to read */
+                ssize_t bytes;
+                char buf[1024];
+
+                bytes = recv (socket_source->poll_fd.fd,
+                              buf,
+                              1024,
+                              0);
+                buf[bytes] = 0;
+
+                g_print ("received %s\n", buf);
+        } else if (socket_source->poll_fd.revents & G_IO_ERR) {
+                /* An error occured */
+                int value;
+                socklen_t size_int;
+
+                value = EINVAL;
+                size_int = sizeof (int);
+                
+                /* Get errno from socket */
+                getsockopt (socket_source->poll_fd.fd,
+                            SOL_SOCKET,
+                            SO_ERROR,
+                            &value,
+                            &size_int);
+
+                emit_error (socket_source->root_device, value);
+        }
+
+        return TRUE;
 }
index 71696c0..9c9547d 100644 (file)
@@ -64,11 +64,16 @@ typedef struct {
         GSSDPDeviceClass parent_class;
 
         /* signals */
-        void (* discoverable_available)   (const char *target,
-                                           const char *usn,
-                                           const char *location);
-        void (* discoverable_unavailable) (const char *target,
-                                           const char *usn);
+        void (* discoverable_available)   (GSSDPRootDevice *root_device,
+                                           const char      *target,
+                                           const char      *usn,
+                                           const char      *location);
+        void (* discoverable_unavailable) (GSSDPRootDevice *root_device,
+                                           const char      *target,
+                                           const char      *usn);
+        
+        void (* error)                    (GSSDPRootDevice *root_device,
+                                           GError          *error);
 
         /* future padding */
         void (* _gssdp_reserved1) (void);
@@ -92,6 +97,13 @@ gssdp_root_device_set_server_id (GSSDPRootDevice *root_device,
 const char *
 gssdp_root_device_get_server_id (GSSDPRootDevice *root_device);
 
+void
+gssdp_root_device_set_mx        (GSSDPRootDevice *root_device,
+                                 gushort          mx);
+
+gushort
+gssdp_root_device_get_mx        (GSSDPRootDevice *root_device);
+
 const GList *
 gssdp_root_device_get_devices   (GSSDPRootDevice *root_device);
 
index 3c73d6a..7baf6db 100644 (file)
@@ -21,5 +21,6 @@
 
 #include "gssdp-device.h"
 #include "gssdp-discoverable.h"
+#include "gssdp-error.h"
 #include "gssdp-root-device.h"
 #include "gssdp-service.h"
index 3f531a2..7ebcc2e 100644 (file)
 
 #include <libgssdp/gssdp.h>
 
+static void
+discoverable_available_cb (GSSDPRootDevice *root_device,
+                           const char      *target,
+                           const char      *usn,
+                           const char      *location)
+{
+        g_print ("Discoverable available:\n"
+                 "\tTarget:\t%s\n"
+                 "\tUSN:\t%s\n"
+                 "\tLocation:\t%s\n",
+                 target,
+                 usn,
+                 location);
+}
+
+static void
+discoverable_unavailable_cb (GSSDPRootDevice *root_device,
+                             const char      *target,
+                             const char      *usn,
+                             const char      *location)
+{
+        g_print ("Discoverable unavailable:\n"
+                 "\tTarget:\t%s\n"
+                 "\tUSN:\t%s\n",
+                 target,
+                 usn);
+}
+
 int
 main (int    argc,
       char **argv)
 {
+        GSSDPRootDevice *root_device;
+        GMainLoop *main_loop;
+
+        g_type_init ();
+
+        root_device = gssdp_root_device_new
+                        ("schemas-upnp-org:device:InternetGatewayDevice",
+                         1,
+                         "http://localhost/");
+
+        gssdp_discoverable_set_available (GSSDP_DISCOVERABLE (root_device),
+                                          TRUE);
+
+        g_signal_connect (root_device,
+                          "discoverable-available",
+                          G_CALLBACK (discoverable_available_cb),
+                          NULL);
+        g_signal_connect (root_device,
+                          "discoverable-unavailable",
+                          G_CALLBACK (discoverable_unavailable_cb),
+                          NULL);
+
+        gssdp_root_device_discover (root_device,
+                                    "upnp:rootdevice");
+
+        main_loop = g_main_loop_new (NULL, FALSE);
+        g_main_loop_run (main_loop);
+        g_main_loop_unref (main_loop);
+
+        g_object_unref (root_device);
+
         return 0;
 }