buffet: load standard GCD command definitions in CommandManager

Changed CommandManager to load the base command definitions
and use it as a base schema for loading device-specific commands
and make sure device vendors are not redefining standard commands
in breaking manner.

BUG=chromium:374861
TEST=USE=buffet P2_TEST_FILTER="buffet::*" FEATURES=test emerge-link platform2

Change-Id: I5f2d5500bc90ef918a8a6df1242bdd1fe3b78615
Reviewed-on: https://chromium-review.googlesource.com/209175
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Christopher Wiley <wiley@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/commands/command_dictionary.cc b/buffet/commands/command_dictionary.cc
index 38c8bce..dcba8c9 100644
--- a/buffet/commands/command_dictionary.cc
+++ b/buffet/commands/command_dictionary.cc
@@ -24,6 +24,7 @@
 
 bool CommandDictionary::LoadCommands(const base::DictionaryValue& json,
                                      const std::string& category,
+                                     const CommandDictionary* base_commands,
                                      ErrorPtr* error) {
   std::map<std::string, std::shared_ptr<const CommandDefinition>> new_defs;
 
@@ -32,29 +33,37 @@
   // Iterate over packages
   base::DictionaryValue::Iterator package_iter(json);
   while (!package_iter.IsAtEnd()) {
-    std::string package = package_iter.key();
+    std::string package_name = package_iter.key();
     const base::DictionaryValue* package_value = nullptr;
     if (!package_iter.value().GetAsDictionary(&package_value)) {
       Error::AddToPrintf(error, errors::commands::kDomain,
                          errors::commands::kTypeMismatch,
                          "Expecting an object for package '%s'",
-                         package.c_str());
+                         package_name.c_str());
       return false;
     }
     // Iterate over command definitions within the current package.
     base::DictionaryValue::Iterator command_iter(*package_value);
     while (!command_iter.IsAtEnd()) {
-      std::string command = command_iter.key();
+      std::string command_name = command_iter.key();
+      if (command_name.empty()) {
+        Error::AddToPrintf(error, errors::commands::kDomain,
+                           errors::commands::kInvalidCommandName,
+                           "Unnamed command encountered in package '%s'",
+                           package_name.c_str());
+        return false;
+      }
       const base::DictionaryValue* command_value = nullptr;
       if (!command_iter.value().GetAsDictionary(&command_value)) {
         Error::AddToPrintf(error, errors::commands::kDomain,
                            errors::commands::kTypeMismatch,
                            "Expecting an object for command '%s'",
-                           command.c_str());
+                           command_name.c_str());
         return false;
       }
       // Construct the compound command name as "pkg_name.cmd_name".
-      std::string command_name = string_utils::Join('.', package, command);
+      std::string full_command_name = string_utils::Join('.', package_name,
+                                                         command_name);
       // Get the "parameters" definition of the command and read it into
       // an object schema.
       const base::DictionaryValue* command_schema_def = nullptr;
@@ -63,21 +72,45 @@
         Error::AddToPrintf(error, errors::commands::kDomain,
                            errors::commands::kPropertyMissing,
                            "Command definition '%s' is missing property '%s'",
-                           command_name.c_str(),
+                           full_command_name.c_str(),
                            commands::attributes::kCommand_Parameters);
         return false;
       }
+
+      const ObjectSchema* base_def = nullptr;
+      if (base_commands) {
+        const CommandDefinition* cmd =
+            base_commands->FindCommand(full_command_name);
+        if (cmd)
+          base_def = cmd->GetParameters().get();
+
+        // If the base command dictionary was provided but the command was not
+        // found in it, this must be a custom (vendor) command. GCD spec states
+        // that all custom command names must begin with "_". Let's enforce
+        // this rule here.
+        if (!base_def) {
+          if (command_name.front() != '_') {
+            Error::AddToPrintf(error, errors::commands::kDomain,
+                               errors::commands::kInvalidCommandName,
+                               "The name of custom command '%s' in package '%s'"
+                               " must start with '_'",
+                               command_name.c_str(), package_name.c_str());
+            return false;
+          }
+        }
+      }
+
       auto command_schema = std::make_shared<ObjectSchema>();
-      if (!command_schema->FromJson(command_schema_def, nullptr, error)) {
+      if (!command_schema->FromJson(command_schema_def, base_def, error)) {
         Error::AddToPrintf(error, errors::commands::kDomain,
                            errors::commands::kInvalidObjectSchema,
                            "Invalid definition for command '%s'",
-                           command_name.c_str());
+                           full_command_name.c_str());
         return false;
       }
       auto command_def = std::make_shared<CommandDefinition>(category,
                                                              command_schema);
-      new_defs.insert(std::make_pair(command_name, command_def));
+      new_defs.insert(std::make_pair(full_command_name, command_def));
 
       command_iter.Advance();
     }