[rygel] core: Add UPnP BasicManagement service support



commit 969261619fa1a8916e7ae70dac78c1e0513ae2a8
Author: Jussi Kukkonen <jussi kukkonen intel com>
Date:   Wed Oct 23 16:19:51 2013 +0300

    core: Add UPnP BasicManagement service support
    
    Add BasicManagement:2 service that contains a bunch of
    tools (Ping, NSLookup and Traceroute to start with). Service is
    useful for DLNA Diagnostics, and can be enabled on a
    plugin-by-plugin basis.
    
    See doc/README.BasicManagement for details.
    
    Commit was partly authored by Christophe Guiraud
    <christophe guiraud intel com>.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=707831

 configure.ac                                       |    2 +-
 data/xml/BasicManagement2.xml.in                   |  418 +++++++++++++++
 data/xml/Makefile.am                               |    1 +
 doc/README.BasicManagement                         |   24 +
 doc/man/rygel.conf.xml                             |   10 +
 src/librygel-core/Makefile.am                      |    1 +
 src/librygel-core/filelist.am                      |    5 +
 .../rygel-basic-management-test-nslookup.vala      |  360 +++++++++++++
 .../rygel-basic-management-test-ping.vala          |  288 +++++++++++
 .../rygel-basic-management-test-traceroute.vala    |  340 ++++++++++++
 src/librygel-core/rygel-basic-management-test.vala |  260 ++++++++++
 src/librygel-core/rygel-basic-management.vala      |  538 ++++++++++++++++++++
 src/librygel-core/rygel-description-file.vala      |    4 +
 src/librygel-core/rygel-plugin.vala                |   20 +-
 14 files changed, 2269 insertions(+), 2 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 84eea63..3de0fe7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -60,7 +60,7 @@ VALAFLAGS="--target-glib=2.32 $VALAFLAGS"
 RYGEL_BASE_MODULES="gupnp-1.0 >= $GUPNP_REQUIRED gee-0.8 >= $GEE_REQUIRED"
 PKG_CHECK_MODULES([LIBRYGEL_CORE_DEPS], [$RYGEL_BASE_MODULES uuid >= $UUID_REQUIRED gssdp-1.0 >= 
$GSSDP_REQUIRED gio-2.0 >= $GIO_REQUIRED gmodule-2.0 libxml-2.0 >= $LIBXML_REQUIRED])
 RYGEL_BASE_MODULES_VALAFLAGS='--pkg gupnp-1.0 --pkg gee-0.8'
-LIBRYGEL_CORE_DEPS_VALAFLAGS="$RYGEL_BASE_MODULES_VALAFLAGS --pkg gssdp-1.0 --pkg gio-2.0 --pkg gmodule-2.0"
+LIBRYGEL_CORE_DEPS_VALAFLAGS="$RYGEL_BASE_MODULES_VALAFLAGS --pkg gssdp-1.0 --pkg gio-2.0 --pkg gmodule-2.0 
--pkg posix"
 AC_SUBST([LIBRYGEL_CORE_DEPS_VALAFLAGS])
 
 RYGEL_COMMON_MODULES="$RYGEL_BASE_MODULES gupnp-av-1.0 >= $GUPNP_AV_REQUIRED"
diff --git a/data/xml/BasicManagement2.xml.in b/data/xml/BasicManagement2.xml.in
new file mode 100644
index 0000000..6a30e26
--- /dev/null
+++ b/data/xml/BasicManagement2.xml.in
@@ -0,0 +1,418 @@
+<?xml version="1.0"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+   <specVersion>
+      <major>1</major>
+      <minor>0</minor>
+   </specVersion>
+
+   <actionList>
+      <action>
+         <name>GetDeviceStatus</name>
+         <argumentList>
+            <argument>
+               <name>DeviceStatus</name>
+               <direction>out</direction>
+               <relatedStateVariable>DeviceStatus</relatedStateVariable>
+            </argument>
+         </argumentList>
+      </action>
+
+      <action>
+        <name>Ping</name>
+        <argumentList>
+            <argument>
+               <name>Host</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_Host</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>NumberOfRepetitions</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_UInt</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Timeout</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_MSecs</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DataBlockSize</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_UShort</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DSCP</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_DSCP</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>TestID</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_TestID</relatedStateVariable>
+            </argument>
+        </argumentList>
+      </action>
+
+      <action>
+        <name>GetPingResult</name>
+        <argumentList>
+            <argument>
+               <name>TestID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_TestID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Status</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_PingStatus</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>AdditionalInfo</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_String</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>SuccessCount</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_UInt</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>FailureCount</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_UInt</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>AverageResponseTime</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_MSecs</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>MinimumResponseTime</name>
+               <direction>out</direction>
+           <relatedStateVariable>A_ARG_TYPE_MSecs</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>MaximumResponseTime</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_MSecs</relatedStateVariable>
+            </argument>
+        </argumentList>
+      </action>
+
+      <action>
+        <name>NSLookup</name>
+        <argumentList>
+            <argument>
+               <name>HostName</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_HostName</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DNSServer</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_Host</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>NumberOfRepetitions</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_UInt</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Timeout</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_MSecs</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>TestID</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_TestID</relatedStateVariable>
+            </argument>
+        </argumentList>
+      </action>
+
+      <action>
+        <name>GetNSLookupResult</name>
+        <argumentList>
+            <argument>
+               <name>TestID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_TestID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Status</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_NSLookupStatus</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>AdditionalInfo</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_String</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>SuccessCount</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_UInt</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Result</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_NSLookupResult</relatedStateVariable>
+            </argument>
+        </argumentList>
+      </action>
+
+      <action>
+        <name>Traceroute</name>
+        <argumentList>
+            <argument>
+               <name>Host</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_Host</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Timeout</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_MSecs</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DataBlockSize</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_UShort</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>MaxHopCount</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_UInt</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>DSCP</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_DSCP</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>TestID</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_TestID</relatedStateVariable>
+            </argument>
+        </argumentList>
+      </action>
+
+      <action>
+        <name>GetTracerouteResult</name>
+        <argumentList>
+            <argument>
+               <name>TestID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_TestID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Status</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_TracerouteStatus</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>AdditionalInfo</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_String</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>ResponseTime</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_MSecs</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>HopHosts</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_Hosts</relatedStateVariable>
+            </argument>
+        </argumentList>
+      </action>
+
+      <action>
+        <name>GetTestIDs</name>
+        <argumentList>
+            <argument>
+               <name>TestIDs</name>
+               <direction>out</direction>
+               <relatedStateVariable>TestIDs</relatedStateVariable>
+            </argument>
+        </argumentList>
+      </action>
+
+      <action>
+        <name>GetActiveTestIDs</name>
+        <argumentList>
+            <argument>
+               <name>TestIDs</name>
+               <direction>out</direction>
+               <relatedStateVariable>ActiveTestIDs</relatedStateVariable>
+            </argument>
+        </argumentList>
+      </action>
+
+      <action>
+        <name>GetTestInfo</name>
+        <argumentList>
+            <argument>
+               <name>TestID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_TestID</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>Type</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_TestType</relatedStateVariable>
+            </argument>
+            <argument>
+               <name>State</name>
+               <direction>out</direction>
+               <relatedStateVariable>A_ARG_TYPE_TestState</relatedStateVariable>
+            </argument>
+        </argumentList>
+      </action>
+
+      <action>
+        <name>CancelTest</name>
+        <argumentList>
+            <argument>
+               <name>TestID</name>
+               <direction>in</direction>
+               <relatedStateVariable>A_ARG_TYPE_TestID</relatedStateVariable>
+            </argument>
+        </argumentList>
+      </action>
+   </actionList>
+
+   <serviceStateTable>
+      <stateVariable sendEvents="yes">
+         <name>DeviceStatus</name>
+         <dataType>string</dataType>
+      </stateVariable>
+
+      <stateVariable sendEvents="yes">
+         <name>TestIDs</name>
+         <dataType>string</dataType>
+         <defaultValue/>
+      </stateVariable>
+
+      <stateVariable sendEvents="yes">
+         <name>ActiveTestIDs</name>
+         <dataType>string</dataType>
+         <defaultValue/>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_String</name>
+         <dataType>string</dataType>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_UShort</name>
+         <dataType>ui2</dataType>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_UInt</name>
+         <dataType>ui4</dataType>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_DateTime</name>
+         <dataType>dateTime.tz</dataType>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_MSecs</name>
+         <dataType>ui4</dataType>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_TestID</name>
+         <dataType>ui4</dataType>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_TestType</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>NSLookup</allowedValue>
+            <allowedValue>Ping</allowedValue>
+            <allowedValue>Traceroute</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_TestState</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>Requested</allowedValue>
+            <allowedValue>InProgress</allowedValue>
+            <allowedValue>Canceled</allowedValue>
+            <allowedValue>Completed</allowedValue>
+        </allowedValueList>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_DSCP</name>
+         <dataType>ui1</dataType>
+         <allowedValueRange>
+            <minimum>0</minimum>
+            <maximum>63</maximum>
+         </allowedValueRange>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_Host</name>
+         <dataType>string</dataType>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_Hosts</name>
+         <dataType>string</dataType>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_HostName</name>
+         <dataType>string</dataType>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_PingStatus</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>Success</allowedValue>
+            <allowedValue>Error_CannotResolveHostName</allowedValue>
+            <allowedValue>Error_Internal</allowedValue>
+            <allowedValue>Error_Other</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_NSLookupStatus</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>Success</allowedValue>
+            <allowedValue>Error_DNSServerNotResolved</allowedValue>
+            <allowedValue>Error_Internal</allowedValue>
+            <allowedValue>Error_Other</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_NSLookupResult</name>
+         <dataType>string</dataType>
+      </stateVariable>
+
+      <stateVariable sendEvents="no">
+         <name>A_ARG_TYPE_TracerouteStatus</name>
+         <dataType>string</dataType>
+         <allowedValueList>
+            <allowedValue>Success</allowedValue>
+            <allowedValue>Error_CannotResolveHostName</allowedValue>
+            <allowedValue>Error_MaxHopCountExceeded</allowedValue>
+            <allowedValue>Error_Internal</allowedValue>
+            <allowedValue>Error_Other</allowedValue>
+         </allowedValueList>
+      </stateVariable>
+   </serviceStateTable>
+</scpd>
diff --git a/data/xml/Makefile.am b/data/xml/Makefile.am
index 2dcdccb..6f32b19 100644
--- a/data/xml/Makefile.am
+++ b/data/xml/Makefile.am
@@ -5,6 +5,7 @@ xml_in_files = MediaServer3.xml.in \
                ConnectionManager.xml.in \
                AVTransport2.xml.in \
                RenderingControl2.xml.in \
