buffet: Allow setting command results.

Next step in command results support: now there is
CommandInstance::SetResults method which allows results
modifications.

BUG=chromium:435607
TEST=cros_workon_make --test buffet

Change-Id: I1f5da9c3613a2996cea3f65f07945cc64bfeda2e
Reviewed-on: https://chromium-review.googlesource.com/231337
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Anton Muhin <antonm@chromium.org>
Commit-Queue: Anton Muhin <antonm@chromium.org>
diff --git a/buffet/commands/cloud_command_proxy.cc b/buffet/commands/cloud_command_proxy.cc
index 5cbf6e6..8aa27ba 100644
--- a/buffet/commands/cloud_command_proxy.cc
+++ b/buffet/commands/cloud_command_proxy.cc
@@ -18,6 +18,12 @@
       device_registration_info_(device_registration_info) {
 }
 
+void CloudCommandProxy::OnResultsChanged(const native_types::Object& results) {
+  base::DictionaryValue patch;
+  patch.Set("results", TypedValueToJson(results, nullptr).get());
+  device_registration_info_->UpdateCommand(command_instance_->GetID(), patch);
+}
+
 void CloudCommandProxy::OnStatusChanged(const std::string& status) {
   base::DictionaryValue patch;
   // TODO(antonm): Change status to state.
diff --git a/buffet/commands/cloud_command_proxy.h b/buffet/commands/cloud_command_proxy.h
index 0c35c0c..e78ff44 100644
--- a/buffet/commands/cloud_command_proxy.h
+++ b/buffet/commands/cloud_command_proxy.h
@@ -24,6 +24,7 @@
   ~CloudCommandProxy() override = default;
 
   // CommandProxyInterface implementation/overloads.
+  void OnResultsChanged(const native_types::Object& results) override;
   void OnStatusChanged(const std::string& status) override;
   void OnProgressChanged(int progress) override;
 
diff --git a/buffet/commands/command_dictionary.cc b/buffet/commands/command_dictionary.cc
index 930e044..58ab823 100644
--- a/buffet/commands/command_dictionary.cc
+++ b/buffet/commands/command_dictionary.cc
@@ -70,8 +70,7 @@
       const ObjectSchema* base_parameters_def = nullptr;
       const ObjectSchema* base_results_def = nullptr;
       if (base_commands) {
-        const CommandDefinition* cmd =
-            base_commands->FindCommand(full_command_name);
+        auto cmd = base_commands->FindCommand(full_command_name);
         if (cmd) {
           base_parameters_def = cmd->GetParameters().get();
           base_results_def = cmd->GetResults().get();
@@ -210,10 +209,11 @@
   return dict;
 }
 
-const CommandDefinition* CommandDictionary::FindCommand(
+std::shared_ptr<const CommandDefinition> CommandDictionary::FindCommand(
     const std::string& command_name) const {
   auto pair = definitions_.find(command_name);
-  return (pair != definitions_.end()) ? pair->second.get() : nullptr;
+  return (pair != definitions_.end()) ? pair->second :
+      std::shared_ptr<const CommandDefinition>();
 }
 
 void CommandDictionary::Clear() {
diff --git a/buffet/commands/command_dictionary.h b/buffet/commands/command_dictionary.h
index c316fd5..d051c62 100644
--- a/buffet/commands/command_dictionary.h
+++ b/buffet/commands/command_dictionary.h
@@ -69,7 +69,8 @@
   // Remove all the command definitions from the dictionary.
   void Clear();
   // Finds a definition for the given command.
-  const CommandDefinition* FindCommand(const std::string& command_name) const;
+  std::shared_ptr<const CommandDefinition> FindCommand(
+      const std::string& command_name) const;
 
  private:
   std::shared_ptr<ObjectSchema> BuildObjectSchema(
diff --git a/buffet/commands/command_instance.cc b/buffet/commands/command_instance.cc
index 6cde43e..7d54ed7 100644
--- a/buffet/commands/command_instance.cc
+++ b/buffet/commands/command_instance.cc
@@ -26,14 +26,22 @@
 const char CommandInstance::kStatusAborted[] = "aborted";
 const char CommandInstance::kStatusExpired[] = "expired";
 
-CommandInstance::CommandInstance(const std::string& name,
-                                 const std::string& category,
-                                 const native_types::Object& parameters)
-    : name_(name), category_(category), parameters_(parameters) {
+CommandInstance::CommandInstance(
+    const std::string& name,
+    const std::shared_ptr<const CommandDefinition>& command_definition,
+    const native_types::Object& parameters)
+    : name_(name),
+      command_definition_(command_definition),
+      parameters_(parameters) {
+  CHECK(command_definition_.get());
 }
 
 CommandInstance::~CommandInstance() = default;
 
+const std::string& CommandInstance::GetCategory() const {
+  return command_definition_->GetCategory();
+}
+
 std::shared_ptr<const PropValue> CommandInstance::FindParameter(
     const std::string& name) const {
   std::shared_ptr<const PropValue> value;
@@ -110,7 +118,7 @@
     return instance;
   }
   // Make sure we know how to handle the command with this name.
-  const CommandDefinition* command_def = dictionary.FindCommand(command_name);
+  auto command_def = dictionary.FindCommand(command_name);
   if (!command_def) {
     chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
                                  errors::commands::kInvalidCommandName,
@@ -120,7 +128,7 @@
   }
 
   native_types::Object parameters;
-  if (!GetCommandParameters(json, command_def, &parameters, error)) {
+  if (!GetCommandParameters(json, command_def.get(), &parameters, error)) {
     chromeos::Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
                                  errors::commands::kCommandFailed,
                                  "Failed to validate command '%s'",
@@ -128,9 +136,7 @@
     return instance;
   }
 
-  instance.reset(new CommandInstance(command_name,
-                                     command_def->GetCategory(),
-                                     parameters));
+  instance.reset(new CommandInstance(command_name, command_def, parameters));
   // TODO(antonm): Move command_id to ctor and remove setter.
   std::string command_id;
   if (json->GetStringWithoutPathExpansion(commands::attributes::kCommand_Id,
@@ -145,6 +151,17 @@
   proxies_.push_back(std::move(proxy));
 }
 
+bool CommandInstance::SetResults(const native_types::Object& results) {
+  // TODO(antonm): Add validation.
+  if (results != results_) {
+    results_ = results;
+    for (auto& proxy : proxies_) {
+      proxy->OnResultsChanged(results_);
+    }
+  }
+  return true;
+}
+
 bool CommandInstance::SetProgress(int progress) {
   if (progress < 0 || progress > 100)
     return false;
diff --git a/buffet/commands/command_instance.h b/buffet/commands/command_instance.h
index b324850..b3bd2e0 100644
--- a/buffet/commands/command_instance.h
+++ b/buffet/commands/command_instance.h
@@ -22,6 +22,7 @@
 
 namespace buffet {
 
+class CommandDefinition;
 class CommandDictionary;
 class CommandProxyInterface;
 class CommandQueue;
@@ -31,9 +32,10 @@
   // Construct a command instance given the full command |name| which must
   // be in format "<package_name>.<command_name>", a command |category| and
   // a list of parameters and their values specified in |parameters|.
-  CommandInstance(const std::string& name,
-                  const std::string& category,
-                  const native_types::Object& parameters);
+  CommandInstance(
+      const std::string& name,
+      const std::shared_ptr<const CommandDefinition>& command_definition,
+      const native_types::Object& parameters);
   ~CommandInstance();
 
   // Returns the full command ID.
@@ -41,13 +43,20 @@
   // Returns the full name of the command.
   const std::string& GetName() const { return name_; }
   // Returns the command category.
-  const std::string& GetCategory() const { return category_; }
+  const std::string& GetCategory() const;
   // Returns the command parameters and their values.
   const native_types::Object& GetParameters() const { return parameters_; }
+  // Returns the command results and their values.
+  const native_types::Object& GetResults() const { return results_; }
   // Finds a command parameter value by parameter |name|. If the parameter
   // with given name does not exist, returns null shared_ptr.
   std::shared_ptr<const PropValue> FindParameter(const std::string& name) const;
 
+  // Returns command definition.
+  std::shared_ptr<const CommandDefinition> GetCommandDefinition() const {
+    return command_definition_;
+  }
+
   // Parses a command instance JSON definition and constructs a CommandInstance
   // object, checking the JSON |value| against the command definition schema
   // found in command |dictionary|. On error, returns null unique_ptr and
@@ -66,8 +75,12 @@
   // Sets the pointer to queue this command is part of.
   void SetCommandQueue(CommandQueue* queue) { queue_ = queue; }
 
+  // Updates the command results. The |results| should match the schema.
+  // Returns false if |results| value is incorrect.
+  bool SetResults(const native_types::Object& results);
+
   // Updates the command execution progress. The |progress| must be between
-  // 0 and 100. Returns false if the progress value is incorrect.
+  // 0 and 100. Returns false if |progress| value is incorrect.
   bool SetProgress(int progress);
   // Aborts command execution.
   void Abort();
@@ -103,12 +116,12 @@
   std::string id_;
   // Full command name as "<package_name>.<command_name>".
   std::string name_;
-  // Command category. See comments for CommandDefinitions::LoadCommands for the
-  // detailed description of what command categories are and what they are used
-  // for.
-  std::string category_;
+  // Command definition.
+  std::shared_ptr<const CommandDefinition> command_definition_;
   // Command parameters and their values.
   native_types::Object parameters_;
+  // Command results.
+  native_types::Object results_;
   // Current command status.
   std::string status_ = kStatusQueued;
   // Current command execution progress.
diff --git a/buffet/commands/command_instance_unittest.cc b/buffet/commands/command_instance_unittest.cc
index d5afc70..420361e 100644
--- a/buffet/commands/command_instance_unittest.cc
+++ b/buffet/commands/command_instance_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "buffet/commands/command_dictionary.h"
 #include "buffet/commands/prop_types.h"
+#include "buffet/commands/schema_utils.h"
 #include "buffet/commands/unittest_utils.h"
 
 using buffet::unittests::CreateDictionaryValue;
@@ -65,26 +66,36 @@
 
 }  // anonymous namespace
 
-TEST(CommandInstance, Test) {
-  buffet::native_types::Object params;
+TEST_F(CommandInstanceTest, Test) {
+  buffet::StringPropType str_prop;
   buffet::IntPropType int_prop;
-  buffet::DoublePropType dbl_prop;
-  params.insert(std::make_pair("freq", dbl_prop.CreateValue(800.5, nullptr)));
-  params.insert(std::make_pair("volume", int_prop.CreateValue(100, nullptr)));
-  buffet::CommandInstance instance("robot._beep", "robotd", params);
+  buffet::native_types::Object params;
+  params["phrase"] = str_prop.CreateValue(std::string("iPityDaFool"),
+                                          nullptr);
+  params["volume"] = int_prop.CreateValue(5, nullptr);
+  buffet::CommandInstance instance("robot.speak",
+                                   dict_.FindCommand("robot.speak"),
+                                   params);
+
+  buffet::native_types::Object results;
+  results["foo"] = int_prop.CreateValue(239, nullptr);
+  instance.SetResults(results);
 
   EXPECT_EQ("", instance.GetID());
-  EXPECT_EQ("robot._beep", instance.GetName());
+  EXPECT_EQ("robot.speak", instance.GetName());
   EXPECT_EQ("robotd", instance.GetCategory());
   EXPECT_EQ(params, instance.GetParameters());
-  EXPECT_DOUBLE_EQ(800.5,
-                   instance.FindParameter("freq")->GetDouble()->GetValue());
-  EXPECT_EQ(100, instance.FindParameter("volume")->GetInt()->GetValue());
+  EXPECT_EQ("iPityDaFool",
+            instance.FindParameter("phrase")->GetString()->GetValue());
+  EXPECT_EQ(5, instance.FindParameter("volume")->GetInt()->GetValue());
   EXPECT_EQ(nullptr, instance.FindParameter("blah").get());
+  EXPECT_EQ(results, instance.GetResults());
 }
 
-TEST(CommandInstance, SetID) {
-  buffet::CommandInstance instance("robot._beep", "robotd", {});
+TEST_F(CommandInstanceTest, SetID) {
+  buffet::CommandInstance instance("robot._beep",
+                                   dict_.FindCommand("robot.speak"),
+                                   {});
   instance.SetID("command_id");
   EXPECT_EQ("command_id", instance.GetID());
 }
diff --git a/buffet/commands/command_proxy_interface.h b/buffet/commands/command_proxy_interface.h
index f9e78d6..f434c75 100644
--- a/buffet/commands/command_proxy_interface.h
+++ b/buffet/commands/command_proxy_interface.h
@@ -7,6 +7,8 @@
 
 #include <string>
 
+#include "buffet/commands/schema_utils.h"
+
 namespace buffet {
 
 // This interface lets the command instance to update its proxy of command
@@ -16,6 +18,7 @@
  public:
   virtual ~CommandProxyInterface() = default;
 
+  virtual void OnResultsChanged(const native_types::Object& results) = 0;
   virtual void OnStatusChanged(const std::string& status) = 0;
   virtual void OnProgressChanged(int progress) = 0;
 };
diff --git a/buffet/commands/command_queue_unittest.cc b/buffet/commands/command_queue_unittest.cc
index bea01c5..97431b8 100644
--- a/buffet/commands/command_queue_unittest.cc
+++ b/buffet/commands/command_queue_unittest.cc
@@ -11,14 +11,20 @@
 #include <chromeos/strings/string_utils.h>
 #include <gtest/gtest.h>
 
+#include "buffet/commands/command_definition.h"
 #include "buffet/commands/command_dispatch_interface.h"
+#include "buffet/commands/object_schema.h"
 
 namespace {
 
 std::unique_ptr<buffet::CommandInstance> CreateDummyCommandInstance(
     const std::string& name, const std::string& id) {
+  auto command_definition = std::make_shared<const buffet::CommandDefinition>(
+      "powerd",
+      std::make_shared<const buffet::ObjectSchema>(),
+      std::make_shared<const buffet::ObjectSchema>());
   auto cmd = std::unique_ptr<buffet::CommandInstance>(
-      new buffet::CommandInstance(name, "powerd", {}));
+      new buffet::CommandInstance(name, command_definition, {}));
   cmd->SetID(id);
   return cmd;
 }
diff --git a/buffet/commands/dbus_command_proxy.cc b/buffet/commands/dbus_command_proxy.cc
index 2778a78..a067772 100644
--- a/buffet/commands/dbus_command_proxy.cc
+++ b/buffet/commands/dbus_command_proxy.cc
@@ -7,7 +7,9 @@
 #include <chromeos/dbus/async_event_sequencer.h>
 #include <chromeos/dbus/exported_object_manager.h>
 
+#include "buffet/commands/command_definition.h"
 #include "buffet/commands/command_instance.h"
+#include "buffet/commands/object_schema.h"
 #include "buffet/commands/prop_constraints.h"
 #include "buffet/commands/prop_types.h"
 
@@ -34,19 +36,20 @@
   dbus_adaptor_.SetId(command_instance_->GetID());
   dbus_adaptor_.SetStatus(command_instance_->GetStatus());
   dbus_adaptor_.SetProgress(command_instance_->GetProgress());
-  // Convert a string-to-PropValue map into a string-to-Any map which can be
-  // sent over D-Bus.
-  chromeos::VariantDictionary params;
-  for (const auto& param_pair : command_instance_->GetParameters()) {
-    params.insert(std::make_pair(param_pair.first,
-                                 param_pair.second->GetValueAsAny()));
-  }
-  dbus_adaptor_.SetParameters(params);
+
+  dbus_adaptor_.SetParameters(ObjectToDBusVariant(
+      command_instance_->GetParameters()));
+  dbus_adaptor_.SetResults(ObjectToDBusVariant(
+      command_instance_->GetResults()));
 
   // Register the command DBus object and expose its methods and properties.
   dbus_object_.RegisterAsync(completion_callback);
 }
 
+void DBusCommandProxy::OnResultsChanged(const native_types::Object& results) {
+  dbus_adaptor_.SetResults(ObjectToDBusVariant(results));
+}
+
 void DBusCommandProxy::OnStatusChanged(const std::string& status) {
   dbus_adaptor_.SetStatus(status);
 }
@@ -71,6 +74,20 @@
   return true;
 }
 
+bool DBusCommandProxy::SetResults(chromeos::ErrorPtr* error,
+                                  const chromeos::VariantDictionary& results) {
+  LOG(INFO) << "Received call to Command<"
+            << command_instance_->GetName() << ">::SetResults()";
+
+  auto results_schema = command_instance_->GetCommandDefinition()->GetResults();
+  native_types::Object obj;
+  if (!ObjectFromDBusVariant(results_schema.get(), results, &obj, error))
+    return false;
+
+  command_instance_->SetResults(obj);
+  return true;
+}
+
 void DBusCommandProxy::Abort() {
   LOG(INFO) << "Received call to Command<"
             << command_instance_->GetName() << ">::Abort()";
diff --git a/buffet/commands/dbus_command_proxy.h b/buffet/commands/dbus_command_proxy.h
index f3f3e4e..2d0f478 100644
--- a/buffet/commands/dbus_command_proxy.h
+++ b/buffet/commands/dbus_command_proxy.h
@@ -39,12 +39,16 @@
           completion_callback);
 
   // CommandProxyInterface implementation/overloads.
+  void OnResultsChanged(const native_types::Object& results) override;
   void OnStatusChanged(const std::string& status) override;
   void OnProgressChanged(int progress) override;
 
  private:
   // Handles calls to org.chromium.Buffet.Command.SetProgress(progress).
   bool SetProgress(chromeos::ErrorPtr* error, int32_t progress) override;
+  // Handles calls to org.chromium.Buffet.Command.SetResults(results).
+  bool SetResults(chromeos::ErrorPtr* error,
+                  const chromeos::VariantDictionary& results) override;
   // Handles calls to org.chromium.Buffet.Command.Abort().
   void Abort() override;
   // Handles calls to org.chromium.Buffet.Command.Cancel().
diff --git a/buffet/commands/dbus_command_proxy_unittest.cc b/buffet/commands/dbus_command_proxy_unittest.cc
index a810464..eac207b 100644
--- a/buffet/commands/dbus_command_proxy_unittest.cc
+++ b/buffet/commands/dbus_command_proxy_unittest.cc
@@ -65,7 +65,14 @@
               'enum': ['_withAirFlip', '_withSpin', '_withKick']
             }
           },
-          'results': {}
+          'results': {
+            'foo': {
+              'type': 'integer'
+            },
+            'bar': {
+              'type': 'string'
+            }
+          }
         }
       }
     })");
@@ -129,6 +136,10 @@
     return GetCommandProxy()->dbus_adaptor_.GetParameters();
   }
 
+  VariantDictionary GetResults() const {
+    return GetCommandProxy()->dbus_adaptor_.GetResults();
+  }
+
   std::unique_ptr<dbus::Response> CallMethod(
       const std::string& method_name,
       const std::function<void(dbus::MessageWriter*)>& param_callback) {
@@ -185,9 +196,12 @@
     {"height", int32_t{53}},
     {"_jumpType", std::string{"_withKick"}},
   };
+  VariantDictionary results;
+
   EXPECT_EQ(CommandInstance::kStatusQueued, GetStatus());
   EXPECT_EQ(0, GetProgress());
   EXPECT_EQ(params, GetParameters());
+  EXPECT_EQ(results, GetResults());
   EXPECT_EQ("robot.jump",
             GetPropertyValue<std::string>(dbus_constants::kCommandName));
   EXPECT_EQ(kTestCommandCategoty,
@@ -200,6 +214,9 @@
   EXPECT_EQ(params,
             GetPropertyValue<VariantDictionary>(
                 dbus_constants::kCommandParameters));
+  EXPECT_EQ(results,
+            GetPropertyValue<VariantDictionary>(
+                dbus_constants::kCommandResults));
 }
 
 TEST_F(DBusCommandProxyTest, SetProgress) {
@@ -226,6 +243,35 @@
   EXPECT_EQ(0, GetProgress());
 }
 
+TEST_F(DBusCommandProxyTest, SetResults) {
+  EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(1);
+  const VariantDictionary results = {
+    {"foo", int32_t{42}},
+    {"bar", std::string{"foobar"}},
+  };
+  auto response = CallMethod(dbus_constants::kCommandSetResults,
+                             [results](dbus::MessageWriter* writer) {
+    chromeos::dbus_utils::AppendValueToWriter(writer, results);
+  });
+  VerifyResponse(response, {});
+  EXPECT_EQ(results, GetResults());
+  EXPECT_EQ(results,
+            GetPropertyValue<VariantDictionary>(
+                dbus_constants::kCommandResults));
+}
+
+TEST_F(DBusCommandProxyTest, SetResults_UnknownProperty) {
+  EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(0);
+  const VariantDictionary results = {
+    {"quux", int32_t{42}},
+  };
+  auto response = CallMethod(dbus_constants::kCommandSetResults,
+                             [results](dbus::MessageWriter* writer) {
+    chromeos::dbus_utils::AppendValueToWriter(writer, results);
+  });
+  EXPECT_TRUE(IsResponseError(response));
+}
+
 TEST_F(DBusCommandProxyTest, Abort) {
   EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(1);
   auto response = CallMethod(dbus_constants::kCommandAbort, {});
diff --git a/buffet/commands/schema_utils.cc b/buffet/commands/schema_utils.cc
index 8e83b1c..043f0ae 100644
--- a/buffet/commands/schema_utils.cc
+++ b/buffet/commands/schema_utils.cc
@@ -9,7 +9,6 @@
 #include <string>
 
 #include <base/json/json_writer.h>
-#include <chromeos/variant_dictionary.h>
 
 #include "buffet/commands/object_schema.h"
 #include "buffet/commands/prop_types.h"
@@ -202,10 +201,13 @@
 chromeos::Any PropValueToDBusVariant(const PropValue* value) {
   if (value->GetType() != ValueType::Object)
     return value->GetValueAsAny();
-  // Special case for object types.
-  // Convert native_types::Object to chromeos::VariantDictionary
+  return ObjectToDBusVariant(value->GetObject()->GetValue());
+}
+
+chromeos::VariantDictionary
+ObjectToDBusVariant(const native_types::Object& object) {
   chromeos::VariantDictionary dict;
-  for (const auto& pair : value->GetObject()->GetValue()) {
+  for (const auto& pair : object) {
     // Since we are inserting the elements from native_types::Object which is
     // a map, the keys are already sorted. So use the "end()" position as a hint
     // for dict.insert() so the destination map can optimize its insertion
@@ -213,7 +215,7 @@
     chromeos::Any prop = PropValueToDBusVariant(pair.second.get());
     dict.emplace_hint(dict.end(), pair.first, std::move(prop));
   }
-  return chromeos::Any(std::move(dict));
+  return dict;
 }
 
 std::shared_ptr<const PropValue> PropValueFromDBusVariant(
@@ -221,28 +223,41 @@
     const chromeos::Any& value,
     chromeos::ErrorPtr* error) {
   std::shared_ptr<const PropValue> result;
-  if (type->GetType() != ValueType::Object) {
+  if (type->GetType() == ValueType::Object) {
+    // Special case for object types.
+    // We expect the |value| to contain chromeos::VariantDictionary, while
+    // PropValue must use native_types::Object instead. Do the conversion.
+    if (!value.IsTypeCompatible<chromeos::VariantDictionary>()) {
+      type->GenerateErrorValueTypeMismatch(error);
+      return {};
+    }
+    CHECK(nullptr != type->GetObjectSchemaPtr())
+        << "An object type must have a schema defined for it";
+    native_types::Object obj;
+    if (!ObjectFromDBusVariant(type->GetObjectSchemaPtr(),
+                               value.Get<chromeos::VariantDictionary>(),
+                               &obj,
+                               error))
+      return {};
+
+    result = type->CreateValue(std::move(obj), error);
+  } else {
     result = type->CreateValue(value, error);
-    if (result && !type->ValidateConstraints(*result, error))
-      result.reset();
-    return result;
   }
 
-  // Special case for object types.
-  // We expect the |value| to contain chromeos::VariantDictionary, while
-  // PropValue must use native_types::Object instead. Do the conversion.
-  if (!value.IsTypeCompatible<chromeos::VariantDictionary>()) {
-    type->GenerateErrorValueTypeMismatch(error);
-    return result;
-  }
-  const auto& dict = value.Get<chromeos::VariantDictionary>();
-  native_types::Object obj;
-  CHECK(nullptr != type->GetObjectSchemaPtr())
-      << "An object type must have a schema defined for it";
+  if (result && !type->ValidateConstraints(*result, error))
+    result.reset();
+  return result;
+}
+
+bool ObjectFromDBusVariant(const ObjectSchema* object_schema,
+                           const chromeos::VariantDictionary& dict,
+                           native_types::Object* obj,
+                           chromeos::ErrorPtr* error) {
   std::set<std::string> keys_processed;
   // First go over all object parameters defined by type's object schema and
   // extract the corresponding parameters from the source dictionary.
-  for (const auto& pair : type->GetObjectSchemaPtr()->GetProps()) {
+  for (const auto& pair : object_schema->GetProps()) {
     const PropValue* def_value = pair.second->GetDefaultValue();
     auto it = dict.find(pair.first);
     if (it != dict.end()) {
@@ -255,22 +270,22 @@
                                      errors::commands::kInvalidPropValue,
                                      "Invalid value for property '%s'",
                                      pair.first.c_str());
-        return result;
+        return false;
       }
-      obj.emplace_hint(obj.end(), pair.first, std::move(prop_value));
+      obj->emplace_hint(obj->end(), pair.first, std::move(prop_value));
     } else if (def_value) {
       std::shared_ptr<const PropValue> prop_value = def_value->Clone();
-      obj.emplace_hint(obj.end(), pair.first, std::move(prop_value));
+      obj->emplace_hint(obj->end(), pair.first, std::move(prop_value));
     } else {
       ErrorMissingProperty(error, pair.first.c_str());
-      return result;
+      return false;
     }
     keys_processed.insert(pair.first);
   }
 
   // Make sure that we processed all the necessary properties and there weren't
   // any extra (unknown) ones specified, unless the schema allows them.
-  if (!type->GetObjectSchemaPtr()->GetExtraPropertiesAllowed()) {
+  if (!object_schema->GetExtraPropertiesAllowed()) {
     for (const auto& pair : dict) {
       if (keys_processed.find(pair.first) == keys_processed.end()) {
         chromeos::Error::AddToPrintf(error, FROM_HERE,
@@ -278,15 +293,12 @@
                                      errors::commands::kUnknownProperty,
                                      "Unrecognized property '%s'",
                                      pair.first.c_str());
-        return result;
+        return false;
       }
     }
   }
 
-  result = type->CreateValue(std::move(obj), error);
-  if (result && !type->ValidateConstraints(*result, error))
-    result.reset();
-  return result;
+  return true;
 }
 
 }  // namespace buffet
diff --git a/buffet/commands/schema_utils.h b/buffet/commands/schema_utils.h
index 33261f0..597a580 100644
--- a/buffet/commands/schema_utils.h
+++ b/buffet/commands/schema_utils.h
@@ -15,12 +15,14 @@
 #include <base/values.h>
 #include <chromeos/any.h>
 #include <chromeos/errors/error.h>
+#include <chromeos/variant_dictionary.h>
 
 namespace buffet {
 
 class PropType;
 class PropValue;
 class ObjectSchema;
+class ObjectValue;
 
 namespace native_types {
 // C++ representation of object values.
@@ -122,6 +124,10 @@
 // Has special handling for Object types where native_types::Object are
 // converted to chromeos::VariantDictionary.
 chromeos::Any PropValueToDBusVariant(const PropValue* value);
+// Converts native_types::Object to chromeos::VariantDictionary
+// with proper conversion of all nested properties.
+chromeos::VariantDictionary
+ObjectToDBusVariant(const native_types::Object& object);
 // Converts D-Bus variant to PropValue.
 // Has special handling for Object types where chromeos::VariantDictionary
 // is converted to native_types::Object.
@@ -129,6 +135,11 @@
     const PropType* type,
     const chromeos::Any& value,
     chromeos::ErrorPtr* error);
+// Converts D-Bus variant to ObjectValue.
+bool ObjectFromDBusVariant(const ObjectSchema* object_schema,
+                           const chromeos::VariantDictionary& dict,
+                           native_types::Object* obj,
+                           chromeos::ErrorPtr* error);
 
 }  // namespace buffet
 
diff --git a/buffet/dbus_bindings/org.chromium.Buffet.Command.xml b/buffet/dbus_bindings/org.chromium.Buffet.Command.xml
index 89d9efd..2f8ce2b 100644
--- a/buffet/dbus_bindings/org.chromium.Buffet.Command.xml
+++ b/buffet/dbus_bindings/org.chromium.Buffet.Command.xml
@@ -7,6 +7,10 @@
       <arg name="progress" type="i" direction="in"/>
       <annotation name="org.chromium.DBus.Method.Kind" value="normal"/>
     </method>
+    <method name="SetResults">
+      <arg name="results" type="a{sv}" direction="in"/>
+      <annotation name="org.chromium.DBus.Method.Kind" value="normal"/>
+    </method>
     <method name="Abort">
       <annotation name="org.chromium.DBus.Method.Kind" value="simple"/>
     </method>
@@ -22,5 +26,6 @@
     <property name="Status" type="s" access="read"/>
     <property name="Progress" type="i" access="read"/>
     <property name="Parameters" type="a{sv}" access="read"/>
+    <property name="Results" type="a{sv}" access="read"/>
   </interface>
 </node>
diff --git a/buffet/libbuffet/dbus_constants.cc b/buffet/libbuffet/dbus_constants.cc
index 16cdb36..475ffba 100644
--- a/buffet/libbuffet/dbus_constants.cc
+++ b/buffet/libbuffet/dbus_constants.cc
@@ -26,6 +26,7 @@
 const char kCommandInterface[] = "org.chromium.Buffet.Command";
 const char kCommandServicePathPrefix[] = "/org/chromium/Buffet/commands/";
 
+const char kCommandSetResults[] = "SetResults";
 const char kCommandSetProgress[] = "SetProgress";
 const char kCommandAbort[] = "Abort";
 const char kCommandCancel[] = "Cancel";
@@ -37,6 +38,7 @@
 const char kCommandStatus[] = "Status";
 const char kCommandProgress[] = "Progress";
 const char kCommandParameters[] = "Parameters";
+const char kCommandResults[] = "Results";
 
 }  // namespace dbus_constants
 
diff --git a/buffet/libbuffet/dbus_constants.h b/buffet/libbuffet/dbus_constants.h
index e0dd22a..22a1447 100644
--- a/buffet/libbuffet/dbus_constants.h
+++ b/buffet/libbuffet/dbus_constants.h
@@ -35,6 +35,7 @@
 LIBBUFFET_EXPORT extern const char kCommandServicePathPrefix[];
 
 // Methods exposed as part of kCommandInterface.
+LIBBUFFET_EXPORT extern const char kCommandSetResults[];
 LIBBUFFET_EXPORT extern const char kCommandSetProgress[];
 LIBBUFFET_EXPORT extern const char kCommandAbort[];
 LIBBUFFET_EXPORT extern const char kCommandCancel[];
@@ -47,6 +48,7 @@
 LIBBUFFET_EXPORT extern const char kCommandStatus[];
 LIBBUFFET_EXPORT extern const char kCommandProgress[];
 LIBBUFFET_EXPORT extern const char kCommandParameters[];
+LIBBUFFET_EXPORT extern const char kCommandResults[];
 
 }  // namespace dbus_constants