Merge branch 'rewrite-buffet' into merge-buffet
diff --git a/buffet/COMMIT-QUEUE.ini b/buffet/COMMIT-QUEUE.ini
new file mode 100644
index 0000000..f5a018a
--- /dev/null
+++ b/buffet/COMMIT-QUEUE.ini
@@ -0,0 +1,11 @@
+# Copyright 2015 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.
+
+# Per-project Commit Queue settings.
+# Documentation: http://goo.gl/5J7oND
+
+[GENERAL]
+
+# Board-specific pre-cq
+pre-cq-configs: gizmo-pre-cq
diff --git a/buffet/HACKING b/buffet/HACKING
new file mode 100644
index 0000000..95c6193
--- /dev/null
+++ b/buffet/HACKING
@@ -0,0 +1,147 @@
+This file is intended for onboarding new SWEs hacking on buffet.
+
+ A. Common workflows.
+ B. Registering your DUT.
+
+---
+A. COMMON WORKFLOWS
+---
+
+Some common workflows for developing with buffet:
+
+# Tell portage that you'd like to make local changes to Buffet:
+cros_workon start --board=${BOARD} buffet
+
+# Edit files in platform2/buffet/
+vim ...
+
+# Compile and install those changes into the chroot:
+USE=buffet emerge-${BOARD} buffet
+
+# Compile and run buffet unittests
+USE=buffet FEATURES=test emerge-${BOARD} buffet
+
+# Deploy the most recently built version of buffet to a DUT:
+cros deploy --board=${BOARD} <remote host> buffet
+
+#To enable additional debug logging in buffet daemon, run it as:
+# buffet --v=<level>, where <level> is verbosity level of debug info:
+# 1 - enable additional tracing of internal object construction and destruction
+# 2 - add tracing of request and response data sent over HTTP (beware of
+# privacy concerns).
+# 3 - enable low-level CURL tracing for HTTP communication.
+buffet --v=2
+
+---
+B. REGISTERING YOUR DUT
+---
+
+This process in described in great detail at
+
+ https://developers.google.com/cloud-devices/v1/dev-guides/getting-started/register
+
+but since these instructions are generic and comprehensive, here's
+exactly what you need to do to get started when working with
+buffet/Brillo, in ten simple steps.
+
+The word DUT in this context is meant as the device that you want to
+associate with the cloud - for most buffet/Brillo developers this will
+be a Chromebook or another embedded device. These notes assume you
+have shell access to the DUT and also have access to a normal Linux
+workstation with shell and browser access.
+
+1. Open an Incognito window in Chrome on your workstation, go to
+https://www.google.com and log in with your test google account (NEVER
+use @google.com credentials on DUTs). In the following we're using
+<GMAIL_TEST_ACCOUNT> which you should replace with whatever you're
+using, e.g. my-testing-account-xyz@gmail.com.
+
+2. First we need an Authorization Code for the test user. This is
+covered in more detail in
+
+ https://developers.google.com/cloud-devices/v1/dev-guides/getting-started/authorizing#code
+
+but basically amounts to entering the following URL in the Incognito window
+
+ https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/clouddevices&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&client_id=58855907228.apps.googleusercontent.com
+
+If you're not using the default buffet gcd-project, replace the
+client_id parameter in the URL with the one for the product you
+registered as per
+
+ https://developers.google.com/cloud-devices/v1/dev-guides/getting-started/authorizing#setup
+
+3. The browser window should display a prompt saying that
+"clouddevicesclient" would like to "Manage your cloud device". Press
+the "Accept" button and write down the Authorization Code
+displayed. It should look something like this
+
+ 4/J23qfSkXYFgF_0H7DCOtwS5O7HO69zF9LtnG9_ILIGA.QhJE9WLeqwcaJvIeHux6iLavlvowlwI
+
+4. Open a bash prompt on your Linux workstation and type the following
+
+ export SETUP_USER=<GMAIL_TEST_ACCOUNT>
+ export SETUP_CODE=4/J23qfSkXYFgF_0H7DCOtwS5O7HO69zF9LtnG9_ILIGA.QhJE9WLeqwcaJvIeHux6iLavlvowlwI
+ export SETUP_CLIENT_ID=58855907228.apps.googleusercontent.com
+ export SETUP_CLIENT_SECRET=eHSAREAHrIqPsHBxCE9zPPBi
+
+replacing the values for SETUP_USER and SETUP_CODE as
+appropriate. Again, if you're not using the default buffet gcd-project
+replace the values SETUP_CLIENT_ID and SETUP_CLIENT_SECRET as
+appropriate.
+
+5. Now we can get an Access Token. Run the following command from the shell:
+
+ curl -d "code=${SETUP_CODE}&client_id=${SETUP_CLIENT_ID}&client_secret=${SETUP_CLIENT_SECRET}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=authorization_code" https://accounts.google.com/o/oauth2/token
+
+It should print out something like this:
+
+ {
+ "access_token" : "ya29.HQE<...>",
+ "token_type" : "Bearer",
+ "expires_in" : 3600,
+ "refresh_token" : "1/iMq4<...>"
+ }
+
+6. Export the access token in the shell:
+
+ export SETUP_ACCESS_TOKEN=ya29.HQE<...>
+
+7. Now we can get the Registration Ticket Id for the device. Run the following
+
+ curl --header "Authorization: Bearer ${SETUP_ACCESS_TOKEN}" --header "Content-Type: application/json; charset=UTF-8" --data "{ \"userEmail\": \"${SETUP_USER}\" }" https://www.googleapis.com/clouddevices/v1/registrationTickets
+
+It should print out something like this
+
+ {
+ "kind": "clouddevices#registrationTicket",
+ "id": "453f1139-bd<...>",
+ "deviceId": "77500a3f-458b-<...>",
+ "userEmail": "<GMAIL_TEST_ACCOUNT>",
+ "creationTimeMs": "1424193538212",
+ "expirationTimeMs": "1424193778212"
+ }
+
+8. Now, open a shell on the DUT and export the following
+
+ export DUT_SETUP_TICKET_ID=453f1139-bd<...>
+
+9. Run the following command on the DUT shell
+
+ buffet_client RegisterDevice ticket_id=${DUT_SETUP_TICKET_ID}
+
+appropriate. If you're not using the default buffet gcd-project you
+also need to pass other parameters such as client_id, client_secret
+and api_key.
+
+It should succeed and print the device-id
+
+ Device registered: 77500a3f-458b-<...>
+
+10. The registered DUT should now show up in the Google account that
+you associated it with. In the Incognito window opened in step 1, go
+to
+
+ https://security.google.com/settings/security/permissions
+
+where you can e.g. revoke access to the device.
diff --git a/buffet/OWNERS b/buffet/OWNERS
new file mode 100644
index 0000000..6000333
--- /dev/null
+++ b/buffet/OWNERS
@@ -0,0 +1,5 @@
+set noparent
+avakulenko@chromium.org
+sosa@chromium.org
+vitalybuka@chromium.org
+wiley@chromium.org
diff --git a/buffet/README b/buffet/README
new file mode 100644
index 0000000..9dc1e87
--- /dev/null
+++ b/buffet/README
@@ -0,0 +1,6 @@
+// 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.
+
+This directory contains the a Brillo service for registering a device and
+sending/receiving remote commands.
diff --git a/buffet/ap_manager_client.cc b/buffet/ap_manager_client.cc
new file mode 100644
index 0000000..6449c0f
--- /dev/null
+++ b/buffet/ap_manager_client.cc
@@ -0,0 +1,114 @@
+// 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/ap_manager_client.h"
+
+namespace buffet {
+
+using org::chromium::apmanager::ConfigProxy;
+using org::chromium::apmanager::ManagerProxy;
+using org::chromium::apmanager::ServiceProxy;
+
+ApManagerClient::ApManagerClient(const scoped_refptr<dbus::Bus>& bus)
+ : bus_(bus) {}
+
+ApManagerClient::~ApManagerClient() {
+ Stop();
+}
+
+void ApManagerClient::Start(const std::string& ssid) {
+ if (service_path_.IsValid()) {
+ return;
+ }
+
+ ssid_ = ssid;
+
+ object_manager_proxy_.reset(
+ new org::chromium::apmanager::ObjectManagerProxy{bus_});
+ object_manager_proxy_->SetManagerAddedCallback(base::Bind(
+ &ApManagerClient::OnManagerAdded, weak_ptr_factory_.GetWeakPtr()));
+ object_manager_proxy_->SetServiceAddedCallback(base::Bind(
+ &ApManagerClient::OnServiceAdded, weak_ptr_factory_.GetWeakPtr()));
+
+ object_manager_proxy_->SetServiceRemovedCallback(base::Bind(
+ &ApManagerClient::OnServiceRemoved, weak_ptr_factory_.GetWeakPtr()));
+ object_manager_proxy_->SetManagerRemovedCallback(base::Bind(
+ &ApManagerClient::OnManagerRemoved, weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ApManagerClient::Stop() {
+ if (manager_proxy_ && service_path_.IsValid()) {
+ RemoveService(service_path_);
+ }
+ service_path_ = dbus::ObjectPath();
+ service_proxy_ = nullptr;
+ manager_proxy_ = nullptr;
+ object_manager_proxy_.reset();
+ ssid_.clear();
+}
+
+void ApManagerClient::RemoveService(const dbus::ObjectPath& object_path) {
+ CHECK(object_path.IsValid());
+ chromeos::ErrorPtr error;
+ if (!manager_proxy_->RemoveService(object_path, &error)) {
+ LOG(ERROR) << "RemoveService failed: " << error->GetMessage();
+ }
+}
+
+void ApManagerClient::OnManagerAdded(ManagerProxy* manager_proxy) {
+ VLOG(1) << "manager added: " << manager_proxy->GetObjectPath().value();
+ manager_proxy_ = manager_proxy;
+
+ if (service_path_.IsValid())
+ return;
+
+ chromeos::ErrorPtr error;
+ if (!manager_proxy_->CreateService(&service_path_, &error)) {
+ LOG(ERROR) << "CreateService failed: " << error->GetMessage();
+ }
+}
+
+void ApManagerClient::OnServiceAdded(ServiceProxy* service_proxy) {
+ VLOG(1) << "service added: " << service_proxy->GetObjectPath().value();
+ if (service_proxy->GetObjectPath() != service_path_) {
+ RemoveService(service_proxy->GetObjectPath());
+ return;
+ }
+ service_proxy_ = service_proxy;
+
+ ConfigProxy* config_proxy =
+ object_manager_proxy_->GetConfigProxy(service_proxy->config());
+ ConfigProxy::PropertySet* properties = config_proxy->GetProperties();
+ properties->ssid.Set(ssid_, base::Bind(&ApManagerClient::OnSsidSet,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ApManagerClient::OnSsidSet(bool success) {
+ if (!success || !service_proxy_) {
+ LOG(ERROR) << "Failed to set ssid.";
+ return;
+ }
+ VLOG(1) << "SSID is set: " << ssid_;
+
+ chromeos::ErrorPtr error;
+ if (!service_proxy_->Start(&error)) {
+ LOG(ERROR) << "Service start failed: " << error->GetMessage();
+ }
+}
+
+void ApManagerClient::OnServiceRemoved(const dbus::ObjectPath& object_path) {
+ VLOG(1) << "service removed: " << object_path.value();
+ if (object_path != service_path_)
+ return;
+ service_path_ = dbus::ObjectPath();
+ service_proxy_ = nullptr;
+}
+
+void ApManagerClient::OnManagerRemoved(const dbus::ObjectPath& object_path) {
+ VLOG(1) << "manager removed: " << object_path.value();
+ manager_proxy_ = nullptr;
+ Stop();
+}
+
+} // namespace buffet
diff --git a/buffet/ap_manager_client.h b/buffet/ap_manager_client.h
new file mode 100644
index 0000000..8397587
--- /dev/null
+++ b/buffet/ap_manager_client.h
@@ -0,0 +1,57 @@
+// 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.
+
+#ifndef BUFFET_AP_MANAGER_CLIENT_H_
+#define BUFFET_AP_MANAGER_CLIENT_H_
+
+#include <memory>
+#include <string>
+
+#include <base/callback.h>
+#include <base/memory/ref_counted.h>
+
+#include "apmanager/dbus-proxies.h"
+
+namespace buffet {
+
+// Manages soft AP for wifi bootstrapping.
+// Once created can handle multiple Start/Stop requests.
+class ApManagerClient final {
+ public:
+ explicit ApManagerClient(const scoped_refptr<dbus::Bus>& bus);
+ ~ApManagerClient();
+
+ void Start(const std::string& ssid);
+ void Stop();
+
+ std::string GetSsid() const { return ssid_; }
+
+ private:
+ void RemoveService(const dbus::ObjectPath& object_path);
+
+ void OnManagerAdded(org::chromium::apmanager::ManagerProxy* manager_proxy);
+ void OnServiceAdded(org::chromium::apmanager::ServiceProxy* service_proxy);
+
+ void OnSsidSet(bool success);
+
+ void OnServiceRemoved(const dbus::ObjectPath& object_path);
+ void OnManagerRemoved(const dbus::ObjectPath& object_path);
+
+ scoped_refptr<dbus::Bus> bus_;
+
+ std::unique_ptr<org::chromium::apmanager::ObjectManagerProxy>
+ object_manager_proxy_;
+ org::chromium::apmanager::ManagerProxy* manager_proxy_{nullptr};
+
+ dbus::ObjectPath service_path_;
+ org::chromium::apmanager::ServiceProxy* service_proxy_{nullptr};
+
+ std::string ssid_;
+
+ base::WeakPtrFactory<ApManagerClient> weak_ptr_factory_{this};
+};
+
+} // namespace buffet
+
+#endif // BUFFET_AP_MANAGER_CLIENT_H_
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp
new file mode 100644
index 0000000..fb2d151
--- /dev/null
+++ b/buffet/buffet.gyp
@@ -0,0 +1,158 @@
+{
+ 'target_defaults': {
+ 'variables': {
+ 'deps': [
+ 'libchrome-<(libbase_ver)',
+ 'libchromeos-<(libbase_ver)',
+ 'system_api',
+ ],
+ },
+ 'include_dirs': [
+ '.',
+ ],
+ },
+ 'targets': [
+ {
+ 'target_name': 'buffet_common',
+ 'type': 'static_library',
+ 'variables': {
+ 'dbus_adaptors_out_dir': 'include/buffet',
+ 'dbus_service_config': 'dbus_bindings/dbus-service-config.json',
+ 'exported_deps': [
+ 'libwebserv-<(libbase_ver)',
+ 'libweave-<(libbase_ver)',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'sources': [
+ 'ap_manager_client.cc',
+ 'dbus_bindings/org.chromium.Buffet.Command.xml',
+ 'dbus_bindings/org.chromium.Buffet.Manager.xml',
+ 'dbus_command_dispatcher.cc',
+ 'dbus_command_proxy.cc',
+ 'dbus_conversion.cc',
+ 'dbus_constants.cc',
+ 'http_transport_client.cc',
+ 'manager.cc',
+ 'peerd_client.cc',
+ 'shill_client.cc',
+ 'webserv_client.cc',
+ ],
+ 'includes': ['../common-mk/generate-dbus-adaptors.gypi'],
+ 'actions': [
+ {
+ 'action_name': 'generate-buffet-proxies',
+ 'variables': {
+ 'dbus_service_config': 'dbus_bindings/dbus-service-config.json',
+ 'proxy_output_file': 'include/buffet/dbus-proxies.h'
+ },
+ 'sources': [
+ 'dbus_bindings/org.chromium.Buffet.Command.xml',
+ 'dbus_bindings/org.chromium.Buffet.Manager.xml',
+ ],
+ 'includes': ['../common-mk/generate-dbus-proxies.gypi'],
+ },
+ {
+ # Import D-Bus bindings from peerd.
+ 'action_name': 'generate-peerd-proxies',
+ 'variables': {
+ 'dbus_service_config': '../peerd/dbus_bindings/dbus-service-config.json',
+ 'proxy_output_file': 'include/peerd/dbus-proxies.h'
+ },
+ 'sources': [
+ '../peerd/dbus_bindings/org.chromium.peerd.Manager.xml',
+ '../peerd/dbus_bindings/org.chromium.peerd.Peer.xml',
+ '../peerd/dbus_bindings/org.chromium.peerd.Service.xml',
+ ],
+ 'includes': ['../common-mk/generate-dbus-proxies.gypi'],
+ },
+ {
+ # Import D-Bus bindings from shill.
+ 'action_name': 'generate-shill-proxies',
+ 'variables': {
+ 'dbus_service_config': '../shill/dbus_bindings/dbus-service-config.json',
+ 'proxy_output_file': 'include/shill/dbus-proxies.h'
+ },
+ 'sources': [
+ '../shill/dbus_bindings/org.chromium.flimflam.Device.xml',
+ '../shill/dbus_bindings/org.chromium.flimflam.Manager.xml',
+ '../shill/dbus_bindings/org.chromium.flimflam.Service.xml',
+ ],
+ 'includes': ['../common-mk/generate-dbus-proxies.gypi'],
+ },
+ {
+ # Import D-Bus bindings from apmanager.
+ 'action_name': 'generate-apmanager-proxies',
+ 'variables': {
+ 'dbus_service_config': '../apmanager/dbus_bindings/dbus-service-config.json',
+ 'proxy_output_file': 'include/apmanager/dbus-proxies.h'
+ },
+ 'sources': [
+ '../apmanager/dbus_bindings/org.chromium.apmanager.Config.xml',
+ '../apmanager/dbus_bindings/org.chromium.apmanager.Device.xml',
+ '../apmanager/dbus_bindings/org.chromium.apmanager.Manager.xml',
+ '../apmanager/dbus_bindings/org.chromium.apmanager.Service.xml',
+ ],
+ 'includes': ['../common-mk/generate-dbus-proxies.gypi'],
+ },
+ ],
+ },
+ {
+ 'target_name': 'buffet',
+ 'type': 'executable',
+ 'dependencies': [
+ 'buffet_common',
+ ],
+ 'sources': [
+ 'main.cc',
+ ],
+ },
+ {
+ 'target_name': 'buffet_test_daemon',
+ 'type': 'executable',
+ 'sources': [
+ 'test_daemon/main.cc',
+ ],
+ },
+ {
+ 'target_name': 'buffet_client',
+ 'type': 'executable',
+ 'sources': [
+ 'buffet_client.cc',
+ ],
+ },
+ ],
+ 'conditions': [
+ ['USE_test == 1', {
+ 'targets': [
+ {
+ 'target_name': 'buffet_testrunner',
+ 'type': 'executable',
+ 'dependencies': [
+ 'buffet_common',
+ ],
+ 'variables': {
+ 'deps': [
+ 'libchrome-test-<(libbase_ver)',
+ 'libchromeos-test-<(libbase_ver)',
+ 'libweave-test-<(libbase_ver)',
+ ],
+ },
+ 'includes': ['../common-mk/common_test.gypi'],
+ 'sources': [
+ 'buffet_testrunner.cc',
+ 'dbus_command_proxy_unittest.cc',
+ 'dbus_conversion_unittest.cc',
+ ],
+ },
+ ],
+ }],
+ ],
+}
diff --git a/buffet/buffet_client.cc b/buffet/buffet_client.cc
new file mode 100644
index 0000000..cf9928f
--- /dev/null
+++ b/buffet/buffet_client.cc
@@ -0,0 +1,400 @@
+// 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 <memory>
+#include <string>
+#include <sysexits.h>
+
+#include <base/cancelable_callback.h>
+#include <base/command_line.h>
+#include <base/json/json_reader.h>
+#include <base/logging.h>
+#include <base/memory/ref_counted.h>
+#include <base/memory/weak_ptr.h>
+#include <base/strings/stringprintf.h>
+#include <base/values.h>
+#include <chromeos/any.h>
+#include <chromeos/daemons/dbus_daemon.h>
+#include <chromeos/data_encoding.h>
+#include <chromeos/dbus/data_serialization.h>
+#include <chromeos/dbus/dbus_method_invoker.h>
+#include <chromeos/errors/error.h>
+#include <chromeos/strings/string_utils.h>
+#include <chromeos/variant_dictionary.h>
+#include <dbus/bus.h>
+#include <dbus/message.h>
+#include <dbus/object_proxy.h>
+#include <dbus/object_manager.h>
+#include <dbus/values_util.h>
+
+#include "buffet/dbus-proxies.h"
+
+using chromeos::Error;
+using chromeos::ErrorPtr;
+using org::chromium::Buffet::ManagerProxy;
+
+namespace {
+
+void usage() {
+ printf(R"(Possible commands:
+ - TestMethod <message>
+ - CheckDeviceRegistered
+ - GetDeviceInfo
+ - RegisterDevice param1=val1¶m2=val2...
+ - AddCommand '{"name":"command_name","parameters":{}}'
+ - UpdateState prop_name prop_value
+ - GetState
+ - PendingCommands
+ - SetCommandVisibility pkg1.cmd1[,pkg2.cm2,...] [all|cloud|local|none]
+)");
+}
+
+// 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 final : public chromeos::DBusDaemon {
+ public:
+ Daemon() = default;
+
+ protected:
+ int OnInit() override {
+ int return_code = chromeos::DBusDaemon::OnInit();
+ if (return_code != EX_OK)
+ return return_code;
+
+ object_manager_.reset(new org::chromium::Buffet::ObjectManagerProxy{bus_});
+ return_code = ScheduleActions();
+ if (return_code == EX_USAGE) {
+ usage();
+ }
+ return return_code;
+ }
+
+ void OnShutdown(int* return_code) override {
+ if (*return_code == EX_OK)
+ *return_code = exit_code_;
+ }
+
+ private:
+ int ScheduleActions() {
+ auto args = base::CommandLine::ForCurrentProcess()->GetArgs();
+
+ // Pop the command off of the args list.
+ std::string command = args.front();
+ args.erase(args.begin());
+ base::Callback<void(ManagerProxy*)> job;
+ if (command.compare("TestMethod") == 0) {
+ if (!args.empty() && !CheckArgs(command, args, 1))
+ return EX_USAGE;
+ std::string message;
+ if (!args.empty())
+ message = args.back();
+ job = base::Bind(&Daemon::CallTestMethod, weak_factory_.GetWeakPtr(),
+ message);
+ } else if (command.compare("CheckDeviceRegistered") == 0 ||
+ command.compare("cr") == 0) {
+ if (!CheckArgs(command, args, 0))
+ return EX_USAGE;
+ job = base::Bind(&Daemon::CallCheckDeviceRegistered,
+ weak_factory_.GetWeakPtr());
+ } else if (command.compare("GetDeviceInfo") == 0 ||
+ command.compare("di") == 0) {
+ if (!CheckArgs(command, args, 0))
+ return EX_USAGE;
+ job = base::Bind(&Daemon::CallGetDeviceInfo,
+ weak_factory_.GetWeakPtr());
+ } else if (command.compare("RegisterDevice") == 0 ||
+ command.compare("rd") == 0) {
+ if (!args.empty() && !CheckArgs(command, args, 1))
+ return EX_USAGE;
+ std::string dict;
+ if (!args.empty())
+ dict = args.back();
+ job = base::Bind(&Daemon::CallRegisterDevice,
+ weak_factory_.GetWeakPtr(), dict);
+ } else if (command.compare("UpdateState") == 0 ||
+ command.compare("us") == 0) {
+ if (!CheckArgs(command, args, 2))
+ return EX_USAGE;
+ job = base::Bind(&Daemon::CallUpdateState, weak_factory_.GetWeakPtr(),
+ args.front(), args.back());
+ } else if (command.compare("GetState") == 0 ||
+ command.compare("gs") == 0) {
+ if (!CheckArgs(command, args, 0))
+ return EX_USAGE;
+ job = base::Bind(&Daemon::CallGetState, weak_factory_.GetWeakPtr());
+ } else if (command.compare("AddCommand") == 0 ||
+ command.compare("ac") == 0) {
+ if (!CheckArgs(command, args, 1))
+ return EX_USAGE;
+ job = base::Bind(&Daemon::CallAddCommand, weak_factory_.GetWeakPtr(),
+ args.back());
+ } else if (command.compare("PendingCommands") == 0 ||
+ command.compare("pc") == 0) {
+ if (!CheckArgs(command, args, 0))
+ return EX_USAGE;
+ // CallGetPendingCommands relies on ObjectManager but it is being
+ // initialized asynchronously without a way to get a callback when
+ // it is ready to be used. So, just wait a bit before calling its
+ // methods.
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&Daemon::CallGetPendingCommands,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromMilliseconds(100));
+ } else {
+ fprintf(stderr, "Unknown command: '%s'\n", command.c_str());
+ return EX_USAGE;
+ }
+ if (!job.is_null())
+ object_manager_->SetManagerAddedCallback(job);
+ timeout_task_.Reset(
+ base::Bind(&Daemon::OnJobTimeout, weak_factory_.GetWeakPtr()));
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ timeout_task_.callback(),
+ base::TimeDelta::FromSeconds(10));
+
+ return EX_OK;
+ }
+
+ void OnJobComplete() {
+ timeout_task_.Cancel();
+ Quit();
+ }
+
+ void OnJobTimeout() {
+ fprintf(stderr, "Timed out before completing request.");
+ Quit();
+ }
+
+ void ReportError(Error* error) {
+ fprintf(stderr, "Failed to receive a response: %s\n",
+ error->GetMessage().c_str());
+ exit_code_ = EX_UNAVAILABLE;
+ OnJobComplete();
+ }
+
+ bool CheckArgs(const std::string& command,
+ const std::vector<std::string>& args,
+ size_t expected_arg_count) {
+ if (args.size() == expected_arg_count)
+ return true;
+ fprintf(stderr, "Invalid number of arguments for command '%s'\n",
+ command.c_str());
+ return false;
+ }
+
+ void CallTestMethod(const std::string& message, ManagerProxy* manager_proxy) {
+ ErrorPtr error;
+ std::string response_message;
+ if (!manager_proxy->TestMethod(message, &response_message, &error)) {
+ return ReportError(error.get());
+ }
+ printf("Received a response: %s\n", response_message.c_str());
+ OnJobComplete();
+ }
+
+ void CallCheckDeviceRegistered(ManagerProxy* manager_proxy) {
+ ErrorPtr error;
+ std::string device_id;
+ if (!manager_proxy->CheckDeviceRegistered(&device_id, &error)) {
+ return ReportError(error.get());
+ }
+
+ printf("Device ID: %s\n",
+ device_id.empty() ? "<unregistered>" : device_id.c_str());
+ OnJobComplete();
+ }
+
+ void CallGetDeviceInfo(ManagerProxy* manager_proxy) {
+ ErrorPtr error;
+ std::string device_info;
+ if (!manager_proxy->GetDeviceInfo(&device_info, &error)) {
+ return ReportError(error.get());
+ }
+
+ printf("%s\n", device_info.c_str());
+ OnJobComplete();
+ }
+
+ void CallRegisterDevice(const std::string& args,
+ ManagerProxy* manager_proxy) {
+ std::string ticket_id;
+ if (!args.empty()) {
+ auto key_values = chromeos::data_encoding::WebParamsDecode(args);
+ for (const auto& pair : key_values) {
+ if (pair.first == "ticket_id")
+ ticket_id = pair.second;
+ }
+ }
+
+ ErrorPtr error;
+ std::string device_id;
+ if (!manager_proxy->RegisterDevice(ticket_id, &device_id, &error)) {
+ return ReportError(error.get());
+ }
+
+ printf("Device registered: %s\n", device_id.c_str());
+ OnJobComplete();
+ }
+
+ void CallUpdateState(const std::string& prop,
+ const std::string& value,
+ ManagerProxy* manager_proxy) {
+ ErrorPtr error;
+ std::string error_message;
+ std::unique_ptr<base::Value> json(
+ base::JSONReader::ReadAndReturnError(value, base::JSON_PARSE_RFC,
+ nullptr, &error_message)
+ .release());
+ 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());
+ }
+ OnJobComplete();
+ }
+
+ void CallGetState(ManagerProxy* manager_proxy) {
+ std::string json;
+ ErrorPtr error;
+ if (!manager_proxy->GetState(&json, &error)) {
+ return ReportError(error.get());
+ }
+ printf("%s\n", json.c_str());
+ OnJobComplete();
+ }
+
+ void CallAddCommand(const std::string& command, ManagerProxy* manager_proxy) {
+ ErrorPtr error;
+ std::string id;
+ if (!manager_proxy->AddCommand(command, "owner", &id, &error)) {
+ return ReportError(error.get());
+ }
+ OnJobComplete();
+ }
+
+ void CallGetPendingCommands() {
+ printf("Pending commands:\n");
+ for (auto* cmd : object_manager_->GetCommandInstances()) {
+ printf("%10s - '%s' (id:%s)\n", cmd->status().c_str(),
+ cmd->name().c_str(), cmd->id().c_str());
+ }
+ OnJobComplete();
+ }
+
+ std::unique_ptr<org::chromium::Buffet::ObjectManagerProxy> object_manager_;
+ int exit_code_{EX_OK};
+ base::CancelableCallback<void()> timeout_task_;
+
+ base::WeakPtrFactory<Daemon> weak_factory_{this};
+ DISALLOW_COPY_AND_ASSIGN(Daemon);
+};
+
+} // anonymous namespace
+
+int main(int argc, char** argv) {
+ base::CommandLine::Init(argc, argv);
+ base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
+ base::CommandLine::StringVector args = cl->GetArgs();
+ if (args.empty()) {
+ usage();
+ return EX_USAGE;
+ }
+
+ Daemon daemon;
+ return daemon.Run();
+}
diff --git a/buffet/buffet_testrunner.cc b/buffet/buffet_testrunner.cc
new file mode 100644
index 0000000..072461d
--- /dev/null
+++ b/buffet/buffet_testrunner.cc
@@ -0,0 +1,12 @@
+// Copyright (c) 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 <base/at_exit.h>
+#include <gtest/gtest.h>
+
+int main(int argc, char** argv) {
+ base::AtExitManager exit_manager;
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/buffet/dbus_bindings/dbus-service-config.json b/buffet/dbus_bindings/dbus-service-config.json
new file mode 100644
index 0000000..e610c3d
--- /dev/null
+++ b/buffet/dbus_bindings/dbus-service-config.json
@@ -0,0 +1,7 @@
+{
+ "service_name": "org.chromium.Buffet",
+ "object_manager": {
+ "name": "org.chromium.Buffet.ObjectManager",
+ "object_path": "/org/chromium/Buffet"
+ }
+}
diff --git a/buffet/dbus_bindings/org.chromium.Buffet.Command.xml b/buffet/dbus_bindings/org.chromium.Buffet.Command.xml
new file mode 100644
index 0000000..c9ed924
--- /dev/null
+++ b/buffet/dbus_bindings/org.chromium.Buffet.Command.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <interface name="org.chromium.Buffet.Command">
+ <method name="SetProgress">
+ <arg name="progress" type="a{sv}" 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">
+ <tp:docstring>
+ Mark the command as aborted. This tells the cloud that the device did
+ not successfully complete executing the command.
+ </tp:docstring>
+ <annotation name="org.chromium.DBus.Method.Kind" value="simple"/>
+ </method>
+ <method name="Cancel">
+ <tp:docstring>
+ Mark the command as cancelled. Unlike Abort() this should be used when
+ the device detects a user request to cancel a command.
+ </tp:docstring>
+ <annotation name="org.chromium.DBus.Method.Kind" value="simple"/>
+ </method>
+ <method name="Done">
+ <tp:docstring>
+ Mark the command as successfully completed.
+ </tp:docstring>
+ <annotation name="org.chromium.DBus.Method.Kind" value="simple"/>
+ </method>
+ <property name="Name" type="s" access="read"/>
+ <property name="Category" type="s" access="read"/>
+ <property name="Id" type="s" access="read"/>
+ <property name="Status" type="s" access="read"/>
+ <property name="Parameters" type="a{sv}" access="read"/>
+ <property name="Progress" type="a{sv}" access="read"/>
+ <property name="Results" type="a{sv}" access="read"/>
+ <property name="Origin" type="s" access="read">
+ <tp:docstring>
+ Specifies the origin of the command. This is a string containing
+ "cloud" or "local" indicating the method of delivery of the command.
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
diff --git a/buffet/dbus_bindings/org.chromium.Buffet.Manager.xml b/buffet/dbus_bindings/org.chromium.Buffet.Manager.xml
new file mode 100644
index 0000000..56370fa
--- /dev/null
+++ b/buffet/dbus_bindings/org.chromium.Buffet.Manager.xml
@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/org/chromium/Buffet/Manager"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <interface name="org.chromium.Buffet.Manager">
+ <tp:docstring>
+ The Manager is responsible for global state of Buffet. It exposes
+ interfaces which affect the entire device such as device registration and
+ device state.
+ </tp:docstring>
+ <method name="CheckDeviceRegistered">
+ <arg name="device_id" type="s" direction="out"/>
+ <annotation name="org.chromium.DBus.Method.Kind" value="async"/>
+ </method>
+ <method name="GetDeviceInfo">
+ <arg name="device_info" type="s" direction="out"/>
+ <annotation name="org.chromium.DBus.Method.Kind" value="async"/>
+ </method>
+ <method name="RegisterDevice">
+ <arg name="ticket_id" type="s" direction="in"/>
+ <arg name="device_id" type="s" direction="out"/>
+ <annotation name="org.chromium.DBus.Method.Kind" value="async"/>
+ </method>
+ <method name="UpdateDeviceInfo">
+ <arg name="name" type="s" direction="in"/>
+ <arg name="description" type="s" direction="in"/>
+ <arg name="location" type="s" direction="in"/>
+ <annotation name="org.chromium.DBus.Method.Kind" value="normal"/>
+ </method>
+ <method name="UpdateServiceConfig">
+ <arg name="client_id" type="s" direction="in"/>
+ <arg name="client_secret" type="s" direction="in"/>
+ <arg name="api_key" type="s" direction="in"/>
+ <arg name="oauth_url" type="s" direction="in"/>
+ <arg name="service_url" type="s" direction="in"/>
+ <annotation name="org.chromium.DBus.Method.Kind" value="normal"/>
+ </method>
+ <method name="UpdateState">
+ <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"/>
+ <arg name="user_role" type="s" direction="in"/>
+ <arg name="id" type="s" direction="out"/>
+ <annotation name="org.chromium.DBus.Method.Kind" value="async"/>
+ </method>
+ <method name="GetCommand">
+ <arg name="id" type="s" direction="in"/>
+ <arg name="json_command" type="s" direction="out"/>
+ <annotation name="org.chromium.DBus.Method.Kind" value="async"/>
+ </method>
+ <method name="TestMethod">
+ <arg name="message" type="s" direction="in"/>
+ <arg name="echoed_message" type="s" direction="out"/>
+ <annotation name="org.chromium.DBus.Method.Kind" value="simple"/>
+ </method>
+ <method name="EnableWiFiBootstrapping">
+ <tp:docstring>
+ Enables WiFiBootstrapping if manual bootstrapping is selected via the
+ configuration file. This will re-purpose a WiFi interface for use in
+ bootstrapping. This breaks any existing WiFi connection on the
+ interface.
+ </tp:docstring>
+ <arg name="listener_path" type="o" direction="in">
+ <tp:docstring>
+ Path to an object exposed by the caller. This object must support
+ the org.chromium.Buffet.WiFiBootstrapListener interface.
+ </tp:docstring>
+ </arg>
+ <arg name="options" type="a{sv}" direction="in"/>
+ <annotation name="org.chromium.DBus.Method.Kind" value="normal"/>
+ </method>
+ <method name="DisableWiFiBootstrapping">
+ <tp:docstring>
+ If a previous call to EnableWiFiBootstrapping was successful and
+ has not been cancelled or completed since, disables that bootstrapping
+ process.
+ </tp:docstring>
+ <annotation name="org.chromium.DBus.Method.Kind" value="normal"/>
+ </method>
+ <method name="EnableGCDBootstrapping">
+ <arg name="listener_path" type="o" direction="in"/>
+ <arg name="options" type="a{sv}" direction="in"/>
+ <annotation name="org.chromium.DBus.Method.Kind" value="normal"/>
+ </method>
+ <method name="DisableGCDBootstrapping">
+ <annotation name="org.chromium.DBus.Method.Kind" value="normal"/>
+ </method>
+
+ <property name="Status" type="s" access="read">
+ <tp:docstring>
+ State of Buffet's cloud registration.
+ Possible values include:
+ "unconfigured": Buffet has no credentials, either from an out of box
+ state, or because device was unregistered.
+
+ "connecting": Buffet is registered and attempting to connect to the
+ cloud.
+
+ "connected": Buffet is online and connected to the cloud. Note that
+ only this state requires internet connectivity.
+
+ "invalid_credentials": Buffet has credentials, but they are no longer
+ valid.
+ </tp:docstring>
+ </property>
+ <property name="DeviceId" type="s" access="read">
+ <tp:docstring>
+ GCD ID if the device is registered or empty otherwise.
+ </tp:docstring>
+ </property>
+ <property name="State" type="s" access="read">
+ <tp:docstring>
+ JSON with state of the devices.
+ </tp:docstring>
+ </property>
+ <property name="OemName" type="s" access="read">
+ <tp:docstring>
+ Name of the device maker.
+ </tp:docstring>
+ </property>
+ <property name="ModelName" type="s" access="read">
+ <tp:docstring>
+ Name of the device model.
+ </tp:docstring>
+ </property>
+ <property name="ModelId" type="s" access="read">
+ <tp:docstring>
+ Five character code assigned by the cloud registry of device models.
+ </tp:docstring>
+ </property>
+ <property name="Name" type="s" access="read">
+ <tp:docstring>
+ Human readable name of the device. Must not be empty.
+ </tp:docstring>
+ </property>
+ <property name="Description" type="s" access="read">
+ <tp:docstring>
+ Human readable description of the device.
+ </tp:docstring>
+ </property>
+ <property name="Location" type="s" access="read">
+ <tp:docstring>
+ Location of the device.
+ </tp:docstring>
+ </property>
+ <property name="AnonymousAccessRole" type="s" access="read">
+ <tp:docstring>
+ Max role granted to anonymous user when accessing device over the local
+ network.
+ Possible values include:
+ "none": Device does not allow local access by unauthenticated users.
+
+ "viewer": Device allows everyone authenticated to access device.
+
+ "user": Device allows everyone authenticated as 'user' to access
+ device.
+
+ "owner": Device allows everyone authenticated as 'owner' to access
+ device.
+ </tp:docstring>
+ </property>
+ <property name="WiFiBootstrapState" type="s" access="read">
+ <tp:docstring>
+ Contains one of the following values describing the state of WiFi
+ bootstrapping:
+ “disabled” - Bootstrapping has been disabled in the config file.
+ “waiting” - buffet is waiting to receive WiFi credentials from
+ a paired peer.
+ “connecting” - buffet has received WiFi credentials, and is now
+ attempting to connect to a WiFi network.
+ “monitoring” - buffet is monitoring our connectivity and will
+ re-enable bootstrapping if connectivity fails in
+ automatic mode.
+
+ Note: more values may be added later to this list.
+ </tp:docstring>
+ </property>
+ <property name="GCDBootstrapState" type="s" access="read">
+ <tp:docstring>
+ Contains one of the following values describing the state of GCD
+ bootstrapping:
+ “disabled” - GCD registration has been disabled in the config file.
+ “offline” - GCD registration is unknown because the device is offline.
+ “connecting” - GCD registration is unknown because the device is still
+ connecting to the cloud.
+ “waiting” - Waiting to be configured with GCD credentials.
+ “registering” - Registering the device with the GCD servers.
+ “online” - Device is online and registered with GCD servers.
+
+ Note: more values may be added later to this list.
+
+ Clients that wish to present a single linear bootstrapping flow to users
+ may treat GCD bootstrapping states as a suffix to WiFi bootstrapping
+ states. If we have no cloud connectivity, we cannot possibly do GCD
+ registration/credential verification.
+ </tp:docstring>
+ </property>
+ <property name="PairingInfo" type="a{sv}" access="read">
+ <tp:docstring>
+ Describes the state of device pairing. While no pairing attempt is in
+ progress, this dictionary will be empty. When a client initiates a
+ pairing transaction via /privet/v3/pairing/start, dictionary will
+ contain the following keys:
+ “sessionId” - ID of the pairing session; generated by device
+ “pairingMode” - Selected type of pairing from /privet/v3/pairing/start
+ (e.g. “pinCode” or “embeddedCode”)
+ “code” - The pin code or embedded code as appropriate to the
+ “pairingMode” value. See design document.
+ This value will be a string.
+ </tp:docstring>
+ </property>
+ </interface>
+</node>
diff --git a/buffet/dbus_command_dispatcher.cc b/buffet/dbus_command_dispatcher.cc
new file mode 100644
index 0000000..1af0983
--- /dev/null
+++ b/buffet/dbus_command_dispatcher.cc
@@ -0,0 +1,39 @@
+// 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/dbus_command_dispatcher.h"
+
+#include <chromeos/dbus/exported_object_manager.h>
+#include <weave/command.h>
+
+#include "buffet/dbus_command_proxy.h"
+#include "buffet/dbus_constants.h"
+
+using chromeos::dbus_utils::AsyncEventSequencer;
+using chromeos::dbus_utils::ExportedObjectManager;
+
+namespace buffet {
+
+DBusCommandDispacher::DBusCommandDispacher(
+ const base::WeakPtr<ExportedObjectManager>& object_manager,
+ weave::Commands* command_manager)
+ : object_manager_{object_manager} {
+ command_manager->AddOnCommandAddedCallback(base::Bind(
+ &DBusCommandDispacher::OnCommandAdded, weak_ptr_factory_.GetWeakPtr()));
+}
+
+void DBusCommandDispacher::OnCommandAdded(weave::Command* command) {
+ if (!object_manager_)
+ return;
+ std::unique_ptr<DBusCommandProxy> proxy{new DBusCommandProxy(
+ object_manager_.get(), object_manager_->GetBus(), command,
+ buffet::kCommandServicePathPrefix + std::to_string(++next_id_))};
+ proxy->RegisterAsync(AsyncEventSequencer::GetDefaultCompletionAction());
+ // DBusCommandProxy::DBusCommandProxy() subscribe itself to weave::Command
+ // notifications. When weave::Command is being destroyed it sends
+ // ::OnCommandDestroyed() and DBusCommandProxy deletes itself.
+ proxy.release();
+}
+
+} // namespace buffet
diff --git a/buffet/dbus_command_dispatcher.h b/buffet/dbus_command_dispatcher.h
new file mode 100644
index 0000000..fa17ad6
--- /dev/null
+++ b/buffet/dbus_command_dispatcher.h
@@ -0,0 +1,53 @@
+// 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.
+
+#ifndef BUFFET_DBUS_COMMAND_DISPATCHER_H_
+#define BUFFET_DBUS_COMMAND_DISPATCHER_H_
+
+#include <map>
+#include <string>
+
+#include <base/macros.h>
+#include <base/memory/weak_ptr.h>
+#include <weave/commands.h>
+
+namespace chromeos {
+namespace dbus_utils {
+class ExportedObjectManager;
+} // namespace dbus_utils
+} // namespace chromeos
+
+namespace buffet {
+
+// Implements D-Bus dispatch of commands. When OnCommandAdded is called,
+// DBusCommandDispacher creates an instance of DBusCommandProxy object and
+// advertises it through ExportedObjectManager on D-Bus. Command handling
+// processes can watch the new D-Bus object appear and communicate with it to
+// update the command handling progress. Once command is handled,
+// DBusCommandProxy::Done() is called and the command is removed from the
+// command queue and D-Bus ExportedObjectManager.
+class DBusCommandDispacher final {
+ public:
+ explicit DBusCommandDispacher(
+ const base::WeakPtr<chromeos::dbus_utils::ExportedObjectManager>&
+ object_manager,
+ weave::Commands* command_manager);
+
+ private:
+ void OnCommandAdded(weave::Command* command);
+
+ base::WeakPtr<chromeos::dbus_utils::ExportedObjectManager> object_manager_;
+ int next_id_{0};
+
+ // Default constructor is used in special circumstances such as for testing.
+ DBusCommandDispacher() = default;
+
+ base::WeakPtrFactory<DBusCommandDispacher> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(DBusCommandDispacher);
+};
+
+} // namespace buffet
+
+#endif // BUFFET_DBUS_COMMAND_DISPATCHER_H_
diff --git a/buffet/dbus_command_proxy.cc b/buffet/dbus_command_proxy.cc
new file mode 100644
index 0000000..064c801
--- /dev/null
+++ b/buffet/dbus_command_proxy.cc
@@ -0,0 +1,105 @@
+// 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/dbus_command_proxy.h"
+
+#include <chromeos/dbus/async_event_sequencer.h>
+#include <chromeos/dbus/exported_object_manager.h>
+#include <weave/enum_to_string.h>
+
+#include "buffet/dbus_conversion.h"
+
+using chromeos::dbus_utils::AsyncEventSequencer;
+using chromeos::dbus_utils::ExportedObjectManager;
+
+namespace buffet {
+
+DBusCommandProxy::DBusCommandProxy(ExportedObjectManager* object_manager,
+ const scoped_refptr<dbus::Bus>& bus,
+ weave::Command* command,
+ std::string object_path)
+ : command_{command},
+ dbus_object_{object_manager, bus, dbus::ObjectPath{object_path}} {
+ observer_.Add(command);
+}
+
+void DBusCommandProxy::RegisterAsync(
+ const AsyncEventSequencer::CompletionAction& completion_callback) {
+ dbus_adaptor_.RegisterWithDBusObject(&dbus_object_);
+
+ // Set the initial property values before registering the DBus object.
+ dbus_adaptor_.SetName(command_->GetName());
+ dbus_adaptor_.SetCategory(command_->GetCategory());
+ dbus_adaptor_.SetId(command_->GetID());
+ dbus_adaptor_.SetStatus(EnumToString(command_->GetStatus()));
+ dbus_adaptor_.SetProgress(
+ DictionaryToDBusVariantDictionary(*command_->GetProgress()));
+ dbus_adaptor_.SetOrigin(EnumToString(command_->GetOrigin()));
+ dbus_adaptor_.SetParameters(
+ DictionaryToDBusVariantDictionary(*command_->GetParameters()));
+ dbus_adaptor_.SetResults(
+ DictionaryToDBusVariantDictionary(*command_->GetResults()));
+
+ // Register the command DBus object and expose its methods and properties.
+ dbus_object_.RegisterAsync(completion_callback);
+}
+
+void DBusCommandProxy::OnResultsChanged() {
+ dbus_adaptor_.SetResults(
+ DictionaryToDBusVariantDictionary(*command_->GetResults()));
+}
+
+void DBusCommandProxy::OnStatusChanged() {
+ dbus_adaptor_.SetStatus(EnumToString(command_->GetStatus()));
+}
+
+void DBusCommandProxy::OnProgressChanged() {
+ dbus_adaptor_.SetProgress(
+ DictionaryToDBusVariantDictionary(*command_->GetProgress()));
+}
+
+void DBusCommandProxy::OnCommandDestroyed() {
+ delete this;
+}
+
+bool DBusCommandProxy::SetProgress(
+ chromeos::ErrorPtr* error,
+ const chromeos::VariantDictionary& progress) {
+ LOG(INFO) << "Received call to Command<" << command_->GetName()
+ << ">::SetProgress()";
+ auto dictionary = DictionaryFromDBusVariantDictionary(progress, error);
+ if (!dictionary)
+ return false;
+ return command_->SetProgress(*dictionary, error);
+}
+
+bool DBusCommandProxy::SetResults(chromeos::ErrorPtr* error,
+ const chromeos::VariantDictionary& results) {
+ LOG(INFO) << "Received call to Command<" << command_->GetName()
+ << ">::SetResults()";
+ auto dictionary = DictionaryFromDBusVariantDictionary(results, error);
+ if (!dictionary)
+ return false;
+ return command_->SetResults(*dictionary, error);
+}
+
+void DBusCommandProxy::Abort() {
+ LOG(INFO) << "Received call to Command<" << command_->GetName()
+ << ">::Abort()";
+ command_->Abort();
+}
+
+void DBusCommandProxy::Cancel() {
+ LOG(INFO) << "Received call to Command<" << command_->GetName()
+ << ">::Cancel()";
+ command_->Cancel();
+}
+
+void DBusCommandProxy::Done() {
+ LOG(INFO) << "Received call to Command<" << command_->GetName()
+ << ">::Done()";
+ command_->Done();
+}
+
+} // namespace buffet
diff --git a/buffet/dbus_command_proxy.h b/buffet/dbus_command_proxy.h
new file mode 100644
index 0000000..c13ee0a
--- /dev/null
+++ b/buffet/dbus_command_proxy.h
@@ -0,0 +1,72 @@
+// 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.
+
+#ifndef BUFFET_DBUS_COMMAND_PROXY_H_
+#define BUFFET_DBUS_COMMAND_PROXY_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <base/scoped_observer.h>
+#include <chromeos/dbus/data_serialization.h>
+#include <chromeos/dbus/dbus_object.h>
+#include <weave/command.h>
+
+#include "buffet/org.chromium.Buffet.Command.h"
+
+namespace chromeos {
+namespace dbus_utils {
+class ExportedObjectManager;
+} // namespace dbus_utils
+} // namespace chromeos
+
+namespace buffet {
+
+class DBusCommandProxy : public weave::Command::Observer,
+ public org::chromium::Buffet::CommandInterface {
+ public:
+ DBusCommandProxy(chromeos::dbus_utils::ExportedObjectManager* object_manager,
+ const scoped_refptr<dbus::Bus>& bus,
+ weave::Command* command,
+ std::string object_path);
+ ~DBusCommandProxy() override = default;
+
+ void RegisterAsync(
+ const chromeos::dbus_utils::AsyncEventSequencer::CompletionAction&
+ completion_callback);
+
+ // CommandProxyInterface implementation/overloads.
+ void OnResultsChanged() override;
+ void OnStatusChanged() override;
+ void OnProgressChanged() override;
+ void OnCommandDestroyed() override;
+
+ private:
+ // Handles calls to org.chromium.Buffet.Command.SetProgress(progress).
+ bool SetProgress(chromeos::ErrorPtr* error,
+ const chromeos::VariantDictionary& 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().
+ void Cancel() override;
+ // Handles calls to org.chromium.Buffet.Command.Done().
+ void Done() override;
+
+ weave::Command* command_;
+ org::chromium::Buffet::CommandAdaptor dbus_adaptor_{this};
+ chromeos::dbus_utils::DBusObject dbus_object_;
+
+ ScopedObserver<weave::Command, weave::Command::Observer> observer_{this};
+
+ friend class DBusCommandProxyTest;
+ friend class DBusCommandDispacherTest;
+ DISALLOW_COPY_AND_ASSIGN(DBusCommandProxy);
+};
+
+} // namespace buffet
+
+#endif // BUFFET_DBUS_COMMAND_PROXY_H_
diff --git a/buffet/dbus_command_proxy_unittest.cc b/buffet/dbus_command_proxy_unittest.cc
new file mode 100644
index 0000000..298df23
--- /dev/null
+++ b/buffet/dbus_command_proxy_unittest.cc
@@ -0,0 +1,209 @@
+// 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/dbus_command_proxy.h"
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include <dbus/mock_bus.h>
+#include <dbus/mock_exported_object.h>
+#include <dbus/property.h>
+#include <chromeos/dbus/dbus_object.h>
+#include <chromeos/dbus/dbus_object_test_helpers.h>
+#include <gtest/gtest.h>
+#include <weave/command.h>
+#include <weave/enum_to_string.h>
+#include <weave/mock_command.h>
+#include <weave/mock_commands.h>
+#include <weave/unittest_utils.h>
+
+#include "buffet/dbus_constants.h"
+
+namespace buffet {
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::Return;
+using ::testing::ReturnRefOfCopy;
+using ::testing::StrictMock;
+
+using chromeos::VariantDictionary;
+using chromeos::dbus_utils::AsyncEventSequencer;
+using weave::unittests::CreateDictionaryValue;
+using weave::unittests::IsEqualValue;
+
+namespace {
+
+const char kTestCommandCategoty[] = "test_command_category";
+const char kTestCommandId[] = "cmd_1";
+
+MATCHER_P(EqualToJson, json, "") {
+ auto json_value = CreateDictionaryValue(json);
+ return IsEqualValue(*json_value, arg);
+}
+
+} // namespace
+
+class DBusCommandProxyTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ EXPECT_CALL(command_, AddObserver(_)).Times(1);
+ EXPECT_CALL(command_, RemoveObserver(_)).Times(1);
+ // Set up a mock DBus bus object.
+ dbus::Bus::Options options;
+ options.bus_type = dbus::Bus::SYSTEM;
+ bus_ = new dbus::MockBus(options);
+ // By default, don't worry about threading assertions.
+ EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber());
+ EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber());
+
+ EXPECT_CALL(command_, GetID())
+ .WillOnce(ReturnRefOfCopy<std::string>(kTestCommandId));
+ // Use WillRepeatedly becase GetName is used for logging.
+ EXPECT_CALL(command_, GetName())
+ .WillRepeatedly(ReturnRefOfCopy<std::string>("robot.jump"));
+ EXPECT_CALL(command_, GetCategory())
+ .WillOnce(ReturnRefOfCopy<std::string>(kTestCommandCategoty));
+ EXPECT_CALL(command_, GetStatus())
+ .WillOnce(Return(weave::CommandStatus::kQueued));
+ EXPECT_CALL(command_, GetOrigin())
+ .WillOnce(Return(weave::CommandOrigin::kLocal));
+ EXPECT_CALL(command_, MockGetParameters())
+ .WillOnce(ReturnRefOfCopy<std::string>(R"({
+ 'height': 53,
+ '_jumpType': '_withKick'
+ })"));
+ EXPECT_CALL(command_, MockGetProgress())
+ .WillOnce(ReturnRefOfCopy<std::string>("{}"));
+ EXPECT_CALL(command_, MockGetResults())
+ .WillOnce(ReturnRefOfCopy<std::string>("{}"));
+
+ // Set up a mock ExportedObject to be used with the DBus command proxy.
+ std::string cmd_path = buffet::kCommandServicePathPrefix;
+ cmd_path += kTestCommandId;
+ const dbus::ObjectPath kCmdObjPath(cmd_path);
+ // Use a mock exported object for the exported object manager.
+ mock_exported_object_command_ =
+ new dbus::MockExportedObject(bus_.get(), kCmdObjPath);
+ EXPECT_CALL(*bus_, GetExportedObject(kCmdObjPath))
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(mock_exported_object_command_.get()));
+ EXPECT_CALL(*mock_exported_object_command_, ExportMethod(_, _, _, _))
+ .Times(AnyNumber());
+
+ proxy_.reset(new DBusCommandProxy(nullptr, bus_, &command_, cmd_path));
+ GetCommandProxy()->RegisterAsync(
+ AsyncEventSequencer::GetDefaultCompletionAction());
+ }
+
+ void TearDown() override {
+ EXPECT_CALL(*mock_exported_object_command_, Unregister()).Times(1);
+ bus_ = nullptr;
+ }
+
+ DBusCommandProxy* GetCommandProxy() const { return proxy_.get(); }
+
+ org::chromium::Buffet::CommandAdaptor* GetCommandAdaptor() const {
+ return &GetCommandProxy()->dbus_adaptor_;
+ }
+
+ org::chromium::Buffet::CommandInterface* GetCommandInterface() const {
+ // DBusCommandProxy also implements CommandInterface.
+ return GetCommandProxy();
+ }
+
+ weave::CommandStatus GetCommandStatus() const {
+ weave::CommandStatus status;
+ EXPECT_TRUE(StringToEnum(GetCommandAdaptor()->GetStatus(), &status));
+ return status;
+ }
+
+ scoped_refptr<dbus::MockExportedObject> mock_exported_object_command_;
+ scoped_refptr<dbus::MockBus> bus_;
+
+ StrictMock<weave::unittests::MockCommand> command_;
+ std::unique_ptr<DBusCommandProxy> proxy_;
+};
+
+TEST_F(DBusCommandProxyTest, Init) {
+ VariantDictionary params = {
+ {"height", int32_t{53}}, {"_jumpType", std::string{"_withKick"}},
+ };
+ EXPECT_EQ(weave::CommandStatus::kQueued, GetCommandStatus());
+ EXPECT_EQ(params, GetCommandAdaptor()->GetParameters());
+ EXPECT_EQ(VariantDictionary{}, GetCommandAdaptor()->GetProgress());
+ EXPECT_EQ(VariantDictionary{}, GetCommandAdaptor()->GetResults());
+ EXPECT_EQ("robot.jump", GetCommandAdaptor()->GetName());
+ EXPECT_EQ(kTestCommandCategoty, GetCommandAdaptor()->GetCategory());
+ EXPECT_EQ(kTestCommandId, GetCommandAdaptor()->GetId());
+}
+
+TEST_F(DBusCommandProxyTest, OnProgressChanged) {
+ EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(1);
+ EXPECT_CALL(command_, MockGetProgress())
+ .WillOnce(ReturnRefOfCopy<std::string>("{'progress': 10}"));
+ proxy_->OnProgressChanged();
+ EXPECT_EQ((VariantDictionary{{"progress", int32_t{10}}}),
+ GetCommandAdaptor()->GetProgress());
+}
+
+TEST_F(DBusCommandProxyTest, OnResultsChanged) {
+ EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(1);
+ EXPECT_CALL(command_, MockGetResults())
+ .WillOnce(ReturnRefOfCopy<std::string>(
+ "{'foo': 42, 'bar': 'foobar', 'resultList': [1, 2, 3]}"));
+ proxy_->OnResultsChanged();
+
+ EXPECT_EQ((VariantDictionary{{"foo", int32_t{42}},
+ {"bar", std::string{"foobar"}},
+ {"resultList", std::vector<int>{1, 2, 3}}}),
+ GetCommandAdaptor()->GetResults());
+}
+
+TEST_F(DBusCommandProxyTest, OnStatusChanged) {
+ EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(1);
+ EXPECT_CALL(command_, GetStatus())
+ .WillOnce(Return(weave::CommandStatus::kInProgress));
+ proxy_->OnStatusChanged();
+ EXPECT_EQ(weave::CommandStatus::kInProgress, GetCommandStatus());
+}
+
+TEST_F(DBusCommandProxyTest, SetProgress) {
+ EXPECT_CALL(command_, SetProgress(EqualToJson("{'progress': 10}"), _))
+ .WillOnce(Return(true));
+ EXPECT_TRUE(
+ GetCommandInterface()->SetProgress(nullptr, {{"progress", int32_t{10}}}));
+}
+
+TEST_F(DBusCommandProxyTest, SetResults) {
+ EXPECT_CALL(
+ command_,
+ SetResults(
+ EqualToJson("{'foo': 42, 'bar': 'foobar', 'resultList': [1, 2, 3]}"),
+ _))
+ .WillOnce(Return(true));
+ EXPECT_TRUE(GetCommandInterface()->SetResults(
+ nullptr, VariantDictionary{{"foo", int32_t{42}},
+ {"bar", std::string{"foobar"}},
+ {"resultList", std::vector<int>{1, 2, 3}}}));
+}
+
+TEST_F(DBusCommandProxyTest, Abort) {
+ EXPECT_CALL(command_, Abort());
+ GetCommandInterface()->Abort();
+}
+
+TEST_F(DBusCommandProxyTest, Cancel) {
+ EXPECT_CALL(command_, Cancel());
+ GetCommandInterface()->Cancel();
+}
+
+TEST_F(DBusCommandProxyTest, Done) {
+ EXPECT_CALL(command_, Done());
+ GetCommandInterface()->Done();
+}
+
+} // namespace buffet
diff --git a/buffet/dbus_constants.cc b/buffet/dbus_constants.cc
new file mode 100644
index 0000000..6ab587a
--- /dev/null
+++ b/buffet/dbus_constants.cc
@@ -0,0 +1,13 @@
+// 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/dbus_constants.h"
+
+namespace buffet {
+
+const char kServiceName[] = "org.chromium.Buffet";
+const char kRootServicePath[] = "/org/chromium/Buffet";
+const char kCommandServicePathPrefix[] = "/org/chromium/Buffet/commands/";
+
+} // namespace buffet
diff --git a/buffet/dbus_constants.h b/buffet/dbus_constants.h
new file mode 100644
index 0000000..0bb72dc
--- /dev/null
+++ b/buffet/dbus_constants.h
@@ -0,0 +1,21 @@
+// 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.
+
+#ifndef BUFFET_DBUS_CONSTANTS_H_
+#define BUFFET_DBUS_CONSTANTS_H_
+
+namespace buffet {
+
+// The service name claimed by the Buffet daemon.
+extern const char kServiceName[];
+
+// The object at this path implements the ObjectManager interface.
+extern const char kRootServicePath[];
+
+// D-Bus object path prefix for Command objects.
+extern const char kCommandServicePathPrefix[];
+
+} // namespace buffet
+
+#endif // BUFFET_DBUS_CONSTANTS_H_
diff --git a/buffet/dbus_conversion.cc b/buffet/dbus_conversion.cc
new file mode 100644
index 0000000..51d35f1
--- /dev/null
+++ b/buffet/dbus_conversion.cc
@@ -0,0 +1,246 @@
+// Copyright 2015 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/dbus_conversion.h"
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <chromeos/type_name_undecorate.h>
+
+namespace buffet {
+
+namespace {
+
+// Helpers for JsonToAny().
+template <typename T>
+chromeos::Any ValueToAny(const base::Value& json,
+ bool (base::Value::*fnc)(T*) const) {
+ T val;
+ CHECK((json.*fnc)(&val));
+ return val;
+}
+
+chromeos::Any ValueToAny(const base::Value& json);
+
+template <typename T>
+chromeos::Any ListToAny(const base::ListValue& list,
+ bool (base::Value::*fnc)(T*) const) {
+ std::vector<T> result;
+ result.reserve(list.GetSize());
+ for (const base::Value* v : list) {
+ T val;
+ CHECK((v->*fnc)(&val));
+ result.push_back(val);
+ }
+ return result;
+}
+
+chromeos::Any DictListToAny(const base::ListValue& list) {
+ std::vector<chromeos::VariantDictionary> result;
+ result.reserve(list.GetSize());
+ for (const base::Value* v : list) {
+ const base::DictionaryValue* dict = nullptr;
+ CHECK(v->GetAsDictionary(&dict));
+ result.push_back(DictionaryToDBusVariantDictionary(*dict));
+ }
+ return result;
+}
+
+chromeos::Any ListListToAny(const base::ListValue& list) {
+ std::vector<chromeos::Any> result;
+ result.reserve(list.GetSize());
+ for (const base::Value* v : list)
+ result.push_back(ValueToAny(*v));
+ return result;
+}
+
+// Converts a JSON value into an Any so it can be sent over D-Bus using
+// UpdateState D-Bus method from Buffet.
+chromeos::Any ValueToAny(const base::Value& json) {
+ chromeos::Any prop_value;
+ switch (json.GetType()) {
+ case base::Value::TYPE_BOOLEAN:
+ prop_value = ValueToAny<bool>(json, &base::Value::GetAsBoolean);
+ break;
+ case base::Value::TYPE_INTEGER:
+ prop_value = ValueToAny<int>(json, &base::Value::GetAsInteger);
+ break;
+ case base::Value::TYPE_DOUBLE:
+ prop_value = ValueToAny<double>(json, &base::Value::GetAsDouble);
+ break;
+ case base::Value::TYPE_STRING:
+ prop_value = ValueToAny<std::string>(json, &base::Value::GetAsString);
+ break;
+ case base::Value::TYPE_DICTIONARY: {
+ const base::DictionaryValue* dict = nullptr;
+ CHECK(json.GetAsDictionary(&dict));
+ prop_value = DictionaryToDBusVariantDictionary(*dict);
+ break;
+ }
+ case base::Value::TYPE_LIST: {
+ const base::ListValue* list = nullptr;
+ CHECK(json.GetAsList(&list));
+ if (list->empty()) {
+ // We don't know type of objects this list intended for, so we just use
+ // vector<chromeos::Any>.
+ prop_value = ListListToAny(*list);
+ break;
+ }
+ auto type = (*list->begin())->GetType();
+ for (const base::Value* v : *list)
+ CHECK_EQ(v->GetType(), type) << "Unsupported different type elements";
+
+ switch (type) {
+ case base::Value::TYPE_BOOLEAN:
+ prop_value = ListToAny<bool>(*list, &base::Value::GetAsBoolean);
+ break;
+ case base::Value::TYPE_INTEGER:
+ prop_value = ListToAny<int>(*list, &base::Value::GetAsInteger);
+ break;
+ case base::Value::TYPE_DOUBLE:
+ prop_value = ListToAny<double>(*list, &base::Value::GetAsDouble);
+ break;
+ case base::Value::TYPE_STRING:
+ prop_value = ListToAny<std::string>(*list, &base::Value::GetAsString);
+ break;
+ case base::Value::TYPE_DICTIONARY:
+ prop_value = DictListToAny(*list);
+ break;
+ case base::Value::TYPE_LIST:
+ // We can't support Any{vector<vector<>>} as the type is only known
+ // in runtime when we need to instantiate templates in compile time.
+ // We can use Any{vector<Any>} instead.
+ prop_value = ListListToAny(*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>
+std::unique_ptr<base::Value> CreateValue(const T& value,
+ chromeos::ErrorPtr* error) {
+ return std::unique_ptr<base::Value>{new base::FundamentalValue{value}};
+}
+
+template <>
+std::unique_ptr<base::Value> CreateValue<std::string>(
+ const std::string& value,
+ chromeos::ErrorPtr* error) {
+ return std::unique_ptr<base::Value>{new base::StringValue{value}};
+}
+
+template <>
+std::unique_ptr<base::Value> CreateValue<chromeos::VariantDictionary>(
+ const chromeos::VariantDictionary& value,
+ chromeos::ErrorPtr* error) {
+ return DictionaryFromDBusVariantDictionary(value, error);
+}
+
+template <typename T>
+std::unique_ptr<base::ListValue> CreateListValue(const std::vector<T>& value,
+ chromeos::ErrorPtr* error) {
+ std::unique_ptr<base::ListValue> list{new base::ListValue};
+
+ for (const T& i : value) {
+ auto item = CreateValue(i, error);
+ if (!item)
+ return nullptr;
+ list->Append(item.release());
+ }
+
+ return list;
+}
+
+// Returns false only in case of error. True can be returned if type is not
+// matched.
+template <typename T>
+bool TryCreateValue(const chromeos::Any& any,
+ std::unique_ptr<base::Value>* value,
+ chromeos::ErrorPtr* error) {
+ if (any.IsTypeCompatible<T>()) {
+ *value = CreateValue(any.Get<T>(), error);
+ return *value != nullptr;
+ }
+
+ if (any.IsTypeCompatible<std::vector<T>>()) {
+ *value = CreateListValue(any.Get<std::vector<T>>(), error);
+ return *value != nullptr;
+ }
+
+ return true; // Not an error, we will try different type.
+}
+
+template <>
+std::unique_ptr<base::Value> CreateValue<chromeos::Any>(
+ const chromeos::Any& any,
+ chromeos::ErrorPtr* error) {
+ std::unique_ptr<base::Value> result;
+ if (!TryCreateValue<bool>(any, &result, error) || result)
+ return result;
+
+ if (!TryCreateValue<int>(any, &result, error) || result)
+ return result;
+
+ if (!TryCreateValue<double>(any, &result, error) || result)
+ return result;
+
+ if (!TryCreateValue<std::string>(any, &result, error) || result)
+ return result;
+
+ if (!TryCreateValue<chromeos::VariantDictionary>(any, &result, error) ||
+ result) {
+ return result;
+ }
+
+ // This will collapse Any{Any{T}} and vector{Any{T}}.
+ if (!TryCreateValue<chromeos::Any>(any, &result, error) || result)
+ return result;
+
+ chromeos::Error::AddToPrintf(
+ error, FROM_HERE, "buffet", "unknown_type", "Type '%s' is not supported.",
+ chromeos::UndecorateTypeName(any.GetType().name()).c_str());
+
+ return nullptr;
+}
+
+} // namespace
+
+// TODO(vitalybuka): Use in buffet_client.
+chromeos::VariantDictionary DictionaryToDBusVariantDictionary(
+ const base::DictionaryValue& object) {
+ chromeos::VariantDictionary result;
+
+ for (base::DictionaryValue::Iterator it(object); !it.IsAtEnd(); it.Advance())
+ result.emplace(it.key(), ValueToAny(it.value()));
+
+ return result;
+}
+
+std::unique_ptr<base::DictionaryValue> DictionaryFromDBusVariantDictionary(
+ const chromeos::VariantDictionary& object,
+ chromeos::ErrorPtr* error) {
+ std::unique_ptr<base::DictionaryValue> result{new base::DictionaryValue};
+
+ for (const auto& pair : object) {
+ auto value = CreateValue(pair.second, error);
+ if (!value)
+ return nullptr;
+ result->SetWithoutPathExpansion(pair.first, value.release());
+ }
+
+ return result;
+}
+
+} // namespace buffet
diff --git a/buffet/dbus_conversion.h b/buffet/dbus_conversion.h
new file mode 100644
index 0000000..20c3f58
--- /dev/null
+++ b/buffet/dbus_conversion.h
@@ -0,0 +1,26 @@
+// Copyright 2015 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.
+
+#ifndef BUFFET_DBUS_CONVERSION_H_
+#define BUFFET_DBUS_CONVERSION_H_
+
+#include <base/values.h>
+#include <chromeos/any.h>
+#include <chromeos/errors/error.h>
+#include <chromeos/variant_dictionary.h>
+
+namespace buffet {
+
+// Converts DictionaryValue to D-Bus variant dictionary.
+chromeos::VariantDictionary DictionaryToDBusVariantDictionary(
+ const base::DictionaryValue& object);
+
+// Converts D-Bus variant dictionary to DictionaryValue.
+std::unique_ptr<base::DictionaryValue> DictionaryFromDBusVariantDictionary(
+ const chromeos::VariantDictionary& object,
+ chromeos::ErrorPtr* error);
+
+} // namespace buffet
+
+#endif // BUFFET_DBUS_CONVERSION_H_
diff --git a/buffet/dbus_conversion_unittest.cc b/buffet/dbus_conversion_unittest.cc
new file mode 100644
index 0000000..0d94576
--- /dev/null
+++ b/buffet/dbus_conversion_unittest.cc
@@ -0,0 +1,187 @@
+// Copyright 2015 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/dbus_conversion.h"
+
+#include <limits>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/guid.h>
+#include <base/rand_util.h>
+#include <base/values.h>
+#include <chromeos/variant_dictionary.h>
+#include <gtest/gtest.h>
+#include <weave/unittest_utils.h>
+
+namespace buffet {
+
+namespace {
+
+using chromeos::Any;
+using chromeos::VariantDictionary;
+using weave::unittests::CreateDictionaryValue;
+using weave::unittests::IsEqualValue;
+
+chromeos::VariantDictionary ToDBus(const base::DictionaryValue& object) {
+ return DictionaryToDBusVariantDictionary(object);
+}
+
+std::unique_ptr<base::DictionaryValue> FromDBus(
+ const chromeos::VariantDictionary& object) {
+ chromeos::ErrorPtr error;
+ auto result = DictionaryFromDBusVariantDictionary(object, &error);
+ EXPECT_TRUE(result || error);
+ return result;
+}
+
+std::unique_ptr<base::Value> CreateRandomValue(int children);
+std::unique_ptr<base::Value> CreateRandomValue(int children,
+ base::Value::Type type);
+
+const base::Value::Type kRandomTypes[] = {
+ base::Value::TYPE_BOOLEAN, base::Value::TYPE_INTEGER,
+ base::Value::TYPE_DOUBLE, base::Value::TYPE_STRING,
+ base::Value::TYPE_DICTIONARY, base::Value::TYPE_LIST,
+};
+
+const base::Value::Type kRandomTypesWithChildren[] = {
+ base::Value::TYPE_DICTIONARY, base::Value::TYPE_LIST,
+};
+
+base::Value::Type CreateRandomValueType(bool with_children) {
+ if (with_children) {
+ return kRandomTypesWithChildren[base::RandInt(
+ 0, arraysize(kRandomTypesWithChildren) - 1)];
+ }
+ return kRandomTypes[base::RandInt(0, arraysize(kRandomTypes) - 1)];
+}
+
+std::unique_ptr<base::DictionaryValue> CreateRandomDictionary(int children) {
+ std::unique_ptr<base::DictionaryValue> result{new base::DictionaryValue};
+
+ while (children > 0) {
+ int sub_children = base::RandInt(1, children);
+ children -= sub_children;
+ result->Set(base::GenerateGUID(),
+ CreateRandomValue(sub_children).release());
+ }
+
+ return result;
+}
+
+std::unique_ptr<base::ListValue> CreateRandomList(int children) {
+ std::unique_ptr<base::ListValue> result{new base::ListValue};
+
+ base::Value::Type type = CreateRandomValueType(children > 0);
+ while (children > 0) {
+ size_t max_children =
+ (type != base::Value::TYPE_DICTIONARY && type != base::Value::TYPE_LIST)
+ ? 1
+ : children;
+ size_t sub_children = base::RandInt(1, max_children);
+ children -= sub_children;
+ result->Append(CreateRandomValue(sub_children, type).release());
+ }
+
+ return result;
+}
+
+std::unique_ptr<base::Value> CreateRandomValue(int children,
+ base::Value::Type type) {
+ CHECK_GE(children, 1);
+ switch (type) {
+ case base::Value::TYPE_INTEGER:
+ return std::unique_ptr<base::Value>{new base::FundamentalValue{
+ base::RandInt(std::numeric_limits<int>::min(),
+ std::numeric_limits<int>::max())}};
+ case base::Value::TYPE_DOUBLE:
+ return std::unique_ptr<base::Value>{
+ new base::FundamentalValue{base::RandDouble()}};
+ case base::Value::TYPE_STRING:
+ return std::unique_ptr<base::Value>{
+ new base::StringValue{base::GenerateGUID()}};
+ case base::Value::TYPE_DICTIONARY:
+ CHECK_GE(children, 1);
+ return CreateRandomDictionary(children - 1);
+ case base::Value::TYPE_LIST:
+ CHECK_GE(children, 1);
+ return CreateRandomList(children - 1);
+ default:
+ return std::unique_ptr<base::Value>{
+ new base::FundamentalValue{base::RandInt(0, 1) != 0}};
+ }
+}
+
+std::unique_ptr<base::Value> CreateRandomValue(int children) {
+ return CreateRandomValue(children, CreateRandomValueType(children > 0));
+}
+
+} // namespace
+
+TEST(DBusConversionTest, DictionaryToDBusVariantDictionary) {
+ EXPECT_EQ((VariantDictionary{{"bool", true}}),
+ ToDBus(*CreateDictionaryValue("{'bool': true}")));
+ EXPECT_EQ((VariantDictionary{{"int", 5}}),
+ ToDBus(*CreateDictionaryValue("{'int': 5}")));
+ EXPECT_EQ((VariantDictionary{{"double", 6.7}}),
+ ToDBus(*CreateDictionaryValue("{'double': 6.7}")));
+ EXPECT_EQ((VariantDictionary{{"string", std::string{"abc"}}}),
+ ToDBus(*CreateDictionaryValue("{'string': 'abc'}")));
+ EXPECT_EQ((VariantDictionary{{"object", VariantDictionary{{"bool", true}}}}),
+ ToDBus(*CreateDictionaryValue("{'object': {'bool': true}}")));
+ EXPECT_EQ((VariantDictionary{{"emptyList", std::vector<Any>{}}}),
+ ToDBus(*CreateDictionaryValue("{'emptyList': []}")));
+ EXPECT_EQ((VariantDictionary{{"intList", std::vector<int>{5}}}),
+ ToDBus(*CreateDictionaryValue("{'intList': [5]}")));
+ EXPECT_EQ((VariantDictionary{
+ {"intListList", std::vector<Any>{std::vector<int>{5},
+ std::vector<int>{6, 7}}}}),
+ ToDBus(*CreateDictionaryValue("{'intListList': [[5], [6, 7]]}")));
+ EXPECT_EQ((VariantDictionary{{"objList",
+ std::vector<VariantDictionary>{
+ {{"string", std::string{"abc"}}}}}}),
+ ToDBus(*CreateDictionaryValue("{'objList': [{'string': 'abc'}]}")));
+}
+
+TEST(DBusConversionTest, DictionaryFromDBusVariantDictionary) {
+ EXPECT_JSON_EQ("{'bool': true}", *FromDBus({{"bool", true}}));
+ EXPECT_JSON_EQ("{'int': 5}", *FromDBus({{"int", 5}}));
+ EXPECT_JSON_EQ("{'double': 6.7}", *FromDBus({{"double", 6.7}}));
+ EXPECT_JSON_EQ("{'string': 'abc'}",
+ *FromDBus({{"string", std::string{"abc"}}}));
+ EXPECT_JSON_EQ("{'object': {'bool': true}}",
+ *FromDBus({{"object", VariantDictionary{{"bool", true}}}}));
+ EXPECT_JSON_EQ("{'emptyList': []}",
+ *FromDBus({{"emptyList", std::vector<bool>{}}}));
+ EXPECT_JSON_EQ("{'intList': [5]}",
+ *FromDBus({{"intList", std::vector<int>{5}}}));
+ EXPECT_JSON_EQ(
+ "{'intListList': [[5], [6, 7]]}",
+ *FromDBus({{"intListList", std::vector<Any>{std::vector<int>{5},
+ std::vector<int>{6, 7}}}}));
+ EXPECT_JSON_EQ(
+ "{'objList': [{'string': 'abc'}]}",
+ *FromDBus({{"objList", std::vector<VariantDictionary>{
+ {{"string", std::string{"abc"}}}}}}));
+ EXPECT_JSON_EQ("{'int': 5}", *FromDBus({{"int", Any{Any{5}}}}));
+}
+
+TEST(DBusConversionTest, DictionaryFromDBusVariantDictionary_Errors) {
+ EXPECT_FALSE(FromDBus({{"cString", "abc"}}));
+ EXPECT_FALSE(FromDBus({{"float", 1.0f}}));
+ EXPECT_FALSE(FromDBus({{"listList", std::vector<std::vector<int>>{}}}));
+ EXPECT_FALSE(FromDBus({{"any", Any{}}}));
+ EXPECT_FALSE(FromDBus({{"null", nullptr}}));
+}
+
+TEST(DBusConversionTest, DBusRandomDictionaryConversion) {
+ auto dict = CreateRandomDictionary(10000);
+ auto varian_dict = ToDBus(*dict);
+ auto dict_restored = FromDBus(varian_dict);
+ EXPECT_PRED2(IsEqualValue, *dict, *dict_restored);
+}
+
+} // namespace buffet
diff --git a/buffet/etc/buffet/base_state.defaults.json b/buffet/etc/buffet/base_state.defaults.json
new file mode 100644
index 0000000..166f2f1
--- /dev/null
+++ b/buffet/etc/buffet/base_state.defaults.json
@@ -0,0 +1,8 @@
+{
+ "base": {
+ "firmwareVersion": "",
+ "localDiscoveryEnabled": false,
+ "localAnonymousAccessMaxRole": "none",
+ "localPairingEnabled": false
+ }
+}
diff --git a/buffet/etc/buffet/base_state.schema.json b/buffet/etc/buffet/base_state.schema.json
new file mode 100644
index 0000000..19cc9b2
--- /dev/null
+++ b/buffet/etc/buffet/base_state.schema.json
@@ -0,0 +1,13 @@
+{
+ "base": {
+ "firmwareVersion": "string",
+ "localDiscoveryEnabled": "boolean",
+ "localAnonymousAccessMaxRole": [ "none", "viewer", "user" ],
+ "localPairingEnabled": "boolean",
+ "network": {
+ "properties": {
+ "name": "string"
+ }
+ }
+ }
+}
diff --git a/buffet/etc/buffet/commands/buffet.json b/buffet/etc/buffet/commands/buffet.json
new file mode 100644
index 0000000..4196a22
--- /dev/null
+++ b/buffet/etc/buffet/commands/buffet.json
@@ -0,0 +1,6 @@
+{
+ "base": {
+ "updateBaseConfiguration": { },
+ "updateDeviceInfo": { }
+ }
+}
diff --git a/buffet/etc/buffet/commands/test.json b/buffet/etc/buffet/commands/test.json
new file mode 100644
index 0000000..911d8f0
--- /dev/null
+++ b/buffet/etc/buffet/commands/test.json
@@ -0,0 +1,17 @@
+{
+ "base": {
+ "_jump": {
+ "parameters": {
+ "_height": "integer"
+ },
+ "progress": {
+ "progress": {
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 100,
+ "isRequired": true
+ }
+ }
+ }
+ }
+}
diff --git a/buffet/etc/buffet/gcd.json b/buffet/etc/buffet/gcd.json
new file mode 100644
index 0000000..368704e
--- /dev/null
+++ b/buffet/etc/buffet/gcd.json
@@ -0,0 +1,35 @@
+{
+ "base": {
+ "updateBaseConfiguration": {
+ "minimalRole": "manager",
+ "parameters": {
+ "localDiscoveryEnabled": "boolean",
+ "localAnonymousAccessMaxRole": [ "none", "viewer", "user" ],
+ "localPairingEnabled": "boolean"
+ },
+ "results": {}
+ },
+
+ "reboot": {
+ "minimalRole": "user",
+ "parameters": {},
+ "results": {}
+ },
+
+ "identify": {
+ "minimalRole": "user",
+ "parameters": {},
+ "results": {}
+ },
+
+ "updateDeviceInfo": {
+ "minimalRole": "manager",
+ "parameters": {
+ "description": "string",
+ "name": "string",
+ "location": "string"
+ },
+ "results": {}
+ }
+ }
+}
diff --git a/buffet/etc/dbus-1/org.chromium.Buffet.conf b/buffet/etc/dbus-1/org.chromium.Buffet.conf
new file mode 100644
index 0000000..23a4ffc
--- /dev/null
+++ b/buffet/etc/dbus-1/org.chromium.Buffet.conf
@@ -0,0 +1,21 @@
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <policy user="root">
+ <allow send_destination="org.chromium.Buffet" />
+ </policy>
+
+ <policy group="buffet">
+ <allow send_destination="org.chromium.Buffet" />
+ </policy>
+
+ <policy user="buffet">
+ <allow own="org.chromium.Buffet" />
+ </policy>
+
+ <policy user="webservd">
+ <allow send_destination="org.chromium.Buffet"
+ send_interface="org.chromium.WebServer.RequestHandler"/>
+ </policy>
+</busconfig>
diff --git a/buffet/etc/init/buffet.conf b/buffet/etc/init/buffet.conf
new file mode 100644
index 0000000..6948605
--- /dev/null
+++ b/buffet/etc/init/buffet.conf
@@ -0,0 +1,44 @@
+# 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.
+
+description "Brillo Buffet Service"
+author "chromium-os-dev@chromium.org"
+
+start on starting system-services
+stop on stopping system-services
+respawn
+
+env BUFFET_LOG_LEVEL=0
+env BUFFET_ENABLE_XMPP=
+env BUFFET_STATE_PATH=
+env BUFFET_CONFIG_PATH=
+env BUFFET_ENABLE_PING=false
+env BUFFET_DEVICE_WHITELIST=
+env BUFFET_DISABLE_PRIVET=false
+env BUFFET_TEST_DEFINITIONS_PATH=
+env BUFFET_DISABLE_SECURITY=false
+env BUFFET_TEST_PRIVET_SSID=
+
+pre-start script
+ mkdir -m 0755 -p /var/lib/buffet
+ chown -R buffet:buffet /var/lib/buffet
+end script
+
+# Minijail actually forks off our desired process.
+expect fork
+
+exec minijail0 -i -g buffet -u buffet /usr/bin/buffet \
+ --v="${BUFFET_LOG_LEVEL}" \
+ --config_path="${BUFFET_CONFIG_PATH}" \
+ --state_path="${BUFFET_STATE_PATH}" \
+ --disable_security="${BUFFET_DISABLE_SECURITY}" \
+ --enable_ping="${BUFFET_ENABLE_PING}" \
+ --device_whitelist="${BUFFET_DEVICE_WHITELIST}" \
+ --disable_privet="${BUFFET_DISABLE_PRIVET}" \
+ --test_definitions_path="${BUFFET_TEST_DEFINITIONS_PATH}" \
+ --enable_xmpp="${BUFFET_ENABLE_XMPP}" \
+ --test_privet_ssid="${BUFFET_TEST_PRIVET_SSID}"
+
+# Wait for daemon to claim its D-Bus name before transitioning to started.
+post-start exec gdbus wait --system --timeout 30 org.chromium.Buffet
diff --git a/buffet/http_transport_client.cc b/buffet/http_transport_client.cc
new file mode 100644
index 0000000..259a247
--- /dev/null
+++ b/buffet/http_transport_client.cc
@@ -0,0 +1,90 @@
+// Copyright 2015 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/http_transport_client.h"
+
+#include <base/bind.h>
+#include <chromeos/errors/error.h>
+#include <chromeos/http/http_request.h>
+#include <chromeos/http/http_utils.h>
+
+namespace buffet {
+
+namespace {
+
+// The number of seconds each HTTP request will be allowed before timing out.
+const int kRequestTimeoutSeconds = 30;
+
+class ResponseImpl : public weave::HttpClient::Response {
+ public:
+ ~ResponseImpl() override = default;
+ explicit ResponseImpl(std::unique_ptr<chromeos::http::Response> response)
+ : response_{std::move(response)},
+ data_{response_->ExtractDataAsString()} {}
+
+ // weave::HttpClient::Response implementation
+ int GetStatusCode() const override { return response_->GetStatusCode(); }
+
+ std::string GetContentType() const override {
+ return response_->GetContentType();
+ }
+
+ const std::string& GetData() const override { return data_; }
+
+ private:
+ std::unique_ptr<chromeos::http::Response> response_;
+ std::string data_;
+ DISALLOW_COPY_AND_ASSIGN(ResponseImpl);
+};
+
+void OnSuccessCallback(
+ const weave::HttpClient::SuccessCallback& success_callback,
+ int id,
+ std::unique_ptr<chromeos::http::Response> response) {
+ success_callback.Run(id, ResponseImpl{std::move(response)});
+}
+
+void OnErrorCallback(const weave::HttpClient::ErrorCallback& error_callback,
+ int id,
+ const chromeos::Error* error) {
+ error_callback.Run(id, error);
+}
+
+} // anonymous namespace
+
+HttpTransportClient::HttpTransportClient()
+ : transport_{chromeos::http::Transport::CreateDefault()} {
+ transport_->SetDefaultTimeout(
+ base::TimeDelta::FromSeconds(kRequestTimeoutSeconds));
+}
+
+HttpTransportClient::~HttpTransportClient() {}
+
+std::unique_ptr<weave::HttpClient::Response>
+HttpTransportClient::SendRequestAndBlock(const std::string& method,
+ const std::string& url,
+ const std::string& data,
+ const std::string& mime_type,
+ const Headers& headers,
+ chromeos::ErrorPtr* error) {
+ return std::unique_ptr<weave::HttpClient::Response>{
+ new ResponseImpl{chromeos::http::SendRequestAndBlock(
+ method, url, data.data(), data.size(), mime_type, headers, transport_,
+ error)}};
+}
+
+int HttpTransportClient::SendRequest(const std::string& method,
+ const std::string& url,
+ const std::string& data,
+ const std::string& mime_type,
+ const Headers& headers,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) {
+ return chromeos::http::SendRequest(
+ method, url, data.data(), data.size(), mime_type, headers, transport_,
+ base::Bind(&OnSuccessCallback, success_callback),
+ base::Bind(&OnErrorCallback, error_callback));
+}
+
+} // namespace buffet
diff --git a/buffet/http_transport_client.h b/buffet/http_transport_client.h
new file mode 100644
index 0000000..dfdaed0
--- /dev/null
+++ b/buffet/http_transport_client.h
@@ -0,0 +1,51 @@
+// Copyright 2015 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.
+
+#ifndef BUFFET_HTTP_TRANSPORT_CLIENT_H_
+#define BUFFET_HTTP_TRANSPORT_CLIENT_H_
+
+#include <memory>
+#include <string>
+
+#include <weave/http_client.h>
+
+namespace chromeos {
+namespace http {
+class Transport;
+}
+}
+
+namespace buffet {
+
+class HttpTransportClient : public weave::HttpClient {
+ public:
+ HttpTransportClient();
+
+ ~HttpTransportClient() override;
+
+ // weave::HttpClient implementation.
+ std::unique_ptr<Response> SendRequestAndBlock(
+ const std::string& method,
+ const std::string& url,
+ const std::string& data,
+ const std::string& mime_type,
+ const Headers& headers,
+ chromeos::ErrorPtr* error) override;
+
+ int SendRequest(const std::string& method,
+ const std::string& url,
+ const std::string& data,
+ const std::string& mime_type,
+ const Headers& headers,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback) override;
+
+ private:
+ std::shared_ptr<chromeos::http::Transport> transport_;
+ DISALLOW_COPY_AND_ASSIGN(HttpTransportClient);
+};
+
+} // namespace buffet
+
+#endif // BUFFET_HTTP_TRANSPORT_CLIENT_H_
diff --git a/buffet/main.cc b/buffet/main.cc
new file mode 100644
index 0000000..276e030
--- /dev/null
+++ b/buffet/main.cc
@@ -0,0 +1,107 @@
+// 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 <string>
+
+#include <base/files/file_path.h>
+#include <chromeos/dbus/async_event_sequencer.h>
+#include <chromeos/dbus/exported_object_manager.h>
+#include <chromeos/daemons/dbus_daemon.h>
+#include <chromeos/flag_helper.h>
+#include <chromeos/strings/string_utils.h>
+#include <chromeos/syslog_logging.h>
+
+#include "buffet/dbus_constants.h"
+#include "buffet/manager.h"
+
+using chromeos::dbus_utils::AsyncEventSequencer;
+using chromeos::DBusServiceDaemon;
+using buffet::kServiceName;
+using buffet::kRootServicePath;
+
+namespace buffet {
+
+class Daemon final : public DBusServiceDaemon {
+ public:
+ Daemon(const weave::Device::Options& options,
+ const std::set<std::string>& device_whitelist)
+ : DBusServiceDaemon(kServiceName, kRootServicePath),
+ options_{options},
+ device_whitelist_{device_whitelist} {}
+
+ protected:
+ void RegisterDBusObjectsAsync(AsyncEventSequencer* sequencer) override {
+ manager_.reset(new buffet::Manager(object_manager_->AsWeakPtr()));
+ manager_->Start(options_, device_whitelist_, sequencer);
+ }
+
+ void OnShutdown(int* return_code) override { manager_->Stop(); }
+
+ private:
+ weave::Device::Options options_;
+ std::set<std::string> device_whitelist_;
+
+ std::unique_ptr<buffet::Manager> manager_;
+ DISALLOW_COPY_AND_ASSIGN(Daemon);
+};
+
+} // namespace buffet
+
+namespace {
+
+const char kDefaultConfigFilePath[] = "/etc/buffet/buffet.conf";
+const char kDefaultStateFilePath[] = "/var/lib/buffet/device_reg_info";
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+ DEFINE_bool(log_to_stderr, false, "log trace messages to stderr as well");
+ DEFINE_string(config_path, kDefaultConfigFilePath,
+ "Path to file containing config information.");
+ DEFINE_string(state_path, kDefaultStateFilePath,
+ "Path to file containing state information.");
+ DEFINE_bool(enable_xmpp, true,
+ "Connect to GCD via a persistent XMPP connection.");
+ DEFINE_bool(disable_privet, false, "disable Privet protocol");
+ DEFINE_bool(enable_ping, false, "enable test HTTP handler at /privet/ping");
+ DEFINE_string(device_whitelist, "",
+ "Comma separated list of network interfaces to monitor for "
+ "connectivity (an empty list enables all interfaces).");
+
+ DEFINE_bool(disable_security, false,
+ "disable Privet security for tests. For test only.");
+ DEFINE_string(test_privet_ssid, "",
+ "Fixed SSID for WiFi bootstrapping. For test only.");
+ DEFINE_string(test_definitions_path, "",
+ "Path to directory containing additional command "
+ "and state definitions. For test only.");
+
+ chromeos::FlagHelper::Init(argc, argv, "Privet protocol handler daemon");
+ if (FLAGS_config_path.empty())
+ FLAGS_config_path = kDefaultConfigFilePath;
+ if (FLAGS_state_path.empty())
+ FLAGS_state_path = kDefaultStateFilePath;
+ int flags = chromeos::kLogToSyslog | chromeos::kLogHeader;
+ if (FLAGS_log_to_stderr)
+ flags |= chromeos::kLogToStderr;
+ chromeos::InitLog(flags);
+
+ auto device_whitelist =
+ chromeos::string_utils::Split(FLAGS_device_whitelist, ",", true, true);
+
+ weave::Device::Options options;
+ options.config_path = base::FilePath{FLAGS_config_path};
+ options.state_path = base::FilePath{FLAGS_state_path};
+ options.definitions_path = base::FilePath{"/etc/buffet"};
+ options.test_definitions_path = base::FilePath{FLAGS_test_definitions_path};
+ options.xmpp_enabled = FLAGS_enable_xmpp;
+ options.disable_privet = FLAGS_disable_privet;
+ options.disable_security = FLAGS_disable_security;
+ options.enable_ping = FLAGS_enable_ping;
+ options.test_privet_ssid = FLAGS_test_privet_ssid;
+
+ buffet::Daemon daemon{options, std::set<std::string>{device_whitelist.begin(),
+ device_whitelist.end()}};
+ return daemon.Run();
+}
diff --git a/buffet/manager.cc b/buffet/manager.cc
new file mode 100644
index 0000000..70c31f6
--- /dev/null
+++ b/buffet/manager.cc
@@ -0,0 +1,326 @@
+// 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/manager.h"
+
+#include <map>
+#include <set>
+#include <string>
+
+#include <base/bind.h>
+#include <base/bind_helpers.h>
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+#include <base/message_loop/message_loop.h>
+#include <base/time/time.h>
+#include <chromeos/dbus/async_event_sequencer.h>
+#include <chromeos/dbus/exported_object_manager.h>
+#include <chromeos/errors/error.h>
+#include <chromeos/http/http_transport.h>
+#include <chromeos/key_value_store.h>
+#include <dbus/bus.h>
+#include <dbus/object_path.h>
+#include <dbus/values_util.h>
+#include <weave/enum_to_string.h>
+
+#include "buffet/dbus_command_dispatcher.h"
+#include "buffet/dbus_conversion.h"
+#include "buffet/http_transport_client.h"
+#include "buffet/peerd_client.h"
+#include "buffet/shill_client.h"
+#include "buffet/webserv_client.h"
+
+using chromeos::dbus_utils::AsyncEventSequencer;
+using chromeos::dbus_utils::ExportedObjectManager;
+
+namespace buffet {
+
+namespace {
+
+const char kPairingSessionIdKey[] = "sessionId";
+const char kPairingModeKey[] = "mode";
+const char kPairingCodeKey[] = "code";
+
+const char kErrorDomain[] = "buffet";
+const char kNotImplemented[] = "notImplemented";
+
+} // anonymous namespace
+
+Manager::Manager(const base::WeakPtr<ExportedObjectManager>& object_manager)
+ : dbus_object_(object_manager.get(),
+ object_manager->GetBus(),
+ org::chromium::Buffet::ManagerAdaptor::GetObjectPath()) {
+}
+
+Manager::~Manager() {
+}
+
+void Manager::Start(const weave::Device::Options& options,
+ const std::set<std::string>& device_whitelist,
+ AsyncEventSequencer* sequencer) {
+ http_client_.reset(new HttpTransportClient);
+ shill_client_.reset(new ShillClient{dbus_object_.GetBus(), device_whitelist});
+ if (!options.disable_privet) {
+ peerd_client_.reset(new PeerdClient{dbus_object_.GetBus()});
+ web_serv_client_.reset(new WebServClient{dbus_object_.GetBus(), sequencer});
+ }
+
+ device_ = weave::Device::Create();
+ device_->Start(options, http_client_.get(), shill_client_.get(),
+ peerd_client_.get(), web_serv_client_.get());
+
+ command_dispatcher_.reset(new DBusCommandDispacher{
+ dbus_object_.GetObjectManager(), device_->GetCommands()});
+
+ device_->GetState()->AddOnChangedCallback(
+ base::Bind(&Manager::OnStateChanged, weak_ptr_factory_.GetWeakPtr()));
+
+ device_->GetConfig()->AddOnChangedCallback(
+ base::Bind(&Manager::OnConfigChanged, weak_ptr_factory_.GetWeakPtr()));
+
+ device_->GetCloud()->AddOnRegistrationChangedCallback(base::Bind(
+ &Manager::OnRegistrationChanged, weak_ptr_factory_.GetWeakPtr()));
+
+ if (device_->GetPrivet()) {
+ device_->GetPrivet()->AddOnWifiSetupChangedCallback(base::Bind(
+ &Manager::UpdateWiFiBootstrapState, weak_ptr_factory_.GetWeakPtr()));
+
+ device_->GetPrivet()->AddOnPairingChangedCallbacks(
+ base::Bind(&Manager::OnPairingStart, weak_ptr_factory_.GetWeakPtr()),
+ base::Bind(&Manager::OnPairingEnd, weak_ptr_factory_.GetWeakPtr()));
+ } else {
+ UpdateWiFiBootstrapState(weave::WifiSetupState::kDisabled);
+ }
+
+ dbus_adaptor_.RegisterWithDBusObject(&dbus_object_);
+ dbus_object_.RegisterAsync(
+ sequencer->GetHandler("Manager.RegisterAsync() failed.", true));
+}
+
+void Manager::Stop() {
+ device_.reset();
+}
+
+// TODO(vitalybuka): Remove, it's just duplicate of property.
+void Manager::CheckDeviceRegistered(
+ DBusMethodResponsePtr<std::string> response) {
+ LOG(INFO) << "Received call to Manager.CheckDeviceRegistered()";
+ response->Return(dbus_adaptor_.GetDeviceId());
+}
+
+// TODO(vitalybuka): Remove or rename to leave for testing.
+void Manager::GetDeviceInfo(DBusMethodResponsePtr<std::string> response) {
+ LOG(INFO) << "Received call to Manager.GetDeviceInfo()";
+ std::shared_ptr<DBusMethodResponse<std::string>> shared_response =
+ std::move(response);
+
+ device_->GetCloud()->GetDeviceInfo(
+ base::Bind(&Manager::OnGetDeviceInfoSuccess,
+ weak_ptr_factory_.GetWeakPtr(), shared_response),
+ base::Bind(&Manager::OnGetDeviceInfoError, weak_ptr_factory_.GetWeakPtr(),
+ shared_response));
+}
+
+void Manager::OnGetDeviceInfoSuccess(
+ const std::shared_ptr<DBusMethodResponse<std::string>>& response,
+ const base::DictionaryValue& device_info) {
+ std::string device_info_str;
+ base::JSONWriter::WriteWithOptions(
+ device_info, base::JSONWriter::OPTIONS_PRETTY_PRINT, &device_info_str);
+ response->Return(device_info_str);
+}
+
+void Manager::OnGetDeviceInfoError(
+ const std::shared_ptr<DBusMethodResponse<std::string>>& response,
+ const chromeos::Error* error) {
+ response->ReplyWithError(error);
+}
+
+void Manager::RegisterDevice(DBusMethodResponsePtr<std::string> response,
+ const std::string& ticket_id) {
+ LOG(INFO) << "Received call to Manager.RegisterDevice()";
+
+ chromeos::ErrorPtr error;
+ std::string device_id =
+ device_->GetCloud()->RegisterDevice(ticket_id, &error);
+ if (!device_id.empty()) {
+ response->Return(device_id);
+ return;
+ }
+ CHECK(error);
+ response->ReplyWithError(error.get());
+}
+
+void Manager::UpdateState(DBusMethodResponsePtr<> response,
+ const chromeos::VariantDictionary& property_set) {
+ chromeos::ErrorPtr error;
+ auto properties = DictionaryFromDBusVariantDictionary(property_set, &error);
+ if (!properties)
+ response->ReplyWithError(error.get());
+
+ if (!device_->GetState()->SetProperties(*properties, &error))
+ response->ReplyWithError(error.get());
+ else
+ response->Return();
+}
+
+bool Manager::GetState(chromeos::ErrorPtr* error, std::string* state) {
+ auto json = device_->GetState()->GetStateValuesAsJson();
+ CHECK(json);
+ base::JSONWriter::WriteWithOptions(
+ *json, base::JSONWriter::OPTIONS_PRETTY_PRINT, state);
+ return true;
+}
+
+void Manager::AddCommand(DBusMethodResponsePtr<std::string> response,
+ const std::string& json_command,
+ const std::string& in_user_role) {
+ std::string error_message;
+ std::unique_ptr<base::Value> value(
+ base::JSONReader::ReadAndReturnError(json_command, base::JSON_PARSE_RFC,
+ nullptr, &error_message)
+ .release());
+ const base::DictionaryValue* command{nullptr};
+ if (!value || !value->GetAsDictionary(&command)) {
+ return response->ReplyWithError(FROM_HERE, chromeos::errors::json::kDomain,
+ chromeos::errors::json::kParseError,
+ error_message);
+ }
+
+ chromeos::ErrorPtr error;
+ weave::UserRole role;
+ if (!StringToEnum(in_user_role, &role)) {
+ chromeos::Error::AddToPrintf(&error, FROM_HERE, kErrorDomain,
+ "invalid_user_role", "Invalid role: '%s'",
+ in_user_role.c_str());
+ return response->ReplyWithError(error.get());
+ }
+
+ std::string id;
+ if (!device_->GetCommands()->AddCommand(*command, role, &id, &error))
+ return response->ReplyWithError(error.get());
+
+ response->Return(id);
+}
+
+void Manager::GetCommand(DBusMethodResponsePtr<std::string> response,
+ const std::string& id) {
+ const weave::Command* command = device_->GetCommands()->FindCommand(id);
+ if (!command) {
+ response->ReplyWithError(FROM_HERE, kErrorDomain, "unknown_command",
+ "Can't find command with id: " + id);
+ return;
+ }
+ std::string command_str;
+ base::JSONWriter::WriteWithOptions(
+ *command->ToJson(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &command_str);
+ response->Return(command_str);
+}
+
+std::string Manager::TestMethod(const std::string& message) {
+ LOG(INFO) << "Received call to test method: " << message;
+ return message;
+}
+
+bool Manager::EnableWiFiBootstrapping(
+ chromeos::ErrorPtr* error,
+ const dbus::ObjectPath& in_listener_path,
+ const chromeos::VariantDictionary& in_options) {
+ chromeos::Error::AddTo(error, FROM_HERE, kErrorDomain, kNotImplemented,
+ "Manual WiFi bootstrapping is not implemented");
+ return false;
+}
+
+bool Manager::DisableWiFiBootstrapping(chromeos::ErrorPtr* error) {
+ chromeos::Error::AddTo(error, FROM_HERE, kErrorDomain, kNotImplemented,
+ "Manual WiFi bootstrapping is not implemented");
+ return false;
+}
+
+bool Manager::EnableGCDBootstrapping(
+ chromeos::ErrorPtr* error,
+ const dbus::ObjectPath& in_listener_path,
+ const chromeos::VariantDictionary& in_options) {
+ chromeos::Error::AddTo(error, FROM_HERE, kErrorDomain, kNotImplemented,
+ "Manual GCD bootstrapping is not implemented");
+ return false;
+}
+
+bool Manager::DisableGCDBootstrapping(chromeos::ErrorPtr* error) {
+ chromeos::Error::AddTo(error, FROM_HERE, kErrorDomain, kNotImplemented,
+ "Manual GCD bootstrapping is not implemented");
+ return false;
+}
+
+bool Manager::UpdateDeviceInfo(chromeos::ErrorPtr* error,
+ const std::string& name,
+ const std::string& description,
+ const std::string& location) {
+ return device_->GetCloud()->UpdateDeviceInfo(name, description, location,
+ error);
+}
+
+bool Manager::UpdateServiceConfig(chromeos::ErrorPtr* error,
+ const std::string& client_id,
+ const std::string& client_secret,
+ const std::string& api_key,
+ const std::string& oauth_url,
+ const std::string& service_url) {
+ return device_->GetCloud()->UpdateServiceConfig(
+ client_id, client_secret, api_key, oauth_url, service_url, error);
+}
+
+void Manager::OnStateChanged() {
+ auto state = device_->GetState()->GetStateValuesAsJson();
+ CHECK(state);
+ std::string json;
+ base::JSONWriter::WriteWithOptions(
+ *state, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
+ dbus_adaptor_.SetState(json);
+}
+
+void Manager::OnRegistrationChanged(weave::RegistrationStatus status) {
+ dbus_adaptor_.SetStatus(weave::EnumToString(status));
+}
+
+void Manager::OnConfigChanged(const weave::Settings& settings) {
+ dbus_adaptor_.SetDeviceId(settings.device_id);
+ dbus_adaptor_.SetOemName(settings.oem_name);
+ dbus_adaptor_.SetModelName(settings.model_name);
+ dbus_adaptor_.SetModelId(settings.model_id);
+ dbus_adaptor_.SetName(settings.name);
+ dbus_adaptor_.SetDescription(settings.description);
+ dbus_adaptor_.SetLocation(settings.location);
+ dbus_adaptor_.SetAnonymousAccessRole(settings.local_anonymous_access_role);
+}
+
+void Manager::UpdateWiFiBootstrapState(weave::WifiSetupState state) {
+ dbus_adaptor_.SetWiFiBootstrapState(weave::EnumToString(state));
+}
+
+void Manager::OnPairingStart(const std::string& session_id,
+ weave::PairingType pairing_type,
+ const std::vector<uint8_t>& code) {
+ // For now, just overwrite the exposed PairInfo with
+ // the most recent pairing attempt.
+ dbus_adaptor_.SetPairingInfo(chromeos::VariantDictionary{
+ {kPairingSessionIdKey, session_id},
+ {kPairingModeKey, weave::EnumToString(pairing_type)},
+ {kPairingCodeKey, code},
+ });
+}
+
+void Manager::OnPairingEnd(const std::string& session_id) {
+ auto exposed_pairing_attempt = dbus_adaptor_.GetPairingInfo();
+ auto it = exposed_pairing_attempt.find(kPairingSessionIdKey);
+ if (it == exposed_pairing_attempt.end()) {
+ return;
+ }
+ std::string exposed_session{it->second.TryGet<std::string>()};
+ if (exposed_session == session_id) {
+ dbus_adaptor_.SetPairingInfo(chromeos::VariantDictionary{});
+ }
+}
+
+} // namespace buffet
diff --git a/buffet/manager.h b/buffet/manager.h
new file mode 100644
index 0000000..1f89a01
--- /dev/null
+++ b/buffet/manager.h
@@ -0,0 +1,135 @@
+// 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.
+
+#ifndef BUFFET_MANAGER_H_
+#define BUFFET_MANAGER_H_
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <base/memory/weak_ptr.h>
+#include <base/values.h>
+#include <chromeos/dbus/data_serialization.h>
+#include <chromeos/dbus/dbus_object.h>
+#include <chromeos/dbus/exported_property_set.h>
+#include <chromeos/errors/error.h>
+#include <weave/device.h>
+
+#include "buffet/org.chromium.Buffet.Manager.h"
+
+namespace chromeos {
+namespace dbus_utils {
+class ExportedObjectManager;
+} // namespace dbus_utils
+} // namespace chromeos
+
+namespace buffet {
+
+class DBusCommandDispacher;
+class HttpTransportClient;
+class PeerdClient;
+class ShillClient;
+class WebServClient;
+
+template<typename... Types>
+using DBusMethodResponsePtr =
+ std::unique_ptr<chromeos::dbus_utils::DBusMethodResponse<Types...>>;
+
+template<typename... Types>
+using DBusMethodResponse =
+ chromeos::dbus_utils::DBusMethodResponse<Types...>;
+
+// The Manager is responsible for global state of Buffet. It exposes
+// interfaces which affect the entire device such as device registration and
+// device state.
+class Manager final : public org::chromium::Buffet::ManagerInterface {
+ public:
+ explicit Manager(
+ const base::WeakPtr<chromeos::dbus_utils::ExportedObjectManager>&
+ object_manager);
+ ~Manager();
+
+ void Start(const weave::Device::Options& options,
+ const std::set<std::string>& device_whitelist,
+ chromeos::dbus_utils::AsyncEventSequencer* sequencer);
+
+ void Stop();
+
+ private:
+ // DBus methods:
+ void CheckDeviceRegistered(
+ DBusMethodResponsePtr<std::string> response) override;
+ void GetDeviceInfo(DBusMethodResponsePtr<std::string> response) override;
+ void RegisterDevice(DBusMethodResponsePtr<std::string> response,
+ const std::string& ticket_id) override;
+ bool UpdateDeviceInfo(chromeos::ErrorPtr* error,
+ const std::string& name,
+ const std::string& description,
+ const std::string& location) override;
+ bool UpdateServiceConfig(chromeos::ErrorPtr* error,
+ const std::string& client_id,
+ const std::string& client_secret,
+ const std::string& api_key,
+ const std::string& oauth_url,
+ const std::string& service_url) override;
+ void UpdateState(DBusMethodResponsePtr<> response,
+ const chromeos::VariantDictionary& property_set) override;
+ bool GetState(chromeos::ErrorPtr* error, std::string* state) override;
+ void AddCommand(DBusMethodResponsePtr<std::string> response,
+ const std::string& json_command,
+ const std::string& in_user_role) override;
+ void GetCommand(DBusMethodResponsePtr<std::string> response,
+ const std::string& id) override;
+ std::string TestMethod(const std::string& message) override;
+ bool EnableWiFiBootstrapping(
+ chromeos::ErrorPtr* error,
+ const dbus::ObjectPath& in_listener_path,
+ const chromeos::VariantDictionary& in_options) override;
+ bool DisableWiFiBootstrapping(chromeos::ErrorPtr* error) override;
+ bool EnableGCDBootstrapping(
+ chromeos::ErrorPtr* error,
+ const dbus::ObjectPath& in_listener_path,
+ const chromeos::VariantDictionary& in_options) override;
+ bool DisableGCDBootstrapping(chromeos::ErrorPtr* error) override;
+
+ void OnGetDeviceInfoSuccess(
+ const std::shared_ptr<DBusMethodResponse<std::string>>& response,
+ const base::DictionaryValue& device_info);
+ void OnGetDeviceInfoError(
+ const std::shared_ptr<DBusMethodResponse<std::string>>& response,
+ const chromeos::Error* error);
+
+ void StartPrivet(const weave::Device::Options& options,
+ chromeos::dbus_utils::AsyncEventSequencer* sequencer);
+
+ void OnStateChanged();
+ void OnRegistrationChanged(weave::RegistrationStatus status);
+ void OnConfigChanged(const weave::Settings& settings);
+ void UpdateWiFiBootstrapState(weave::WifiSetupState state);
+ void OnPairingStart(const std::string& session_id,
+ weave::PairingType pairing_type,
+ const std::vector<uint8_t>& code);
+ void OnPairingEnd(const std::string& session_id);
+
+ org::chromium::Buffet::ManagerAdaptor dbus_adaptor_{this};
+ chromeos::dbus_utils::DBusObject dbus_object_;
+
+ std::unique_ptr<HttpTransportClient> http_client_;
+ std::unique_ptr<ShillClient> shill_client_;
+ std::unique_ptr<PeerdClient> peerd_client_;
+ std::unique_ptr<WebServClient> web_serv_client_;
+ std::unique_ptr<weave::Device> device_;
+ std::unique_ptr<DBusCommandDispacher> command_dispatcher_;
+
+ base::WeakPtrFactory<Manager> weak_ptr_factory_{this};
+ DISALLOW_COPY_AND_ASSIGN(Manager);
+};
+
+} // namespace buffet
+
+#endif // BUFFET_MANAGER_H_
diff --git a/buffet/peerd_client.cc b/buffet/peerd_client.cc
new file mode 100644
index 0000000..cfbf64a
--- /dev/null
+++ b/buffet/peerd_client.cc
@@ -0,0 +1,148 @@
+// 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/peerd_client.h"
+
+#include <map>
+
+#include <base/message_loop/message_loop.h>
+#include <chromeos/errors/error.h>
+#include <chromeos/strings/string_utils.h>
+
+using org::chromium::peerd::PeerProxy;
+
+namespace buffet {
+
+namespace {
+
+// Commit changes only if no update request happened during the timeout.
+// Usually updates happen in batches, so we don't want to flood network with
+// updates relevant for a short amount of time.
+const int kCommitTimeoutSeconds = 1;
+
+const char kSelfPath[] = "/org/chromium/peerd/Self";
+
+void OnError(const std::string& operation, chromeos::Error* error) {
+ LOG(ERROR) << operation << " failed:" << error->GetMessage();
+}
+
+} // namespace
+
+PeerdClient::PeerdClient(const scoped_refptr<dbus::Bus>& bus)
+ : peerd_object_manager_proxy_{bus} {
+ peerd_object_manager_proxy_.SetManagerAddedCallback(
+ base::Bind(&PeerdClient::OnPeerdOnline, weak_ptr_factory_.GetWeakPtr()));
+ peerd_object_manager_proxy_.SetManagerRemovedCallback(
+ base::Bind(&PeerdClient::OnPeerdOffline, weak_ptr_factory_.GetWeakPtr()));
+ peerd_object_manager_proxy_.SetPeerAddedCallback(
+ base::Bind(&PeerdClient::OnNewPeer, weak_ptr_factory_.GetWeakPtr()));
+}
+
+PeerdClient::~PeerdClient() {
+ RemoveService();
+}
+
+std::string PeerdClient::GetId() const {
+ return device_id_;
+}
+
+void PeerdClient::PublishService(
+ const std::string& service_name,
+ uint16_t port,
+ const std::map<std::string, std::string>& txt) {
+ // Only one service supported.
+ CHECK(service_name_.empty() || service_name_ == service_name);
+ service_name_ = service_name;
+ port_ = port;
+ txt_ = txt;
+
+ Update();
+}
+
+void PeerdClient::StopPublishing(const std::string& service_name) {
+ // Only one service supported.
+ CHECK(service_name_.empty() || service_name_ == service_name);
+ service_name_ = service_name;
+ port_ = 0;
+
+ Update();
+}
+
+void PeerdClient::Update() {
+ // Abort pending updates, and wait for more changes.
+ restart_weak_ptr_factory_.InvalidateWeakPtrs();
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, base::Bind(&PeerdClient::UpdateImpl,
+ restart_weak_ptr_factory_.GetWeakPtr()),
+ base::TimeDelta::FromSeconds(kCommitTimeoutSeconds));
+}
+
+void PeerdClient::OnNewPeer(PeerProxy* peer) {
+ if (!peer || peer->GetObjectPath().value() != kSelfPath)
+ return;
+ peer->SetPropertyChangedCallback(base::Bind(
+ &PeerdClient::OnPeerPropertyChanged, weak_ptr_factory_.GetWeakPtr()));
+ OnPeerPropertyChanged(peer, PeerProxy::UUIDName());
+}
+
+void PeerdClient::OnPeerPropertyChanged(PeerProxy* peer,
+ const std::string& property_name) {
+ if (property_name != PeerProxy::UUIDName() ||
+ peer->GetObjectPath().value() != kSelfPath)
+ return;
+ const std::string new_id{peer->uuid()};
+ if (new_id != device_id_) {
+ device_id_ = new_id;
+ Update();
+ }
+}
+
+void PeerdClient::OnPeerdOnline(
+ org::chromium::peerd::ManagerProxy* manager_proxy) {
+ peerd_manager_proxy_ = manager_proxy;
+ VLOG(1) << "Peerd manager is online at '"
+ << manager_proxy->GetObjectPath().value() << "'.";
+ Update();
+}
+
+void PeerdClient::OnPeerdOffline(const dbus::ObjectPath& object_path) {
+ peerd_manager_proxy_ = nullptr;
+ VLOG(1) << "Peerd manager is now offline.";
+}
+
+void PeerdClient::ExposeService() {
+ // Do nothing if peerd hasn't started yet.
+ if (peerd_manager_proxy_ == nullptr)
+ return;
+ VLOG(1) << "Starting peerd advertising.";
+ CHECK_NE(port_, 0);
+ CHECK(!service_name_.empty());
+ CHECK(!txt_.empty());
+ std::map<std::string, chromeos::Any> mdns_options{
+ {"port", chromeos::Any{port_}},
+ };
+
+ peerd_manager_proxy_->ExposeServiceAsync(
+ service_name_, txt_, {{"mdns", mdns_options}}, base::Closure(),
+ base::Bind(&OnError, "ExposeService"));
+}
+
+void PeerdClient::RemoveService() {
+ if (peerd_manager_proxy_ == nullptr)
+ return;
+
+ VLOG(1) << "Stopping peerd advertising.";
+ if (!service_name_.empty()) {
+ peerd_manager_proxy_->RemoveExposedServiceAsync(
+ service_name_, base::Closure(), base::Bind(&OnError, "RemoveService"));
+ }
+}
+
+void PeerdClient::UpdateImpl() {
+ if (port_ == 0)
+ return RemoveService();
+ ExposeService();
+}
+
+} // namespace buffet
diff --git a/buffet/peerd_client.h b/buffet/peerd_client.h
new file mode 100644
index 0000000..ac75e19
--- /dev/null
+++ b/buffet/peerd_client.h
@@ -0,0 +1,71 @@
+// 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.
+
+#ifndef BUFFET_PEERD_CLIENT_H_
+#define BUFFET_PEERD_CLIENT_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <base/callback.h>
+#include <base/memory/ref_counted.h>
+#include <weave/mdns.h>
+
+#include "peerd/dbus-proxies.h"
+
+namespace dbus {
+class Bus;
+} // namespace dbus
+
+namespace buffet {
+
+// Publishes privet service on mDns using peerd.
+class PeerdClient : public weave::Mdns {
+ public:
+ explicit PeerdClient(const scoped_refptr<dbus::Bus>& bus);
+ ~PeerdClient() override;
+
+ // Mdns implementation.
+ void PublishService(const std::string& service_name,
+ uint16_t port,
+ const std::map<std::string, std::string>& txt) override;
+ void StopPublishing(const std::string& service_name) override;
+ std::string GetId() const override;
+
+ private:
+ void OnPeerdOnline(org::chromium::peerd::ManagerProxy* manager_proxy);
+ void OnPeerdOffline(const dbus::ObjectPath& object_path);
+ void OnNewPeer(org::chromium::peerd::PeerProxy* peer_proxy);
+ void OnPeerPropertyChanged(org::chromium::peerd::PeerProxy* peer_proxy,
+ const std::string& property_name);
+
+ // Updates published information. Removes service if HTTP is not alive.
+ void Update();
+
+ void ExposeService();
+ void RemoveService();
+
+ void UpdateImpl();
+
+ org::chromium::peerd::ObjectManagerProxy peerd_object_manager_proxy_;
+ // |peerd_manager_proxy_| is owned by |peerd_object_manager_proxy_|.
+ org::chromium::peerd::ManagerProxy* peerd_manager_proxy_{nullptr};
+
+ // Cached value of the device ID that we got from peerd.
+ std::string device_id_;
+
+ std::string service_name_;
+ uint16_t port_{0};
+ std::map<std::string, std::string> txt_;
+
+ base::WeakPtrFactory<PeerdClient> restart_weak_ptr_factory_{this};
+ base::WeakPtrFactory<PeerdClient> weak_ptr_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(PeerdClient);
+};
+
+} // namespace buffet
+
+#endif // BUFFET_PEERD_CLIENT_H_
diff --git a/buffet/shill_client.cc b/buffet/shill_client.cc
new file mode 100644
index 0000000..c549fd4
--- /dev/null
+++ b/buffet/shill_client.cc
@@ -0,0 +1,499 @@
+// 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/shill_client.h"
+
+#include <set>
+
+#include <base/message_loop/message_loop.h>
+#include <base/stl_util.h>
+#include <chromeos/any.h>
+#include <chromeos/dbus/service_constants.h>
+#include <chromeos/errors/error.h>
+#include <chromeos/errors/error_codes.h>
+#include <weave/enum_to_string.h>
+
+#include "buffet/ap_manager_client.h"
+
+using chromeos::Any;
+using chromeos::VariantDictionary;
+using dbus::ObjectPath;
+using org::chromium::flimflam::DeviceProxy;
+using org::chromium::flimflam::ServiceProxy;
+using std::map;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace buffet {
+
+namespace {
+
+void IgnoreDetachEvent() {}
+
+bool GetStateForService(ServiceProxy* service, string* state) {
+ CHECK(service) << "|service| was nullptr in GetStateForService()";
+ VariantDictionary properties;
+ if (!service->GetProperties(&properties, nullptr)) {
+ LOG(WARNING) << "Failed to read properties from service.";
+ return false;
+ }
+ auto property_it = properties.find(shill::kStateProperty);
+ if (property_it == properties.end()) {
+ LOG(WARNING) << "No state found in service properties.";
+ return false;
+ }
+ string new_state = property_it->second.TryGet<string>();
+ if (new_state.empty()) {
+ LOG(WARNING) << "Invalid state value.";
+ return false;
+ }
+ *state = new_state;
+ return true;
+}
+
+weave::NetworkState ShillServiceStateToNetworkState(const string& state) {
+ // TODO(wiley) What does "unconfigured" mean in a world with multiple sets
+ // of WiFi credentials?
+ // TODO(wiley) Detect disabled devices, update state appropriately.
+ if ((state.compare(shill::kStateReady) == 0) ||
+ (state.compare(shill::kStatePortal) == 0) ||
+ (state.compare(shill::kStateOnline) == 0)) {
+ return weave::NetworkState::kConnected;
+ }
+ if ((state.compare(shill::kStateAssociation) == 0) ||
+ (state.compare(shill::kStateConfiguration) == 0)) {
+ return weave::NetworkState::kConnecting;
+ }
+ if ((state.compare(shill::kStateFailure) == 0) ||
+ (state.compare(shill::kStateActivationFailure) == 0)) {
+ // TODO(wiley) Get error information off the service object.
+ return weave::NetworkState::kFailure;
+ }
+ if ((state.compare(shill::kStateIdle) == 0) ||
+ (state.compare(shill::kStateOffline) == 0) ||
+ (state.compare(shill::kStateDisconnect) == 0)) {
+ return weave::NetworkState::kOffline;
+ }
+ LOG(WARNING) << "Unknown state found: '" << state << "'";
+ return weave::NetworkState::kOffline;
+}
+
+} // namespace
+
+ShillClient::ShillClient(const scoped_refptr<dbus::Bus>& bus,
+ const set<string>& device_whitelist)
+ : bus_{bus},
+ manager_proxy_{bus_, ObjectPath{"/"}},
+ device_whitelist_{device_whitelist},
+ ap_manager_client_{new ApManagerClient(bus)} {
+ manager_proxy_.RegisterPropertyChangedSignalHandler(
+ base::Bind(&ShillClient::OnManagerPropertyChange,
+ weak_factory_.GetWeakPtr()),
+ base::Bind(&ShillClient::OnManagerPropertyChangeRegistration,
+ weak_factory_.GetWeakPtr()));
+ auto owner_changed_cb = base::Bind(&ShillClient::OnShillServiceOwnerChange,
+ weak_factory_.GetWeakPtr());
+ bus_->GetObjectProxy(shill::kFlimflamServiceName, ObjectPath{"/"})
+ ->SetNameOwnerChangedCallback(owner_changed_cb);
+}
+
+ShillClient::~ShillClient() {}
+
+void ShillClient::Init() {
+ VLOG(2) << "ShillClient::Init();";
+ CleanupConnectingService(false);
+ devices_.clear();
+ connectivity_state_ = weave::NetworkState::kOffline;
+ VariantDictionary properties;
+ if (!manager_proxy_.GetProperties(&properties, nullptr)) {
+ LOG(ERROR) << "Unable to get properties from Manager, waiting for "
+ "Manager to come back online.";
+ return;
+ }
+ auto it = properties.find(shill::kDevicesProperty);
+ CHECK(it != properties.end()) << "shill should always publish a device list.";
+ OnManagerPropertyChange(shill::kDevicesProperty, it->second);
+}
+
+bool ShillClient::ConnectToService(const string& ssid,
+ const string& passphrase,
+ const base::Closure& on_success,
+ chromeos::ErrorPtr* error) {
+ CleanupConnectingService(false);
+ VariantDictionary service_properties;
+ service_properties[shill::kTypeProperty] = Any{string{shill::kTypeWifi}};
+ service_properties[shill::kSSIDProperty] = Any{ssid};
+ service_properties[shill::kPassphraseProperty] = Any{passphrase};
+ service_properties[shill::kSecurityProperty] = Any{
+ string{passphrase.empty() ? shill::kSecurityNone : shill::kSecurityPsk}};
+ service_properties[shill::kSaveCredentialsProperty] = Any{true};
+ service_properties[shill::kAutoConnectProperty] = Any{true};
+ ObjectPath service_path;
+ if (!manager_proxy_.ConfigureService(service_properties, &service_path,
+ error)) {
+ return false;
+ }
+ if (!manager_proxy_.RequestScan(shill::kTypeWifi, error)) {
+ return false;
+ }
+ connecting_service_.reset(new ServiceProxy{bus_, service_path});
+ on_connect_success_.Reset(on_success);
+ connecting_service_->RegisterPropertyChangedSignalHandler(
+ base::Bind(&ShillClient::OnServicePropertyChange,
+ weak_factory_.GetWeakPtr(), service_path),
+ base::Bind(&ShillClient::OnServicePropertyChangeRegistration,
+ weak_factory_.GetWeakPtr(), service_path));
+ return true;
+}
+
+weave::NetworkState ShillClient::GetConnectionState() const {
+ return connectivity_state_;
+}
+
+void ShillClient::EnableAccessPoint(const std::string& ssid) {
+ ap_manager_client_->Start(ssid);
+}
+
+void ShillClient::DisableAccessPoint() {
+ ap_manager_client_->Stop();
+}
+
+void ShillClient::AddOnConnectionChangedCallback(
+ const OnConnectionChangedCallback& listener) {
+ connectivity_listeners_.push_back(listener);
+}
+
+bool ShillClient::IsMonitoredDevice(DeviceProxy* device) {
+ if (device_whitelist_.empty()) {
+ return true;
+ }
+ VariantDictionary device_properties;
+ if (!device->GetProperties(&device_properties, nullptr)) {
+ LOG(ERROR) << "Devices without properties aren't whitelisted.";
+ return false;
+ }
+ auto it = device_properties.find(shill::kInterfaceProperty);
+ if (it == device_properties.end()) {
+ LOG(ERROR) << "Failed to find interface property in device properties.";
+ return false;
+ }
+ return ContainsKey(device_whitelist_, it->second.TryGet<string>());
+}
+
+void ShillClient::OnShillServiceOwnerChange(const string& old_owner,
+ const string& new_owner) {
+ VLOG(1) << "Shill service owner name changed to '" << new_owner << "'";
+ if (new_owner.empty()) {
+ CleanupConnectingService(false);
+ devices_.clear();
+ connectivity_state_ = weave::NetworkState::kOffline;
+ } else {
+ Init(); // New service owner means shill reset!
+ }
+}
+
+void ShillClient::OnManagerPropertyChangeRegistration(const string& interface,
+ const string& signal_name,
+ bool success) {
+ VLOG(3) << "Registered ManagerPropertyChange handler.";
+ CHECK(success) << "privetd requires Manager signals.";
+ VariantDictionary properties;
+ if (!manager_proxy_.GetProperties(&properties, nullptr)) {
+ LOG(ERROR) << "Unable to get properties from Manager, waiting for "
+ "Manager to come back online.";
+ return;
+ }
+ auto it = properties.find(shill::kDevicesProperty);
+ CHECK(it != properties.end()) << "Shill should always publish a device list.";
+ OnManagerPropertyChange(shill::kDevicesProperty, it->second);
+}
+
+void ShillClient::OnManagerPropertyChange(const string& property_name,
+ const Any& property_value) {
+ if (property_name != shill::kDevicesProperty) {
+ return;
+ }
+ VLOG(3) << "Manager's device list has changed.";
+ // We're going to remove every device we haven't seen in the update.
+ set<ObjectPath> device_paths_to_remove;
+ for (const auto& kv : devices_) {
+ device_paths_to_remove.insert(kv.first);
+ }
+ for (const auto& device_path : property_value.TryGet<vector<ObjectPath>>()) {
+ if (!device_path.IsValid()) {
+ LOG(ERROR) << "Ignoring invalid device path in Manager's device list.";
+ return;
+ }
+ auto it = devices_.find(device_path);
+ if (it != devices_.end()) {
+ // Found an existing proxy. Since the whitelist never changes,
+ // this still a valid device.
+ device_paths_to_remove.erase(device_path);
+ continue;
+ }
+ std::unique_ptr<DeviceProxy> device{new DeviceProxy{bus_, device_path}};
+ if (!IsMonitoredDevice(device.get())) {
+ continue;
+ }
+ device->RegisterPropertyChangedSignalHandler(
+ base::Bind(&ShillClient::OnDevicePropertyChange,
+ weak_factory_.GetWeakPtr(), device_path),
+ base::Bind(&ShillClient::OnDevicePropertyChangeRegistration,
+ weak_factory_.GetWeakPtr(), device_path));
+ VLOG(3) << "Creating device proxy at " << device_path.value();
+ devices_[device_path].device = std::move(device);
+ }
+ // Clean up devices/services related to removed devices.
+ if (!device_paths_to_remove.empty()) {
+ for (const ObjectPath& device_path : device_paths_to_remove) {
+ devices_.erase(device_path);
+ }
+ UpdateConnectivityState();
+ }
+}
+
+void ShillClient::OnDevicePropertyChangeRegistration(
+ const ObjectPath& device_path,
+ const string& interface,
+ const string& signal_name,
+ bool success) {
+ VLOG(3) << "Registered DevicePropertyChange handler.";
+ auto it = devices_.find(device_path);
+ if (it == devices_.end()) {
+ return;
+ }
+ CHECK(success) << "Failed to subscribe to Device property changes.";
+ DeviceProxy* device = it->second.device.get();
+ VariantDictionary properties;
+ if (!device->GetProperties(&properties, nullptr)) {
+ LOG(WARNING) << "Failed to get device properties?";
+ return;
+ }
+ auto prop_it = properties.find(shill::kSelectedServiceProperty);
+ if (prop_it == properties.end()) {
+ LOG(WARNING) << "Failed to get device's selected service?";
+ return;
+ }
+ OnDevicePropertyChange(device_path, shill::kSelectedServiceProperty,
+ prop_it->second);
+}
+
+void ShillClient::OnDevicePropertyChange(const ObjectPath& device_path,
+ const string& property_name,
+ const Any& property_value) {
+ // We only care about selected services anyway.
+ if (property_name != shill::kSelectedServiceProperty) {
+ return;
+ }
+ // If the device isn't our list of whitelisted devices, ignore it.
+ auto it = devices_.find(device_path);
+ if (it == devices_.end()) {
+ return;
+ }
+ DeviceState& device_state = it->second;
+ ObjectPath service_path{property_value.TryGet<ObjectPath>()};
+ if (!service_path.IsValid()) {
+ LOG(ERROR) << "Device at " << device_path.value()
+ << " selected invalid service path.";
+ return;
+ }
+ VLOG(3) << "Device at " << it->first.value() << " has selected service at "
+ << service_path.value();
+ bool removed_old_service{false};
+ if (device_state.selected_service) {
+ if (device_state.selected_service->GetObjectPath() == service_path) {
+ return; // Spurious update?
+ }
+ device_state.selected_service.reset();
+ device_state.service_state = weave::NetworkState::kOffline;
+ removed_old_service = true;
+ }
+ std::shared_ptr<ServiceProxy> new_service;
+ const bool reuse_connecting_service =
+ service_path.value() != "/" && connecting_service_ &&
+ connecting_service_->GetObjectPath() == service_path;
+ if (reuse_connecting_service) {
+ new_service = connecting_service_;
+ // When we reuse the connecting service, we need to make sure that our
+ // cached state is correct. Normally, we do this by relying reading the
+ // state when our signal handlers finish registering, but this may have
+ // happened long in the past for the connecting service.
+ string state;
+ if (GetStateForService(new_service.get(), &state)) {
+ device_state.service_state = ShillServiceStateToNetworkState(state);
+ } else {
+ LOG(WARNING) << "Failed to read properties from existing service "
+ "on selection.";
+ }
+ } else if (service_path.value() != "/") {
+ // The device has selected a new service we haven't see before.
+ new_service.reset(new ServiceProxy{bus_, service_path});
+ new_service->RegisterPropertyChangedSignalHandler(
+ base::Bind(&ShillClient::OnServicePropertyChange,
+ weak_factory_.GetWeakPtr(), service_path),
+ base::Bind(&ShillClient::OnServicePropertyChangeRegistration,
+ weak_factory_.GetWeakPtr(), service_path));
+ }
+ device_state.selected_service = new_service;
+ if (reuse_connecting_service || removed_old_service) {
+ UpdateConnectivityState();
+ }
+}
+
+void ShillClient::OnServicePropertyChangeRegistration(const ObjectPath& path,
+ const string& interface,
+ const string& signal_name,
+ bool success) {
+ VLOG(3) << "OnServicePropertyChangeRegistration(" << path.value() << ");";
+ ServiceProxy* service{nullptr};
+ if (connecting_service_ && connecting_service_->GetObjectPath() == path) {
+ // Note that the connecting service might also be a selected service.
+ service = connecting_service_.get();
+ if (!success) {
+ CleanupConnectingService(false);
+ }
+ } else {
+ for (const auto& kv : devices_) {
+ if (kv.second.selected_service &&
+ kv.second.selected_service->GetObjectPath() == path) {
+ service = kv.second.selected_service.get();
+ break;
+ }
+ }
+ }
+ if (service == nullptr || !success) {
+ return; // A failure or success for a proxy we no longer care about.
+ }
+ VariantDictionary properties;
+ if (!service->GetProperties(&properties, nullptr)) {
+ return;
+ }
+ // Give ourselves property changed signals for the initial property
+ // values.
+ auto it = properties.find(shill::kStateProperty);
+ if (it != properties.end()) {
+ OnServicePropertyChange(path, shill::kStateProperty, it->second);
+ }
+ it = properties.find(shill::kSignalStrengthProperty);
+ if (it != properties.end()) {
+ OnServicePropertyChange(path, shill::kSignalStrengthProperty, it->second);
+ }
+}
+
+void ShillClient::OnServicePropertyChange(const ObjectPath& service_path,
+ const string& property_name,
+ const Any& property_value) {
+ VLOG(3) << "ServicePropertyChange(" << service_path.value() << ", "
+ << property_name << ", ...);";
+ if (property_name == shill::kStateProperty) {
+ const string state{property_value.TryGet<string>()};
+ if (state.empty()) {
+ VLOG(3) << "Invalid service state update.";
+ return;
+ }
+ VLOG(3) << "New service state=" << state;
+ OnStateChangeForSelectedService(service_path, state);
+ OnStateChangeForConnectingService(service_path, state);
+ } else if (property_name == shill::kSignalStrengthProperty) {
+ OnStrengthChangeForConnectingService(service_path,
+ property_value.TryGet<uint8_t>());
+ }
+}
+
+void ShillClient::OnStateChangeForConnectingService(
+ const ObjectPath& service_path,
+ const string& state) {
+ if (!connecting_service_ ||
+ connecting_service_->GetObjectPath() != service_path ||
+ ShillServiceStateToNetworkState(state) !=
+ weave::NetworkState::kConnected) {
+ return;
+ }
+ connecting_service_reset_pending_ = true;
+ on_connect_success_.callback().Run();
+ CleanupConnectingService(true);
+}
+
+void ShillClient::OnStrengthChangeForConnectingService(
+ const ObjectPath& service_path,
+ uint8_t signal_strength) {
+ if (!connecting_service_ ||
+ connecting_service_->GetObjectPath() != service_path ||
+ signal_strength <= 0 || have_called_connect_) {
+ return;
+ }
+ VLOG(1) << "Connecting service has signal. Calling Connect().";
+ have_called_connect_ = true;
+ // Failures here indicate that we've already connected,
+ // or are connecting, or some other very unexciting thing.
+ // Ignore all that, and rely on state changes to detect
+ // connectivity.
+ connecting_service_->Connect(nullptr);
+}
+
+void ShillClient::OnStateChangeForSelectedService(
+ const ObjectPath& service_path,
+ const string& state) {
+ // Find the device/service pair responsible for this update
+ VLOG(3) << "State for potentially selected service " << service_path.value()
+ << " have changed to " << state;
+ for (auto& kv : devices_) {
+ if (kv.second.selected_service &&
+ kv.second.selected_service->GetObjectPath() == service_path) {
+ VLOG(3) << "Updated cached connection state for selected service.";
+ kv.second.service_state = ShillServiceStateToNetworkState(state);
+ UpdateConnectivityState();
+ return;
+ }
+ }
+}
+
+void ShillClient::UpdateConnectivityState() {
+ // Update the connectivity state of the device by picking the
+ // state of the currently most connected selected service.
+ weave::NetworkState new_connectivity_state{weave::NetworkState::kOffline};
+ for (const auto& kv : devices_) {
+ if (kv.second.service_state > new_connectivity_state) {
+ new_connectivity_state = kv.second.service_state;
+ }
+ }
+ VLOG(1) << "Connectivity changed: " << EnumToString(connectivity_state_)
+ << " -> " << EnumToString(new_connectivity_state);
+ // Notify listeners even if state changed to the same value. Listeners may
+ // want to handle this event.
+ connectivity_state_ = new_connectivity_state;
+ // We may call UpdateConnectivityState whenever we mutate a data structure
+ // such that our connectivity status could change. However, we don't want
+ // to allow people to call into ShillClient while some other operation is
+ // underway. Therefore, call our callbacks later, when we're in a good
+ // state.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ShillClient::NotifyConnectivityListeners,
+ weak_factory_.GetWeakPtr(),
+ GetConnectionState() == weave::NetworkState::kConnected));
+}
+
+void ShillClient::NotifyConnectivityListeners(bool am_online) {
+ VLOG(3) << "Notifying connectivity listeners that online=" << am_online;
+ for (const auto& listener : connectivity_listeners_) {
+ listener.Run(am_online);
+ }
+}
+
+void ShillClient::CleanupConnectingService(bool check_for_reset_pending) {
+ if (check_for_reset_pending && !connecting_service_reset_pending_) {
+ return; // Must have called connect before we got here.
+ }
+ if (connecting_service_) {
+ connecting_service_->ReleaseObjectProxy(base::Bind(&IgnoreDetachEvent));
+ connecting_service_.reset();
+ }
+ on_connect_success_.Cancel();
+ have_called_connect_ = false;
+ connecting_service_reset_pending_ = false;
+}
+
+} // namespace buffet
diff --git a/buffet/shill_client.h b/buffet/shill_client.h
new file mode 100644
index 0000000..4d54d60
--- /dev/null
+++ b/buffet/shill_client.h
@@ -0,0 +1,121 @@
+// 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.
+
+#ifndef BUFFET_SHILL_CLIENT_H_
+#define BUFFET_SHILL_CLIENT_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/cancelable_callback.h>
+#include <base/macros.h>
+#include <base/memory/ref_counted.h>
+#include <base/memory/weak_ptr.h>
+#include <dbus/bus.h>
+#include <weave/network.h>
+
+#include "shill/dbus-proxies.h"
+
+namespace buffet {
+
+class ApManagerClient;
+
+class ShillClient final : public weave::Network {
+ public:
+ ShillClient(const scoped_refptr<dbus::Bus>& bus,
+ const std::set<std::string>& device_whitelist);
+ ~ShillClient();
+
+ void Init();
+
+ // Network implementation.
+ void AddOnConnectionChangedCallback(
+ const OnConnectionChangedCallback& listener) override;
+ bool ConnectToService(const std::string& ssid,
+ const std::string& passphrase,
+ const base::Closure& on_success,
+ chromeos::ErrorPtr* error) override;
+ weave::NetworkState GetConnectionState() const override;
+ void EnableAccessPoint(const std::string& ssid) override;
+ void DisableAccessPoint() override;
+
+ private:
+ struct DeviceState {
+ std::unique_ptr<org::chromium::flimflam::DeviceProxy> device;
+ // ServiceProxy objects are shared because the connecting service will
+ // also be the selected service for a device, but is not always the selected
+ // service (for instance, in the period between configuring a WiFi service
+ // with credentials, and when Connect() is called.)
+ std::shared_ptr<org::chromium::flimflam::ServiceProxy> selected_service;
+ weave::NetworkState service_state{weave::NetworkState::kOffline};
+ };
+
+ bool IsMonitoredDevice(org::chromium::flimflam::DeviceProxy* device);
+ void OnShillServiceOwnerChange(const std::string& old_owner,
+ const std::string& new_owner);
+ void OnManagerPropertyChangeRegistration(const std::string& interface,
+ const std::string& signal_name,
+ bool success);
+ void OnManagerPropertyChange(const std::string& property_name,
+ const chromeos::Any& property_value);
+ void OnDevicePropertyChangeRegistration(const dbus::ObjectPath& device_path,
+ const std::string& interface,
+ const std::string& signal_name,
+ bool success);
+ void OnDevicePropertyChange(const dbus::ObjectPath& device_path,
+ const std::string& property_name,
+ const chromeos::Any& property_value);
+ void OnServicePropertyChangeRegistration(const dbus::ObjectPath& path,
+ const std::string& interface,
+ const std::string& signal_name,
+ bool success);
+ void OnServicePropertyChange(const dbus::ObjectPath& service_path,
+ const std::string& property_name,
+ const chromeos::Any& property_value);
+
+ void OnStateChangeForConnectingService(const dbus::ObjectPath& service_path,
+ const std::string& state);
+ void OnStrengthChangeForConnectingService(
+ const dbus::ObjectPath& service_path,
+ uint8_t signal_strength);
+ void OnStateChangeForSelectedService(const dbus::ObjectPath& service_path,
+ const std::string& state);
+ void UpdateConnectivityState();
+ void NotifyConnectivityListeners(bool am_online);
+ // Clean up state related to a connecting service. If
+ // |check_for_reset_pending| is set, then we'll check to see if we've called
+ // ConnectToService() in the time since a task to call this function was
+ // posted.
+ void CleanupConnectingService(bool check_for_reset_pending);
+
+ const scoped_refptr<dbus::Bus> bus_;
+ org::chromium::flimflam::ManagerProxy manager_proxy_;
+ // There is logic that assumes we will never change this device list
+ // in OnManagerPropertyChange. Do not be tempted to remove this const.
+ const std::set<std::string> device_whitelist_;
+ std::vector<OnConnectionChangedCallback> connectivity_listeners_;
+
+ // State for tracking where we are in our attempts to connect to a service.
+ bool connecting_service_reset_pending_{false};
+ bool have_called_connect_{false};
+ std::shared_ptr<org::chromium::flimflam::ServiceProxy> connecting_service_;
+ base::CancelableClosure on_connect_success_;
+
+ // State for tracking our online connectivity.
+ std::map<dbus::ObjectPath, DeviceState> devices_;
+ weave::NetworkState connectivity_state_{weave::NetworkState::kOffline};
+
+ std::unique_ptr<ApManagerClient> ap_manager_client_;
+
+ base::WeakPtrFactory<ShillClient> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(ShillClient);
+};
+
+} // namespace buffet
+
+#endif // BUFFET_SHILL_CLIENT_H_
diff --git a/buffet/test_daemon/main.cc b/buffet/test_daemon/main.cc
new file mode 100644
index 0000000..eeb0caf
--- /dev/null
+++ b/buffet/test_daemon/main.cc
@@ -0,0 +1,178 @@
+// 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.
+
+// This is a sample daemon that "handles" Buffet commands.
+// It just prints the information about the command received to stdout and
+// marks the command as processed.
+
+#include <string>
+#include <sysexits.h>
+
+#include <base/bind.h>
+#include <base/command_line.h>
+#include <base/format_macros.h>
+#include <base/json/json_writer.h>
+#include <base/values.h>
+#include <chromeos/daemons/dbus_daemon.h>
+#include <chromeos/map_utils.h>
+#include <chromeos/strings/string_utils.h>
+#include <chromeos/syslog_logging.h>
+
+#include "buffet/dbus-proxies.h"
+
+namespace {
+const char kTestCommandCategory[] = "test";
+
+std::unique_ptr<base::DictionaryValue> DictionaryToJson(
+ const chromeos::VariantDictionary& dictionary);
+
+std::unique_ptr<base::Value> AnyToJson(const chromeos::Any& value) {
+ if (value.IsTypeCompatible<chromeos::VariantDictionary>())
+ return DictionaryToJson(value.Get<chromeos::VariantDictionary>());
+
+ if (value.IsTypeCompatible<std::string>()) {
+ return std::unique_ptr<base::Value>{
+ new base::StringValue(value.Get<std::string>())};
+ }
+
+ if (value.IsTypeCompatible<double>()) {
+ return std::unique_ptr<base::Value>{
+ new base::FundamentalValue(value.Get<double>())};
+ }
+
+ if (value.IsTypeCompatible<bool>()) {
+ return std::unique_ptr<base::Value>{
+ new base::FundamentalValue(value.Get<bool>())};
+ }
+
+ if (value.IsTypeCompatible<int>()) {
+ return std::unique_ptr<base::Value>{
+ new base::FundamentalValue(value.Get<int>())};
+ }
+
+ LOG(FATAL) << "Unsupported type:" << value.GetType().name();
+ return {};
+}
+
+std::unique_ptr<base::DictionaryValue> DictionaryToJson(
+ const chromeos::VariantDictionary& dictionary) {
+ std::unique_ptr<base::DictionaryValue> result{new base::DictionaryValue};
+ for (const auto& it : dictionary)
+ result->Set(it.first, AnyToJson(it.second).release());
+ return result;
+}
+
+std::string DictionaryToString(const chromeos::VariantDictionary& dictionary) {
+ std::unique_ptr<base::DictionaryValue> json{DictionaryToJson(dictionary)};
+ std::string str;
+ base::JSONWriter::Write(*json, &str);
+ return str;
+}
+
+} // anonymous namespace
+
+class Daemon final : public chromeos::DBusDaemon {
+ public:
+ Daemon() = default;
+
+ protected:
+ int OnInit() override;
+ void OnShutdown(int* return_code) override;
+
+ private:
+ std::unique_ptr<org::chromium::Buffet::ObjectManagerProxy> object_manager_;
+
+ void OnBuffetCommand(org::chromium::Buffet::CommandProxy* command);
+ void OnBuffetCommandRemoved(const dbus::ObjectPath& object_path);
+ void OnPropertyChange(org::chromium::Buffet::CommandProxy* command,
+ const std::string& property_name);
+ void OnCommandProgress(org::chromium::Buffet::CommandProxy* command,
+ int progress);
+
+ DISALLOW_COPY_AND_ASSIGN(Daemon);
+};
+
+int Daemon::OnInit() {
+ int return_code = chromeos::DBusDaemon::OnInit();
+ if (return_code != EX_OK)
+ return return_code;
+
+ object_manager_.reset(new org::chromium::Buffet::ObjectManagerProxy{bus_});
+ object_manager_->SetCommandAddedCallback(
+ base::Bind(&Daemon::OnBuffetCommand, base::Unretained(this)));
+ object_manager_->SetCommandRemovedCallback(
+ base::Bind(&Daemon::OnBuffetCommandRemoved, base::Unretained(this)));
+
+ printf("Waiting for commands...\n");
+ return EX_OK;
+}
+
+void Daemon::OnShutdown(int* return_code) {
+ printf("Shutting down...\n");
+}
+
+void Daemon::OnPropertyChange(org::chromium::Buffet::CommandProxy* command,
+ const std::string& property_name) {
+ printf("Notification: property '%s' on command '%s' changed.\n",
+ property_name.c_str(), command->id().c_str());
+ printf(" Current command status: '%s'\n", command->status().c_str());
+ std::string progress = DictionaryToString(command->progress());
+ printf(" Current command progress: %s\n", progress.c_str());
+ std::string results = DictionaryToString(command->results());
+ printf(" Current command results: %s\n", results.c_str());
+}
+
+void Daemon::OnBuffetCommand(org::chromium::Buffet::CommandProxy* command) {
+ // "Handle" only commands that belong to this daemon's category.
+ if (command->category() != kTestCommandCategory ||
+ command->status() == "done") {
+ return;
+ }
+
+ command->SetPropertyChangedCallback(base::Bind(&Daemon::OnPropertyChange,
+ base::Unretained(this)));
+ printf("++++++++++++++++++++++++++++++++++++++++++++++++\n");
+ printf("Command received: %s\n", command->name().c_str());
+ printf("DBus Object Path: %s\n", command->GetObjectPath().value().c_str());
+ printf(" category: %s\n", command->category().c_str());
+ printf(" ID: %s\n", command->id().c_str());
+ printf(" status: %s\n", command->status().c_str());
+ printf(" origin: %s\n", command->origin().c_str());
+ std::string param_names = DictionaryToString(command->parameters());
+ printf(" parameters: %s\n", param_names.c_str());
+ OnCommandProgress(command, 0);
+}
+
+void Daemon::OnCommandProgress(org::chromium::Buffet::CommandProxy* command,
+ int progress) {
+ printf("Updating command '%s' progress to %d%%\n", command->id().c_str(),
+ progress);
+ auto new_progress = command->progress();
+ new_progress["progress"] = progress;
+ command->SetProgress(new_progress, nullptr);
+
+ if (progress >= 100) {
+ command->Done(nullptr);
+ } else {
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, base::Bind(&Daemon::OnCommandProgress,
+ base::Unretained(this), command, progress + 10),
+ base::TimeDelta::FromSeconds(1));
+ }
+}
+
+void Daemon::OnBuffetCommandRemoved(const dbus::ObjectPath& object_path) {
+ printf("------------------------------------------------\n");
+ printf("Command removed\n");
+ printf("DBus Object Path: %s\n", object_path.value().c_str());
+}
+
+int main(int argc, char* argv[]) {
+ base::CommandLine::Init(argc, argv);
+ chromeos::InitLog(chromeos::kLogToSyslog |
+ chromeos::kLogToStderr |
+ chromeos::kLogHeader);
+ Daemon daemon;
+ return daemon.Run();
+}
diff --git a/buffet/webserv_client.cc b/buffet/webserv_client.cc
new file mode 100644
index 0000000..2ae5910
--- /dev/null
+++ b/buffet/webserv_client.cc
@@ -0,0 +1,135 @@
+// Copyright 2015 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/webserv_client.h"
+
+#include <memory>
+#include <string>
+
+#include <libwebserv/protocol_handler.h>
+#include <libwebserv/request.h>
+#include <libwebserv/response.h>
+#include <libwebserv/server.h>
+
+#include "buffet/dbus_constants.h"
+
+namespace buffet {
+
+namespace {
+
+class RequestImpl : public weave::HttpServer::Request {
+ public:
+ explicit RequestImpl(std::unique_ptr<libwebserv::Request> request)
+ : request_{std::move(request)} {}
+ ~RequestImpl() override {}
+
+ // HttpServer::Request implementation.
+ const std::string& GetPath() const override { return request_->GetPath(); }
+ std::string GetFirstHeader(const std::string& name) const override {
+ return request_->GetFirstHeader(name);
+ }
+ const std::vector<uint8_t>& GetData() const override {
+ return request_->GetData();
+ }
+
+ private:
+ std::unique_ptr<libwebserv::Request> request_;
+
+ DISALLOW_COPY_AND_ASSIGN(RequestImpl);
+};
+
+} // namespace
+
+WebServClient::~WebServClient() {
+ web_server_->Disconnect();
+}
+
+void WebServClient::AddOnStateChangedCallback(
+ const OnStateChangedCallback& callback) {
+ on_state_changed_callbacks_.push_back(callback);
+ callback.Run(*this);
+}
+
+void WebServClient::AddRequestHandler(const std::string& path_prefix,
+ const OnRequestCallback& callback) {
+ web_server_->GetDefaultHttpHandler()->AddHandlerCallback(
+ path_prefix, "", base::Bind(&WebServClient::OnRequest,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+ web_server_->GetDefaultHttpsHandler()->AddHandlerCallback(
+ path_prefix, "", base::Bind(&WebServClient::OnRequest,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+uint16_t WebServClient::GetHttpPort() const {
+ return http_port_;
+}
+
+uint16_t WebServClient::GetHttpsPort() const {
+ return https_port_;
+}
+
+const chromeos::Blob& WebServClient::GetHttpsCertificateFingerprint() const {
+ return certificate_;
+}
+
+WebServClient::WebServClient(
+ const scoped_refptr<dbus::Bus>& bus,
+ chromeos::dbus_utils::AsyncEventSequencer* sequencer) {
+ web_server_.reset(new libwebserv::Server);
+ web_server_->OnProtocolHandlerConnected(
+ base::Bind(&WebServClient::OnProtocolHandlerConnected,
+ weak_ptr_factory_.GetWeakPtr()));
+ web_server_->OnProtocolHandlerDisconnected(
+ base::Bind(&WebServClient::OnProtocolHandlerDisconnected,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ web_server_->Connect(bus, buffet::kServiceName,
+ sequencer->GetHandler("Server::Connect failed.", true),
+ base::Bind(&base::DoNothing),
+ base::Bind(&base::DoNothing));
+}
+
+void WebServClient::OnRequest(const OnRequestCallback& callback,
+ std::unique_ptr<libwebserv::Request> request,
+ std::unique_ptr<libwebserv::Response> response) {
+ callback.Run(
+ RequestImpl{std::move(request)},
+ base::Bind(&WebServClient::OnResponse, weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&response)));
+}
+
+void WebServClient::OnResponse(std::unique_ptr<libwebserv::Response> response,
+ int status_code,
+ const std::string& data,
+ const std::string& mime_type) {
+ response->Reply(status_code, data.data(), data.size(), mime_type);
+}
+
+void WebServClient::OnProtocolHandlerConnected(
+ libwebserv::ProtocolHandler* protocol_handler) {
+ if (protocol_handler->GetName() == libwebserv::ProtocolHandler::kHttp) {
+ http_port_ = *protocol_handler->GetPorts().begin();
+ } else if (protocol_handler->GetName() ==
+ libwebserv::ProtocolHandler::kHttps) {
+ https_port_ = *protocol_handler->GetPorts().begin();
+ certificate_ = protocol_handler->GetCertificateFingerprint();
+ }
+ for (const auto& cb : on_state_changed_callbacks_)
+ cb.Run(*this);
+}
+
+void WebServClient::OnProtocolHandlerDisconnected(
+ libwebserv::ProtocolHandler* protocol_handler) {
+ if (protocol_handler->GetName() == libwebserv::ProtocolHandler::kHttp) {
+ http_port_ = 0;
+ } else if (protocol_handler->GetName() ==
+ libwebserv::ProtocolHandler::kHttps) {
+ https_port_ = 0;
+ certificate_.clear();
+ }
+ for (const auto& cb : on_state_changed_callbacks_)
+ cb.Run(*this);
+}
+
+} // namespace buffet
diff --git a/buffet/webserv_client.h b/buffet/webserv_client.h
new file mode 100644
index 0000000..d7b5433
--- /dev/null
+++ b/buffet/webserv_client.h
@@ -0,0 +1,80 @@
+// Copyright 2015 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.
+
+#ifndef BUFFET_WEBSERV_CLIENT_H_
+#define BUFFET_WEBSERV_CLIENT_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/memory/weak_ptr.h>
+#include <weave/http_server.h>
+
+namespace dbus {
+class Bus;
+}
+
+namespace chromeos {
+namespace dbus_utils {
+class AsyncEventSequencer;
+}
+}
+
+namespace libwebserv {
+class ProtocolHandler;
+class Request;
+class Response;
+class Server;
+}
+
+namespace buffet {
+
+// Wrapper around libwebserv that implements HttpServer interface.
+class WebServClient : public weave::HttpServer {
+ public:
+ WebServClient(const scoped_refptr<dbus::Bus>& bus,
+ chromeos::dbus_utils::AsyncEventSequencer* sequencer);
+ ~WebServClient() override;
+
+ // HttpServer implementation.
+ void AddOnStateChangedCallback(
+ const OnStateChangedCallback& callback) override;
+ void AddRequestHandler(const std::string& path_prefix,
+ const OnRequestCallback& callback) override;
+ uint16_t GetHttpPort() const override;
+ uint16_t GetHttpsPort() const override;
+ const chromeos::Blob& GetHttpsCertificateFingerprint() const override;
+
+ private:
+ void OnRequest(const OnRequestCallback& callback,
+ std::unique_ptr<libwebserv::Request> request,
+ std::unique_ptr<libwebserv::Response> response);
+
+ void OnResponse(std::unique_ptr<libwebserv::Response> response,
+ int status_code,
+ const std::string& data,
+ const std::string& mime_type);
+
+ void OnProtocolHandlerConnected(
+ libwebserv::ProtocolHandler* protocol_handler);
+
+ void OnProtocolHandlerDisconnected(
+ libwebserv::ProtocolHandler* protocol_handler);
+
+ uint16_t http_port_{0};
+ uint16_t https_port_{0};
+ chromeos::Blob certificate_;
+
+ std::vector<OnStateChangedCallback> on_state_changed_callbacks_;
+
+ std::unique_ptr<libwebserv::Server> web_server_;
+
+ base::WeakPtrFactory<WebServClient> weak_ptr_factory_{this};
+ DISALLOW_COPY_AND_ASSIGN(WebServClient);
+};
+
+} // namespace buffet
+
+#endif // BUFFET_WEBSERV_CLIENT_H_