+               BasicManagement2.xml.in \
                X_MS_MediaReceiverRegistrar1.xml.in
 
 xml_DATA = $(xml_in_files:.xml.in=.xml)
diff --git a/doc/README.BasicManagement b/doc/README.BasicManagement
new file mode 100644
index 0000000..fc77e51
--- /dev/null
+++ b/doc/README.BasicManagement
@@ -0,0 +1,24 @@
+UPnP BasicManagement / DLNA +DIAGE+
+===================================
+
+Rygel includes an optional BasicManagement:2[1] service implementation.
+The implementation allows running diagnostic tools (Ping, NSLookup and
+Traceroute) and querying their results remotely. Typically this service
+would be running on a mediarenderer or mediaserver device that wants to
+be compliant with the DLNA Diagnostics Guidelines[2].
+
+The BasicManagement service is not started by default. To enable it for
+a plugin add "diagnostics=true" in the configuration section for that
+plugin in rygel.conf:
+  [Playbin]
+  diagnostics=true
+
+The service implementation is portable in the sense of compiling on any
+Posix platform but it does use system tools (ping, nslookup, traceroute)
+and parses their output. It has only been tested with standard linux
+implementations but is likely to not produce good results on other OSes,
+even busybox may require additional work.
+
+[1] http://upnp.org/specs/dm/UPnP-dm-BasicManagement-v2-Service.pdf
+[2] https://members.dlna.org/apps/org/workgroup/mcac/download.php/28678/latest
+    (unfortunately only available to DLNA members)
diff --git a/doc/man/rygel.conf.xml b/doc/man/rygel.conf.xml
index 7cd4d83..150159c 100644
--- a/doc/man/rygel.conf.xml
+++ b/doc/man/rygel.conf.xml
@@ -274,6 +274,16 @@ man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/
           </para>
         </listitem>
       </varlistentry>
+      <varlistentry>
+        <term>
+          <option>diagnostics</option>
+        </term>
+        <listitem>
+          <para>
+            Set to <userinput>true</userinput> if you would like the UPnP device to contain a 
BasicManagement:2 service that allows running tools like ping, nslookup and traceroute remotely.
+          </para>
+        </listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
   <refsect1>
diff --git a/src/librygel-core/Makefile.am b/src/librygel-core/Makefile.am
index 64194f4..eaca37a 100644
--- a/src/librygel-core/Makefile.am
+++ b/src/librygel-core/Makefile.am
@@ -15,6 +15,7 @@ librygel_core_2_0_la_VALAFLAGS = \
        -H rygel-core.h -C --library=rygel-core-2.0 \
        --vapidir=$(srcdir) \
        --pkg uuid \
+       --pkg posix \
        $(LIBRYGEL_CORE_DEPS_VALAFLAGS) \
        $(RYGEL_COMMON_VALAFLAGS)
 
diff --git a/src/librygel-core/filelist.am b/src/librygel-core/filelist.am
index bea3b1e..ecd8abd 100644
--- a/src/librygel-core/filelist.am
+++ b/src/librygel-core/filelist.am
@@ -1,5 +1,10 @@
 LIBRYGEL_CORE_VAPI_SOURCE_FILES = \
        rygel-connection-manager.vala \
+       rygel-basic-management.vala \
+       rygel-basic-management-test.vala \
+       rygel-basic-management-test-ping.vala \
+       rygel-basic-management-test-nslookup.vala \
+       rygel-basic-management-test-traceroute.vala \
        rygel-description-file.vala \
        rygel-root-device.vala \
        rygel-root-device-factory.vala \
