[rygel] core: Add UPnP BasicManagement service support
- From: Jens Georg <jensgeorg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [rygel] core: Add UPnP BasicManagement service support
- Date: Sat, 2 Nov 2013 17:30:45 +0000 (UTC)
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]