[rygel/wip/basic-management: 12/49] Add UPnP basic management services support



commit 010358780193655407939db0d1ad920753e8a248
Author: Christophe Guiraud <christophe guiraud intel com>
Date:   Tue May 28 15:19:24 2013 +0200

    Add UPnP basic management services support
    
    BasicManagement:2 service [1] contains a bunch of tools to indicate
    overall device status, perform maintenance functions (e.g. reboot) and
    to run some diagnostic tools (e.g. Ping). This commit is the service
    skeleton without the tools.
    
    [1] http://upnp.org/specs/dm/UPnP-dm-BasicManagement-v2-Service.pdf

 data/xml/BasicManagement2.xml.in                   |  417 ++++++++++++++++
 data/xml/Makefile.am                               |    1 +
 src/librygel-core/filelist.am                      |    5 +
 src/librygel-core/rygel-basic-management.vala      |  519 ++++++++++++++++++++
 src/librygel-core/rygel-bm-test-nslookup.vala      |  107 ++++
 src/librygel-core/rygel-bm-test-ping.vala          |  132 +++++
 src/librygel-core/rygel-bm-test-traceroute.vala    |  122 +++++
 src/librygel-core/rygel-bm-test.vala               |   43 ++
 .../rygel-media-renderer-plugin.vala               |   10 +-
 9 files changed, 1355 insertions(+), 1 deletions(-)