diff --git a/src/librygel-core/rygel-basic-management-test-nslookup.vala 
b/src/librygel-core/rygel-basic-management-test-nslookup.vala
new file mode 100644
index 0000000..c74ac83
--- /dev/null
+++ b/src/librygel-core/rygel-basic-management-test-nslookup.vala
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2013 Intel Corporation.
+ *
+ * Author: Christophe Guiraud,
+ *         Jussi Kukkonen
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using GLib;
+
+internal class Rygel.BasicManagementTestNSLookup : BasicManagementTest {
+    private const string HEADER =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+        "<bms:NSLookupResult " +
+            "xmlns:bms=\"urn:schemas-upnp-org:dm:bms\" " +
+            "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"; " +
+            "xsi:schemaLocation=\"" +
+                "urn:schemas-upnp-org:dm:bms " +
+                "http://www.upnp.org/schemas/dm/bms.xsd\";>\n";
+
+    private const string FOOTER = "</bms:NSLookupResult>\n";
+
+    private enum ProcessState {
+        INIT,
+        SERVER,
+        NAME,
+    }
+
+    private enum GenericStatus {
+        SUCCESS,
+        ERROR_DNS_SERVER_NOT_RESOLVED,
+        ERROR_INTERNAL,
+        ERROR_OTHER;
+
+        public string to_string () {
+            switch (this) {
+                case SUCCESS:
+                    return "Success";
+                case ERROR_DNS_SERVER_NOT_RESOLVED:
+                    return "Error_DNSServerNotResolved";
+                case ERROR_INTERNAL:
+                    return "Error_Internal";
+                case ERROR_OTHER:
+                    return "Error_Other";
+                default:
+                    assert_not_reached ();
+            }
+        }
+    }
+
+    private enum ResultStatus {
+        SUCCESS,
+        ERROR_DNS_SERVER_NOT_AVAILABLE,
+        ERROR_HOSTNAME_NOT_RESOLVED,
+        ERROR_TIMEOUT,
+        ERROR_OTHER;
+
+        public string to_string () {
+            switch (this) {
+                case SUCCESS:
+                    return "Success";
+                case ERROR_DNS_SERVER_NOT_AVAILABLE:
+                    return "Error_DNSServerNotAvailable";
+                case ERROR_HOSTNAME_NOT_RESOLVED:
+                    return "Error_HostNameNotResolved";
+                case ERROR_TIMEOUT:
+                    return "Error_Timeout";
+                case ERROR_OTHER:
+                    return "Error_Other";
+                default:
+                    assert_not_reached ();
+            }
+        }
+    }
+
+    private enum AnswerType {
+        NONE,
+        AUTHORITATIVE,
+        NON_AUTHORITATIVE;
+
+        public string to_string () {
+            switch (this) {
+                case NONE:
+                    return "None";
+                case AUTHORITATIVE:
+                    return "Authoritative";
+                case NON_AUTHORITATIVE:
+                    return "NonAuthoritative";
+                default:
+                    assert_not_reached ();
+            }
+        }
+    }
+
+    private static const uint MAX_REPETITIONS = 100;
+    private static const uint DEFAULT_REPETITIONS = 1;
+    private static const uint MIN_INTERVAL_TIMEOUT = 1000;
+    private static const uint MAX_INTERVAL_TIMEOUT = 30000;
+    private static const uint DEFAULT_INTERVAL_TIMEOUT = 1000;
+
+    private struct Result {
+        private ProcessState state;
+        private string name_server_address;
+        private string returned_host_name;
+        private string[] addresses;
+        private ResultStatus status;
+        private AnswerType answer_type;
+        uint execution_time;
+
+        private string get_addresses_csv () {
+            var builder = new StringBuilder ("");
+            foreach (var address in this.addresses) {
+                if (builder.len != 0) {
+                    builder.append (",");
+                }
+                builder.append (address);
+            }
+
+            return builder.str;
+        }
+
+        public string to_xml_fragment () {
+            return ("<Result>\n" +
+                    "<Status>%s</Status>\n" +
+                    "<AnswerType>%s</AnswerType>\n" +
+                    "<HostNameReturned>%s</HostNameReturned>\n" +
+                    "<IPAddresses>%s</IPAddresses>\n" +
+                    "<DNSServerIP>%s</DNSServerIP>\n" +
+                    "<ResponseTime>%u</ResponseTime>\n" +
+                    "</Result>\n").printf (this.status.to_string (),
+                                           this.answer_type.to_string (),
+                                           this.returned_host_name,
+                                           this.get_addresses_csv (),
+                                           this.name_server_address,
+                                           this.execution_time);
+        }
+    }
+
+    public string host_name { construct; private get; default = ""; }
+    public string? name_server { construct; private get; default = null; }
+
+    private uint _interval_time_out;
+    public uint interval_time_out {
+        construct {
+            this._interval_time_out = value;
+            if (this._interval_time_out == 0)
+                this._interval_time_out = DEFAULT_INTERVAL_TIMEOUT;
+        }
+        private get {
+            return this._interval_time_out;
+        }
+        default = DEFAULT_INTERVAL_TIMEOUT;
+    }
+
+    public uint repetitions {
+        construct {
+            this.iterations = value;
+            if (this.iterations == 0) {
+                this.iterations = DEFAULT_REPETITIONS;
+            }
+        }
+
+        private get {
+            return this.iterations;
+        }
+        default = DEFAULT_REPETITIONS;
+    }
+
+    private Result[] results;
+    private GenericStatus generic_status;
+    private string additional_info;
+    private Timer timer = new Timer ();
+
+    public override string method_type {
+        get {
+            return "NSLookup";
+        }
+    }
+
+    public override string results_type {
+        get {
+            return "GetNSLookupResult";
+        }
+    }
+
+    public BasicManagementTestNSLookup (string host_name,
+                                        string? name_server,
+                                        uint repetitions,
+                                        uint32 interval_time_out) {
+        Object (host_name: host_name,
+                name_server: name_server,
+                repetitions: repetitions,
+                interval_time_out: interval_time_out);
+    }
+
+    public override void constructed () {
+        base.constructed ();
+
+        this.generic_status = GenericStatus.ERROR_INTERNAL;
+        this.additional_info = "";
+        this.results = {};
+
+        this.command = { "nslookup",
+                         "-timeout=%u".printf (this.interval_time_out/1000),
+                         host_name };
+        if (name_server != null && name_server.length > 0) {
+            this.command += name_server;
+        }
+
+        /* Fail early if internal parameter limits are violated */
+        if (this.repetitions > MAX_REPETITIONS) {
+            init_state = InitState.INVALID_PARAMETER;
+            var msg = "NumberOfRepetitions %u is not in allowed range [0, %u]";
+            this.additional_info = msg.printf (this.repetitions,
+                                               MAX_REPETITIONS);
+        } else if (this.interval_time_out < MIN_INTERVAL_TIMEOUT ||
+                   this.interval_time_out > MAX_INTERVAL_TIMEOUT) {
+            init_state = InitState.INVALID_PARAMETER;
+            var msg = "Timeout %u is not in allowed range [%u, %u]";
+            this.additional_info = msg.printf (this.interval_time_out,
+                                               MIN_INTERVAL_TIMEOUT,
+                                               MAX_INTERVAL_TIMEOUT);
+        }
+    }
+
+    protected override void init_iteration () {
+        base.init_iteration ();
+        var result = Result () {
+            state = ProcessState.INIT,
+            name_server_address = "",
+            returned_host_name = "",
+            addresses = {},
+            status = ResultStatus.ERROR_OTHER,
+            answer_type = AnswerType.NONE,
+            execution_time = 0
+        };
+        this.results += result;
+
+        this.timer.start ();
+    }
+
+    protected override bool finish_iteration () {
+        switch (this.init_state) {
+            case InitState.SPAWN_FAILED:
+                /* quitting early */
+                this.generic_status = GenericStatus.ERROR_INTERNAL;
+                this.additional_info = "Failed to spawn nslookup";
+                this.results[results.length - 1].status =
+                                        ResultStatus.ERROR_OTHER;
+
+                break;
+            case InitState.INVALID_PARAMETER:
+                /* quitting early */
+                /* constructed () has set info already */
+                this.generic_status = GenericStatus.ERROR_OTHER;
+                this.results[results.length - 1].status =
+                                        ResultStatus.ERROR_OTHER;
+
+                break;
+            default:
+                var elapsed_msec = this.timer.elapsed (null) * 1000;
+                var exec_time = (uint)Math.round (elapsed_msec);
+                this.results[results.length - 1].execution_time = exec_time;
+
+                break;
+        }
+
+        return base.finish_iteration ();
+    }
+
+    protected override void handle_error (string line) {
+        unowned Result* result = &this.results[results.length - 1];
+
+        if (line.contains ("couldn't get address for")) {
+            this.generic_status = GenericStatus.ERROR_DNS_SERVER_NOT_RESOLVED;
+            this.execution_state = ExecutionState.COMPLETED;
+            result.status = ResultStatus.ERROR_DNS_SERVER_NOT_AVAILABLE;
+        }
+    }
+
+    protected override void handle_output (string line) {
+        unowned Result* result = &this.results[results.length - 1];
+
+        line.strip ();
+        if (line.has_prefix ("Server:")) {
+            if (result.state != ProcessState.INIT) {
+                debug ("nslookup parser: Unexpected 'Server:' line.\n");
+            }
+            result.state = ProcessState.SERVER;
+        } else if (line.has_prefix ("Name:")) {
+            if (result.state == ProcessState.INIT) {
+                debug ("nslookup parser: Unexpected 'Name:' line");
+            } else if (result.state == ProcessState.SERVER) {
+                var name = line.substring ("Name:".length).strip ();
+                result.returned_host_name = name;
+            }
+            result.state = ProcessState.NAME;
+        } else if (line.has_prefix ("Address:")) {
+            if (result.state == ProcessState.SERVER) {
+                var address = line.substring ("Address:".length).strip ();
+                result.name_server_address = address.split ("#", 2)[0];
+                this.generic_status = GenericStatus.SUCCESS;
+            } else if (result.state == ProcessState.NAME) {
+                result.addresses += line.substring ("Address:".length).strip ();
+                result.status = ResultStatus.SUCCESS;
+                if (result.answer_type == AnswerType.NONE) {
+                    result.answer_type = AnswerType.AUTHORITATIVE;
+                }
+            } else {
+                debug ("nslookup parser: Unexpected 'Address:' line");
+            }
+        } else if (line.has_prefix ("Non-authoritative answer:")) {
+            result.answer_type = AnswerType.NON_AUTHORITATIVE;
+        } else if (line.contains ("server can't find")) {
+            result.status = ResultStatus.ERROR_HOSTNAME_NOT_RESOLVED;
+        } else if (line.contains ("couldn't get address for")) {
+            this.generic_status = GenericStatus.ERROR_DNS_SERVER_NOT_RESOLVED;
+            result.status = ResultStatus.ERROR_DNS_SERVER_NOT_AVAILABLE;
+            this.execution_state = ExecutionState.COMPLETED;
+        } else if (line.contains ("no servers could be reached")) {
+            result.status = ResultStatus.ERROR_DNS_SERVER_NOT_AVAILABLE;
+        }
+
+    }
+
+    public void get_results (out string status,
+                             out string additional_info,
+                             out uint success_count,
+                             out string result_string) {
+        success_count = 0;
+        StringBuilder builder = new StringBuilder (HEADER);
+
+        foreach (var result in this.results) {
+            builder.append (result.to_xml_fragment ());
+            if (result.status == ResultStatus.SUCCESS) {
+                success_count++;
+            }
+        }
+        builder.append (FOOTER);
+        result_string = builder.str;
+
+        status = this.generic_status.to_string ();
+        additional_info = this.additional_info;
+    }
+}
diff --git a/src/librygel-core/rygel-basic-management-test-ping.vala 
b/src/librygel-core/rygel-basic-management-test-ping.vala
new file mode 100644
index 0000000..4698a7e
--- /dev/null
+++ b/src/librygel-core/rygel-basic-management-test-ping.vala
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2013 Intel Corporation.
+ *
+ * Author: Christophe Guiraud,
+ *         Jussi Kukkonen
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using GLib;
+
+// Helper class for BasicManagementTestPing.
+internal class Rygel.BasicManagementTestPing : BasicManagementTest {
+    private static const uint MAX_REPEAT_COUNT = 100;
+    private static const uint DEFAULT_REPEAT_COUNT = 1;
+    private static const uint DEFAULT_REPLY_TIMEOUT = 10000;
+    private static const uint MIN_REQUEST_INTERVAL_TIMEOUT = 1000;
+    private static const uint MAX_REQUEST_INTERVAL_TIMEOUT = 30000;
+    private static const uint DEFAULT_REQUEST_INTERVAL_TIMEOUT = 1000;
+    private static const uint MIN_DATA_BLOCK_SIZE = 20;
+    private static const uint MAX_DATA_BLOCK_SIZE = 2048;
+    private static const uint DEFAULT_DATA_BLOCK_SIZE = 32;
+    private static const uint MAX_DSCP = 64;
+    private static const uint DEFAULT_DSCP = 30;
+
+    private enum ProcessState {
+        INIT,
+        STATISTICS,
+        RTT,
+    }
+
+    private enum Status {
+        SUCCESS,
+        ERROR_CANNOT_RESOLVE_HOSTNAME,
+        ERROR_INTERNAL,
+        ERROR_OTHER;
+
+        public string to_string () {
+            switch (this) {
+                case SUCCESS:
+                    return "Success";
+                case ERROR_CANNOT_RESOLVE_HOSTNAME:
+                    return "Error_CannotResolveHostName";
+                case ERROR_INTERNAL:
+                    return "Error_Internal";
+                case ERROR_OTHER:
+                    return "Error_Other";
+                default:
+                    assert_not_reached ();
+            }
+        }
+    }
+
+    public string host { construct; get; default = ""; }
+
+    private uint _repeat_count;
+    public uint repeat_count {
+        construct {
+            this._repeat_count = value;
+            if (this._repeat_count == 0) {
+                this._repeat_count = DEFAULT_REPEAT_COUNT;
+            }
+        }
+
+        get {
+            return this._repeat_count;
+        }
+
+        default = DEFAULT_REPEAT_COUNT;
+    }
+
+    private uint _data_block_size;
+    public uint data_block_size {
+        construct {
+            this._data_block_size = value;
+            if (this._data_block_size == 0) {
+                this._data_block_size = DEFAULT_DATA_BLOCK_SIZE;
+            }
+        }
+
+        get {
+            return this._data_block_size;
+        }
+
+        default = DEFAULT_DATA_BLOCK_SIZE;
+    }
+
+    private uint _dscp;
+    public uint dscp {
+        construct {
+            this._dscp = value;
+            if (this._dscp == 0) {
+                this._dscp = DEFAULT_DSCP;
+            }
+        }
+
+        get {
+            return this._dscp;
+        }
+
+        default = DEFAULT_DSCP;
+    }
+
+    private uint32 _interval_time_out;
+    public uint32 interval_time_out {
+        construct {
+            this._interval_time_out = value;
+            if (this._interval_time_out == 0) {
+                this._interval_time_out = DEFAULT_REQUEST_INTERVAL_TIMEOUT;
+            }
+        }
+
+        get {
+            return _interval_time_out;
+        }
+
+        default = DEFAULT_REQUEST_INTERVAL_TIMEOUT;
+    }
+
+
+    private ProcessState state;
+    private Status status;
+    private string additional_info;
+    private uint success_count;
+    private uint failure_count;
+    private uint32 avg_response_time;
+    private uint32 min_response_time;
+    private uint32 max_response_time;
+
+    public override string method_type {
+        get {
+            return "Ping";
+        }
+    }
+
+    public override string results_type {
+        get {
+            return "GetPingResult";
+        }
+    }
+
+    public BasicManagementTestPing (string host,
+                                    uint repeat_count,
+                                    uint32 interval_time_out,
+                                    uint data_block_size,
+                                    uint dscp) {
+        Object (host: host,
+                repeat_count: repeat_count,
+                interval_time_out: interval_time_out,
+                data_block_size: data_block_size,
+                dscp: dscp);
+    }
+
+    public override void constructed () {
+        base.constructed ();
+
+        this.status = Status.ERROR_INTERNAL;
+        this.state = ProcessState.INIT;
+        this.additional_info = "";
+        this.success_count = 0;
+        this.failure_count = 0;
+        this.avg_response_time = 0;
+        this.min_response_time = 0;
+        this.max_response_time = 0;
+
+        this.command = { "ping",
+                         "-c", this.repeat_count.to_string (),
+                         "-W", (DEFAULT_REPLY_TIMEOUT / 1000).to_string (),
+                         "-i", (this.interval_time_out / 1000).to_string (),
+                         "-s", this.data_block_size.to_string (),
+                         "-Q", (this.dscp >> 2).to_string (),
+                         this.host };
+
+        if (this.repeat_count > MAX_REPEAT_COUNT) {
+            this.init_state = InitState.INVALID_PARAMETER;
+            this.status = Status.ERROR_OTHER;
+            var msg = "NumberOfRepetitions %u is not in allowed range [0, %u]";
+            this.additional_info = msg.printf (this.repeat_count,
+                                               MAX_REPEAT_COUNT);
+
+        } else if (this.interval_time_out < MIN_REQUEST_INTERVAL_TIMEOUT ||
+                   this.interval_time_out > MAX_REQUEST_INTERVAL_TIMEOUT) {
+            this.init_state = InitState.INVALID_PARAMETER;
+            this.status = Status.ERROR_OTHER;
+            var msg = "Timeout %u is not in allowed range [%u, %u]";
+            this.additional_info = msg.printf (this.interval_time_out,
+                                               MIN_REQUEST_INTERVAL_TIMEOUT,
+                                               MAX_REQUEST_INTERVAL_TIMEOUT);
+
+        } else if (this.data_block_size < MIN_DATA_BLOCK_SIZE ||
+                   this.data_block_size > MAX_DATA_BLOCK_SIZE) {
+            this.init_state = InitState.INVALID_PARAMETER;
+            this.status = Status.ERROR_OTHER;
+            var msg = "DataBlockSize %u is not in allowed range [%u, %u]";
+            this.additional_info = msg.printf (this.data_block_size,
+                                               MIN_DATA_BLOCK_SIZE,
+                                               MAX_DATA_BLOCK_SIZE);
+        } else if (this.dscp > MAX_DSCP) {
+            this.init_state = InitState.INVALID_PARAMETER;
+            this.status = Status.ERROR_OTHER;
+            var msg = "DSCP %u is not in allowed range [0, %u]";
+            this.additional_info = msg.printf (this.dscp, MAX_DSCP);
+        }
+    }
+
+    protected override bool finish_iteration () {
+        if (this.init_state == InitState.SPAWN_FAILED) {
+            this.status = Status.ERROR_INTERNAL;
+            this.additional_info = "Failed to spawn ping";
+        }
+
+        return base.finish_iteration ();
+    }
+
+    protected override void handle_error (string line) {
+        if (line.contains ("ping: unknown host")) {
+            this.status = Status.ERROR_CANNOT_RESOLVE_HOSTNAME;
+        } else if (line.contains ("ping:")) {
+            this.status = Status.ERROR_OTHER;
+            this.additional_info = line.substring ("ping:".length).strip ();
+        }
+    }
+
+    protected override void handle_output (string line) {
+        line.strip ();
+        if (this.state == ProcessState.INIT) {
+            if (line.contains ("statistics ---")) {
+                this.state = ProcessState.STATISTICS;
+                this.status = Status.SUCCESS;
+            }
+        } else if (this.state == ProcessState.STATISTICS) {
+            if (line.contains ("packets transmitted")) {
+                this.state = ProcessState.RTT;
+
+                var rtt_values = line.split (", ", 3);
+                uint tx = int.parse (rtt_values[0].split (" ", 3)[0]);
+                uint rx = int.parse (rtt_values[1].split (" ", 3)[0]);
+                this.failure_count = tx - rx;
+                this.success_count = rx;
+            }
+        } else if (this.state == ProcessState.RTT) {
+            if (line.contains ("min/avg/max")) {
+                var rtt = line.split ("=", 2);
+                if (rtt.length >= 2) {
+                    var rtt_values = rtt[1].split ("/", 4);
+                    if (rtt_values.length >= 3) {
+                        this.min_response_time = (uint) Math.round
+                                        (double.parse (rtt_values[0]));
+                        this.avg_response_time = (uint) Math.round
+                                        (double.parse (rtt_values[1]));
+                        this.max_response_time = (uint) Math.round
+                                        (double.parse (rtt_values[2]));
+                    }
+                }
+            }
+        }
+    }
+
+    public void get_results (out string status,
+                             out string additional_info,
+                             out uint success_count,
+                             out uint failure_count,
+                             out uint32 avg_response_time,
+                             out uint32 min_response_time,
+                             out uint32 max_response_time) {
+        status = this.status.to_string ();
+        additional_info = this.additional_info;
+        success_count = this.success_count;
+        failure_count = this.failure_count;
+        avg_response_time = this.avg_response_time;
+        min_response_time = this.min_response_time;
+        max_response_time = this.max_response_time;
+    }
+}
diff --git a/src/librygel-core/rygel-basic-management-test-traceroute.vala 
b/src/librygel-core/rygel-basic-management-test-traceroute.vala
new file mode 100644
index 0000000..3147f17
--- /dev/null
+++ b/src/librygel-core/rygel-basic-management-test-traceroute.vala
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2013 Intel Corporation.
+ *
+ * Author: Christophe Guiraud,
+ *         Jussi Kukkonen
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using GLib;
+
+// Helper class for BasicManagementTestTraceroute.
+internal class Rygel.BasicManagementTestTraceroute : BasicManagementTest {
+    private static const uint MIN_TIMEOUT = 1000;
+    private static const uint MAX_TIMEOUT = 30000;
+    private static const uint DEFAULT_TIMEOUT = 5000;
+    private static const uint MIN_DATA_BLOCK_SIZE = 20;
+    private static const uint MAX_DATA_BLOCK_SIZE = 2048;
+    private static const uint DEFAULT_DATA_BLOCK_SIZE = 32;
+    private static const uint MAX_DSCP = 64;
+    private static const uint DEFAULT_DSCP = 30;
+    private static const uint MAX_HOPS = 64;
+    private static const uint DEFAULT_HOPS = 30;
+    private static const uint MAX_HOSTS = 2048;
+    private static const uint MAX_RESULT_SIZE = 4;
+
+    private enum ProcessState {
+        INIT,
+        HOPS,
+    }
+
+    private enum Status {
+        SUCCESS,
+        ERROR_CANNOT_RESOLVE_HOSTNAME,
+        ERROR_MAX_HOP_COUNT_EXCEEDED,
+        ERROR_INTERNAL,
+        ERROR_OTHER;
+
+        public string to_string () {
+            switch (this) {
+                case SUCCESS:
+                    return "Success";
+                case ERROR_CANNOT_RESOLVE_HOSTNAME:
+                    return "Error_CannotResolveHostName";
+                case ERROR_MAX_HOP_COUNT_EXCEEDED:
+                    return "Error_MaxHopCountExceeded";
+                case ERROR_INTERNAL:
+                    return "Error_Internal";
+                case ERROR_OTHER:
+                    return "Error_Other";
+                default:
+                    assert_not_reached ();
+            }
+        }
+    }
+
+    public string host { construct; get; default = ""; }
+
+    private uint32 _wait_time_out;
+    public uint32 wait_time_out {
+        construct {
+            this._wait_time_out = value;
+            if (this._wait_time_out == 0) {
+                this._wait_time_out = DEFAULT_TIMEOUT;
+            }
+        }
+
+        get {
+            return this._wait_time_out;
+        }
+
+        default = DEFAULT_TIMEOUT;
+    }
+
+    private uint _data_block_size;
+    public uint data_block_size {
+        construct {
+            this._data_block_size = value;
+            if (this._data_block_size == 0) {
+                this._data_block_size = DEFAULT_DATA_BLOCK_SIZE;
+            }
+        }
+
+        get {
+            return this._data_block_size;
+        }
+
+        default = DEFAULT_DATA_BLOCK_SIZE;
+    }
+
+    private uint _max_hop_count;
+    public uint max_hop_count {
+        construct {
+            this._max_hop_count = value;
+            if (this._max_hop_count == 0) {
+                this._max_hop_count = DEFAULT_HOPS;
+            }
+        }
+
+        get {
+            return this._max_hop_count;
+        }
+
+        default = DEFAULT_HOPS;
+    }
+
+    private uint _dscp;
+    public uint dscp {
+        construct {
+            this._dscp = value;
+            if (this._dscp == 0) {
+                this._dscp = DEFAULT_DSCP;
+            }
+        }
+
+        get {
+            return this._dscp;
+        }
+
+        default = DEFAULT_DSCP;
+    }
+
+    private Regex regex;
+    private Regex rtt_regex;
+    private Status status;
+    private bool error_set;
+    private ProcessState state;
+    private string host_ip;
+    private string additional_info;
+    private uint32 response_time;
+    private string hop_ips;
+
+    public override string method_type {
+        get {
+            return "Traceroute";
+        }
+    }
+
+    public override string results_type {
+        get {
+            return "GetTracerouteResult";
+        }
+    }
+
+    public BasicManagementTestTraceroute (string host,
+                                          uint32 wait_time_out,
+                                          uint data_block_size,
+                                          uint max_hop_count,
+                                          uint dscp) {
+        Object (host: host,
+                wait_time_out: wait_time_out,
+                data_block_size: data_block_size,
+                max_hop_count: max_hop_count,
+                dscp: dscp);
+    }
+
+    public override void constructed () {
+        base.constructed ();
+
+        try {
+            this.regex = new Regex ("^\\s*(\\d+)\\s+(\\S+)\\s*(.*)$", 0, 0);
+            this.rtt_regex = new Regex ("(\\S+)\\s+ms\\b", 0, 0);
+        } catch (Error e) {
+            assert_not_reached ();
+        }
+
+        this.state = ProcessState.INIT;
+        this.status = Status.ERROR_INTERNAL;
+        this.error_set = false;
+        this.hop_ips = "";
+
+        this.command = { "traceroute",
+                         "-m", this.max_hop_count.to_string (),
+                         "-w", (this.wait_time_out / 1000).to_string (),
+                         "-t", (this.dscp >> 2).to_string (),
+                         "-n",
+                         this.host,
+                         this.data_block_size.to_string () };
+
+        /* Fail early if internal parameter limits are violated */
+        if (this.wait_time_out < MIN_TIMEOUT ||
+            this.wait_time_out > MAX_TIMEOUT) {
+
+            this.init_state = InitState.INVALID_PARAMETER;
+            var msg = "Timeout %u is not in allowed range [%u, %u]";
+            this.additional_info = msg.printf (this.wait_time_out,
+                                               MIN_TIMEOUT,
+                                               MAX_TIMEOUT);
+
+        } else if (this.data_block_size < MIN_DATA_BLOCK_SIZE ||
+                   this.data_block_size > MAX_DATA_BLOCK_SIZE) {
+            this.init_state = InitState.INVALID_PARAMETER;
+            var msg = "DataBlockSize %u is not in allowed range [%u, %u]";
+            this.additional_info = msg.printf (this.data_block_size,
+                                               MIN_DATA_BLOCK_SIZE,
+                                               MAX_DATA_BLOCK_SIZE);
+
+        } else if (this.max_hop_count > MAX_HOPS) {
+            this.init_state = InitState.INVALID_PARAMETER;
+            var msg = "MaxHopCount %u is not in allowed range [0, %u]";
+            this.additional_info = msg.printf (this.max_hop_count,
+                                               MAX_HOPS);
+
+        } else if (this.dscp > MAX_DSCP) {
+            this.init_state = InitState.INVALID_PARAMETER;
+            var msg = "DSCP %u is not in allowed range [0, %u]";
+            this.additional_info = msg.printf (this.dscp, MAX_DSCP);
+        }
+    }
+
+    private void set_error (Status status, string info) {
+        this.error_set = true;
+        this.additional_info = info;
+        this.status = status;
+    }
+
+    protected override void handle_error (string line) {
+        if (line.contains ("Cannot handle \"host\" cmdline arg")) {
+            this.set_error (Status.ERROR_CANNOT_RESOLVE_HOSTNAME, "");
+        } else if (line.contains ("Network is unreachable")) {
+            this.set_error (Status.ERROR_OTHER, "Network is unreachable.");
+        } else {
+            this.set_error (Status.ERROR_INTERNAL, line);
+        }
+    }
+
+    protected override void handle_output (string line) {
+        string error = null;
+
+        line.strip ();
+        switch (this.state) {
+        case ProcessState.INIT:
+            if (line.contains ("traceroute to ")) {
+                this.state = ProcessState.HOPS;
+                var start = line.index_of_char ('(');
+                var end = line.index_of_char (')', start);
+                if (end > start) {
+                    this.host_ip = line.slice (start + 1, end);
+                }
+            } else {
+                debug ("traceroute parser: Unexpected line '%s'", line);
+            }
+            break;
+        case ProcessState.HOPS:
+            if (line.contains (" !H ")) {
+                error = "Host is unreachable.";
+            } else if (line.contains (" !N ")) {
+                error = "Network is unreachable.";
+            } else if (line.contains (" !P ")) {
+                error = "Protocol is unreachable.";
+            } else if (line.contains (" !S ")) {
+                error = "Source route failed.";
+            } else if (line.contains (" !F ")) {
+                error = "Fragmentation needed.";
+            } else if (line.contains (" !X ")) {
+                error = "Network blocks traceroute.";
+            }
+
+            if (error != null) {
+                this.set_error (Status.ERROR_OTHER, error);
+
+                return;
+            }
+            MatchInfo info;
+            if (!this.regex.match (line, 0, out info)) {
+                debug ("traceroute parser: Unexpected line '%s'", line);
+
+                return;
+            }
+
+            var ip_address = info.fetch (2);
+            if (!this.error_set) {
+                if (ip_address == this.host_ip) {
+                    this.status = Status.SUCCESS;
+                } else {
+                    /* set this error as placeholder: normally a later
+                     * handle_output () call will set status to SUCCESS */
+                    this.status = Status.ERROR_MAX_HOP_COUNT_EXCEEDED;
+                }
+            }
+
+            if (ip_address == "*") {
+                ip_address = "";
+            }
+
+            var rtt_string = info.fetch (3);
+            this.rtt_regex.match (rtt_string, 0, out info);
+            var rtt_count = 0;
+            var rtt_average = 0.0;
+            try {
+                while (info.matches ()) {
+                    rtt_count++;
+                    rtt_average += double.parse (info.fetch (1));
+                    info.next ();
+                }
+            } catch (RegexError e) {
+                debug ("Failed to parse round trip time values '%s': %s",
+                       rtt_string,
+                       e.message);
+            }
+
+            if (rtt_count > 0) {
+                rtt_average = rtt_average / rtt_count;
+            }
+
+            this.response_time = (uint) Math.round (rtt_average);
+            if (this.hop_ips.length != 0) {
+                this.hop_ips += ",";
+            }
+            this.hop_ips += ip_address;
+
+            break;
+       default:
+            assert_not_reached ();
+        }
+    }
+    public void get_results (out string status,
+                             out string additional_info,
+                             out uint32 response_time,
+                             out string hop_ips) {
+        status = this.status.to_string ();
+        additional_info = this.additional_info;
+        response_time = this.response_time;
+        hop_ips = this.hop_ips;
+    }
+}
diff --git a/src/librygel-core/rygel-basic-management-test.vala 
b/src/librygel-core/rygel-basic-management-test.vala
new file mode 100644
index 0000000..17aea78
--- /dev/null
+++ b/src/librygel-core/rygel-basic-management-test.vala
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2013 Intel Corporation.
+ *
+ * Author: Christophe Guiraud,
+ *         Jussi Kukkonen
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using GLib;
+
+internal abstract class Rygel.BasicManagementTest : Object, StateMachine {
+    protected Cancellable _cancellable;
+    public Cancellable cancellable {
+        get {
+            return this._cancellable;
+        }
+        set {
+            this._cancellable = value;
+            this._cancellable.cancelled.connect (() => {
+                if (this.execution_state == ExecutionState.IN_PROGRESS) {
+                    Posix.killpg (this.child_pid, Posix.SIGTERM);
+                    this.execution_state = ExecutionState.CANCELED;
+                }
+            });
+        }
+    }
+
+    protected enum InitState {
+        OK,
+        SPAWN_FAILED,
+        INVALID_PARAMETER,
+    }
+    protected InitState init_state;
+
+    public enum ExecutionState {
+        REQUESTED,
+        IN_PROGRESS,
+        COMPLETED,
+        CANCELED;
+
+        /* Return values fit for A_ARG_TYPE_TestState */
+        public string to_string () {
+            switch (this) {
+                case REQUESTED:
+                    return "Requested";
+                case IN_PROGRESS:
+                    return "InProgress";
+                case COMPLETED:
+                    return "Completed";
+                case CANCELED:
+                    return "Canceled";
+                default:
+                    assert_not_reached ();
+            }
+        }
+    }
+
+    public ExecutionState execution_state {
+        get;
+        protected set;
+        default = ExecutionState.REQUESTED;
+    }
+    public string id;
+
+    /* properties implementations need to provide */
+    public abstract string method_type { get; }
+    public abstract string results_type { get; }
+
+    /* properties for implementations to access */
+    protected uint iterations;
+    protected SpawnFlags flags = SpawnFlags.SEARCH_PATH |
+                                 SpawnFlags.LEAVE_DESCRIPTORS_OPEN;
+    protected string[] command;
+
+    private uint eof_count;
+    private int std_out;
+    private int std_err;
+    private Pid child_pid;
+    private SourceFunc async_callback;
+    private uint current_iteration;
+
+    /* These virtual/abstract functions will be called from run ():
+     * - For every iteration:
+     *    - init_iteration()
+     *    - calls to handle_output() and handle_error(),
+     *    - finish_iteration()
+     */
+    protected virtual void init_iteration () {}
+    protected virtual void handle_output (string line) {}
+    protected virtual void handle_error (string line) {
+        debug ("%s stderr: %s", command[0], line);
+    }
+    protected virtual bool finish_iteration () {
+        this.current_iteration++;
+
+        /* No more iterations if
+         *  - init failed, recovery is impossible or
+         *  - execution has been canceled,
+         *  - execution has ended prematurely (skip remaining iterations),
+         *  - the specified nr of iterations have been executed already
+         */
+        if (this.init_state != InitState.OK ||
+            (this.current_iteration >= this.iterations &&
+             this.execution_state == ExecutionState.IN_PROGRESS)) {
+            this.execution_state = ExecutionState.COMPLETED;
+        }
+
+        if (this.execution_state != ExecutionState.IN_PROGRESS) {
+            this.async_callback ();
+        } else {
+            this.run_iteration ();
+        }
+
+        return false;
+    }
+
+    private void child_setup () {
+        /* try to prevent possible changes in output */
+        Environment.set_variable ("LC_MESSAGES", "C", true);
+
+        /* Create new session to detach from tty, but set a process
+         * group so all children can be ḱilled if need be */
+        Posix.setsid ();
+        Posix.setpgid (0, 0);
+    }
+
+    private void run_iteration () {
+        this.init_iteration ();
+
+        /*if we failed to initialize, skip spawning */
+        if (this.init_state != InitState.OK) {
+            Idle.add (this.finish_iteration);
+
+            return;
+        }
+
+        try {
+
+            this.eof_count = 0;
+            Process.spawn_async_with_pipes (null,
+                                            this.command,
+                                            null,
+                                            this.flags,
+                                            this.child_setup,
+                                            out this.child_pid,
+                                            null,
+                                            out this.std_out,
+                                            out this.std_err);
+
+            var out_channel = new IOChannel.unix_new (std_out);
+            out_channel.add_watch (IOCondition.OUT | IOCondition.HUP,
+                                   this.out_watch);
+
+            var err_channel = new IOChannel.unix_new (std_err);
+            err_channel.add_watch (IOCondition.OUT | IOCondition.HUP,
+                                   this.err_watch);
+        } catch (SpawnError e) {
+            /* Let the async function yeild, then let the Test
+             * implementation handle this in finish_iteration */
+            this.init_state = InitState.SPAWN_FAILED;
+            Idle.add (this.finish_iteration);
+        }
+    }
+
+    private bool out_watch (IOChannel channel, IOCondition condition) {
+        try {
+            string line;
+            IOStatus status = channel.read_line (out line, null, null);
+            if (line != null) {
+                this.handle_output (line);
+            }
+
+            if (status == IOStatus.EOF) {
+                this.eof_count++;
+                if (this.eof_count > 1) {
+                    this.finish_iteration ();
+                }
+
+                return false;
+            }
+        } catch (Error e) {
+            warning (_("Failed to read standard output from %s: %s"),
+                     this.method_type,
+                     e.message);
+            this.finish_iteration ();
+
+            return false;
+        }
+
+        return true;
+    }
+
+    private bool err_watch (IOChannel channel, IOCondition condition) {
+        try {
+            string line;
+            IOStatus status = channel.read_line (out line, null, null);
+            if (line != null) {
+                this.handle_error (line);
+            }
+
+            if (status == IOStatus.EOF) {
+                this.eof_count++;
+                if (this.eof_count > 1) {
+                    this.finish_iteration ();
+                }
+
+                return false;
+            }
+        } catch (Error e) {
+            warning (_("Failed to read error output from %s: %s"),
+                     this.method_type,
+                     e.message);
+            this.finish_iteration ();
+
+            return false;
+        }
+
+        return true;
+    }
+
+    public bool is_active () {
+        return this.execution_state == ExecutionState.REQUESTED ||
+               this.execution_state == ExecutionState.IN_PROGRESS;
+    }
+
+    public async virtual void run () {
+        if (this.execution_state != ExecutionState.REQUESTED) {
+            debug ("Not running test: already started");
+
+            return;
+        }
+        if (this.cancellable == null) {
+            this.cancellable = new Cancellable ();
+        }
+        this.execution_state = ExecutionState.IN_PROGRESS;
+        this.current_iteration = 0;
+        this.async_callback = run.callback;
+
+        this.run_iteration ();
+        yield;
+
+        this.completed ();
+        return;
+    }
+}
diff --git a/src/librygel-core/rygel-basic-management.vala b/src/librygel-core/rygel-basic-management.vala
new file mode 100644
index 0000000..3916aac
--- /dev/null
+++ b/src/librygel-core/rygel-basic-management.vala
@@ -0,0 +1,538 @@
+/*
+ * Copyright (C) 2013 Intel Corporation.
+ *
+ * Author: Christophe Guiraud,
+ *         Jussi Kukkonen
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using Gee;
+using GLib;
+using GUPnP;
+
+/**
+ * Basic implementation of UPnP BasicManagement service version 2.
+ */
+public class Rygel.BasicManagement : Service {
+    public const string UPNP_ID = "urn:upnp-org:serviceId:BasicManagement";
+    public const string UPNP_TYPE = "urn:schemas-upnp-org:service:BasicManagement:2";
+    public const string DESCRIPTION_PATH = "xml/BasicManagement2.xml";
+
+    public uint max_history_size { get; set; default = 10; }
+
+    private HashMap<string, BasicManagementTest> tests_map;
+    private HashMap<string, LinkedList<string>> test_ids_by_type;
+
+    private uint current_id;
+    protected string device_status;
+
+    public override void constructed () {
+        base.constructed ();
+
+        this.tests_map = new HashMap<string, BasicManagementTest> ();
+        this.test_ids_by_type = new HashMap<string, LinkedList> ();
+
+        var now = TimeVal ();
+        now.tv_usec = 0;
+
+        this.device_status = "OK," + now.to_iso8601 ();
+
+        this.query_variable["DeviceStatus"].connect
+                                        (this.query_device_status_cb);
+        this.query_variable["TestIDs"].connect (this.query_test_ids_cb);
+        this.query_variable["ActiveTestIDs"].connect
+                                        (this.query_active_test_ids_cb);
+
+        this.action_invoked["GetDeviceStatus"].connect
+                                        (this.get_device_status_cb);
+        this.action_invoked["Ping"].connect (this.ping_cb);
+        this.action_invoked["GetPingResult"].connect (this.ping_result_cb);
+        this.action_invoked["NSLookup"].connect (this.nslookup_cb);
+        this.action_invoked["GetNSLookupResult"].connect
+                                        (this.nslookup_result_cb);
+        this.action_invoked["Traceroute"].connect (this.traceroute_cb);
+        this.action_invoked["GetTracerouteResult"].connect
+                                        (this.traceroute_result_cb);
+        this.action_invoked["GetTestIDs"].connect (this.get_test_ids_cb);
+        this.action_invoked["GetActiveTestIDs"].connect
+                                        (this.get_active_test_ids_cb);
+        this.action_invoked["GetTestInfo"].connect (this.get_test_info_cb);
+        this.action_invoked["CancelTest"].connect (this.cancel_test_cb);
+    }
+
+    private string create_test_ids_list (bool active_only) {
+        string test_ids_list = "";
+
+        foreach (var test in this.tests_map.values) {
+            if (active_only && !test.is_active ()) {
+                continue;
+            }
+
+            if (test_ids_list.length > 0) {
+                test_ids_list += ",";
+            }
+
+            test_ids_list += test.id;
+        }
+
+        return test_ids_list;
+    }
+
+    private string add_test (BasicManagementTest test) {
+        this.current_id++;
+        test.id = this.current_id.to_string ();
+
+        this.tests_map.set (test.id, test);
+
+        /* Add test to a list of ids of that method type
+           (creating the list if needed) */
+        LinkedList<string> type_test_ids;
+        type_test_ids = this.test_ids_by_type[test.method_type];
+        if (type_test_ids == null) {
+            type_test_ids = new LinkedList<string> ();
+            this.test_ids_by_type.set (test.method_type, type_test_ids);
+        }
+        type_test_ids.add (test.id);
+
+        /* remove oldest of same type, if needed */
+        if (type_test_ids.size > this.max_history_size) {
+            var old_id = type_test_ids.poll_head ();
+
+            this.tests_map[old_id].cancellable.cancel ();
+            this.tests_map.unset (old_id);
+        }
+
+        this.notify ("TestIDs", typeof (string), create_test_ids_list (false));
+        this.notify ("ActiveTestIDs",
+                     typeof (string),
+                     create_test_ids_list (true));
+
+        return test.id;
+    }
+
+    private void add_test_and_return_action (BasicManagementTest bm_test,
+                                             ServiceAction       action) {
+        var id = this.add_test (bm_test);
+
+        /* NOTE: it might be useful queue the execution but this is not
+         * currently done: if "BandwidthTest" is implemented queueing is
+         * practically required. */
+        bm_test.run.begin ((obj,res) => {
+            bm_test.run.end (res);
+            this.notify ("ActiveTestIDs",
+                         typeof (string),
+                         create_test_ids_list (true));
+        });
+
+        action.set ("TestID", typeof (string), id);
+
+        action.return ();
+    }
+
+    private bool ensure_test_exists (ServiceAction           action,
+                                     out BasicManagementTest bm_test) {
+
+        string test_id;
+
+        action.get ("TestID", typeof (string), out test_id);
+
+        bm_test = this.tests_map[test_id];
+        var action_name = action.get_name ();
+
+        if (bm_test == null) {
+            /// No test with the specified TestID was found
+            action.return_error (706, _("No Such Test"));
+
+            return false;
+        } else if ((bm_test.results_type != action_name) &&
+                   ((action_name == "GetPingResult") ||
+                    (action_name == "GetNSLookupResult") ||
+                    (action_name == "GetTracerouteResult"))) {
+            /// TestID is valid but refers to the wrong test type
+            action.return_error (707, _("Wrong Test Type"));
+
+            return false;
+        } else if ((bm_test.execution_state != BasicManagementTest.ExecutionState.COMPLETED) &&
+                   ((action_name == "GetPingResult") ||
+                    (action_name == "GetNSLookupResult") ||
+                    (action_name == "GetTracerouteResult"))) {
+            /// TestID is valid but the test Results are not available
+            action.return_error (708, _("Invalid Test State '%s'").printf (
+                                        bm_test.execution_state.to_string ()));
+
+            return false;
+        } else if ((action_name == "CancelTest") && !bm_test.is_active ()) {
+            /// TestID is valid but the test can't be canceled
+            action.return_error (709, _("State '%s' Precludes Cancel").printf (
+                                        bm_test.execution_state.to_string ()));
+
+            return false;
+        }
+
+        return true;
+    }
+
+    private void query_device_status_cb (Service   bm,
+                                         string    var,
+                                         ref Value val) {
+        val.init (typeof (string));
+        val.set_string (device_status);
+    }
+
+    private void query_test_ids_cb (Service   bm,
+                                    string    var,
+                                    ref Value val) {
+        val.init (typeof (string));
+        val.set_string (create_test_ids_list (false));
+    }
+
+    private void query_active_test_ids_cb (Service   bm,
+                                           string    var,
+                                           ref Value val) {
+        val.init (typeof (string));
+        val.set_string (create_test_ids_list (true));
+    }
+
+    private void get_device_status_cb (Service       bm,
+                                       ServiceAction action) {
+        if (action.get_argument_count () != 0) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        action.set ("DeviceStatus",
+                        typeof (string),
+                        this.device_status);
+
+        action.return ();
+    }
+
+    private void ping_cb (Service       bm,
+                          ServiceAction action) {
+        if (action.get_argument_count () != 5) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        string host;
+        uint repeat_count, data_block_size, dscp;
+        uint32 interval_time_out;
+
+        action.get ("Host",
+                        typeof (string),
+                        out host,
+                    "NumberOfRepetitions",
+                        typeof (uint),
+                        out repeat_count,
+                    "Timeout",
+                        typeof (uint32),
+                        out interval_time_out,
+                    "DataBlockSize",
+                        typeof (uint),
+                        out data_block_size,
+                    "DSCP",
+                        typeof (uint),
+                        out dscp);
+
+        var ping = new BasicManagementTestPing (host,
+                                                repeat_count,
+                                                interval_time_out,
+                                                data_block_size,
+                                                dscp);
+        this.add_test_and_return_action (ping as BasicManagementTest, action);
+    }
+
+    private void ping_result_cb (Service       bm,
+                                 ServiceAction action) {
+        if (action.get_argument_count () != 1) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        BasicManagementTest bm_test;
+
+        if (!this.ensure_test_exists (action, out bm_test)) {
+            return;
+        }
+
+        string status, additional_info;
+        uint success_count, failure_count;
+        uint32 avg_response_time, min_response_time, max_response_time;
+
+        (bm_test as BasicManagementTestPing).get_results
+                                        (out status,
+                                         out additional_info,
+                                         out success_count,
+                                         out failure_count,
+                                         out avg_response_time,
+                                         out min_response_time,
+                                         out max_response_time);
+
+        action.set ("Status",
+                        typeof (string),
+                        status,
+                    "AdditionalInfo",
+                        typeof (string),
+                        additional_info,
+                    "SuccessCount",
+                        typeof (uint),
+                        success_count,
+                    "FailureCount",
+                        typeof (uint),
+                        failure_count,
+                    "AverageResponseTime",
+                        typeof (uint32),
+                        avg_response_time,
+                    "MinimumResponseTime",
+                        typeof (uint32),
+                        min_response_time,
+                    "MaximumResponseTime",
+                        typeof (uint32),
+                        max_response_time);
+
+        action.return ();
+    }
+
+    private void nslookup_cb (Service       bm,
+                              ServiceAction action) {
+        if (action.get_argument_count () != 4) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        string hostname;
+        string dns_server;
+        uint repeat_count;
+        uint32 interval_time_out;
+
+        action.get ("HostName",
+                        typeof (string),
+                        out hostname,
+                    "DNSServer",
+                        typeof (string),
+                        out dns_server,
+                    "NumberOfRepetitions",
+                        typeof (uint),
+                        out repeat_count,
+                    "Timeout",
+                        typeof (uint32),
+                        out interval_time_out);
+
+        var nslookup = new BasicManagementTestNSLookup (hostname,
+                                                        dns_server,
+                                                        repeat_count,
+                                                        interval_time_out);
+        this.add_test_and_return_action (nslookup as BasicManagementTest,
+                                         action);
+    }
+
+    private void nslookup_result_cb (Service       bm,
+                                     ServiceAction action) {
+        if (action.get_argument_count () != 1) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        BasicManagementTest bm_test;
+
+        if (!this.ensure_test_exists (action, out bm_test)) {
+            return;
+        }
+
+        string status, additional_info, result;
+        uint success_count;
+
+        (bm_test as BasicManagementTestNSLookup).get_results
+                                        (out status,
+                                         out additional_info,
+                                         out success_count,
+                                         out result);
+
+        action.set ("Status",
+                        typeof (string),
+                        status,
+                    "AdditionalInfo",
+                        typeof (string),
+                        additional_info,
+                    "SuccessCount",
+                        typeof (uint),
+                        success_count,
+                    "Result",
+                        typeof (string),
+                        result);
+
+        action.return ();
+    }
+
+    private void traceroute_cb (Service       bm,
+                                ServiceAction action) {
+        if (action.get_argument_count () != 5) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        string host;
+        uint32 wait_time_out;
+        uint data_block_size, max_hop_count, dscp;
+
+        action.get ("Host",
+                        typeof (string),
+                        out host,
+                    "Timeout",
+                        typeof (uint32),
+                        out wait_time_out,
+                    "DataBlockSize",
+                        typeof (uint),
+                        out data_block_size,
+                    "MaxHopCount",
+                        typeof (uint),
+                        out max_hop_count,
+                    "DSCP",
+                        typeof (uint),
+                        out dscp);
+
+        var traceroute = new BasicManagementTestTraceroute (host,
+                                                            wait_time_out,
+                                                            data_block_size,
+                                                            max_hop_count,
+                                                            dscp);
+        this.add_test_and_return_action (traceroute as BasicManagementTest,
+                                         action);
+    }
+
+    private void traceroute_result_cb (Service       bm,
+                                       ServiceAction action) {
+        if (action.get_argument_count () != 1) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        BasicManagementTest bm_test;
+
+        if (!this.ensure_test_exists (action, out bm_test)) {
+            return;
+        }
+
+        string status, additional_info, hop_hosts;
+        uint32 response_time;
+
+        (bm_test as BasicManagementTestTraceroute).get_results
+                                        (out status,
+                                         out additional_info,
+                                         out response_time,
+                                         out hop_hosts);
+
+        action.set ("Status",
+                        typeof (string),
+                        status,
+                    "AdditionalInfo",
+                        typeof (string),
+                        additional_info,
+                    "ResponseTime",
+                        typeof (uint32),
+                        response_time,
+                    "HopHosts",
+                        typeof (string),
+                        hop_hosts);
+
+        action.return ();
+    }
+
+    private void get_test_ids_cb (Service       bm,
+                                  ServiceAction action) {
+        if (action.get_argument_count () != 0) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        action.set ("TestIDs",
+                        typeof (string),
+                        create_test_ids_list (false));
+
+        action.return ();
+    }
+
+    private void get_active_test_ids_cb (Service       bm,
+                                         ServiceAction action) {
+        if (action.get_argument_count () != 0) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        action.set ("TestIDs",
+                        typeof (string),
+                        create_test_ids_list (true));
+
+        action.return ();
+    }
+
+    private void get_test_info_cb (Service       bm,
+                                   ServiceAction action) {
+        if (action.get_argument_count () != 1) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        BasicManagementTest bm_test;
+
+        if (!this.ensure_test_exists (action, out bm_test)) {
+            return;
+        }
+
+        action.set ("Type",
+                        typeof (string),
+                        bm_test.method_type,
+                    "State",
+                        typeof (string),
+                        bm_test.execution_state.to_string ());
+
+        action.return ();
+    }
+
+    private void cancel_test_cb (Service       bm,
+                                 ServiceAction action) {
+        if (action.get_argument_count () != 1) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        BasicManagementTest bm_test;
+
+        if (!this.ensure_test_exists (action, out bm_test)) {
+            return;
+        }
+
+        bm_test.cancellable.cancel ();
+
+        /* ActiveTestIDs notification is handled by
+         * the tests' run callback */
+
+        action.return ();
+    }
+}
diff --git a/src/librygel-core/rygel-description-file.vala b/src/librygel-core/rygel-description-file.vala
index 37978de..4c1e82d 100644
--- a/src/librygel-core/rygel-description-file.vala
+++ b/src/librygel-core/rygel-description-file.vala
@@ -231,6 +231,10 @@ public class Rygel.DescriptionFile : Object {
             flags += "create-child-container";
         }
 
