shill: Service: Implement "SetProperties" DBus call

Configure multiple Service properties at once in a single call.
This method differs from Manager::ConfigureService in two important
ways: Firstly, since this is a service method, the service is
found via its RPC identifier as opposed to its GUID or other
properties.  Secondly, only the service properties are set -- it
is not assigned a profile like ConfigureService -- in fact the
"Profile" property is silently ignored if provided to this call.

BUG=None
TEST=Unit tests

Change-Id: Ied4b75184bc4c27d88652dc14d14ef3f49b1574e
Reviewed-on: https://gerrit.chromium.org/gerrit/60775
Reviewed-by: Steven Bennetts <stevenjb@chromium.org>
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Paul Stewart <pstew@chromium.org>
diff --git a/dbus_bindings/org.chromium.flimflam.Service.xml b/dbus_bindings/org.chromium.flimflam.Service.xml
index c178c52..01240fe 100644
--- a/dbus_bindings/org.chromium.flimflam.Service.xml
+++ b/dbus_bindings/org.chromium.flimflam.Service.xml
@@ -10,6 +10,9 @@
 			<arg type="s" direction="in"/>
 			<arg type="v" direction="in"/>
 		</method>
+		<method name="SetProperties">
+			<arg type="a{sv}" direction="in"/>
+		</method>
 		<method name="ClearProperty">
 			<arg type="s" direction="in"/>
 		</method>
diff --git a/doc/service-api.txt b/doc/service-api.txt
index 907bfa9..45d10fb 100644
--- a/doc/service-api.txt
+++ b/doc/service-api.txt
@@ -22,6 +22,22 @@
 					 [service].Error.InvalidService
 					 [service].Error.InvalidPassphrase
 
+		void SetProperties(dict properties)
+
+			Set multiple properties in a Service at once.
+			Each property from the dict is applied, excluding
+			the "Profile" property and all properties intrinsic
+			to the service including "Type" for all services,
+			as well as "Mode", "SSID" and "Security" for WiFi
+			services.  In the event of multiple errors while
+			applying properties to the service, the first error
+			code is returned by this call.
+
+			Possible Errors: [service].Error.InvalidArguments
+					 [service].Error.InvalidProperty
+					 [service].Error.InvalidService
+					 [service].Error.InvalidPassphrase
+
 		void ClearProperty(string name)
 
 			Clear the value of the specified property. Only
diff --git a/manager.h b/manager.h
index f1afc96..0d86f7f 100644
--- a/manager.h
+++ b/manager.h
@@ -96,9 +96,9 @@
   ProfileRefPtr LookupProfileByRpcIdentifier(const std::string &profile_rpcid);
 
   // Called via RPC call on Service (|to_set|) to set the "Profile" property.
-  void SetProfileForService(const ServiceRefPtr &to_set,
-                            const std::string &profile,
-                            Error *error);
+  virtual void SetProfileForService(const ServiceRefPtr &to_set,
+                                    const std::string &profile,
+                                    Error *error);
 
   virtual void RegisterDevice(const DeviceRefPtr &to_manage);
   virtual void DeregisterDevice(const DeviceRefPtr &to_forget);
diff --git a/mock_manager.h b/mock_manager.h
index 8d3ab28..9ac6ce8 100644
--- a/mock_manager.h
+++ b/mock_manager.h
@@ -29,6 +29,9 @@
   MOCK_CONST_METHOD0(run_path, const base::FilePath &());
   MOCK_METHOD0(Start, void());
   MOCK_METHOD0(Stop, void());
+  MOCK_METHOD3(SetProfileForService, void(const ServiceRefPtr &to_set,
+                                          const std::string &profile,
+                                          Error *error));
   MOCK_METHOD1(RegisterDevice, void(const DeviceRefPtr &to_manage));
   MOCK_METHOD1(DeregisterDevice, void(const DeviceRefPtr &to_forget));
   MOCK_METHOD1(HasService, bool(const ServiceRefPtr &to_manage));
diff --git a/service_dbus_adaptor.cc b/service_dbus_adaptor.cc
index 1dcc351..71003d6 100644
--- a/service_dbus_adaptor.cc
+++ b/service_dbus_adaptor.cc
@@ -94,6 +94,21 @@
   DBusAdaptor::SetProperty(service_->mutable_store(), name, value, &error);
 }
 
+void ServiceDBusAdaptor::SetProperties(
+    const map<string, ::DBus::Variant> &args,
+    ::DBus::Error &error) {
+  SLOG(DBus, 2) << __func__;
+  KeyValueStore args_store;
+  Error key_value_store_error;
+  DBusAdaptor::ArgsToKeyValueStore(args, &args_store, &key_value_store_error);
+  if (key_value_store_error.ToDBusError(&error)) {
+    return;
+  }
+  Error configure_error;
+  service_->Configure(args_store, &configure_error);
+  configure_error.ToDBusError(&error);
+}
+
 void ServiceDBusAdaptor::ClearProperty(const string &name,
                                        ::DBus::Error &error) {
   SLOG(DBus, 2) << __func__ << ": " << name;
diff --git a/service_dbus_adaptor.h b/service_dbus_adaptor.h
index 2d3d451..521d064 100644
--- a/service_dbus_adaptor.h
+++ b/service_dbus_adaptor.h
@@ -54,6 +54,8 @@
   void SetProperty(const std::string& name,
                    const ::DBus::Variant& value,
                    ::DBus::Error &error);
+  void SetProperties(const std::map<std::string, ::DBus::Variant> &args,
+                     ::DBus::Error &error);
   void ClearProperty(const std::string&, ::DBus::Error &error);
   std::vector<bool> ClearProperties(const std::vector<std::string>&,
                                     ::DBus::Error &error);
diff --git a/service_unittest.cc b/service_unittest.cc
index f0dd01a..6f657cf 100644
--- a/service_unittest.cc
+++ b/service_unittest.cc
@@ -867,6 +867,16 @@
   EXPECT_FALSE(service_->auto_connect());
 }
 
+TEST_F(ServiceTest, ConfigureProfileProperty) {
+  // Ensure that the Profile property is always ignored.
+  KeyValueStore args;
+  args.SetString(flimflam::kProfileProperty, "profile");
+  Error error;
+  EXPECT_CALL(mock_manager_, SetProfileForService(_, _, _)).Times(0);
+  service_->Configure(args, &error);
+  EXPECT_TRUE(error.IsSuccess());
+}
+
 TEST_F(ServiceTest, DoPropertiesMatch) {
   service_->SetAutoConnect(false);
   const string kGUID0 = "guid_zero";