buffet: Add device state manager

Added StateManager class to buffet and all the internals to
load vendor-provided state definition fragments, apply state
property defaults, expose the state property values over D-Bus
to be updated by daemons (using Buffet.UpdateState method) and
sent the current device state to GCD server as part of device
draft provided during device registration.

BUG=chromium:415364
TEST=FEATURES=test emerge-link buffet

Change-Id: I78e470c98d906064dfbe925614613ee6a91ff3cf
Reviewed-on: https://chromium-review.googlesource.com/218743
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/states/state_package.cc b/buffet/states/state_package.cc
new file mode 100644
index 0000000..ce71c50
--- /dev/null
+++ b/buffet/states/state_package.cc
@@ -0,0 +1,116 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "buffet/states/state_package.h"
+
+#include <base/logging.h>
+#include <base/values.h>
+#include <chromeos/dbus/data_serialization.h>
+
+#include "buffet/commands/prop_types.h"
+#include "buffet/commands/prop_values.h"
+#include "buffet/commands/schema_utils.h"
+#include "buffet/states/error_codes.h"
+
+namespace buffet {
+
+StatePackage::StatePackage(const std::string& name) : name_(name) {
+}
+
+bool StatePackage::AddSchemaFromJson(const base::DictionaryValue* json,
+                                     chromeos::ErrorPtr* error) {
+  ObjectSchema schema;
+  if (!schema.FromJson(json, nullptr, error))
+    return false;
+
+  // Scan first to make sure we have no property redefinitions.
+  for (const auto& pair : schema.GetProps()) {
+    if (types_.GetProp(pair.first)) {
+      chromeos::Error::AddToPrintf(error, errors::state::kDomain,
+                                    errors::state::kPropertyRedefinition,
+                                    "State property '%s.%s' is already defined",
+                                    name_.c_str(), pair.first.c_str());
+      return false;
+    }
+  }
+
+  // Now move all the properties to |types_| object.
+  for (const auto& pair : schema.GetProps()) {
+    types_.AddProp(pair.first, pair.second);
+    // Create default value for this state property.
+    values_.insert(std::make_pair(pair.first, pair.second->CreateValue()));
+  }
+
+  return true;
+}
+
+bool StatePackage::AddValuesFromJson(const base::DictionaryValue* json,
+                                     chromeos::ErrorPtr* error) {
+  base::DictionaryValue::Iterator iter(*json);
+  while (!iter.IsAtEnd()) {
+    std::string property_name = iter.key();
+    auto it = values_.find(property_name);
+    if (it == values_.end()) {
+      chromeos::Error::AddToPrintf(error, errors::state::kDomain,
+                                    errors::state::kPropertyNotDefined,
+                                    "State property '%s.%s' is not defined",
+                                    name_.c_str(), property_name.c_str());
+      return false;
+    }
+    auto new_value = it->second->GetPropType()->CreateValue();
+    if (!new_value->FromJson(&iter.value(), error))
+      return false;
+    it->second = new_value;
+    iter.Advance();
+  }
+  return true;
+}
+
+std::unique_ptr<base::DictionaryValue> StatePackage::GetValuesAsJson(
+      chromeos::ErrorPtr* error) const {
+  std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+  for (const auto& pair : values_) {
+    auto value = pair.second->ToJson(error);
+    if (!value) {
+      dict.reset();
+      break;
+    }
+    dict->SetWithoutPathExpansion(pair.first, value.release());
+  }
+  return dict;
+}
+
+chromeos::Any StatePackage::GetPropertyValue(const std::string& property_name,
+                                             chromeos::ErrorPtr* error) const {
+  auto it = values_.find(property_name);
+  if (it == values_.end()) {
+    chromeos::Error::AddToPrintf(error, errors::state::kDomain,
+                                  errors::state::kPropertyNotDefined,
+                                  "State property '%s.%s' is not defined",
+                                  name_.c_str(), property_name.c_str());
+    return chromeos::Any();
+  }
+  return PropValueToDBusVariant(it->second.get());
+}
+
+bool StatePackage::SetPropertyValue(const std::string& property_name,
+                                    const chromeos::Any& value,
+                                    chromeos::ErrorPtr* error) {
+  auto it = values_.find(property_name);
+  if (it == values_.end()) {
+    chromeos::Error::AddToPrintf(error, errors::state::kDomain,
+                                  errors::state::kPropertyNotDefined,
+                                  "State property '%s.%s' is not defined",
+                                  name_.c_str(), property_name.c_str());
+    return false;
+  }
+  auto new_value = PropValueFromDBusVariant(it->second->GetPropType(),
+                                            value, error);
+  if (!new_value)
+    return false;
+  it->second = new_value;
+  return true;
+}
+
+}  // namespace buffet