+        if (PluginCapabilities.DIAGNOSTICS in capabilities) {
+            flags += "+DIAGE+";
+        }
+
         // Set the flags we found; otherwise remove whatever is in the
         // template.
         if (flags.length > 0) {
diff --git a/src/librygel-core/rygel-plugin.vala b/src/librygel-core/rygel-plugin.vala
index cee238b..ddaa842 100644
--- a/src/librygel-core/rygel-plugin.vala
+++ b/src/librygel-core/rygel-plugin.vala
@@ -49,9 +49,12 @@ public enum Rygel.PluginCapabilities {
     TRACK_CHANGES,
 
     /// Server supports container creation
-    CREATE_CONTAINERS
+    CREATE_CONTAINERS,
 
     /* Renderer caps */
+
+    /* Diagnostics (DIAGE) support */
+    DIAGNOSTICS,
 }
 
 /**
@@ -146,6 +149,21 @@ public class Rygel.Plugin : GUPnP.ResourceFactory {
         }
 
         this.resource_infos = new ArrayList<ResourceInfo> ();
+
+        /* Enable BasicManagement service on this device if needed */
+        var config = MetaConfig.get_default ();
+        try {
+            if (config.get_bool (this.name, "diagnostics")) {
+                var resource = new ResourceInfo (BasicManagement.UPNP_ID,
+                                                 BasicManagement.UPNP_TYPE,
+                                                 BasicManagement.DESCRIPTION_PATH,
+                                                 typeof (BasicManagement));
+                this.add_resource (resource);
+
+                this.capabilities |= PluginCapabilities.DIAGNOSTICS;
+            }
+        } catch (GLib.Error error) {}
+
         this.icon_infos = new ArrayList<IconInfo> ();
         this.default_icons = new ArrayList<IconInfo> ();
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]