buffet: Fix state update notification to GCD cloud server
Device state notification from buffet's state manager are coming
with state property names in form "package_name.property_name" and
that's how they were reported to the GCD server instead of being
split in JSON as {"package_name": {"property_name": value}}.
Also added "buffet_client GetState" command to retrieve actual
internal device state from buffet bypassing the GCD server-dependent
GetDeviceInfo. This is useful for debugging and also in case of
local-only workflow (with no GCD server present).
Finally, modified "buffet_client UpdateState" to allow complex
non-string state properties to be set. Now this command expects
a property name followed by a properly formed json value for the
property. Strings now require double-quotes. Integers, Booleans,
lists and dictionaries can now be set too.
BUG=chromium:449250
TEST=FEATURES=test USE=examples emerge-link buffet
Deployed and tested on device
Change-Id: Id2c97b422c56ec6648994ab37bb47284a6fb2041
Reviewed-on: https://chromium-review.googlesource.com/241955
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Anton Muhin <antonm@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/buffet_client.cc b/buffet/buffet_client.cc
index 0fa3f58..10efa6e 100644
--- a/buffet/buffet_client.cc
+++ b/buffet/buffet_client.cc
@@ -7,6 +7,7 @@
#include <sysexits.h>
#include <base/command_line.h>
+#include <base/json/json_reader.h>
#include <base/logging.h>
#include <base/memory/ref_counted.h>
#include <base/strings/stringprintf.h>
@@ -40,10 +41,100 @@
- RegisterDevice param1=val1¶m2=val2...
- AddCommand '{"name":"command_name","parameters":{}}'
- UpdateState prop_name prop_value
+ - GetState
- PendingCommands
)");
}
+// Helpers for JsonToAny().
+template<typename T>
+chromeos::Any GetJsonValue(const base::Value& json,
+ bool(base::Value::*fnc)(T*) const) {
+ T val;
+ CHECK((json.*fnc)(&val));
+ return val;
+}
+
+template<typename T>
+chromeos::Any GetJsonList(const base::ListValue& list); // Prototype.
+
+// Converts a JSON value into an Any so it can be sent over D-Bus using
+// UpdateState D-Bus method from Buffet.
+chromeos::Any JsonToAny(const base::Value& json) {
+ chromeos::Any prop_value;
+ switch (json.GetType()) {
+ case base::Value::TYPE_NULL:
+ prop_value = nullptr;
+ break;
+ case base::Value::TYPE_BOOLEAN:
+ prop_value = GetJsonValue<bool>(json, &base::Value::GetAsBoolean);
+ break;
+ case base::Value::TYPE_INTEGER:
+ prop_value = GetJsonValue<int>(json, &base::Value::GetAsInteger);
+ break;
+ case base::Value::TYPE_DOUBLE:
+ prop_value = GetJsonValue<double>(json, &base::Value::GetAsDouble);
+ break;
+ case base::Value::TYPE_STRING:
+ prop_value = GetJsonValue<std::string>(json, &base::Value::GetAsString);
+ break;
+ case base::Value::TYPE_BINARY:
+ LOG(FATAL) << "Binary values should not happen";
+ break;
+ case base::Value::TYPE_DICTIONARY: {
+ const base::DictionaryValue* dict = nullptr; // Still owned by |json|.
+ CHECK(json.GetAsDictionary(&dict));
+ chromeos::VariantDictionary var_dict;
+ base::DictionaryValue::Iterator it(*dict);
+ while (!it.IsAtEnd()) {
+ var_dict.emplace(it.key(), JsonToAny(it.value()));
+ it.Advance();
+ }
+ prop_value = var_dict;
+ break;
+ }
+ case base::Value::TYPE_LIST: {
+ const base::ListValue* list = nullptr; // Still owned by |json|.
+ CHECK(json.GetAsList(&list));
+ CHECK(!list->empty()) << "Unable to deduce the type of list elements.";
+ switch ((*list->begin())->GetType()) {
+ case base::Value::TYPE_BOOLEAN:
+ prop_value = GetJsonList<bool>(*list);
+ break;
+ case base::Value::TYPE_INTEGER:
+ prop_value = GetJsonList<int>(*list);
+ break;
+ case base::Value::TYPE_DOUBLE:
+ prop_value = GetJsonList<double>(*list);
+ break;
+ case base::Value::TYPE_STRING:
+ prop_value = GetJsonList<std::string>(*list);
+ break;
+ case base::Value::TYPE_DICTIONARY:
+ prop_value = GetJsonList<chromeos::VariantDictionary>(*list);
+ break;
+ default:
+ LOG(FATAL) << "Unsupported JSON value type for list element: "
+ << (*list->begin())->GetType();
+ }
+ break;
+ }
+ default:
+ LOG(FATAL) << "Unexpected JSON value type: " << json.GetType();
+ break;
+ }
+ return prop_value;
+}
+
+template<typename T>
+chromeos::Any GetJsonList(const base::ListValue& list) {
+ std::vector<T> val;
+ val.reserve(list.GetSize());
+ for (const base::Value* v : list)
+ val.push_back(JsonToAny(*v).Get<T>());
+ return val;
+}
+
class Daemon : public chromeos::DBusDaemon {
public:
Daemon() = default;
@@ -94,6 +185,10 @@
command.compare("us") == 0) {
if (CheckArgs(command, args, 2))
PostTask(&Daemon::CallUpdateState, args.front(), args.back());
+ } else if (command.compare("GetState") == 0 ||
+ command.compare("gs") == 0) {
+ if (CheckArgs(command, args, 0))
+ PostTask(&Daemon::CallGetState);
} else if (command.compare("AddCommand") == 0 ||
command.compare("ac") == 0) {
if (CheckArgs(command, args, 1))
@@ -219,13 +314,32 @@
void CallUpdateState(const std::string& prop, const std::string& value) {
ErrorPtr error;
- chromeos::VariantDictionary property_set{{prop, value}};
+ std::string error_message;
+ std::unique_ptr<base::Value> json(base::JSONReader::ReadAndReturnError(
+ value, base::JSON_PARSE_RFC, nullptr, &error_message));
+ if (!json) {
+ Error::AddTo(&error, FROM_HERE, chromeos::errors::json::kDomain,
+ chromeos::errors::json::kParseError, error_message);
+ return ReportError(error.get());
+ }
+
+ chromeos::VariantDictionary property_set{{prop, JsonToAny(*json)}};
if (!manager_proxy_->UpdateState(property_set, &error)) {
return ReportError(error.get());
}
Quit();
}
+ void CallGetState() {
+ std::string json;
+ ErrorPtr error;
+ if (!manager_proxy_->GetState(&json, &error)) {
+ return ReportError(error.get());
+ }
+ printf("Device State: %s\n", json.c_str());
+ Quit();
+ }
+
void CallAddCommand(const std::string& command) {
ErrorPtr error;
if (!manager_proxy_->AddCommand(command, &error)) {
diff --git a/buffet/dbus_bindings/org.chromium.Buffet.Manager.xml b/buffet/dbus_bindings/org.chromium.Buffet.Manager.xml
index 4490dac..daf1458 100644
--- a/buffet/dbus_bindings/org.chromium.Buffet.Manager.xml
+++ b/buffet/dbus_bindings/org.chromium.Buffet.Manager.xml
@@ -28,6 +28,10 @@
<arg name="property_set" type="a{sv}" direction="in"/>
<annotation name="org.chromium.DBus.Method.Kind" value="async"/>
</method>
+ <method name="GetState">
+ <arg name="device_info" type="s" direction="out"/>
+ <annotation name="org.chromium.DBus.Method.Kind" value="normal"/>
+ </method>
<method name="AddCommand">
<arg name="json_command" type="s" direction="in"/>
<annotation name="org.chromium.DBus.Method.Kind" value="async"/>
diff --git a/buffet/device_registration_info.cc b/buffet/device_registration_info.cc
index 0b95c90..fb59f3b 100644
--- a/buffet/device_registration_info.cc
+++ b/buffet/device_registration_info.cc
@@ -805,7 +805,11 @@
if (!value) {
return;
}
- changes->SetWithoutPathExpansion(pair.first, value.release());
+ // The key in |pair.first| is the full property name in format
+ // "package.property_name", so must use DictionaryValue::Set() instead of
+ // DictionaryValue::SetWithoutPathExpansion to recreate the JSON
+ // property tree properly.
+ changes->Set(pair.first, value.release());
}
patch->Set("patch", changes.release());
diff --git a/buffet/manager.cc b/buffet/manager.cc
index 171eeac..04fcc36 100644
--- a/buffet/manager.cc
+++ b/buffet/manager.cc
@@ -155,6 +155,14 @@
response->Return();
}
+bool Manager::GetState(chromeos::ErrorPtr* error, std::string* state) {
+ auto json = state_manager_->GetStateValuesAsJson(error);
+ if (!json)
+ return false;
+ base::JSONWriter::Write(json.get(), state);
+ return true;
+}
+
void Manager::AddCommand(DBusMethodResponse<> response,
const std::string& json_command) {
static int next_id = 0;
diff --git a/buffet/manager.h b/buffet/manager.h
index 318ee74..ab5171f 100644
--- a/buffet/manager.h
+++ b/buffet/manager.h
@@ -63,6 +63,8 @@
// Handles calls to org.chromium.Buffet.Manager.UpdateState().
void UpdateState(DBusMethodResponse<> response,
const chromeos::VariantDictionary& property_set) override;
+ // Handles calls to org.chromium.Buffet.Manager.GetState().
+ bool GetState(chromeos::ErrorPtr* error, std::string* state) override;
// Handles calls to org.chromium.Buffet.Manager.AddCommand().
void AddCommand(DBusMethodResponse<> response,
const std::string& json_command) override;