---
diff --git a/data/xml/BasicManagement2.xml.in b/data/xml/BasicManagement2.xml.in
new file mode 100644
index 0000000..62baeda
--- /dev/null
+++ b/data/xml/BasicManagement2.xml.in
@@ -0,0 +1,417 @@
+<?xml version="1.0"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+   <specVersion>
+      <major>1</major>
+      <minor>0</minor>
+   </specVersion>
+   <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>
+
+   <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>
+</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/src/librygel-core/filelist.am b/src/librygel-core/filelist.am
index bea3b1e..59a91ba 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-bm-test.vala \
+       rygel-bm-test-ping.vala \
+       rygel-bm-test-nslookup.vala \
+       rygel-bm-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.vala b/src/librygel-core/rygel-basic-management.vala
new file mode 100644
index 0000000..2d67c01
--- /dev/null
+++ b/src/librygel-core/rygel-basic-management.vala
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2008 OpenedHand Ltd.
+ * Copyright (C) 2008 Zeeshan Ali <zeenix gmail com>.
+ * Copyright (C) 2013 Intel Corporation.
+ *
+ * Author: Jorn Baayen <jorn openedhand com>
+ *         Zeeshan Ali <zeenix gmail com>
+ *
+ * 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";
+
+    private LinkedList<BMTest> tests_list;
+    private LinkedList<BMTest> active_tests_list;
+
+    private static uint current_id = 0;
+
+    protected string device_status;
+    protected string test_ids;
+    protected string active_test_ids;
+
+    public override void constructed () {
+        base.constructed ();
+
+        this.tests_list = new LinkedList<BMTest> ();
+        this.active_tests_list = new LinkedList<BMTest> ();
+
+        this.device_status   = "OK,2009-06-15T12:00:00,Details";
+        this.test_ids        = "";
+        this.active_test_ids = "";
+
+        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 void add_test (BMTest bm_test) {
+        current_id++;
+        bm_test.id = current_id.to_string ();
+
+        this.tests_list.add (bm_test);
+
+        this.test_ids = "";
+        foreach (BMTest test in this.tests_list) {
+             if (this.test_ids == "") {
+                 this.test_ids = test.id;
+             } else {
+                 this.test_ids += "," + test.id;
+             }
+        }
+    }
+
+    // Error out if 'TestID' is wrong
+    private bool check_test_id (ServiceAction action, out BMTest bm_test) {
+
+        string test_id;
+
+        action.get ("TestID", typeof (string), out test_id);
+
+        bm_test = null;
+
+        foreach (BMTest test in this.tests_list) {
+             if (test.id == test_id) {
+                 bm_test = test;
+
+                 break;
+             }
+        }
+
+        if (bm_test == null) {
+            // No test with the specified TestID was found
+            action.return_error (706, _("No Such Test"));
+
+            return false;
+        } else if ((action.get_name() != "CancelTest") &&
+                   (action.get_name() != "GetTestInfo") &&
+                   (bm_test.type != action.get_name())) {
+            // TestID is valid but refers to the wrong test type
+            action.return_error (707, _("Wrong Test Type"));
+
+            return false;
+        } else if ((action.get_name() != "GetTestInfo") &&
+                   (bm_test.state != "Completed")) {
+            // TestID is valid but the test Results are not available
+            action.return_error (708, _("Invalid Test State"));
+
+            return false;
+        }
+
+        return true;
+    }
+
+    private void query_device_status_cb (Service   cm,
+                                         string    var,
+                                         ref Value val) {
+        val.init (typeof (string));
+        val.set_string (device_status);
+    }
+
+    private void query_test_ids_cb (Service   cm,
+                                    string    var,
+                                    ref Value val) {
+        val.init (typeof (string));
+        val.set_string (test_ids);
+    }
+
+    private void query_active_test_ids_cb (Service   cm,
+                                           string    var,
+                                           ref Value val) {
+        val.init (typeof (string));
+        val.set_string (active_test_ids);
+    }
+
+    private void get_device_status_cb (Service             cm,
+                                       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             cm,
+                          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);
+
+        BMTestPing ping = new BMTestPing();
+        if (!ping.init (host, repeat_count, interval_time_out,
+                        data_block_size, dscp)) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        // test_id to be added to TestIDs and ActiveTestID
+        this.add_test (ping as BMTest);
+
+        ping.execute ();
+
+        action.set ("TestID",
+                        typeof (string),
+                        ping.id);
+
+        action.return ();
+    }
+
+    private void ping_result_cb (Service             cm,
+                                 ServiceAction action) {
+        if (action.get_argument_count () != 1) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        BMTest bm_test;
+
+        if (!this.check_test_id (action, out bm_test)) {
+            return;
+        }
+
+        string status, additional_info;
+        uint success_count, failure_count;
+        uint32 average_response_time, min_response_time, max_response_time;
+
+        (bm_test as BMTestPing).get_results (out status, out additional_info,
+                                             out success_count,
+                                             out failure_count,
+                                             out average_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),
+                        average_response_time,
+                    "MinimumResponseTime",
+                        typeof (uint32),
+                        min_response_time,
+                    "MaximumResponseTime",
+                        typeof (uint32),
+                        max_response_time);
+
+        action.return ();
+    }
+
+    private void nslookup_cb (Service             cm,
+                              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 (string),
+                        out repeat_count,
+                    "Timeout",
+                        typeof (string),
+                        out interval_time_out);
+
+        BMTestNSLookup nslookup = new BMTestNSLookup();
+        if (!nslookup.init (hostname, dns_server, repeat_count,
+                            interval_time_out)) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        this.add_test (nslookup as BMTest);
+
+        nslookup.execute ();
+
+        action.set ("TestID",
+                        typeof (string),
+                        nslookup.id);
+
+        action.return ();
+    }
+
+    private void nslookup_result_cb (Service             cm,
+                                     ServiceAction action) {
+        if (action.get_argument_count () != 1) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        BMTest bm_test;
+
+        if (!this.check_test_id (action, out bm_test)) {
+            return;
+        }
+
+        string status, additional_info, result;
+        uint success_count;
+
+        (bm_test as BMTestNSLookup).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             cm,
+                                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 (string),
+                        out data_block_size,
+                    "MaxHopCount",
+                        typeof (uint),
+                        out max_hop_count,
+                    "DSCP",
+                        typeof (uint),
+                        out dscp);
+
+        BMTestTraceroute traceroute = new BMTestTraceroute();
+        if (!traceroute.init (host, wait_time_out, data_block_size,
+                              max_hop_count, dscp)) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        this.add_test (traceroute as BMTest);
+
+        traceroute.execute ();
+
+        action.set ("TestID",
+                        typeof (string),
+                        traceroute.id);
+
+        action.return ();
+    }
+
+    private void traceroute_result_cb (Service             cm,
+                                       ServiceAction action) {
+        if (action.get_argument_count () != 1) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        BMTest bm_test;
+
+        if (!this.check_test_id (action, out bm_test)) {
+            return;
+        }
+
+        string status, additional_info, hop_hosts;
+        uint32 response_time;
+
+        (bm_test as BMTestTraceroute).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             cm,
+                                  ServiceAction action) {
+        if (action.get_argument_count () != 0) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        action.set ("TestIDs",
+                        typeof (string),
+                        this.test_ids);
+
+        action.return ();
+    }
+
+    private void get_active_test_ids_cb (Service             cm,
+                                         ServiceAction action) {
+        if (action.get_argument_count () != 0) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        action.set ("TestIDs",
+                        typeof (string),
+                        this.active_test_ids);
+
+        action.return ();
+    }
+
+    private void get_test_info_cb (Service             cm,
+                                   ServiceAction action) {
+        if (action.get_argument_count () != 1) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        BMTest bm_test;
+
+        if (!this.check_test_id (action, out bm_test)) {
+            return;
+        }
+
+        action.set ("Type",
+                        typeof (string),
+                        bm_test.type,
+                    "State",
+                        typeof (string),
+                        bm_test.state);
+
+        action.return ();
+    }
+
+    private void cancel_test_cb (Service             cm,
+                                 ServiceAction action) {
+        if (action.get_argument_count () != 1) {
+            action.return_error (402, _("Invalid argument"));
+
+            return;
+        }
+
+        BMTest bm_test;
+
+        if (!this.check_test_id (action, out bm_test)) {
+            return;
+        }
+
+        bm_test.cancel();
+
+        action.return ();
+    }
+}
diff --git a/src/librygel-core/rygel-bm-test-nslookup.vala b/src/librygel-core/rygel-bm-test-nslookup.vala
new file mode 100644
index 0000000..6c0e8f9
--- /dev/null
+++ b/src/librygel-core/rygel-bm-test-nslookup.vala
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2008 OpenedHand Ltd.
+ * Copyright (C) 2008 Zeeshan Ali <zeenix gmail com>.
+ * Copyright (C) 2013 Intel Corporation.
+ *
+ * Author: Jorn Baayen <jorn openedhand com>
+ *         Zeeshan Ali <zeenix gmail com>
+ *
+ * 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 BMTestNSLookup.
+internal class Rygel.BMTestNSLookup : BMTest {
+
+    private string hostname;
+    private string dns_server;
+    private uint repeat_count;
+    private uint32 interval_time_out;
+
+    private string status;
+    private string additional_info;
+    private uint success_count;
+    private string result;
+
+    private static const uint NSLOOKUP_MIN_REPEAT_COUNT = 1;
+    private static const uint NSLOOKUP_MAX_REPEAT_COUNT = 100;
+    private static const uint NSLOOKUP_DEFAULT_REPEAT_COUNT = 1;
+    private static const uint NSLOOKUP_MIN_REQUEST_INTERVAL_TIMEOUT = 1000;
+    private static const uint NSLOOKUP_MAX_REQUEST_INTERVAL_TIMEOUT = 30000;
+    private static const uint NSLOOKUP_DEFAULT_REQUEST_INTERVAL_TIMEOUT = 10000;
+    private static const uint NSLOOKUP_MAX_RESULT_ANSWER_STR_SIZE = 32;
+    private static const uint NSLOOKUP_MAX_RESULT_NAME_STR_SIZE = 256;
+    private static const uint NSLOOKUP_MAX_RESULT_IPS_STR_SIZE = 1024;
+    private static const uint NSLOOKUP_MAX_RESULT_ARRAY_SIZE = 7;
+
+    public BMTestNSLookup() {
+        base("NSLookup");
+    }
+
+    public override void execute() {
+        stdout.printf("*NSLookup* execute()\n");
+    }
+
+    public override void cancel() {
+        stdout.printf("*NSLookup* cancel()\n");
+    }
+
+    public bool init(string hostname, string dns_server, uint repeat_count,
+                     uint32 interval_time_out) {
+        stdout.printf("*NSLookup* init()\n");
+
+        if (hostname == null) {
+            return false;
+        }
+
+        if (dns_server == null) {
+            return false;
+        }
+
+        if (repeat_count == 0) {
+            repeat_count = NSLOOKUP_DEFAULT_REPEAT_COUNT;
+        } else if (repeat_count < NSLOOKUP_MIN_REPEAT_COUNT &&
+                   repeat_count > NSLOOKUP_MAX_REPEAT_COUNT) {
+            return false;
+        }
+
+        if (interval_time_out == 0) {
+            interval_time_out = NSLOOKUP_DEFAULT_REQUEST_INTERVAL_TIMEOUT;
+        } else if (interval_time_out < NSLOOKUP_MIN_REQUEST_INTERVAL_TIMEOUT &&
+                   interval_time_out > NSLOOKUP_MAX_REQUEST_INTERVAL_TIMEOUT) {
+            return false;
+        }
+
+        this.hostname = hostname;
+        this.dns_server = dns_server;
+        this.repeat_count = repeat_count;
+        this.interval_time_out = interval_time_out;
+
+        return true;
+    }
+
+    public void get_results(out string status, out string additional_info,
+                            out uint success_count, out string result) {
+        stdout.printf("*NSLookup* get_results()\n");
+
+        status = this.status;
+        additional_info = this.additional_info;
+        success_count = this.success_count;
+        result = this.result;
+    }
+}
diff --git a/src/librygel-core/rygel-bm-test-ping.vala b/src/librygel-core/rygel-bm-test-ping.vala
new file mode 100644
index 0000000..969a2db
--- /dev/null
+++ b/src/librygel-core/rygel-bm-test-ping.vala
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2008 OpenedHand Ltd.
+ * Copyright (C) 2008 Zeeshan Ali <zeenix gmail com>.
+ * Copyright (C) 2013 Intel Corporation.
+ *
+ * Author: Jorn Baayen <jorn openedhand com>
+ *         Zeeshan Ali <zeenix gmail com>
+ *
+ * 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 BMTestPing.
+internal class Rygel.BMTestPing : BMTest {
+
+    private string host;
+    private uint repeat_count;
+    private uint data_block_size;
+    private uint dscp;
+    private uint32 interval_time_out;
+
+    private string status;
+    private string additional_info;
+    private uint success_count;
+    private uint failure_count;
+    private uint32 average_response_time;
+    private uint32 min_response_time;
+    private uint32 max_response_time;
+
+    private static const uint PING_MIN_REPEAT_COUNT = 1;
+    private static const uint PING_MAX_REPEAT_COUNT = 100;
+    private static const uint PING_DEFAULT_REPEAT_COUNT = 1;
+    private static const uint PING_MIN_REQUEST_INTERVAL_TIMEOUT = 1000;
+    private static const uint PING_MAX_REQUEST_INTERVAL_TIMEOUT = 30000;
+    private static const uint PING_DEFAULT_REQUEST_INTERVAL_TIMEOUT = 10000;
+    private static const uint PING_MIN_DATA_BLOCK_SIZE = 20;
+    private static const uint PING_MAX_DATA_BLOCK_SIZE = 2048;
+    private static const uint PING_DEFAULT_DATA_BLOCK_SIZE = 32;
+    private static const uint PING_MIN_DSCP = 1;
+    private static const uint PING_MAX_DSCP = 64;
+    private static const uint PING_DEFAULT_DSCP = 30;
+    private static const uint PING_MAX_ADDITIONAL_INFO_STR_SIZE = 1024;
+    private static const uint PING_MAX_STATUS_STR_SIZE = 32;
+    private static const uint PING_MAX_RESULT_ARRAY_SIZE = 7;
+
+    public BMTestPing() {
+        base("Ping");
+    }
+
+    public override void execute() {
+        stdout.printf("*Ping* execute()\n");
+    }
+
+    public override void cancel() {
+        stdout.printf("*Ping* cancel()\n");
+    }
+
+    public bool init(string host, uint repeat_count, uint data_block_size,
+                     uint dscp, uint32 interval_time_out) {
+        stdout.printf("*Ping* init()\n");
+
+        if (host == null || host == "") {
+            return false;
+        }
+
+        if (repeat_count == 0) {
+            repeat_count = PING_DEFAULT_REPEAT_COUNT;
+        } else if (repeat_count < PING_MIN_REPEAT_COUNT &&
+                   repeat_count > PING_MAX_REPEAT_COUNT) {
+            return false;
+        }
+
+        if (interval_time_out == 0) {
+            interval_time_out = PING_DEFAULT_REQUEST_INTERVAL_TIMEOUT;
+        } else if (interval_time_out < PING_MIN_REQUEST_INTERVAL_TIMEOUT &&
+                   interval_time_out > PING_MAX_REQUEST_INTERVAL_TIMEOUT) {
+            return false;
+        }
+
+        if (data_block_size == 0) {
+            data_block_size = PING_DEFAULT_DATA_BLOCK_SIZE;
+        } else if (data_block_size < PING_MIN_DATA_BLOCK_SIZE &&
+                   data_block_size > PING_MAX_DATA_BLOCK_SIZE) {
+            return false;
+        }
+
+        if (dscp == 0) {
+            dscp = PING_DEFAULT_DSCP;
+        } else if (dscp < PING_MIN_DSCP && dscp > PING_MAX_DSCP) {
+            return false;
+        }
+
+        this.host = host;
+        this.repeat_count = repeat_count;
+        this.data_block_size = data_block_size;
+        this.dscp = dscp;
+        this.interval_time_out = interval_time_out;
+
+        return true;
+    }
+
+    public void get_results(out string status, out string additional_info,
+                            out uint success_count, out uint failure_count,
+                            out uint32 average_response_time,
+                            out uint32 min_response_time,
+                            out uint32 max_response_time) {
+        stdout.printf("*Ping* get_results()\n");
+
+        status = this.status;
+        additional_info = this.additional_info;
+        success_count = this.success_count;
+        failure_count = this.failure_count;
+        average_response_time = this.average_response_time;
+        min_response_time = this.min_response_time;
+        max_response_time = this.max_response_time;
+    }
+}
diff --git a/src/librygel-core/rygel-bm-test-traceroute.vala b/src/librygel-core/rygel-bm-test-traceroute.vala
new file mode 100644
index 0000000..963ad7d
--- /dev/null
+++ b/src/librygel-core/rygel-bm-test-traceroute.vala
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2008 OpenedHand Ltd.
+ * Copyright (C) 2008 Zeeshan Ali <zeenix gmail com>.
+ * Copyright (C) 2013 Intel Corporation.
+ *
+ * Author: Jorn Baayen <jorn openedhand com>
+ *         Zeeshan Ali <zeenix gmail com>
+ *
+ * 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 BMTestTraceroute.
+internal class Rygel.BMTestTraceroute : BMTest {
+
+    private string host;
+    private uint32 wait_time_out;
+    private uint data_block_size;
+    private uint max_hop_count;
+    private uint dscp;
+
+    private string status;
+    private string additional_info;
+    private uint32 response_time;
+    private string hop_hosts;
+
+    private static const uint TRACEROUTE_MIN_REQUEST_TIMEOUT = 1000;
+    private static const uint TRACEROUTE_MAX_REQUEST_TIMEOUT = 30000;
+    private static const uint TRACEROUTE_DEFAULT_REQUEST_TIMEOUT = 5000;
+    private static const uint TRACEROUTE_MIN_DATA_BLOCK_SIZE = 20;
+    private static const uint TRACEROUTE_MAX_DATA_BLOCK_SIZE = 2048;
+    private static const uint TRACEROUTE_DEFAULT_DATA_BLOCK_SIZE = 32;
+    private static const uint TRACEROUTE_MIN_DSCP = 1;
+    private static const uint TRACEROUTE_MAX_DSCP = 64;
+    private static const uint TRACEROUTE_DEFAULT_DSCP = 30;
+    private static const uint TRACEROUTE_MIN_HOPS = 1;
+    private static const uint TRACEROUTE_MAX_HOPS = 64;
+    private static const uint TRACEROUTE_DEFAULT_HOPS = 30;
+    private static const uint TRACEROUTE_MAX_HOSTS = 2048;
+    private static const uint TRACEROUTE_MAX_RESULT_SIZE = 4;
+
+    public BMTestTraceroute() {
+        base("Traceroute");
+    }
+
+    public override void execute() {
+        stdout.printf("*Traceroute* execute()\n");
+    }
+
+    public override void cancel() {
+        stdout.printf("*Traceroute* cancel()\n");
+    }
+
+    public bool init(string host, uint32 wait_time_out, uint data_block_size,
+                     uint max_hop_count, uint dscp) {
+        stdout.printf("*Traceroute* init()\n");
+
+        if (host == null || host == "") {
+            return false;
+        }
+
+        if (wait_time_out == 0) {
+            wait_time_out = TRACEROUTE_DEFAULT_REQUEST_TIMEOUT;
+        } else if (wait_time_out < TRACEROUTE_MIN_REQUEST_TIMEOUT &&
+                   wait_time_out > TRACEROUTE_MAX_REQUEST_TIMEOUT) {
+            return false;
+        }
+
+        if (data_block_size == 0) {
+            data_block_size = TRACEROUTE_DEFAULT_DATA_BLOCK_SIZE;
+        } else if (data_block_size < TRACEROUTE_MIN_DATA_BLOCK_SIZE &&
+                   data_block_size > TRACEROUTE_MAX_DATA_BLOCK_SIZE) {
+            return false;
+        }
+
+        if (max_hop_count == 0) {
+            max_hop_count = TRACEROUTE_DEFAULT_HOPS;
+        } else if (max_hop_count < TRACEROUTE_MIN_HOPS &&
+                 max_hop_count > TRACEROUTE_MAX_HOPS) {
+            return false;
+        }
+
+        if (dscp == 0) {
+            dscp = TRACEROUTE_DEFAULT_DSCP;
+        } else if (dscp < TRACEROUTE_MIN_DSCP && dscp > TRACEROUTE_MAX_DSCP) {
+            return false;
+        }
+
+        this.host = host;
+        this.wait_time_out = wait_time_out;
+        this.data_block_size = data_block_size;
+        this.max_hop_count = max_hop_count;
+        this.dscp = dscp;
+
+        return true;
+    }
+
+    public void get_results(out string status, out string additional_info,
+                            out uint32 response_time, out string hop_hosts) {
+        stdout.printf("*Traceroute* get_results()\n");
+
+        status = this.status;
+        additional_info = this.additional_info;
+        response_time = this.response_time;
+        hop_hosts = this.hop_hosts;
+    }
+}
diff --git a/src/librygel-core/rygel-bm-test.vala b/src/librygel-core/rygel-bm-test.vala
new file mode 100644
index 0000000..fb2f6ae
--- /dev/null
+++ b/src/librygel-core/rygel-bm-test.vala
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2008 OpenedHand Ltd.
+ * Copyright (C) 2008 Zeeshan Ali <zeenix gmail com>.
+ * Copyright (C) 2013 Intel Corporation.
+ *
+ * Author: Jorn Baayen <jorn openedhand com>
+ *         Zeeshan Ali <zeenix gmail com>
+ *
+ * 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 BMTest.
+internal abstract class Rygel.BMTest : Object {
+    public string type;
+    public string state;
+    public string id;
+
+    public BMTest(string type) {
+        this.type = type;
+        this.state = "Requested";
+        this.id = null;
+    }
+
+    public abstract void execute();
+
+    public abstract void cancel();
+}
diff --git a/src/librygel-renderer/rygel-media-renderer-plugin.vala 
b/src/librygel-renderer/rygel-media-renderer-plugin.vala
index ebb0ebe..d8872d0 100644
--- a/src/librygel-renderer/rygel-media-renderer-plugin.vala
+++ b/src/librygel-renderer/rygel-media-renderer-plugin.vala
@@ -103,6 +103,13 @@ public class Rygel.MediaRendererPlugin : Rygel.Plugin {
                                      RenderingControl.DESCRIPTION_PATH,
                                      typeof (RenderingControl));
         this.add_resource (resource);
+
+        resource = new ResourceInfo (BasicManagement.UPNP_ID,
+                                     BasicManagement.UPNP_TYPE,
+                                     BasicManagement.DESCRIPTION_PATH,
+                                     typeof (BasicManagement));
+        this.add_resource (resource);
+
     }
 
     public virtual MediaPlayer? get_player () {
@@ -123,7 +130,8 @@ public class Rygel.MediaRendererPlugin : Rygel.Plugin {
                                       throws Error {
         string[] services = { AVTransport.UPNP_TYPE,
                               RenderingControl.UPNP_TYPE,
-                              ConnectionManager.UPNP_TYPE };
+                              ConnectionManager.UPNP_TYPE,
+                              BasicManagement.UPNP_TYPE };
         var v1_hacks = new V1Hacks (DMR, services);
         v1_hacks.apply_on_device (device, description_path);
     }


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