/* * 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.1 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 library; 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 tests_map; private HashMap> test_ids_by_type; private uint current_id; protected string device_status; public override void constructed () { base.constructed (); this.tests_map = new HashMap (); this.test_ids_by_type = new HashMap (); var now = new GLib.DateTime.now_utc (); // TODO: Replace with to_iso8601() once glib 2.62 is a bit more widespread this.device_status = "OK,%sZ".printf (now.format ("%Y-%m-%dT%H:%M:%S")); 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 type_test_ids; type_test_ids = this.test_ids_by_type[test.method_type]; if (type_test_ids == null) { type_test_ids = new LinkedList (); 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_success (); } 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_success (); } 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); if (host == "") { warning (_("Cannot run “Ping” action: Host is empty")); action.return_error (402, _("Invalid argument")); return; } 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; } var ping = bm_test as BasicManagementTestPing; if (ping == null) { action.return_error (707, _("Wrong Test Type")); return; } string status, additional_info; uint success_count, failure_count; uint32 avg_response_time, min_response_time, max_response_time; ping.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_success (); } 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); if (hostname == "") { warning (_("Cannot run “NSLookup” action: HostName is empty")); action.return_error (402, _("Invalid argument")); return; } 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; } var nslookup = bm_test as BasicManagementTestNSLookup; if (nslookup == null) { action.return_error (707, _("Wrong Test Type")); return; } string status, additional_info, result; uint success_count; nslookup.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_success (); } 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); if (host == "") { warning (_("Cannot run “Traceroute” action: Host is empty")); action.return_error (402, _("Invalid argument")); return; } 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; } var traceroute = bm_test as BasicManagementTestTraceroute; if (traceroute == null) { action.return_error (707, _("Wrong Test Type")); return; } string status, additional_info, hop_hosts; uint32 response_time; traceroute.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_success (); } 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_success (); } 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_success (); } 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_success (); } 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_success (); } }