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&param2=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_