buffet: Move privetd sources into buffet

No functional changes, only renaming, fixed include paths and include
guards to avoid resubmit warnings.

BUG=brillo:1161
CQ-DEPEND=CL:276521
TEST=none

Change-Id: Icacff92aef47fdd46542bc96eba3ffbb4df6241a
Reviewed-on: https://chromium-review.googlesource.com/276319
Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
Commit-Queue: Vitaly Buka <vitalybuka@chromium.org>
Tested-by: Vitaly Buka <vitalybuka@chromium.org>
diff --git a/buffet/privet/ap_manager_client.cc b/buffet/privet/ap_manager_client.cc
new file mode 100644
index 0000000..b123f1f
--- /dev/null
+++ b/buffet/privet/ap_manager_client.cc
@@ -0,0 +1,115 @@
+// 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/privet/ap_manager_client.h"
+
+namespace privetd {
+
+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 privetd
diff --git a/buffet/privet/ap_manager_client.h b/buffet/privet/ap_manager_client.h
new file mode 100644
index 0000000..ec99a6a
--- /dev/null
+++ b/buffet/privet/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_PRIVET_AP_MANAGER_CLIENT_H_
+#define BUFFET_PRIVET_AP_MANAGER_CLIENT_H_
+
+#include <memory>
+#include <string>
+
+#include <base/callback.h>
+#include <base/memory/ref_counted.h>
+
+#include "apmanager/dbus-proxies.h"
+
+namespace privetd {
+
+// 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 privetd
+
+#endif  // BUFFET_PRIVET_AP_MANAGER_CLIENT_H_
diff --git a/buffet/privet/cloud_delegate.cc b/buffet/privet/cloud_delegate.cc
new file mode 100644
index 0000000..5de432e
--- /dev/null
+++ b/buffet/privet/cloud_delegate.cc
@@ -0,0 +1,532 @@
+// 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/privet/cloud_delegate.h"
+
+#include <map>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+#include <base/logging.h>
+#include <base/memory/weak_ptr.h>
+#include <base/message_loop/message_loop.h>
+#include <base/values.h>
+#include <chromeos/errors/error.h>
+#include <chromeos/variant_dictionary.h>
+#include <dbus/bus.h>
+
+#include "buffet/dbus-proxies.h"
+#include "buffet/privet/constants.h"
+
+namespace privetd {
+
+namespace {
+
+using chromeos::ErrorPtr;
+using chromeos::VariantDictionary;
+using org::chromium::Buffet::ManagerProxy;
+using org::chromium::Buffet::ObjectManagerProxy;
+
+const int kMaxSetupRetries = 5;
+const int kFirstRetryTimeoutSec = 1;
+
+class CloudDelegateImpl : public CloudDelegate {
+ public:
+  CloudDelegateImpl(const scoped_refptr<dbus::Bus>& bus,
+                    bool is_gcd_setup_enabled)
+      : object_manager_{bus}, is_gcd_setup_enabled_(is_gcd_setup_enabled) {
+    object_manager_.SetManagerAddedCallback(
+        base::Bind(&CloudDelegateImpl::OnManagerAdded,
+                   weak_factory_.GetWeakPtr()));
+    object_manager_.SetManagerRemovedCallback(
+        base::Bind(&CloudDelegateImpl::OnManagerRemoved,
+                   weak_factory_.GetWeakPtr()));
+    object_manager_.SetCommandRemovedCallback(base::Bind(
+        &CloudDelegateImpl::OnCommandRemoved, weak_factory_.GetWeakPtr()));
+  }
+
+  ~CloudDelegateImpl() override = default;
+
+  bool GetModelId(std::string* id, chromeos::ErrorPtr* error) const override {
+    if (!IsManagerReady(error))
+      return false;
+    if (manager_->model_id().size() != 5) {
+      chromeos::Error::AddToPrintf(
+          error, FROM_HERE, errors::kDomain, errors::kInvalidState,
+          "Model ID is invalid: %s", manager_->model_id().c_str());
+      return false;
+    }
+    *id = manager_->model_id();
+    return true;
+  }
+
+  bool GetName(std::string* name, chromeos::ErrorPtr* error) const override {
+    if (!IsManagerReady(error))
+      return false;
+    *name = manager_->name();
+    return true;
+  }
+
+  std::string GetDescription() const override {
+    return manager_ ? manager_->description() : std::string{};
+  }
+
+  std::string GetLocation() const override {
+    return manager_ ? manager_->location() : std::string{};
+  }
+
+  void UpdateDeviceInfo(const std::string& name,
+                        const std::string& description,
+                        const std::string& location,
+                        const base::Closure& success_callback,
+                        const ErrorCallback& error_callback) override {
+    chromeos::ErrorPtr error;
+    if (!IsManagerReady(&error))
+      return error_callback.Run(error.get());
+
+    if (name == manager_->name() && description == manager_->description() &&
+        location == manager_->location()) {
+      return success_callback.Run();
+    }
+
+    manager_->UpdateDeviceInfoAsync(name, description, location,
+                                    success_callback, error_callback);
+  }
+
+  std::string GetOemName() const override {
+    return manager_ ? manager_->oem_name() : std::string{};
+  }
+
+  std::string GetModelName() const override {
+    return manager_ ? manager_->model_name() : std::string{};
+  }
+
+  std::set<std::string> GetServices() const override {
+    std::set<std::string> result;
+    for (base::DictionaryValue::Iterator it{command_defs_}; !it.IsAtEnd();
+         it.Advance()) {
+      result.emplace(it.key());
+    }
+    return result;
+  }
+
+  AuthScope GetAnonymousMaxScope() const override {
+    if (manager_) {
+      AuthScope scope;
+      if (StringToAuthScope(manager_->anonymous_access_role(), &scope))
+        return scope;
+    }
+    return AuthScope::kNone;
+  }
+
+  const ConnectionState& GetConnectionState() const override {
+    return connection_state_;
+  }
+
+  const SetupState& GetSetupState() const override { return setup_state_; }
+
+  bool Setup(const std::string& ticket_id,
+             const std::string& user,
+             chromeos::ErrorPtr* error) override {
+    if (!is_gcd_setup_enabled_) {
+      chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                             errors::kSetupUnavailable,
+                             "GCD setup unavailible");
+      return false;
+    }
+    if (!object_manager_.GetManagerProxy()) {
+      chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                             errors::kDeviceBusy, "Buffet is not ready");
+      return false;
+    }
+    if (setup_state_.IsStatusEqual(SetupState::kInProgress)) {
+      chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                             errors::kDeviceBusy, "Setup in progress");
+      return false;
+    }
+    VLOG(1) << "GCD Setup started. ticket_id: " << ticket_id
+            << ", user:" << user;
+    setup_state_ = SetupState(SetupState::kInProgress);
+    setup_weak_factory_.InvalidateWeakPtrs();
+    base::MessageLoop::current()->PostDelayedTask(
+        FROM_HERE, base::Bind(&CloudDelegateImpl::CallManagerRegisterDevice,
+                              setup_weak_factory_.GetWeakPtr(), ticket_id, 0),
+        base::TimeDelta::FromSeconds(kSetupDelaySeconds));
+    // Return true because we tried setup.
+    return true;
+  }
+
+  std::string GetCloudId() const override {
+    return manager_ ? manager_->device_id() : std::string{};
+  }
+
+  const base::DictionaryValue& GetState() const override { return state_; }
+
+  const base::DictionaryValue& GetCommandDef() const override {
+    return command_defs_;
+  }
+
+  void AddCommand(const base::DictionaryValue& command,
+                  const UserInfo& user_info,
+                  const SuccessCallback& success_callback,
+                  const ErrorCallback& error_callback) override {
+    CHECK(user_info.scope() != AuthScope::kNone);
+
+    chromeos::ErrorPtr error;
+    if (!IsManagerReady(&error))
+      return error_callback.Run(error.get());
+
+    std::string command_str;
+    base::JSONWriter::Write(&command, &command_str);
+    manager_->AddCommandAsync(
+        command_str, AuthScopeToString(user_info.scope()),
+        base::Bind(&CloudDelegateImpl::OnAddCommandSucceeded,
+                   weak_factory_.GetWeakPtr(), success_callback,
+                   error_callback),
+        error_callback);
+  }
+
+  void GetCommand(const std::string& id,
+                  const UserInfo& user_info,
+                  const SuccessCallback& success_callback,
+                  const ErrorCallback& error_callback) override {
+    CHECK(user_info.scope() != AuthScope::kNone);
+    chromeos::ErrorPtr error;
+    if (!CanAccessCommand(id, user_info)) {
+      chromeos::Error::AddTo(&error, FROM_HERE, errors::kDomain,
+                             errors::kAccessDenied,
+                             "Need to be owner of the command.");
+      return error_callback.Run(error.get());
+    }
+
+    GetCommandInternal(id, success_callback, error_callback);
+  }
+
+  void CancelCommand(const std::string& id,
+                     const UserInfo& user_info,
+                     const SuccessCallback& success_callback,
+                     const ErrorCallback& error_callback) override {
+    CHECK(user_info.scope() != AuthScope::kNone);
+    chromeos::ErrorPtr error;
+    if (!CanAccessCommand(id, user_info)) {
+      chromeos::Error::AddTo(&error, FROM_HERE, errors::kDomain,
+                             errors::kAccessDenied,
+                             "Need to be owner of the command.");
+      return error_callback.Run(error.get());
+    }
+
+    for (auto command : object_manager_.GetCommandInstances()) {
+      if (command->id() == id) {
+        return command->CancelAsync(
+            base::Bind(&CloudDelegateImpl::GetCommandInternal,
+                       weak_factory_.GetWeakPtr(), id, success_callback,
+                       error_callback),
+            error_callback);
+      }
+    }
+
+    chromeos::Error::AddToPrintf(&error, FROM_HERE, errors::kDomain,
+                                 errors::kNotFound,
+                                 "Command not found, ID='%s'", id.c_str());
+    error_callback.Run(error.get());
+  }
+
+  void ListCommands(const UserInfo& user_info,
+                    const SuccessCallback& success_callback,
+                    const ErrorCallback& error_callback) override {
+    CHECK(user_info.scope() != AuthScope::kNone);
+
+    std::vector<org::chromium::Buffet::CommandProxy*> commands{
+        object_manager_.GetCommandInstances()};
+
+    auto ids = std::make_shared<std::vector<std::string>>();
+    for (auto command : commands) {
+      if (CanAccessCommand(command->id(), user_info))
+        ids->push_back(command->id());
+    }
+
+    GetNextCommand(ids, std::make_shared<base::ListValue>(), success_callback,
+                   error_callback, base::DictionaryValue{});
+  }
+
+ private:
+  void GetCommandInternal(const std::string& id,
+                          const SuccessCallback& success_callback,
+                          const ErrorCallback& error_callback) {
+    chromeos::ErrorPtr error;
+    if (!IsManagerReady(&error))
+      return error_callback.Run(error.get());
+    manager_->GetCommandAsync(
+        id, base::Bind(&CloudDelegateImpl::OnGetCommandSucceeded,
+                       weak_factory_.GetWeakPtr(), success_callback,
+                       error_callback),
+        error_callback);
+  }
+
+  void OnManagerAdded(ManagerProxy* manager) {
+    manager_ = manager;
+    manager_->SetPropertyChangedCallback(
+        base::Bind(&CloudDelegateImpl::OnManagerPropertyChanged,
+                   weak_factory_.GetWeakPtr()));
+    // Read all initial values.
+    OnManagerPropertyChanged(manager, std::string{});
+  }
+
+  void OnCommandRemoved(const dbus::ObjectPath& object_path) {
+    command_owners_.erase(object_manager_.GetCommandProxy(object_path)->id());
+  }
+
+  void OnManagerPropertyChanged(ManagerProxy* manager,
+                                const std::string& property_name) {
+    CHECK_EQ(manager_, manager);
+
+    if (property_name.empty() || property_name == ManagerProxy::StatusName()) {
+      OnStatusPropertyChanged();
+    }
+
+    if (property_name.empty() ||
+        property_name == ManagerProxy::DeviceIdName() ||
+        property_name == ManagerProxy::OemNameName() ||
+        property_name == ManagerProxy::ModelNameName() ||
+        property_name == ManagerProxy::ModelIdName() ||
+        property_name == ManagerProxy::NameName() ||
+        property_name == ManagerProxy::DescriptionName() ||
+        property_name == ManagerProxy::LocationName() ||
+        property_name == ManagerProxy::AnonymousAccessRoleName()) {
+      NotifyOnDeviceInfoChanged();
+    }
+
+    if (property_name.empty() || property_name == ManagerProxy::StateName()) {
+      OnStatePropertyChanged();
+    }
+
+    if (property_name.empty() ||
+        property_name == ManagerProxy::CommandDefsName()) {
+      OnCommandDefsPropertyChanged();
+    }
+  }
+
+  void OnStatusPropertyChanged() {
+    const std::string& status = manager_->status();
+    if (status == "unconfigured") {
+      connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
+    } else if (status == "connecting") {
+      // TODO(vitalybuka): Find conditions for kOffline.
+      connection_state_ = ConnectionState{ConnectionState::kConnecting};
+    } else if (status == "connected") {
+      connection_state_ = ConnectionState{ConnectionState::kOnline};
+    } else {
+      chromeos::ErrorPtr error;
+      chromeos::Error::AddToPrintf(
+          &error, FROM_HERE, errors::kDomain, errors::kInvalidState,
+          "Unexpected buffet status: %s", status.c_str());
+      connection_state_ = ConnectionState{std::move(error)};
+    }
+    NotifyOnDeviceInfoChanged();
+  }
+
+  void OnStatePropertyChanged() {
+    state_.Clear();
+    std::unique_ptr<base::Value> value{
+        base::JSONReader::Read(manager_->state())};
+    const base::DictionaryValue* state{nullptr};
+    if (value && value->GetAsDictionary(&state))
+      state_.MergeDictionary(state);
+    NotifyOnStateChanged();
+  }
+
+  void OnCommandDefsPropertyChanged() {
+    command_defs_.Clear();
+    std::unique_ptr<base::Value> value{
+        base::JSONReader::Read(manager_->command_defs())};
+    const base::DictionaryValue* defs{nullptr};
+    if (value && value->GetAsDictionary(&defs))
+      command_defs_.MergeDictionary(defs);
+    NotifyOnCommandDefsChanged();
+  }
+
+  void OnManagerRemoved(const dbus::ObjectPath& path) {
+    manager_ = nullptr;
+    connection_state_ = ConnectionState(ConnectionState::kDisabled);
+    state_.Clear();
+    command_defs_.Clear();
+    command_owners_.clear();
+    NotifyOnDeviceInfoChanged();
+    NotifyOnCommandDefsChanged();
+    NotifyOnStateChanged();
+  }
+
+  void RetryRegister(const std::string& ticket_id,
+                     int retries,
+                     chromeos::Error* error) {
+    if (retries >= kMaxSetupRetries) {
+      chromeos::ErrorPtr new_error{error ? error->Clone() : nullptr};
+      chromeos::Error::AddTo(&new_error, FROM_HERE, errors::kDomain,
+                             errors::kInvalidState,
+                             "Failed to register device");
+      setup_state_ = SetupState{std::move(new_error)};
+      return;
+    }
+    base::MessageLoop::current()->PostDelayedTask(
+        FROM_HERE,
+        base::Bind(&CloudDelegateImpl::CallManagerRegisterDevice,
+                   setup_weak_factory_.GetWeakPtr(), ticket_id, retries + 1),
+        base::TimeDelta::FromSeconds(kFirstRetryTimeoutSec << retries));
+  }
+
+  void OnRegisterSuccess(const std::string& device_id) {
+    VLOG(1) << "Device registered: " << device_id;
+    setup_state_ = SetupState(SetupState::kSuccess);
+  }
+
+  void CallManagerRegisterDevice(const std::string& ticket_id, int retries) {
+    auto manager_proxy = object_manager_.GetManagerProxy();
+    if (!manager_proxy) {
+      LOG(ERROR) << "Couldn't register because Buffet was offline.";
+      RetryRegister(ticket_id, retries, nullptr);
+      return;
+    }
+    manager_proxy->RegisterDeviceAsync(
+        ticket_id, base::Bind(&CloudDelegateImpl::OnRegisterSuccess,
+                              setup_weak_factory_.GetWeakPtr()),
+        base::Bind(&CloudDelegateImpl::RetryRegister,
+                   setup_weak_factory_.GetWeakPtr(), ticket_id, retries));
+  }
+
+  void OnAddCommandSucceeded(const SuccessCallback& success_callback,
+                             const ErrorCallback& error_callback,
+                             const std::string& id) {
+    GetCommandInternal(id, success_callback, error_callback);
+  }
+
+  void OnGetCommandSucceeded(const SuccessCallback& success_callback,
+                             const ErrorCallback& error_callback,
+                             const std::string& json_command) {
+    std::unique_ptr<base::Value> value{base::JSONReader::Read(json_command)};
+    base::DictionaryValue* command{nullptr};
+    if (!value || !value->GetAsDictionary(&command)) {
+      chromeos::ErrorPtr error;
+      chromeos::Error::AddTo(&error, FROM_HERE, errors::kDomain,
+                             errors::kInvalidFormat,
+                             "Buffet returned invalid JSON");
+      return error_callback.Run(error.get());
+    }
+    success_callback.Run(*command);
+  }
+
+  void GetNextCommandSkipError(
+      const std::shared_ptr<std::vector<std::string>>& ids,
+      const std::shared_ptr<base::ListValue>& commands,
+      const SuccessCallback& success_callback,
+      const ErrorCallback& error_callback,
+      chromeos::Error*) {
+    // Ignore if we can't get some commands. Maybe they were removed.
+    GetNextCommand(ids, commands, success_callback, error_callback,
+                   base::DictionaryValue{});
+  }
+
+  void GetNextCommand(const std::shared_ptr<std::vector<std::string>>& ids,
+                      const std::shared_ptr<base::ListValue>& commands,
+                      const SuccessCallback& success_callback,
+                      const ErrorCallback& error_callback,
+                      const base::DictionaryValue& json) {
+    if (!json.empty())
+      commands->Append(json.DeepCopy());
+
+    if (ids->empty()) {
+      base::DictionaryValue commands_json;
+      commands_json.Set("commands", commands->DeepCopy());
+      return success_callback.Run(commands_json);
+    }
+
+    std::string next_id = ids->back();
+    ids->pop_back();
+
+    auto on_success = base::Bind(&CloudDelegateImpl::GetNextCommand,
+                                 weak_factory_.GetWeakPtr(), ids, commands,
+                                 success_callback, error_callback);
+
+    auto on_error = base::Bind(&CloudDelegateImpl::GetNextCommandSkipError,
+                               weak_factory_.GetWeakPtr(), ids, commands,
+                               success_callback, error_callback);
+
+    GetCommandInternal(next_id, on_success, on_error);
+  }
+
+  bool IsManagerReady(chromeos::ErrorPtr* error) const {
+    if (!manager_) {
+      chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                             errors::kDeviceBusy, "Buffet is not ready");
+      return false;
+    }
+    return true;
+  }
+
+  bool CanAccessCommand(const std::string& command_id,
+                        const UserInfo& user_info) const {
+    if (user_info.scope() == AuthScope::kOwner)
+      return true;
+    auto it = command_owners_.find(command_id);
+    return it != command_owners_.end() && it->second == user_info.user_id();
+  }
+
+  ObjectManagerProxy object_manager_;
+
+  bool is_gcd_setup_enabled_{false};
+
+  ManagerProxy* manager_{nullptr};
+
+  // Primary state of GCD.
+  ConnectionState connection_state_{ConnectionState::kDisabled};
+
+  // State of the current or last setup.
+  SetupState setup_state_{SetupState::kNone};
+
+  // Current device state.
+  base::DictionaryValue state_;
+
+  // Current commands definitions.
+  base::DictionaryValue command_defs_;
+
+  // Map of command IDs to user IDs.
+  std::map<std::string, uint64_t> command_owners_;
+
+  // |setup_weak_factory_| tracks the lifetime of callbacks used in connection
+  // with a particular invocation of Setup().
+  base::WeakPtrFactory<CloudDelegateImpl> setup_weak_factory_{this};
+  // |weak_factory_| tracks the lifetime of |this|.
+  base::WeakPtrFactory<CloudDelegateImpl> weak_factory_{this};
+};
+
+}  // namespace
+
+CloudDelegate::CloudDelegate() {
+}
+
+CloudDelegate::~CloudDelegate() {
+}
+
+// static
+std::unique_ptr<CloudDelegate> CloudDelegate::CreateDefault(
+    const scoped_refptr<dbus::Bus>& bus,
+    bool is_gcd_setup_enabled) {
+  return std::unique_ptr<CloudDelegateImpl>{
+      new CloudDelegateImpl{bus, is_gcd_setup_enabled}};
+}
+
+void CloudDelegate::NotifyOnDeviceInfoChanged() {
+  FOR_EACH_OBSERVER(Observer, observer_list_, OnDeviceInfoChanged());
+}
+
+void CloudDelegate::NotifyOnCommandDefsChanged() {
+  FOR_EACH_OBSERVER(Observer, observer_list_, OnCommandDefsChanged());
+}
+
+void CloudDelegate::NotifyOnStateChanged() {
+  FOR_EACH_OBSERVER(Observer, observer_list_, OnStateChanged());
+}
+
+}  // namespace privetd
diff --git a/buffet/privet/cloud_delegate.h b/buffet/privet/cloud_delegate.h
new file mode 100644
index 0000000..0d027f9
--- /dev/null
+++ b/buffet/privet/cloud_delegate.h
@@ -0,0 +1,143 @@
+// 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_PRIVET_CLOUD_DELEGATE_H_
+#define BUFFET_PRIVET_CLOUD_DELEGATE_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include <base/callback.h>
+#include <base/memory/ref_counted.h>
+#include <base/observer_list.h>
+
+#include "buffet/privet/privet_types.h"
+#include "buffet/privet/security_delegate.h"
+
+namespace base {
+class DictionaryValue;
+}  // namespace base
+
+namespace dbus {
+class Bus;
+}  // namespace dbus
+
+namespace privetd {
+
+// Interface to provide GCD functionality for PrivetHandler.
+// TODO(vitalybuka): Rename to BuffetDelegate.
+class CloudDelegate {
+ public:
+  CloudDelegate();
+  virtual ~CloudDelegate();
+
+  using SuccessCallback = base::Callback<void(const base::DictionaryValue&)>;
+  using ErrorCallback = base::Callback<void(chromeos::Error*)>;
+
+  class Observer {
+   public:
+    virtual ~Observer() = default;
+
+    virtual void OnDeviceInfoChanged() {}
+    virtual void OnCommandDefsChanged() {}
+    virtual void OnStateChanged() {}
+  };
+
+  // Returns the model ID of the device.
+  virtual bool GetModelId(std::string* id, chromeos::ErrorPtr* error) const = 0;
+
+  // Returns the name of device.
+  virtual bool GetName(std::string* name, chromeos::ErrorPtr* error) const = 0;
+
+  // Returns the description of the device.
+  virtual std::string GetDescription() const = 0;
+
+  // Returns the location of the device.
+  virtual std::string GetLocation() const = 0;
+
+  // Update basic device information.
+  virtual void UpdateDeviceInfo(const std::string& name,
+                                const std::string& description,
+                                const std::string& location,
+                                const base::Closure& success_callback,
+                                const ErrorCallback& error_callback) = 0;
+
+  // Returns the name of the maker.
+  virtual std::string GetOemName() const = 0;
+
+  // Returns the model name of the device.
+  virtual std::string GetModelName() const = 0;
+
+  // Returns the list of services supported by device.
+  // E.g. printer, scanner etc. Should match services published on mDNS.
+  virtual std::set<std::string> GetServices() const = 0;
+
+  // Returns max scope available for anonymous user.
+  virtual AuthScope GetAnonymousMaxScope() const = 0;
+
+  // Returns status of the GCD connection.
+  virtual const ConnectionState& GetConnectionState() const = 0;
+
+  // Returns status of the last setup.
+  virtual const SetupState& GetSetupState() const = 0;
+
+  // Starts GCD setup.
+  virtual bool Setup(const std::string& ticket_id,
+                     const std::string& user,
+                     chromeos::ErrorPtr* error) = 0;
+
+  // Returns cloud id if the registered device or empty string if unregistered.
+  virtual std::string GetCloudId() const = 0;
+
+  // Returns dictionary with device state.
+  virtual const base::DictionaryValue& GetState() const = 0;
+
+  // Returns dictionary with commands definitions.
+  virtual const base::DictionaryValue& GetCommandDef() const = 0;
+
+  // Adds command created from the given JSON representation.
+  virtual void AddCommand(const base::DictionaryValue& command,
+                          const UserInfo& user_info,
+                          const SuccessCallback& success_callback,
+                          const ErrorCallback& error_callback) = 0;
+
+  // Returns command with the given ID.
+  virtual void GetCommand(const std::string& id,
+                          const UserInfo& user_info,
+                          const SuccessCallback& success_callback,
+                          const ErrorCallback& error_callback) = 0;
+
+  // Cancels command with the given ID.
+  virtual void CancelCommand(const std::string& id,
+                             const UserInfo& user_info,
+                             const SuccessCallback& success_callback,
+                             const ErrorCallback& error_callback) = 0;
+
+  // Lists commands.
+  virtual void ListCommands(const UserInfo& user_info,
+                            const SuccessCallback& success_callback,
+                            const ErrorCallback& error_callback) = 0;
+
+  void AddObserver(Observer* observer) { observer_list_.AddObserver(observer); }
+  void RemoveObserver(Observer* observer) {
+    observer_list_.RemoveObserver(observer);
+  }
+
+  void NotifyOnDeviceInfoChanged();
+  void NotifyOnCommandDefsChanged();
+  void NotifyOnStateChanged();
+
+  // Create default instance.
+  static std::unique_ptr<CloudDelegate> CreateDefault(
+      const scoped_refptr<dbus::Bus>& bus,
+      bool is_gcd_setup_enabled);
+
+ private:
+  ObserverList<Observer> observer_list_;
+};
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_CLOUD_DELEGATE_H_
diff --git a/buffet/privet/constants.cc b/buffet/privet/constants.cc
new file mode 100644
index 0000000..e94b541
--- /dev/null
+++ b/buffet/privet/constants.cc
@@ -0,0 +1,36 @@
+// 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/privet/constants.h"
+
+namespace privetd {
+
+namespace errors {
+
+const char kDomain[] = "privetd";
+
+const char kInvalidClientCommitment[] = "invalidClientCommitment";
+const char kInvalidFormat[] = "invalidFormat";
+const char kMissingAuthorization[] = "missingAuthorization";
+const char kInvalidAuthorization[] = "invalidAuthorization";
+const char kInvalidAuthorizationScope[] = "invalidAuthorizationScope";
+const char kAuthorizationExpired[] = "authorizationExpired";
+const char kCommitmentMismatch[] = "commitmentMismatch";
+const char kUnknownSession[] = "unknownSession";
+const char kInvalidAuthCode[] = "invalidAuthCode";
+const char kInvalidAuthMode[] = "invalidAuthMode";
+const char kInvalidRequestedScope[] = "invalidRequestedScope";
+const char kAccessDenied[] = "accessDenied";
+const char kInvalidParams[] = "invalidParams";
+const char kSetupUnavailable[] = "setupUnavailable";
+const char kDeviceBusy[] = "deviceBusy";
+const char kInvalidState[] = "invalidState";
+const char kInvalidSsid[] = "invalidSsid";
+const char kInvalidPassphrase[] = "invalidPassphrase";
+const char kNotFound[] = "notFound";
+const char kNotImplemented[] = "notImplemented";
+
+}  // namespace errors
+
+}  // namespace privetd
diff --git a/buffet/privet/constants.h b/buffet/privet/constants.h
new file mode 100644
index 0000000..c408ca1
--- /dev/null
+++ b/buffet/privet/constants.h
@@ -0,0 +1,41 @@
+// 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_PRIVET_CONSTANTS_H_
+#define BUFFET_PRIVET_CONSTANTS_H_
+
+namespace privetd {
+
+namespace errors {
+
+extern const char kDomain[];
+
+extern const char kInvalidClientCommitment[];
+extern const char kInvalidFormat[];
+extern const char kMissingAuthorization[];
+extern const char kInvalidAuthorization[];
+extern const char kInvalidAuthorizationScope[];
+extern const char kAuthorizationExpired[];
+extern const char kCommitmentMismatch[];
+extern const char kUnknownSession[];
+extern const char kInvalidAuthCode[];
+extern const char kInvalidAuthMode[];
+extern const char kInvalidRequestedScope[];
+extern const char kAccessDenied[];
+extern const char kInvalidParams[];
+extern const char kSetupUnavailable[];
+extern const char kDeviceBusy[];
+extern const char kInvalidState[];
+extern const char kInvalidSsid[];
+extern const char kInvalidPassphrase[];
+extern const char kNotFound[];
+extern const char kNotImplemented[];
+}  // namespace errors
+
+// Time to reply on privet HTTP.
+const int kSetupDelaySeconds = 1;
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_CONSTANTS_H_
diff --git a/buffet/privet/daemon_state.cc b/buffet/privet/daemon_state.cc
new file mode 100644
index 0000000..cdbe808
--- /dev/null
+++ b/buffet/privet/daemon_state.cc
@@ -0,0 +1,33 @@
+// 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/privet/daemon_state.h"
+
+namespace privetd {
+
+namespace state_key {
+
+const char kDeviceId[] = "id";
+const char kDeviceName[] = "name";
+const char kDeviceDescription[] = "description";
+const char kDeviceLocation[] = "description";
+
+const char kWifiHasBeenBootstrapped[] = "have_ever_been_bootstrapped";
+const char kWifiLastConfiguredSSID[] = "last_configured_ssid";
+
+}  // namespace state_key
+
+DaemonState::DaemonState(const base::FilePath& state_path)
+    : state_path_(state_path) {
+}
+
+void DaemonState::Init() {
+  Load(state_path_);
+}
+
+void DaemonState::Save() const {
+  KeyValueStore::Save(state_path_);
+}
+
+}  // namespace privetd
diff --git a/buffet/privet/daemon_state.h b/buffet/privet/daemon_state.h
new file mode 100644
index 0000000..a2d7616
--- /dev/null
+++ b/buffet/privet/daemon_state.h
@@ -0,0 +1,42 @@
+// 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_PRIVET_DAEMON_STATE_H_
+#define BUFFET_PRIVET_DAEMON_STATE_H_
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <chromeos/key_value_store.h>
+
+namespace privetd {
+
+namespace state_key {
+
+extern const char kDeviceId[];
+extern const char kDeviceName[];
+extern const char kDeviceDescription[];
+extern const char kDeviceLocation[];
+
+extern const char kWifiHasBeenBootstrapped[];
+extern const char kWifiLastConfiguredSSID[];
+
+}  // namespace state_key
+
+class DaemonState : public chromeos::KeyValueStore {
+ public:
+  explicit DaemonState(const base::FilePath& state_path);
+  // Load initial state from disk.
+  void Init();
+  // Save state to disk.
+  void Save() const;
+
+ private:
+  const base::FilePath state_path_;
+
+  DISALLOW_COPY_AND_ASSIGN(DaemonState);
+};
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_DAEMON_STATE_H_
diff --git a/buffet/privet/dbus_manager.cc b/buffet/privet/dbus_manager.cc
new file mode 100644
index 0000000..a336a19
--- /dev/null
+++ b/buffet/privet/dbus_manager.cc
@@ -0,0 +1,140 @@
+// 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/privet/dbus_manager.h"
+
+#include <base/memory/ref_counted.h>
+#include <chromeos/any.h>
+
+#include "buffet/privet/constants.h"
+#include "buffet/privet/security_delegate.h"
+#include "buffet/privet/security_manager.h"
+
+using chromeos::Any;
+using chromeos::dbus_utils::AsyncEventSequencer;
+using chromeos::dbus_utils::DBusObject;
+using chromeos::dbus_utils::ExportedObjectManager;
+using org::chromium::privetd::ManagerAdaptor;
+
+namespace privetd {
+
+namespace {
+
+const char kPingResponse[] = "Hello world!";
+const char kPairingSessionIdKey[] = "sessionId";
+const char kPairingModeKey[] = "mode";
+const char kPairingCodeKey[] = "code";
+
+}  // namespace
+
+DBusManager::DBusManager(ExportedObjectManager* object_manager,
+                         WifiBootstrapManager* wifi_bootstrap_manager,
+                         CloudDelegate* cloud_delegate,
+                         SecurityManager* security_manager)
+  : dbus_object_{new DBusObject{object_manager,
+                                object_manager->GetBus(),
+                                ManagerAdaptor::GetObjectPath()}} {
+  if (wifi_bootstrap_manager) {
+    wifi_bootstrap_manager->RegisterStateListener(
+        base::Bind(&DBusManager::UpdateWiFiBootstrapState,
+                   weak_ptr_factory_.GetWeakPtr()));
+  } else {
+    UpdateWiFiBootstrapState(WifiBootstrapManager::kDisabled);
+  }
+  security_manager->RegisterPairingListeners(
+      base::Bind(&DBusManager::OnPairingStart,
+                 weak_ptr_factory_.GetWeakPtr()),
+      base::Bind(&DBusManager::OnPairingEnd,
+                 weak_ptr_factory_.GetWeakPtr()));
+  // TODO(wiley) Watch for appropriate state variables from |cloud_delegate|.
+}
+
+void DBusManager::RegisterAsync(const CompletionAction& on_done) {
+  scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer());
+  dbus_adaptor_.RegisterWithDBusObject(dbus_object_.get());
+  dbus_object_->RegisterAsync(
+      sequencer->GetHandler("Failed exporting DBusManager.", true));
+  sequencer->OnAllTasksCompletedCall({on_done});
+}
+
+bool DBusManager::EnableWiFiBootstrapping(
+    chromeos::ErrorPtr* error,
+    const dbus::ObjectPath& in_listener_path,
+    const chromeos::VariantDictionary& in_options) {
+  chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                         errors::kNotImplemented,
+                         "Manual WiFi bootstrapping is not implemented");
+  return false;
+}
+
+bool DBusManager::DisableWiFiBootstrapping(chromeos::ErrorPtr* error) {
+  chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                         errors::kNotImplemented,
+                         "Manual WiFi bootstrapping is not implemented");
+  return false;
+}
+
+bool DBusManager::EnableGCDBootstrapping(
+    chromeos::ErrorPtr* error,
+    const dbus::ObjectPath& in_listener_path,
+    const chromeos::VariantDictionary& in_options) {
+  chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                         errors::kNotImplemented,
+                         "Manual GCD bootstrapping is not implemented");
+  return false;
+}
+
+bool DBusManager::DisableGCDBootstrapping(chromeos::ErrorPtr* error) {
+  chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                         errors::kNotImplemented,
+                         "Manual GCD bootstrapping is not implemented");
+  return false;
+}
+
+std::string DBusManager::Ping() {
+  return kPingResponse;
+}
+
+void DBusManager::UpdateWiFiBootstrapState(WifiBootstrapManager::State state) {
+  switch (state) {
+    case WifiBootstrapManager::kDisabled:
+      dbus_adaptor_.SetWiFiBootstrapState("disabled");
+      break;
+    case WifiBootstrapManager::kBootstrapping:
+      dbus_adaptor_.SetWiFiBootstrapState("waiting");
+      break;
+    case WifiBootstrapManager::kMonitoring:
+      dbus_adaptor_.SetWiFiBootstrapState("monitoring");
+      break;
+    case WifiBootstrapManager::kConnecting:
+      dbus_adaptor_.SetWiFiBootstrapState("connecting");
+      break;
+  }
+}
+
+void DBusManager::OnPairingStart(const std::string& session_id,
+                                 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, PairingTypeToString(pairing_type)},
+      {kPairingCodeKey, code},
+  });
+}
+
+void DBusManager::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 privetd
diff --git a/buffet/privet/dbus_manager.h b/buffet/privet/dbus_manager.h
new file mode 100644
index 0000000..03cce45
--- /dev/null
+++ b/buffet/privet/dbus_manager.h
@@ -0,0 +1,76 @@
+// 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_PRIVET_DBUS_MANAGER_H_
+#define BUFFET_PRIVET_DBUS_MANAGER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <chromeos/dbus/async_event_sequencer.h>
+#include <chromeos/dbus/dbus_object.h>
+#include <chromeos/errors/error.h>
+#include <chromeos/variant_dictionary.h>
+#include <dbus/object_path.h>
+
+#include "buffet/privet/org.chromium.privetd.Manager.h"
+#include "buffet/privet/wifi_bootstrap_manager.h"
+
+namespace chromeos {
+namespace dbus_utils {
+class ExportedObjectManager;
+}  // dbus_utils
+}  // chromeos
+
+namespace privetd {
+
+class CloudDelegate;
+class SecurityManager;
+enum class PairingType;
+
+// Exposes most of the privetd DBus interface.
+class DBusManager : public org::chromium::privetd::ManagerInterface {
+ public:
+  using CompletionAction =
+      chromeos::dbus_utils::AsyncEventSequencer::CompletionAction;
+
+  DBusManager(chromeos::dbus_utils::ExportedObjectManager* object_manager,
+              WifiBootstrapManager* wifi_bootstrap_manager,
+              CloudDelegate* cloud_delegate,
+              SecurityManager* security_manager);
+  ~DBusManager() override = default;
+  void RegisterAsync(const CompletionAction& on_done);
+
+  // DBus handlers
+  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;
+  std::string Ping() override;
+
+ private:
+  void UpdateWiFiBootstrapState(WifiBootstrapManager::State state);
+  void OnPairingStart(const std::string& session_id,
+                      PairingType pairing_type,
+                      const std::vector<uint8_t>& code);
+  void OnPairingEnd(const std::string& session_id);
+
+  org::chromium::privetd::ManagerAdaptor dbus_adaptor_{this};
+  std::unique_ptr<chromeos::dbus_utils::DBusObject> dbus_object_;
+  base::WeakPtrFactory<DBusManager> weak_ptr_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(DBusManager);
+};
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_DBUS_MANAGER_H_
diff --git a/buffet/privet/device_delegate.cc b/buffet/privet/device_delegate.cc
new file mode 100644
index 0000000..2953284
--- /dev/null
+++ b/buffet/privet/device_delegate.cc
@@ -0,0 +1,58 @@
+// 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/privet/device_delegate.h"
+
+#include <base/files/file_util.h>
+#include <base/guid.h>
+
+#include "buffet/privet/constants.h"
+
+namespace privetd {
+
+namespace {
+
+class DeviceDelegateImpl : public DeviceDelegate {
+ public:
+  DeviceDelegateImpl() = default;
+  ~DeviceDelegateImpl() override = default;
+
+  std::pair<uint16_t, uint16_t> GetHttpEnpoint() const override {
+    return std::make_pair(http_port_, http_port_);
+  }
+  std::pair<uint16_t, uint16_t> GetHttpsEnpoint() const override {
+    return std::make_pair(https_port_, https_port_);
+  }
+  base::TimeDelta GetUptime() const override {
+    return base::Time::Now() - start_time_;
+  }
+
+  void SetHttpPort(uint16_t port) override {
+    http_port_ = port;
+  }
+
+  void SetHttpsPort(uint16_t port) override {
+    https_port_ = port;
+  }
+
+ private:
+  uint16_t http_port_{0};
+  uint16_t https_port_{0};
+  base::Time start_time_{base::Time::Now()};
+};
+
+}  // namespace
+
+DeviceDelegate::DeviceDelegate() {
+}
+
+DeviceDelegate::~DeviceDelegate() {
+}
+
+// static
+std::unique_ptr<DeviceDelegate> DeviceDelegate::CreateDefault() {
+  return std::unique_ptr<DeviceDelegate>(new DeviceDelegateImpl());
+}
+
+}  // namespace privetd
diff --git a/buffet/privet/device_delegate.h b/buffet/privet/device_delegate.h
new file mode 100644
index 0000000..9888dfd
--- /dev/null
+++ b/buffet/privet/device_delegate.h
@@ -0,0 +1,45 @@
+// 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_PRIVET_DEVICE_DELEGATE_H_
+#define BUFFET_PRIVET_DEVICE_DELEGATE_H_
+
+#include <memory>
+#include <utility>
+
+#include <base/time/time.h>
+
+namespace privetd {
+
+// Interface to provide access to general information about device.
+class DeviceDelegate {
+ public:
+  DeviceDelegate();
+  virtual ~DeviceDelegate();
+
+  // Returns HTTP ports for Privet. The first one is the primary port,
+  // the second is the port for a pooling updates requests. The second value
+  // could be 0. In this case the first port would be use for regular and for
+  // updates requests.
+  virtual std::pair<uint16_t, uint16_t> GetHttpEnpoint() const = 0;
+
+  // The same |GetHttpEnpoint| but for HTTPS.
+  virtual std::pair<uint16_t, uint16_t> GetHttpsEnpoint() const = 0;
+
+  // Returns device update.
+  virtual base::TimeDelta GetUptime() const = 0;
+
+  // Updates the HTTP port value.
+  virtual void SetHttpPort(uint16_t port) = 0;
+
+  // Updates the HTTPS port value.
+  virtual void SetHttpsPort(uint16_t port) = 0;
+
+  // Create default instance.
+  static std::unique_ptr<DeviceDelegate> CreateDefault();
+};
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_DEVICE_DELEGATE_H_
diff --git a/buffet/privet/identity_delegate.h b/buffet/privet/identity_delegate.h
new file mode 100644
index 0000000..5ab97d1
--- /dev/null
+++ b/buffet/privet/identity_delegate.h
@@ -0,0 +1,24 @@
+// 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_PRIVET_IDENTITY_DELEGATE_H_
+#define BUFFET_PRIVET_IDENTITY_DELEGATE_H_
+
+#include <string>
+
+namespace privetd {
+
+// Interface for an object that can identify ourselves.
+class IdentityDelegate {
+ public:
+  IdentityDelegate() = default;
+  virtual ~IdentityDelegate() = default;
+
+  // Returns a unique identifier for this device.
+  virtual std::string GetId() const = 0;
+};
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_IDENTITY_DELEGATE_H_
diff --git a/buffet/privet/main.cc b/buffet/privet/main.cc
new file mode 100644
index 0000000..a590776
--- /dev/null
+++ b/buffet/privet/main.cc
@@ -0,0 +1,311 @@
+// 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 <set>
+#include <string>
+#include <sysexits.h>
+
+#include <base/bind.h>
+#include <base/command_line.h>
+#include <base/json/json_reader.h>
+#include <base/memory/weak_ptr.h>
+#include <base/scoped_observer.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/values.h>
+#include <chromeos/daemons/dbus_daemon.h>
+#include <chromeos/flag_helper.h>
+#include <chromeos/http/http_request.h>
+#include <chromeos/key_value_store.h>
+#include <chromeos/mime_utils.h>
+#include <chromeos/strings/string_utils.h>
+#include <chromeos/syslog_logging.h>
+#include <libwebserv/protocol_handler.h>
+#include <libwebserv/request.h>
+#include <libwebserv/response.h>
+#include <libwebserv/server.h>
+
+#include "buffet/privet/ap_manager_client.h"
+#include "buffet/privet/cloud_delegate.h"
+#include "buffet/privet/constants.h"
+#include "buffet/privet/daemon_state.h"
+#include "buffet/privet/dbus_manager.h"
+#include "buffet/privet/device_delegate.h"
+#include "buffet/privet/peerd_client.h"
+#include "buffet/privet/privet_handler.h"
+#include "buffet/privet/privetd_conf_parser.h"
+#include "buffet/privet/security_manager.h"
+#include "buffet/privet/shill_client.h"
+#include "buffet/privet/wifi_bootstrap_manager.h"
+
+namespace privetd {
+
+namespace {
+
+using chromeos::dbus_utils::AsyncEventSequencer;
+using libwebserv::ProtocolHandler;
+using libwebserv::Request;
+using libwebserv::Response;
+
+const char kDefaultConfigFilePath[] = "/etc/privetd/privetd.conf";
+const char kDefaultStateFilePath[] = "/var/lib/privetd/privetd.state";
+
+std::string GetFirstHeader(const Request& request, const std::string& name) {
+  std::vector<std::string> headers = request.GetHeader(name);
+  return headers.empty() ? std::string() : headers.front();
+}
+
+const char kServiceName[] = "org.chromium.privetd";
+const char kRootPath[] = "/org/chromium/privetd";
+
+class Daemon : public chromeos::DBusServiceDaemon,
+               public CloudDelegate::Observer {
+ public:
+  Daemon(bool disable_security,
+         bool enable_ping,
+         const std::set<std::string>& device_whitelist,
+         const base::FilePath& config_path,
+         const base::FilePath& state_path)
+      : DBusServiceDaemon(kServiceName, kRootPath),
+        disable_security_(disable_security),
+        enable_ping_(enable_ping),
+        device_whitelist_(device_whitelist),
+        config_path_(config_path),
+        state_store_(new DaemonState(state_path)) {}
+
+  void RegisterDBusObjectsAsync(AsyncEventSequencer* sequencer) override {
+    chromeos::KeyValueStore config_store;
+    if (!config_store.Load(config_path_)) {
+      LOG(ERROR) << "Failed to read privetd config file from "
+                 << config_path_.value();
+    } else {
+      CHECK(parser_.Parse(config_store))
+          << "Failed to read configuration file.";
+    }
+    state_store_->Init();
+    // This state store key doesn't exist naturally, but developers
+    // sometime put it in their state store to cause the device to bring
+    // up WiFi bootstrapping while being connected to an ethernet interface.
+    std::string test_device_whitelist;
+    if (device_whitelist_.empty() &&
+        state_store_->GetString(kWiFiBootstrapInterfaces,
+                                &test_device_whitelist)) {
+      auto interfaces =
+          chromeos::string_utils::Split(test_device_whitelist, ",", true, true);
+      device_whitelist_.insert(interfaces.begin(), interfaces.end());
+    }
+    device_ = DeviceDelegate::CreateDefault();
+    cloud_ = CloudDelegate::CreateDefault(
+        bus_, parser_.gcd_bootstrap_mode() != GcdBootstrapMode::kDisabled);
+    cloud_observer_.Add(cloud_.get());
+    security_.reset(new SecurityManager(parser_.pairing_modes(),
+                                        parser_.embedded_code_path(),
+                                        disable_security_));
+    shill_client_.reset(new ShillClient(
+        bus_, device_whitelist_.empty() ? parser_.automatic_wifi_interfaces()
+                                        : device_whitelist_));
+    shill_client_->RegisterConnectivityListener(
+        base::Bind(&Daemon::OnConnectivityChanged, base::Unretained(this)));
+    ap_manager_client_.reset(new ApManagerClient(bus_));
+
+    if (parser_.wifi_bootstrap_mode() != WiFiBootstrapMode::kDisabled) {
+      VLOG(1) << "Enabling WiFi bootstrapping.";
+      wifi_bootstrap_manager_.reset(new WifiBootstrapManager(
+          state_store_.get(), shill_client_.get(), ap_manager_client_.get(),
+          cloud_.get(), parser_.connect_timeout_seconds(),
+          parser_.bootstrap_timeout_seconds(),
+          parser_.monitor_timeout_seconds()));
+      wifi_bootstrap_manager_->Init();
+    }
+
+    peerd_client_.reset(new PeerdClient(bus_, device_.get(), cloud_.get(),
+                                        wifi_bootstrap_manager_.get()));
+
+    privet_handler_.reset(new PrivetHandler(cloud_.get(), device_.get(),
+                                            security_.get(),
+                                            wifi_bootstrap_manager_.get(),
+                                            peerd_client_.get()));
+
+    web_server_.OnProtocolHandlerConnected(
+        base::Bind(&Daemon::OnProtocolHandlerConnected,
+                   weak_ptr_factory_.GetWeakPtr()));
+    web_server_.OnProtocolHandlerDisconnected(
+        base::Bind(&Daemon::OnProtocolHandlerDisconnected,
+                   weak_ptr_factory_.GetWeakPtr()));
+
+    web_server_.Connect(
+        bus_,
+        kServiceName,
+        sequencer->GetHandler("Server::Connect failed.", true),
+        base::Bind(&base::DoNothing),
+        base::Bind(&base::DoNothing));
+
+    web_server_.GetDefaultHttpHandler()->AddHandlerCallback(
+        "/privet/", "",
+        base::Bind(&Daemon::PrivetRequestHandler, base::Unretained(this)));
+    web_server_.GetDefaultHttpsHandler()->AddHandlerCallback(
+        "/privet/", "",
+        base::Bind(&Daemon::PrivetRequestHandler, base::Unretained(this)));
+    if (enable_ping_) {
+      web_server_.GetDefaultHttpHandler()->AddHandlerCallback(
+          "/privet/ping", chromeos::http::request_type::kGet,
+          base::Bind(&Daemon::HelloWorldHandler, base::Unretained(this)));
+      web_server_.GetDefaultHttpsHandler()->AddHandlerCallback(
+          "/privet/ping", chromeos::http::request_type::kGet,
+          base::Bind(&Daemon::HelloWorldHandler, base::Unretained(this)));
+    }
+
+    dbus_manager_.reset(new DBusManager{object_manager_.get(),
+                                        wifi_bootstrap_manager_.get(),
+                                        cloud_.get(),
+                                        security_.get()});
+    dbus_manager_->RegisterAsync(
+        sequencer->GetHandler("DBusManager.RegisterAsync() failed.", true));
+  }
+
+  void OnShutdown(int* return_code) override {
+    web_server_.Disconnect();
+    DBusDaemon::OnShutdown(return_code);
+  }
+
+  void OnDeviceInfoChanged() override { OnChanged(); };
+
+ private:
+  void PrivetRequestHandler(std::unique_ptr<Request> request,
+                            std::unique_ptr<Response> response) {
+    std::string auth_header = GetFirstHeader(
+        *request, chromeos::http::request_header::kAuthorization);
+    if (auth_header.empty() && disable_security_)
+      auth_header = "Privet anonymous";
+    std::string data(request->GetData().begin(), request->GetData().end());
+    VLOG(3) << "Input: " << data;
+
+    base::DictionaryValue empty;
+    std::unique_ptr<base::Value> value;
+    const base::DictionaryValue* dictionary = nullptr;
+
+    if (data.empty()) {
+      dictionary = &empty;
+    } else {
+      std::string content_type =
+          chromeos::mime::RemoveParameters(GetFirstHeader(
+              *request, chromeos::http::request_header::kContentType));
+      if (content_type == chromeos::mime::application::kJson) {
+        value.reset(base::JSONReader::Read(data));
+        if (value)
+          value->GetAsDictionary(&dictionary);
+      }
+    }
+
+    privet_handler_->HandleRequest(
+        request->GetPath(), auth_header, dictionary,
+        base::Bind(&Daemon::PrivetResponseHandler, base::Unretained(this),
+                   base::Passed(&response)));
+  }
+
+  void PrivetResponseHandler(std::unique_ptr<Response> response,
+                             int status,
+                             const base::DictionaryValue& output) {
+    VLOG(3) << "status: " << status << ", Output: " << output;
+    response->ReplyWithJson(status, &output);
+  }
+
+  void HelloWorldHandler(std::unique_ptr<Request> request,
+                         std::unique_ptr<Response> response) {
+    response->ReplyWithText(chromeos::http::status_code::Ok, "Hello, world!",
+                            chromeos::mime::text::kPlain);
+  }
+
+  void OnChanged() {
+    if (peerd_client_)
+      peerd_client_->Update();
+  }
+
+  void OnConnectivityChanged(bool online) {
+    OnChanged();
+  }
+
+  void OnProtocolHandlerConnected(ProtocolHandler* protocol_handler) {
+    if (protocol_handler->GetName() == ProtocolHandler::kHttp) {
+      device_->SetHttpPort(*protocol_handler->GetPorts().begin());
+      if (peerd_client_)
+        peerd_client_->Update();
+    } else if (protocol_handler->GetName() == ProtocolHandler::kHttps) {
+      device_->SetHttpsPort(*protocol_handler->GetPorts().begin());
+      security_->SetCertificateFingerprint(
+          protocol_handler->GetCertificateFingerprint());
+    }
+  }
+
+  void OnProtocolHandlerDisconnected(ProtocolHandler* protocol_handler) {
+    if (protocol_handler->GetName() == ProtocolHandler::kHttp) {
+      device_->SetHttpPort(0);
+      if (peerd_client_)
+        peerd_client_->Update();
+    } else if (protocol_handler->GetName() == ProtocolHandler::kHttps) {
+      device_->SetHttpsPort(0);
+      security_->SetCertificateFingerprint({});
+    }
+  }
+
+  bool disable_security_;
+  bool enable_ping_;
+  PrivetdConfigParser parser_;
+  std::set<std::string> device_whitelist_;
+  base::FilePath config_path_;
+  std::unique_ptr<DaemonState> state_store_;
+  std::unique_ptr<CloudDelegate> cloud_;
+  std::unique_ptr<DeviceDelegate> device_;
+  std::unique_ptr<SecurityManager> security_;
+  std::unique_ptr<ShillClient> shill_client_;
+  std::unique_ptr<ApManagerClient> ap_manager_client_;
+  std::unique_ptr<WifiBootstrapManager> wifi_bootstrap_manager_;
+  std::unique_ptr<PeerdClient> peerd_client_;
+  std::unique_ptr<PrivetHandler> privet_handler_;
+  ScopedObserver<CloudDelegate, CloudDelegate::Observer> cloud_observer_{this};
+  std::unique_ptr<DBusManager> dbus_manager_;
+  libwebserv::Server web_server_;
+
+  base::WeakPtrFactory<Daemon> weak_ptr_factory_{this};
+  DISALLOW_COPY_AND_ASSIGN(Daemon);
+};
+
+}  // anonymous namespace
+
+}  // namespace privetd
+
+int main(int argc, char* argv[]) {
+  DEFINE_bool(disable_security, false, "disable Privet security for tests");
+  DEFINE_bool(enable_ping, false, "enable test HTTP handler at /privet/ping");
+  DEFINE_bool(log_to_stderr, false, "log trace messages to stderr as well");
+  DEFINE_string(config_path, privetd::kDefaultConfigFilePath,
+                "Path to file containing config information.");
+  DEFINE_string(state_path, privetd::kDefaultStateFilePath,
+                "Path to file containing state information.");
+  DEFINE_string(device_whitelist, "",
+                "Comma separated list of network interfaces to monitor for "
+                "connectivity (an empty list enables all interfaces).");
+
+  chromeos::FlagHelper::Init(argc, argv, "Privet protocol handler daemon");
+
+  int flags = chromeos::kLogToSyslog;
+  if (FLAGS_log_to_stderr)
+    flags |= chromeos::kLogToStderr;
+  chromeos::InitLog(flags | chromeos::kLogHeader);
+
+  if (FLAGS_config_path.empty())
+    FLAGS_config_path = privetd::kDefaultConfigFilePath;
+
+  if (FLAGS_state_path.empty())
+    FLAGS_state_path = privetd::kDefaultStateFilePath;
+
+  auto device_whitelist =
+      chromeos::string_utils::Split(FLAGS_device_whitelist, ",", true, true);
+
+  privetd::Daemon daemon(
+      FLAGS_disable_security, FLAGS_enable_ping,
+      std::set<std::string>(device_whitelist.begin(), device_whitelist.end()),
+      base::FilePath(FLAGS_config_path), base::FilePath(FLAGS_state_path));
+  return daemon.Run();
+}
diff --git a/buffet/privet/mock_delegates.h b/buffet/privet/mock_delegates.h
new file mode 100644
index 0000000..ff8c11b
--- /dev/null
+++ b/buffet/privet/mock_delegates.h
@@ -0,0 +1,229 @@
+// 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_PRIVET_MOCK_DELEGATES_H_
+#define BUFFET_PRIVET_MOCK_DELEGATES_H_
+
+#include <set>
+#include <string>
+#include <utility>
+
+#include <base/values.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "buffet/privet/cloud_delegate.h"
+#include "buffet/privet/device_delegate.h"
+#include "buffet/privet/identity_delegate.h"
+#include "buffet/privet/security_delegate.h"
+#include "buffet/privet/wifi_delegate.h"
+
+using testing::_;
+using testing::Return;
+using testing::ReturnRef;
+using testing::SetArgPointee;
+
+namespace privetd {
+
+ACTION_TEMPLATE(RunCallback,
+                HAS_1_TEMPLATE_PARAMS(int, k),
+                AND_0_VALUE_PARAMS()) {
+  return std::get<k>(args).Run();
+}
+
+ACTION_TEMPLATE(RunCallback,
+                HAS_1_TEMPLATE_PARAMS(int, k),
+                AND_1_VALUE_PARAMS(p0)) {
+  return std::get<k>(args).Run(p0);
+}
+
+class MockDeviceDelegate : public DeviceDelegate {
+  using IntPair = std::pair<uint16_t, uint16_t>;
+
+ public:
+  MOCK_CONST_METHOD0(GetHttpEnpoint, IntPair());
+  MOCK_CONST_METHOD0(GetHttpsEnpoint, IntPair());
+  MOCK_CONST_METHOD0(GetUptime, base::TimeDelta());
+  MOCK_METHOD1(SetHttpPort, void(uint16_t));
+  MOCK_METHOD1(SetHttpsPort, void(uint16_t));
+
+  MockDeviceDelegate() {
+    EXPECT_CALL(*this, GetHttpEnpoint())
+        .WillRepeatedly(Return(std::make_pair(0, 0)));
+    EXPECT_CALL(*this, GetHttpsEnpoint())
+        .WillRepeatedly(Return(std::make_pair(0, 0)));
+    EXPECT_CALL(*this, GetUptime())
+        .WillRepeatedly(Return(base::TimeDelta::FromHours(1)));
+  }
+};
+
+class MockSecurityDelegate : public SecurityDelegate {
+ public:
+  MOCK_METHOD2(CreateAccessToken,
+               std::string(const UserInfo&, const base::Time&));
+  MOCK_CONST_METHOD2(ParseAccessToken,
+                     UserInfo(const std::string&, base::Time*));
+  MOCK_CONST_METHOD0(GetPairingTypes, std::set<PairingType>());
+  MOCK_CONST_METHOD0(GetCryptoTypes, std::set<CryptoType>());
+  MOCK_CONST_METHOD1(IsValidPairingCode, bool(const std::string&));
+  MOCK_METHOD5(StartPairing,
+               bool(PairingType,
+                    CryptoType,
+                    std::string*,
+                    std::string*,
+                    chromeos::ErrorPtr*));
+  MOCK_METHOD5(ConfirmPairing,
+               bool(const std::string&,
+                    const std::string&,
+                    std::string*,
+                    std::string*,
+                    chromeos::ErrorPtr*));
+  MOCK_METHOD2(CancelPairing, bool(const std::string&, chromeos::ErrorPtr*));
+
+  MockSecurityDelegate() {
+    EXPECT_CALL(*this, CreateAccessToken(_, _))
+        .WillRepeatedly(Return("GuestAccessToken"));
+
+    EXPECT_CALL(*this, ParseAccessToken(_, _))
+        .WillRepeatedly(DoAll(SetArgPointee<1>(base::Time::Now()),
+                              Return(UserInfo{AuthScope::kViewer, 1234567})));
+
+    EXPECT_CALL(*this, GetPairingTypes())
+        .WillRepeatedly(Return(std::set<PairingType>{
+            PairingType::kPinCode,
+            PairingType::kEmbeddedCode,
+            PairingType::kUltrasound32,
+            PairingType::kAudible32,
+        }));
+
+    EXPECT_CALL(*this, GetCryptoTypes())
+        .WillRepeatedly(Return(std::set<CryptoType>{
+            CryptoType::kSpake_p224, CryptoType::kSpake_p256,
+        }));
+
+    EXPECT_CALL(*this, StartPairing(_, _, _, _, _))
+        .WillRepeatedly(DoAll(SetArgPointee<2>("testSession"),
+                              SetArgPointee<3>("testCommitment"),
+                              Return(true)));
+
+    EXPECT_CALL(*this, ConfirmPairing(_, _, _, _, _))
+        .WillRepeatedly(DoAll(SetArgPointee<2>("testFingerprint"),
+                              SetArgPointee<3>("testSignature"), Return(true)));
+    EXPECT_CALL(*this, CancelPairing(_, _)).WillRepeatedly(Return(true));
+  }
+};
+
+class MockWifiDelegate : public WifiDelegate {
+ public:
+  MOCK_CONST_METHOD0(GetConnectionState, const ConnectionState&());
+  MOCK_CONST_METHOD0(GetSetupState, const SetupState&());
+  MOCK_METHOD3(ConfigureCredentials,
+               bool(const std::string&,
+                    const std::string&,
+                    chromeos::ErrorPtr*));
+  MOCK_CONST_METHOD0(GetCurrentlyConnectedSsid, std::string());
+  MOCK_CONST_METHOD0(GetHostedSsid, std::string());
+  MOCK_CONST_METHOD0(GetTypes, std::set<WifiType>());
+
+  MockWifiDelegate() {
+    EXPECT_CALL(*this, GetConnectionState())
+        .WillRepeatedly(ReturnRef(connection_state_));
+    EXPECT_CALL(*this, GetSetupState()).WillRepeatedly(ReturnRef(setup_state_));
+    EXPECT_CALL(*this, GetCurrentlyConnectedSsid())
+        .WillRepeatedly(Return("TestSsid"));
+    EXPECT_CALL(*this, GetHostedSsid()).WillRepeatedly(Return(""));
+    EXPECT_CALL(*this, GetTypes())
+        .WillRepeatedly(Return(std::set<WifiType>{WifiType::kWifi24}));
+  }
+
+  ConnectionState connection_state_{ConnectionState::kOffline};
+  SetupState setup_state_{SetupState::kNone};
+};
+
+class MockCloudDelegate : public CloudDelegate {
+ public:
+  MOCK_CONST_METHOD2(GetModelId, bool(std::string*, chromeos::ErrorPtr*));
+  MOCK_CONST_METHOD2(GetName, bool(std::string*, chromeos::ErrorPtr*));
+  MOCK_CONST_METHOD0(GetDescription, std::string());
+  MOCK_CONST_METHOD0(GetLocation, std::string());
+  MOCK_METHOD5(UpdateDeviceInfo,
+               void(const std::string&,
+                    const std::string&,
+                    const std::string&,
+                    const base::Closure&,
+                    const ErrorCallback&));
+  MOCK_CONST_METHOD0(GetOemName, std::string());
+  MOCK_CONST_METHOD0(GetModelName, std::string());
+  MOCK_CONST_METHOD0(GetServices, std::set<std::string>());
+  MOCK_CONST_METHOD0(GetAnonymousMaxScope, AuthScope());
+  MOCK_CONST_METHOD0(GetConnectionState, const ConnectionState&());
+  MOCK_CONST_METHOD0(GetSetupState, const SetupState&());
+  MOCK_METHOD3(Setup,
+               bool(const std::string&,
+                    const std::string&,
+                    chromeos::ErrorPtr*));
+  MOCK_CONST_METHOD0(GetCloudId, std::string());
+  MOCK_CONST_METHOD0(GetState, const base::DictionaryValue&());
+  MOCK_CONST_METHOD0(GetCommandDef, const base::DictionaryValue&());
+  MOCK_METHOD4(AddCommand,
+               void(const base::DictionaryValue&,
+                    const UserInfo&,
+                    const SuccessCallback&,
+                    const ErrorCallback&));
+  MOCK_METHOD4(GetCommand,
+               void(const std::string&,
+                    const UserInfo&,
+                    const SuccessCallback&,
+                    const ErrorCallback&));
+  MOCK_METHOD4(CancelCommand,
+               void(const std::string&,
+                    const UserInfo&,
+                    const SuccessCallback&,
+                    const ErrorCallback&));
+  MOCK_METHOD3(ListCommands,
+               void(const UserInfo&,
+                    const SuccessCallback&,
+                    const ErrorCallback&));
+
+  MockCloudDelegate() {
+    EXPECT_CALL(*this, GetModelId(_, _))
+        .WillRepeatedly(DoAll(SetArgPointee<0>("ABMID"), Return(true)));
+    EXPECT_CALL(*this, GetName(_, _))
+        .WillRepeatedly(DoAll(SetArgPointee<0>("TestDevice"), Return(true)));
+    EXPECT_CALL(*this, GetDescription()).WillRepeatedly(Return(""));
+    EXPECT_CALL(*this, GetLocation()).WillRepeatedly(Return(""));
+    EXPECT_CALL(*this, UpdateDeviceInfo(_, _, _, _, _))
+        .WillRepeatedly(RunCallback<3>());
+    EXPECT_CALL(*this, GetOemName()).WillRepeatedly(Return("Chromium"));
+    EXPECT_CALL(*this, GetModelName()).WillRepeatedly(Return("Brillo"));
+    EXPECT_CALL(*this, GetServices())
+        .WillRepeatedly(Return(std::set<std::string>{}));
+    EXPECT_CALL(*this, GetAnonymousMaxScope())
+        .WillRepeatedly(Return(AuthScope::kUser));
+    EXPECT_CALL(*this, GetConnectionState())
+        .WillRepeatedly(ReturnRef(connection_state_));
+    EXPECT_CALL(*this, GetSetupState()).WillRepeatedly(ReturnRef(setup_state_));
+    EXPECT_CALL(*this, GetCloudId()).WillRepeatedly(Return("TestCloudId"));
+    test_dict_.Set("test", new base::DictionaryValue);
+    EXPECT_CALL(*this, GetCommandDef()).WillRepeatedly(ReturnRef(test_dict_));
+    EXPECT_CALL(*this, GetState()).WillRepeatedly(ReturnRef(test_dict_));
+  }
+
+  ConnectionState connection_state_{ConnectionState::kOnline};
+  SetupState setup_state_{SetupState::kNone};
+  base::DictionaryValue test_dict_;
+};
+
+class MockIdentityDelegate : public IdentityDelegate {
+ public:
+  MOCK_CONST_METHOD0(GetId, std::string());
+
+  MockIdentityDelegate() {
+    EXPECT_CALL(*this, GetId()).WillRepeatedly(Return("TestId"));
+  }
+};
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_MOCK_DELEGATES_H_
diff --git a/buffet/privet/openssl_utils.cc b/buffet/privet/openssl_utils.cc
new file mode 100644
index 0000000..17cc16f
--- /dev/null
+++ b/buffet/privet/openssl_utils.cc
@@ -0,0 +1,26 @@
+// 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/privet/openssl_utils.h"
+
+#include <algorithm>
+
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+
+#include <base/logging.h>
+
+namespace privetd {
+
+chromeos::Blob HmacSha256(const chromeos::SecureBlob& key,
+                          const chromeos::Blob& data) {
+  chromeos::Blob mac(kSha256OutputSize);
+  uint32_t len = 0;
+  CHECK(HMAC(EVP_sha256(), key.data(), key.size(), data.data(),
+             data.size(), mac.data(), &len));
+  CHECK_EQ(len, kSha256OutputSize);
+  return mac;
+}
+
+}  // namespace privetd
diff --git a/buffet/privet/openssl_utils.h b/buffet/privet/openssl_utils.h
new file mode 100644
index 0000000..587b60c
--- /dev/null
+++ b/buffet/privet/openssl_utils.h
@@ -0,0 +1,22 @@
+// 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_PRIVET_OPENSSL_UTILS_H_
+#define BUFFET_PRIVET_OPENSSL_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include <chromeos/secure_blob.h>
+
+namespace privetd {
+
+const size_t kSha256OutputSize = 32;
+
+chromeos::Blob HmacSha256(const chromeos::SecureBlob& key,
+                          const chromeos::Blob& data);
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_OPENSSL_UTILS_H_
diff --git a/buffet/privet/peerd_client.cc b/buffet/privet/peerd_client.cc
new file mode 100644
index 0000000..76f88ce
--- /dev/null
+++ b/buffet/privet/peerd_client.cc
@@ -0,0 +1,170 @@
+// 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/privet/peerd_client.h"
+
+#include <map>
+
+#include <base/message_loop/message_loop.h>
+#include <chromeos/errors/error.h>
+#include <chromeos/strings/string_utils.h>
+
+#include "buffet/privet/cloud_delegate.h"
+#include "buffet/privet/device_delegate.h"
+#include "buffet/privet/wifi_bootstrap_manager.h"
+#include "buffet/privet/wifi_ssid_generator.h"
+
+using chromeos::string_utils::Join;
+using org::chromium::peerd::PeerProxy;
+
+namespace privetd {
+
+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;
+
+// The name of the service we'll expose via peerd.
+const char kPrivetServiceId[] = "privet";
+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,
+                         const DeviceDelegate* device,
+                         const CloudDelegate* cloud,
+                         const WifiDelegate* wifi)
+    : peerd_object_manager_proxy_{bus},
+      device_{device},
+      cloud_{cloud},
+      wifi_{wifi} {
+  CHECK(device_);
+  CHECK(cloud_);
+  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::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;
+
+  std::string name;
+  std::string model_id;
+  if (!cloud_->GetName(&name, nullptr) ||
+      !cloud_->GetModelId(&model_id, nullptr)) {
+    return;
+  }
+  DCHECK_EQ(model_id.size(), 5U);
+
+  VLOG(1) << "Starting peerd advertising.";
+  const uint16_t port = device_->GetHttpEnpoint().first;
+  std::map<std::string, chromeos::Any> mdns_options{
+      {"port", chromeos::Any{port}},
+  };
+  DCHECK_NE(port, 0);
+
+  std::string services;
+  if (!cloud_->GetServices().empty())
+    services += "_";
+  services += Join(",_", cloud_->GetServices());
+
+  std::map<std::string, std::string> txt_record{
+      {"txtvers", "3"},
+      {"ty", name},
+      {"services", services},
+      {"id", GetId()},
+      {"mmid", model_id},
+      {"flags", WifiSsidGenerator{cloud_, wifi_}.GenerateFlags()},
+  };
+
+  if (!cloud_->GetCloudId().empty())
+    txt_record.emplace("gcd_id", cloud_->GetCloudId());
+
+  if (!cloud_->GetDescription().empty())
+    txt_record.emplace("note", cloud_->GetDescription());
+
+  peerd_manager_proxy_->ExposeServiceAsync(
+      kPrivetServiceId, txt_record, {{"mdns", mdns_options}}, base::Closure(),
+      base::Bind(&OnError, "ExposeService"));
+}
+
+void PeerdClient::RemoveService() {
+  if (peerd_manager_proxy_ == nullptr)
+    return;
+
+  VLOG(1) << "Stopping peerd advertising.";
+  peerd_manager_proxy_->RemoveExposedServiceAsync(
+      kPrivetServiceId, base::Closure(), base::Bind(&OnError, "RemoveService"));
+}
+
+void PeerdClient::UpdateImpl() {
+  if (device_->GetHttpEnpoint().first == 0)
+    return RemoveService();
+  ExposeService();
+}
+
+}  // namespace privetd
diff --git a/buffet/privet/peerd_client.h b/buffet/privet/peerd_client.h
new file mode 100644
index 0000000..ae3d306
--- /dev/null
+++ b/buffet/privet/peerd_client.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_PRIVET_PEERD_CLIENT_H_
+#define BUFFET_PRIVET_PEERD_CLIENT_H_
+
+#include <memory>
+#include <string>
+
+#include <base/callback.h>
+#include <base/memory/ref_counted.h>
+
+#include "buffet/privet/identity_delegate.h"
+#include "peerd/dbus-proxies.h"
+
+namespace dbus {
+class Bus;
+}  // namespace dbus
+
+namespace privetd {
+
+class CloudDelegate;
+class DeviceDelegate;
+class WifiDelegate;
+
+// Publishes prived service on mDns using peerd.
+class PeerdClient : public IdentityDelegate {
+ public:
+  PeerdClient(const scoped_refptr<dbus::Bus>& bus,
+              const DeviceDelegate* device,
+              const CloudDelegate* cloud,
+              const WifiDelegate* wifi);
+  ~PeerdClient();
+
+  // Get the unique identifier for this device.  Note that if peerd has
+  // never been seen, this may be the empty string.
+  std::string GetId() const override;
+
+  // Updates published information.  Removes service if HTTP is not alive.
+  void Update();
+
+ 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);
+
+  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};
+
+  const DeviceDelegate* device_{nullptr};
+  const CloudDelegate* cloud_{nullptr};
+  const WifiDelegate* wifi_{nullptr};
+
+  // Cached value of the device ID that we got from peerd.
+  std::string device_id_;
+
+  base::WeakPtrFactory<PeerdClient> restart_weak_ptr_factory_{this};
+  base::WeakPtrFactory<PeerdClient> weak_ptr_factory_{this};
+};
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_PEERD_CLIENT_H_
diff --git a/buffet/privet/privet_handler.cc b/buffet/privet/privet_handler.cc
new file mode 100644
index 0000000..5c3d0c2
--- /dev/null
+++ b/buffet/privet/privet_handler.cc
@@ -0,0 +1,948 @@
+// 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/privet/privet_handler.h"
+
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/stl_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+#include <base/values.h>
+#include <chromeos/http/http_request.h>
+#include <chromeos/strings/string_utils.h>
+
+#include "buffet/privet/cloud_delegate.h"
+#include "buffet/privet/constants.h"
+#include "buffet/privet/device_delegate.h"
+#include "buffet/privet/identity_delegate.h"
+#include "buffet/privet/security_delegate.h"
+#include "buffet/privet/wifi_delegate.h"
+
+namespace privetd {
+
+namespace {
+
+const char kInfoVersionKey[] = "version";
+const char kInfoVersionValue[] = "3.0";
+
+const char kNameKey[] = "name";
+const char kDescrptionKey[] = "description";
+const char kLocationKey[] = "location";
+
+const char kGcdKey[] = "gcd";
+const char kWifiKey[] = "wifi";
+const char kStatusKey[] = "status";
+const char kErrorKey[] = "error";
+const char kCryptoKey[] = "crypto";
+const char kStatusErrorValue[] = "error";
+
+const char kInfoIdKey[] = "id";
+const char kInfoServicesKey[] = "services";
+
+const char kInfoEndpointsKey[] = "endpoints";
+const char kInfoEndpointsHttpPortKey[] = "httpPort";
+const char kInfoEndpointsHttpUpdatePortKey[] = "httpUpdatesPort";
+const char kInfoEndpointsHttpsPortKey[] = "httpsPort";
+const char kInfoEndpointsHttpsUpdatePortKey[] = "httpsUpdatesPort";
+
+const char kInfoModelIdKey[] = "modelManifestId";
+const char kInfoModelManifestKey[] = "basicModelManifest";
+const char kInfoManifestUiDeviceKind[] = "uiDeviceKind";
+const char kInfoManifestOemName[] = "oemName";
+const char kInfoManifestModelName[] = "modelName";
+
+const char kInfoAuthenticationKey[] = "authentication";
+
+const char kInfoAuthAnonymousMaxScopeKey[] = "anonymousMaxScope";
+
+const char kInfoWifiCapabilitiesKey[] = "capabilities";
+const char kInfoWifiSsidKey[] = "ssid";
+const char kInfoWifiHostedSsidKey[] = "hostedSsid";
+
+const char kInfoUptimeKey[] = "uptime";
+
+const char kPairingKey[] = "pairing";
+const char kPairingSessionIdKey[] = "sessionId";
+const char kPairingDeviceCommitmentKey[] = "deviceCommitment";
+const char kPairingClientCommitmentKey[] = "clientCommitment";
+const char kPairingFingerprintKey[] = "certFingerprint";
+const char kPairingSignatureKey[] = "certSignature";
+
+const char kAuthTypeAnonymousValue[] = "anonymous";
+const char kAuthTypePairingValue[] = "pairing";
+
+const char kAuthModeKey[] = "mode";
+const char kAuthCodeKey[] = "authCode";
+const char kAuthRequestedScopeKey[] = "requestedScope";
+const char kAuthScopeAutoValue[] = "auto";
+
+const char kAuthAccessTokenKey[] = "accessToken";
+const char kAuthTokenTypeKey[] = "tokenType";
+const char kAuthExpiresInKey[] = "expiresIn";
+const char kAuthScopeKey[] = "scope";
+
+const char kAuthorizationHeaderPrefix[] = "Privet";
+
+const char kErrorCodeKey[] = "code";
+const char kErrorMessageKey[] = "message";
+const char kErrorDebugInfoKey[] = "debugInfo";
+
+const char kSetupStartSsidKey[] = "ssid";
+const char kSetupStartPassKey[] = "passphrase";
+const char kSetupStartTicketIdKey[] = "ticketId";
+const char kSetupStartUserKey[] = "user";
+
+const char kFingerprintKey[] = "fingerprint";
+const char kStateKey[] = "state";
+const char kCommandsKey[] = "commands";
+const char kCommandsIdKey[] = "id";
+
+const char kInvalidParamValueFormat[] = "Invalid parameter: '%s'='%s'";
+
+const int kAccessTokenExpirationSeconds = 3600;
+
+// Threshold to reduce probability of expiration because of clock difference
+// between device and client. Value is just a guess.
+const int kAccessTokenExpirationThresholdSeconds = 300;
+
+template <class Container>
+std::unique_ptr<base::ListValue> ToValue(const Container& list) {
+  std::unique_ptr<base::ListValue> value_list(new base::ListValue());
+  for (const std::string& val : list)
+    value_list->AppendString(val);
+  return value_list;
+}
+
+template <typename T>
+class EnumToStringMap final {
+ public:
+  static std::string FindNameById(T id) {
+    for (const Map& m : kMap) {
+      if (m.id == id) {
+        CHECK(m.name);
+        return m.name;
+      }
+    }
+    NOTREACHED() << static_cast<int>(id) << " is not part of "
+                 << typeid(T).name();
+    return std::string();
+  }
+
+  static bool FindIdByName(const std::string& name, T* id) {
+    for (const Map& m : kMap) {
+      if (m.name && m.name == name) {
+        *id = m.id;
+        return true;
+      }
+    }
+    return false;
+  }
+
+ private:
+  struct Map {
+    const T id;
+    const char* const name;
+  };
+  static const Map kMap[];
+};
+
+template <>
+const EnumToStringMap<ConnectionState::Status>::Map
+    EnumToStringMap<ConnectionState::Status>::kMap[] = {
+        {ConnectionState::kDisabled, "disabled"},
+        {ConnectionState::kUnconfigured, "unconfigured"},
+        {ConnectionState::kConnecting, "connecting"},
+        {ConnectionState::kOnline, "online"},
+        {ConnectionState::kOffline, "offline"},
+};
+
+template <>
+const EnumToStringMap<SetupState::Status>::Map
+    EnumToStringMap<SetupState::Status>::kMap[] = {
+        {SetupState::kNone, nullptr},
+        {SetupState::kInProgress, "inProgress"},
+        {SetupState::kSuccess, "success"},
+};
+
+template <>
+const EnumToStringMap<WifiType>::Map EnumToStringMap<WifiType>::kMap[] = {
+    {WifiType::kWifi24, "2.4GHz"},
+    {WifiType::kWifi50, "5.0GHz"},
+};
+
+template <>
+const EnumToStringMap<PairingType>::Map EnumToStringMap<PairingType>::kMap[] = {
+    {PairingType::kPinCode, "pinCode"},
+    {PairingType::kEmbeddedCode, "embeddedCode"},
+    {PairingType::kUltrasound32, "ultrasound32"},
+    {PairingType::kAudible32, "audible32"},
+};
+
+template <>
+const EnumToStringMap<CryptoType>::Map EnumToStringMap<CryptoType>::kMap[] = {
+    {CryptoType::kNone, "none"},
+    {CryptoType::kSpake_p224, "p224_spake2"},
+    {CryptoType::kSpake_p256, "p256_spake2"},
+};
+
+template <>
+const EnumToStringMap<AuthScope>::Map EnumToStringMap<AuthScope>::kMap[] = {
+    {AuthScope::kNone, "none"},
+    {AuthScope::kViewer, "viewer"},
+    {AuthScope::kUser, "user"},
+    {AuthScope::kOwner, "owner"},
+};
+
+struct {
+  const char* const reason;
+  int code;
+} kReasonToCode[] = {
+    {errors::kInvalidClientCommitment, chromeos::http::status_code::Forbidden},
+    {errors::kInvalidFormat, chromeos::http::status_code::BadRequest},
+    {errors::kMissingAuthorization, chromeos::http::status_code::Denied},
+    {errors::kInvalidAuthorization, chromeos::http::status_code::Denied},
+    {errors::kInvalidAuthorizationScope,
+     chromeos::http::status_code::Forbidden},
+    {errors::kAuthorizationExpired, chromeos::http::status_code::Forbidden},
+    {errors::kCommitmentMismatch, chromeos::http::status_code::Forbidden},
+    {errors::kUnknownSession, chromeos::http::status_code::NotFound},
+    {errors::kInvalidAuthCode, chromeos::http::status_code::Forbidden},
+    {errors::kInvalidAuthMode, chromeos::http::status_code::BadRequest},
+    {errors::kInvalidRequestedScope, chromeos::http::status_code::BadRequest},
+    {errors::kAccessDenied, chromeos::http::status_code::Forbidden},
+    {errors::kInvalidParams, chromeos::http::status_code::BadRequest},
+    {errors::kSetupUnavailable, chromeos::http::status_code::BadRequest},
+    {errors::kDeviceBusy, chromeos::http::status_code::ServiceUnavailable},
+    {errors::kInvalidState, chromeos::http::status_code::InternalServerError},
+    {errors::kNotFound, chromeos::http::status_code::NotFound},
+    {errors::kNotImplemented, chromeos::http::status_code::NotSupported},
+};
+
+template <typename T>
+std::string EnumToString(T id) {
+  return EnumToStringMap<T>::FindNameById(id);
+}
+
+template <typename T>
+bool StringToEnum(const std::string& name, T* id) {
+  return EnumToStringMap<T>::FindIdByName(name, id);
+}
+
+AuthScope AuthScopeFromString(const std::string& scope, AuthScope auto_scope) {
+  if (scope == kAuthScopeAutoValue)
+    return auto_scope;
+  AuthScope scope_id = AuthScope::kNone;
+  StringToEnum(scope, &scope_id);
+  return scope_id;
+}
+
+std::string GetAuthTokenFromAuthHeader(const std::string& auth_header) {
+  std::string name;
+  std::string value;
+  chromeos::string_utils::SplitAtFirst(auth_header, " ", &name, &value);
+  return value;
+}
+
+std::unique_ptr<base::DictionaryValue> ErrorInfoToJson(
+    const chromeos::Error& error) {
+  std::unique_ptr<base::DictionaryValue> output{new base::DictionaryValue};
+  output->SetString(kErrorMessageKey, error.GetMessage());
+  output->SetString(kErrorCodeKey, error.GetCode());
+  return output;
+}
+
+// Creates JSON similar to GCD server error format.
+std::unique_ptr<base::DictionaryValue> ErrorToJson(
+    const chromeos::Error& error) {
+  std::unique_ptr<base::DictionaryValue> output{ErrorInfoToJson(error)};
+
+  // Optional debug information.
+  std::unique_ptr<base::ListValue> errors{new base::ListValue};
+  for (const chromeos::Error* it = &error; it; it = it->GetInnerError()) {
+    std::unique_ptr<base::DictionaryValue> inner{ErrorInfoToJson(*it)};
+    tracked_objects::Location location{it->GetLocation().function_name.c_str(),
+                                       it->GetLocation().file_name.c_str(),
+                                       it->GetLocation().line_number,
+                                       nullptr};
+    inner->SetString(kErrorDebugInfoKey, location.ToString());
+    errors->Append(inner.release());
+  }
+  output->Set(kErrorDebugInfoKey, errors.release());
+  return output;
+}
+
+template <class T>
+void SetState(const T& state, base::DictionaryValue* parent) {
+  if (!state.error()) {
+    parent->SetString(kStatusKey, EnumToString(state.status()));
+    return;
+  }
+  parent->SetString(kStatusKey, kStatusErrorValue);
+  parent->Set(kErrorKey, ErrorToJson(*state.error()).release());
+}
+
+void ReturnError(const chromeos::Error& error,
+                 const PrivetHandler::RequestCallback& callback) {
+  int code = chromeos::http::status_code::InternalServerError;
+  for (const auto& it : kReasonToCode) {
+    if (error.HasError(errors::kDomain, it.reason)) {
+      code = it.code;
+      break;
+    }
+  }
+  std::unique_ptr<base::DictionaryValue> output{new base::DictionaryValue};
+  output->Set(kErrorKey, ErrorToJson(error).release());
+  callback.Run(code, *output);
+}
+
+void OnCommandRequestSucceeded(const PrivetHandler::RequestCallback& callback,
+                               const base::DictionaryValue& output) {
+  callback.Run(chromeos::http::status_code::Ok, output);
+}
+
+void OnCommandRequestFailed(const PrivetHandler::RequestCallback& callback,
+                            chromeos::Error* error) {
+  if (error->HasError("gcd", "unknown_command")) {
+    chromeos::ErrorPtr new_error = error->Clone();
+    chromeos::Error::AddTo(&new_error, FROM_HERE, errors::kDomain,
+                           errors::kNotFound, "Unknown command ID");
+    return ReturnError(*new_error, callback);
+  }
+  if (error->HasError("gcd", "access_denied")) {
+    chromeos::ErrorPtr new_error = error->Clone();
+    chromeos::Error::AddTo(&new_error, FROM_HERE, errors::kDomain,
+                           errors::kAccessDenied, error->GetMessage());
+    return ReturnError(*new_error, callback);
+  }
+  return ReturnError(*error, callback);
+}
+
+std::string GetDeviceKind(const std::string& manifest_id) {
+  CHECK_EQ(5u, manifest_id.size());
+  std::string kind = manifest_id.substr(0, 2);
+  if (kind == "AC")
+    return "accessPoint";
+  if (kind == "AK")
+    return "aggregator";
+  if (kind == "AM")
+    return "camera";
+  if (kind == "AB")
+    return "developmentBoard";
+  if (kind == "AE")
+    return "printer";
+  if (kind == "AF")
+    return "scanner";
+  if (kind == "AD")
+    return "speaker";
+  if (kind == "AL")
+    return "storage";
+  if (kind == "AJ")
+    return "toy";
+  if (kind == "AA")
+    return "vendor";
+  if (kind == "AN")
+    return "video";
+  LOG(FATAL) << "Invalid model id: " << manifest_id;
+  return std::string();
+}
+
+std::unique_ptr<base::DictionaryValue> CreateManifestSection(
+    const std::string& model_id,
+    const CloudDelegate& cloud) {
+  std::unique_ptr<base::DictionaryValue> manifest(new base::DictionaryValue());
+  manifest->SetString(kInfoManifestUiDeviceKind, GetDeviceKind(model_id));
+  manifest->SetString(kInfoManifestOemName, cloud.GetOemName());
+  manifest->SetString(kInfoManifestModelName, cloud.GetModelName());
+  return manifest;
+}
+
+std::unique_ptr<base::DictionaryValue> CreateEndpointsSection(
+    const DeviceDelegate& device) {
+  std::unique_ptr<base::DictionaryValue> endpoints(new base::DictionaryValue());
+  auto http_endpoint = device.GetHttpEnpoint();
+  endpoints->SetInteger(kInfoEndpointsHttpPortKey, http_endpoint.first);
+  endpoints->SetInteger(kInfoEndpointsHttpUpdatePortKey, http_endpoint.second);
+
+  auto https_endpoint = device.GetHttpsEnpoint();
+  endpoints->SetInteger(kInfoEndpointsHttpsPortKey, https_endpoint.first);
+  endpoints->SetInteger(kInfoEndpointsHttpsUpdatePortKey,
+                        https_endpoint.second);
+
+  return endpoints;
+}
+
+std::unique_ptr<base::DictionaryValue> CreateInfoAuthSection(
+    const SecurityDelegate& security,
+    AuthScope anonymous_max_scope) {
+  std::unique_ptr<base::DictionaryValue> auth(new base::DictionaryValue());
+
+  auth->SetString(kInfoAuthAnonymousMaxScopeKey,
+                  EnumToString(anonymous_max_scope));
+
+  std::unique_ptr<base::ListValue> pairing_types(new base::ListValue());
+  for (PairingType type : security.GetPairingTypes())
+    pairing_types->AppendString(EnumToString(type));
+  auth->Set(kPairingKey, pairing_types.release());
+
+  std::unique_ptr<base::ListValue> auth_types(new base::ListValue());
+  auth_types->AppendString(kAuthTypeAnonymousValue);
+  auth_types->AppendString(kAuthTypePairingValue);
+
+  // TODO(vitalybuka): Implement cloud auth.
+  // if (cloud.GetConnectionState().IsStatusEqual(ConnectionState::kOnline)) {
+  //   auth_types->AppendString(kAuthTypeCloudValue);
+  // }
+  auth->Set(kAuthModeKey, auth_types.release());
+
+  std::unique_ptr<base::ListValue> crypto_types(new base::ListValue());
+  for (CryptoType type : security.GetCryptoTypes())
+    crypto_types->AppendString(EnumToString(type));
+  auth->Set(kCryptoKey, crypto_types.release());
+
+  return auth;
+}
+
+std::unique_ptr<base::DictionaryValue> CreateWifiSection(
+    const WifiDelegate& wifi) {
+  std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue());
+
+  std::unique_ptr<base::ListValue> capabilities(new base::ListValue());
+  for (WifiType type : wifi.GetTypes())
+    capabilities->AppendString(EnumToString(type));
+  result->Set(kInfoWifiCapabilitiesKey, capabilities.release());
+
+  result->SetString(kInfoWifiSsidKey, wifi.GetCurrentlyConnectedSsid());
+
+  std::string hosted_ssid = wifi.GetHostedSsid();
+  const ConnectionState& state = wifi.GetConnectionState();
+  if (!hosted_ssid.empty()) {
+    DCHECK(!state.IsStatusEqual(ConnectionState::kDisabled));
+    DCHECK(!state.IsStatusEqual(ConnectionState::kOnline));
+    result->SetString(kInfoWifiHostedSsidKey, hosted_ssid);
+  }
+  SetState(state, result.get());
+  return result;
+}
+
+std::unique_ptr<base::DictionaryValue> CreateGcdSection(
+    const CloudDelegate& cloud) {
+  std::unique_ptr<base::DictionaryValue> gcd(new base::DictionaryValue());
+  gcd->SetString(kInfoIdKey, cloud.GetCloudId());
+  SetState(cloud.GetConnectionState(), gcd.get());
+  return gcd;
+}
+
+AuthScope GetAnonymousMaxScope(const CloudDelegate& cloud,
+                               const WifiDelegate* wifi) {
+  if (wifi && !wifi->GetHostedSsid().empty())
+    return AuthScope::kNone;
+  return cloud.GetAnonymousMaxScope();
+}
+
+}  // namespace
+
+PrivetHandler::PrivetHandler(CloudDelegate* cloud,
+                             DeviceDelegate* device,
+                             SecurityDelegate* security,
+                             WifiDelegate* wifi,
+                             IdentityDelegate* identity)
+    : cloud_(cloud),
+      device_(device),
+      security_(security),
+      wifi_(wifi),
+      identity_(identity) {
+  CHECK(cloud_);
+  CHECK(device_);
+  CHECK(security_);
+  cloud_observer_.Add(cloud_);
+
+  AddHandler("/privet/info", &PrivetHandler::HandleInfo, AuthScope::kNone);
+  AddHandler("/privet/v3/pairing/start", &PrivetHandler::HandlePairingStart,
+             AuthScope::kNone);
+  AddHandler("/privet/v3/pairing/confirm", &PrivetHandler::HandlePairingConfirm,
+             AuthScope::kNone);
+  AddHandler("/privet/v3/pairing/cancel", &PrivetHandler::HandlePairingCancel,
+             AuthScope::kNone);
+  AddHandler("/privet/v3/auth", &PrivetHandler::HandleAuth, AuthScope::kNone);
+  AddHandler("/privet/v3/setup/start", &PrivetHandler::HandleSetupStart,
+             AuthScope::kOwner);
+  AddHandler("/privet/v3/setup/status", &PrivetHandler::HandleSetupStatus,
+             AuthScope::kOwner);
+  AddHandler("/privet/v3/state", &PrivetHandler::HandleState,
+             AuthScope::kViewer);
+  AddHandler("/privet/v3/commandDefs", &PrivetHandler::HandleCommandDefs,
+             AuthScope::kViewer);
+  AddHandler("/privet/v3/commands/execute",
+             &PrivetHandler::HandleCommandsExecute, AuthScope::kViewer);
+  AddHandler("/privet/v3/commands/status", &PrivetHandler::HandleCommandsStatus,
+             AuthScope::kViewer);
+  AddHandler("/privet/v3/commands/cancel", &PrivetHandler::HandleCommandsCancel,
+             AuthScope::kViewer);
+  AddHandler("/privet/v3/commands/list", &PrivetHandler::HandleCommandsList,
+             AuthScope::kViewer);
+}
+
+PrivetHandler::~PrivetHandler() {
+}
+
+void PrivetHandler::OnCommandDefsChanged() {
+  ++command_defs_fingerprint_;
+}
+
+void PrivetHandler::OnStateChanged() {
+  ++state_fingerprint_;
+}
+
+void PrivetHandler::HandleRequest(const std::string& api,
+                                  const std::string& auth_header,
+                                  const base::DictionaryValue* input,
+                                  const RequestCallback& callback) {
+  chromeos::ErrorPtr error;
+  if (!input) {
+    chromeos::Error::AddTo(&error, FROM_HERE, errors::kDomain,
+                           errors::kInvalidFormat, "Malformed JSON");
+    return ReturnError(*error, callback);
+  }
+  auto handler = handlers_.find(api);
+  if (handler == handlers_.end()) {
+    chromeos::Error::AddTo(&error, FROM_HERE, errors::kDomain,
+                           errors::kNotFound, "Path not found");
+    return ReturnError(*error, callback);
+  }
+  if (auth_header.empty()) {
+    chromeos::Error::AddTo(&error, FROM_HERE, errors::kDomain,
+                           errors::kMissingAuthorization,
+                           "Authorization header must not be empty");
+    return ReturnError(*error, callback);
+  }
+  std::string token = GetAuthTokenFromAuthHeader(auth_header);
+  if (token.empty()) {
+    chromeos::Error::AddToPrintf(
+        &error, FROM_HERE, errors::kDomain, errors::kInvalidAuthorization,
+        "Invalid authorization header: %s", auth_header.c_str());
+    return ReturnError(*error, callback);
+  }
+  UserInfo user_info;
+  if (token != kAuthTypeAnonymousValue) {
+    base::Time time;
+    user_info = security_->ParseAccessToken(token, &time);
+    if (user_info.scope() == AuthScope::kNone) {
+      chromeos::Error::AddToPrintf(&error, FROM_HERE, errors::kDomain,
+                                   errors::kInvalidAuthorization,
+                                   "Invalid access token: %s", token.c_str());
+      return ReturnError(*error, callback);
+    }
+    time += base::TimeDelta::FromSeconds(kAccessTokenExpirationSeconds);
+    time +=
+        base::TimeDelta::FromSeconds(kAccessTokenExpirationThresholdSeconds);
+    if (time < base::Time::Now()) {
+      chromeos::Error::AddToPrintf(&error, FROM_HERE, errors::kDomain,
+                                   errors::kAuthorizationExpired,
+                                   "Token expired: %s", token.c_str());
+      return ReturnError(*error, callback);
+    }
+  }
+
+  if (handler->second.first > user_info.scope()) {
+    chromeos::Error::AddToPrintf(
+        &error, FROM_HERE, errors::kDomain, errors::kInvalidAuthorizationScope,
+        "Scope '%s' does not allow '%s'",
+        EnumToString(user_info.scope()).c_str(), api.c_str());
+    return ReturnError(*error, callback);
+  }
+  (this->*handler->second.second)(*input, user_info, callback);
+}
+
+void PrivetHandler::AddHandler(const std::string& path,
+                               ApiHandler handler,
+                               AuthScope scope) {
+  CHECK(handlers_.emplace(path, std::make_pair(scope, handler)).second);
+}
+
+void PrivetHandler::HandleInfo(const base::DictionaryValue&,
+                               const UserInfo& user_info,
+                               const RequestCallback& callback) {
+  base::DictionaryValue output;
+
+  chromeos::ErrorPtr error;
+
+  std::string name;
+  std::string model_id;
+  if (!cloud_->GetName(&name, &error) ||
+      !cloud_->GetModelId(&model_id, &error)) {
+    return ReturnError(*error, callback);
+  }
+
+  output.SetString(kInfoVersionKey, kInfoVersionValue);
+  output.SetString(kInfoIdKey, identity_->GetId());
+  output.SetString(kNameKey, name);
+
+  std::string description{cloud_->GetDescription()};
+  if (!description.empty())
+    output.SetString(kDescrptionKey, description);
+
+  std::string location{cloud_->GetLocation()};
+  if (!location.empty())
+    output.SetString(kLocationKey, location);
+
+  output.SetString(kInfoModelIdKey, model_id);
+  output.Set(kInfoModelManifestKey,
+             CreateManifestSection(model_id, *cloud_).release());
+  output.Set(kInfoServicesKey, ToValue(cloud_->GetServices()).release());
+
+  output.Set(kInfoAuthenticationKey,
+             CreateInfoAuthSection(
+                 *security_, GetAnonymousMaxScope(*cloud_, wifi_)).release());
+
+  output.Set(kInfoEndpointsKey, CreateEndpointsSection(*device_).release());
+
+  if (wifi_)
+    output.Set(kWifiKey, CreateWifiSection(*wifi_).release());
+
+  output.Set(kGcdKey, CreateGcdSection(*cloud_).release());
+
+  output.SetInteger(kInfoUptimeKey, device_->GetUptime().InSeconds());
+
+  callback.Run(chromeos::http::status_code::Ok, output);
+}
+
+void PrivetHandler::HandlePairingStart(const base::DictionaryValue& input,
+                                       const UserInfo& user_info,
+                                       const RequestCallback& callback) {
+  chromeos::ErrorPtr error;
+
+  std::string pairing_str;
+  input.GetString(kPairingKey, &pairing_str);
+
+  std::string crypto_str;
+  input.GetString(kCryptoKey, &crypto_str);
+
+  PairingType pairing;
+  std::set<PairingType> modes = security_->GetPairingTypes();
+  if (!StringToEnum(pairing_str, &pairing) || !ContainsKey(modes, pairing)) {
+    chromeos::Error::AddToPrintf(
+        &error, FROM_HERE, errors::kDomain, errors::kInvalidParams,
+        kInvalidParamValueFormat, kPairingKey, pairing_str.c_str());
+    return ReturnError(*error, callback);
+  }
+
+  CryptoType crypto;
+  std::set<CryptoType> cryptos = security_->GetCryptoTypes();
+  if (!StringToEnum(crypto_str, &crypto) || !ContainsKey(cryptos, crypto)) {
+    chromeos::Error::AddToPrintf(
+        &error, FROM_HERE, errors::kDomain, errors::kInvalidParams,
+        kInvalidParamValueFormat, kCryptoKey, crypto_str.c_str());
+    return ReturnError(*error, callback);
+  }
+
+  std::string id;
+  std::string commitment;
+  if (!security_->StartPairing(pairing, crypto, &id, &commitment, &error))
+    return ReturnError(*error, callback);
+
+  base::DictionaryValue output;
+  output.SetString(kPairingSessionIdKey, id);
+  output.SetString(kPairingDeviceCommitmentKey, commitment);
+  callback.Run(chromeos::http::status_code::Ok, output);
+}
+
+void PrivetHandler::HandlePairingConfirm(const base::DictionaryValue& input,
+                                         const UserInfo& user_info,
+                                         const RequestCallback& callback) {
+  std::string id;
+  input.GetString(kPairingSessionIdKey, &id);
+
+  std::string commitment;
+  input.GetString(kPairingClientCommitmentKey, &commitment);
+
+  std::string fingerprint;
+  std::string signature;
+  chromeos::ErrorPtr error;
+  if (!security_->ConfirmPairing(id, commitment, &fingerprint, &signature,
+                                 &error)) {
+    return ReturnError(*error, callback);
+  }
+
+  base::DictionaryValue output;
+  output.SetString(kPairingFingerprintKey, fingerprint);
+  output.SetString(kPairingSignatureKey, signature);
+  callback.Run(chromeos::http::status_code::Ok, output);
+}
+
+void PrivetHandler::HandlePairingCancel(const base::DictionaryValue& input,
+                                        const UserInfo& user_info,
+                                        const RequestCallback& callback) {
+  std::string id;
+  input.GetString(kPairingSessionIdKey, &id);
+
+  chromeos::ErrorPtr error;
+  if (!security_->CancelPairing(id, &error))
+    return ReturnError(*error, callback);
+
+  base::DictionaryValue output;
+  callback.Run(chromeos::http::status_code::Ok, output);
+}
+
+void PrivetHandler::HandleAuth(const base::DictionaryValue& input,
+                               const UserInfo& user_info,
+                               const RequestCallback& callback) {
+  chromeos::ErrorPtr error;
+
+  std::string auth_code_type;
+  input.GetString(kAuthModeKey, &auth_code_type);
+
+  std::string auth_code;
+  input.GetString(kAuthCodeKey, &auth_code);
+
+  AuthScope max_auth_scope = AuthScope::kNone;
+  if (auth_code_type == kAuthTypeAnonymousValue) {
+    max_auth_scope = GetAnonymousMaxScope(*cloud_, wifi_);
+  } else if (auth_code_type == kAuthTypePairingValue) {
+    if (!security_->IsValidPairingCode(auth_code)) {
+      chromeos::Error::AddToPrintf(
+          &error, FROM_HERE, errors::kDomain, errors::kInvalidAuthCode,
+          kInvalidParamValueFormat, kAuthCodeKey, auth_code.c_str());
+      return ReturnError(*error, callback);
+    }
+    max_auth_scope = AuthScope::kOwner;
+  } else {
+    chromeos::Error::AddToPrintf(
+        &error, FROM_HERE, errors::kDomain, errors::kInvalidAuthMode,
+        kInvalidParamValueFormat, kAuthModeKey, auth_code_type.c_str());
+    return ReturnError(*error, callback);
+  }
+
+  std::string requested_scope;
+  input.GetString(kAuthRequestedScopeKey, &requested_scope);
+
+  AuthScope requested_auth_scope =
+      AuthScopeFromString(requested_scope, max_auth_scope);
+  if (requested_auth_scope == AuthScope::kNone) {
+    chromeos::Error::AddToPrintf(
+        &error, FROM_HERE, errors::kDomain, errors::kInvalidRequestedScope,
+        kInvalidParamValueFormat, kAuthRequestedScopeKey,
+        requested_scope.c_str());
+    return ReturnError(*error, callback);
+  }
+
+  if (requested_auth_scope > max_auth_scope) {
+    chromeos::Error::AddToPrintf(
+        &error, FROM_HERE, errors::kDomain, errors::kAccessDenied,
+        "Scope '%s' is not allowed for '%s'",
+        EnumToString(requested_auth_scope).c_str(), auth_code.c_str());
+    return ReturnError(*error, callback);
+  }
+
+  base::DictionaryValue output;
+  output.SetString(
+      kAuthAccessTokenKey,
+      security_->CreateAccessToken(
+          UserInfo{requested_auth_scope, ++last_user_id_}, base::Time::Now()));
+  output.SetString(kAuthTokenTypeKey, kAuthorizationHeaderPrefix);
+  output.SetInteger(kAuthExpiresInKey, kAccessTokenExpirationSeconds);
+  output.SetString(kAuthScopeKey, EnumToString(requested_auth_scope));
+  callback.Run(chromeos::http::status_code::Ok, output);
+}
+
+void PrivetHandler::HandleSetupStart(const base::DictionaryValue& input,
+                                     const UserInfo& user_info,
+                                     const RequestCallback& callback) {
+  std::string name;
+  chromeos::ErrorPtr error;
+  if (!cloud_->GetName(&name, &error))
+    return ReturnError(*error, callback);
+  input.GetString(kNameKey, &name);
+
+  std::string description{cloud_->GetDescription()};
+  input.GetString(kDescrptionKey, &description);
+
+  std::string location{cloud_->GetLocation()};
+  input.GetString(kLocationKey, &location);
+
+  std::string ssid;
+  std::string passphrase;
+  std::string ticket;
+  std::string user;
+
+  const base::DictionaryValue* wifi = nullptr;
+  if (input.GetDictionary(kWifiKey, &wifi)) {
+    if (!wifi_ || wifi_->GetTypes().empty()) {
+      chromeos::Error::AddTo(&error, FROM_HERE, errors::kDomain,
+                             errors::kSetupUnavailable,
+                             "WiFi setup unavailible");
+      return ReturnError(*error, callback);
+    }
+    wifi->GetString(kSetupStartSsidKey, &ssid);
+    if (ssid.empty()) {
+      chromeos::Error::AddToPrintf(
+          &error, FROM_HERE, errors::kDomain, errors::kInvalidParams,
+          kInvalidParamValueFormat, kSetupStartSsidKey, "");
+      return ReturnError(*error, callback);
+    }
+    wifi->GetString(kSetupStartPassKey, &passphrase);
+  }
+
+  const base::DictionaryValue* registration = nullptr;
+  if (input.GetDictionary(kGcdKey, &registration)) {
+    registration->GetString(kSetupStartTicketIdKey, &ticket);
+    if (ticket.empty()) {
+      chromeos::Error::AddToPrintf(
+          &error, FROM_HERE, errors::kDomain, errors::kInvalidParams,
+          kInvalidParamValueFormat, kSetupStartTicketIdKey, "");
+      return ReturnError(*error, callback);
+    }
+    registration->GetString(kSetupStartUserKey, &user);
+  }
+
+  cloud_->UpdateDeviceInfo(name, description, location,
+                           base::Bind(&PrivetHandler::OnUpdateDeviceInfoDone,
+                                      weak_ptr_factory_.GetWeakPtr(), ssid,
+                                      passphrase, ticket, user, callback),
+                           base::Bind(&OnCommandRequestFailed, callback));
+}
+
+void PrivetHandler::OnUpdateDeviceInfoDone(
+    const std::string& ssid,
+    const std::string& passphrase,
+    const std::string& ticket,
+    const std::string& user,
+    const RequestCallback& callback) const {
+  chromeos::ErrorPtr error;
+
+  if (!ssid.empty() && !wifi_->ConfigureCredentials(ssid, passphrase, &error))
+    return ReturnError(*error, callback);
+
+  if (!ticket.empty() && !cloud_->Setup(ticket, user, &error))
+    return ReturnError(*error, callback);
+
+  ReplyWithSetupStatus(callback);
+}
+
+void PrivetHandler::HandleSetupStatus(const base::DictionaryValue&,
+                                      const UserInfo& user_info,
+                                      const RequestCallback& callback) {
+  ReplyWithSetupStatus(callback);
+}
+
+void PrivetHandler::ReplyWithSetupStatus(
+    const RequestCallback& callback) const {
+  base::DictionaryValue output;
+
+  const SetupState& state = cloud_->GetSetupState();
+  if (!state.IsStatusEqual(SetupState::kNone)) {
+    base::DictionaryValue* gcd = new base::DictionaryValue;
+    output.Set(kGcdKey, gcd);
+    SetState(state, gcd);
+    if (state.IsStatusEqual(SetupState::kSuccess))
+      gcd->SetString(kInfoIdKey, cloud_->GetCloudId());
+  }
+
+  if (wifi_) {
+    const SetupState& state = wifi_->GetSetupState();
+    if (!state.IsStatusEqual(SetupState::kNone)) {
+      base::DictionaryValue* wifi = new base::DictionaryValue;
+      output.Set(kWifiKey, wifi);
+      SetState(state, wifi);
+      if (state.IsStatusEqual(SetupState::kSuccess))
+        wifi->SetString(kInfoWifiSsidKey, wifi_->GetCurrentlyConnectedSsid());
+    }
+  }
+
+  callback.Run(chromeos::http::status_code::Ok, output);
+}
+
+void PrivetHandler::HandleState(const base::DictionaryValue& input,
+                                const UserInfo& user_info,
+                                const RequestCallback& callback) {
+  base::DictionaryValue output;
+  base::DictionaryValue* defs = cloud_->GetState().DeepCopy();
+  output.Set(kStateKey, defs);
+  output.SetString(kFingerprintKey, base::IntToString(state_fingerprint_));
+
+  callback.Run(chromeos::http::status_code::Ok, output);
+}
+
+void PrivetHandler::HandleCommandDefs(const base::DictionaryValue& input,
+                                      const UserInfo& user_info,
+                                      const RequestCallback& callback) {
+  base::DictionaryValue output;
+  base::DictionaryValue* defs = cloud_->GetCommandDef().DeepCopy();
+  output.Set(kCommandsKey, defs);
+  output.SetString(kFingerprintKey,
+                   base::IntToString(command_defs_fingerprint_));
+
+  callback.Run(chromeos::http::status_code::Ok, output);
+}
+
+void PrivetHandler::HandleCommandsExecute(const base::DictionaryValue& input,
+                                          const UserInfo& user_info,
+                                          const RequestCallback& callback) {
+  cloud_->AddCommand(input, user_info,
+                     base::Bind(&OnCommandRequestSucceeded, callback),
+                     base::Bind(&OnCommandRequestFailed, callback));
+}
+
+void PrivetHandler::HandleCommandsStatus(const base::DictionaryValue& input,
+                                         const UserInfo& user_info,
+                                         const RequestCallback& callback) {
+  std::string id;
+  if (!input.GetString(kCommandsIdKey, &id)) {
+    chromeos::ErrorPtr error;
+    chromeos::Error::AddToPrintf(
+        &error, FROM_HERE, errors::kDomain, errors::kInvalidParams,
+        kInvalidParamValueFormat, kCommandsIdKey, id.c_str());
+    return ReturnError(*error, callback);
+  }
+  cloud_->GetCommand(id, user_info,
+                     base::Bind(&OnCommandRequestSucceeded, callback),
+                     base::Bind(&OnCommandRequestFailed, callback));
+}
+
+void PrivetHandler::HandleCommandsList(const base::DictionaryValue& input,
+                                       const UserInfo& user_info,
+                                       const RequestCallback& callback) {
+  cloud_->ListCommands(user_info,
+                       base::Bind(&OnCommandRequestSucceeded, callback),
+                       base::Bind(&OnCommandRequestFailed, callback));
+}
+
+void PrivetHandler::HandleCommandsCancel(const base::DictionaryValue& input,
+                                         const UserInfo& user_info,
+                                         const RequestCallback& callback) {
+  std::string id;
+  if (!input.GetString(kCommandsIdKey, &id)) {
+    chromeos::ErrorPtr error;
+    chromeos::Error::AddToPrintf(
+        &error, FROM_HERE, errors::kDomain, errors::kInvalidParams,
+        kInvalidParamValueFormat, kCommandsIdKey, id.c_str());
+    return ReturnError(*error, callback);
+  }
+  cloud_->CancelCommand(id, user_info,
+                        base::Bind(&OnCommandRequestSucceeded, callback),
+                        base::Bind(&OnCommandRequestFailed, callback));
+}
+
+bool StringToPairingType(const std::string& mode, PairingType* id) {
+  return StringToEnum(mode, id);
+}
+
+std::string PairingTypeToString(PairingType id) {
+  return EnumToString(id);
+}
+
+bool StringToAuthScope(const std::string& scope, AuthScope* id) {
+  return StringToEnum(scope, id);
+}
+
+std::string AuthScopeToString(AuthScope id) {
+  return EnumToString(id);
+}
+
+}  // namespace privetd
diff --git a/buffet/privet/privet_handler.h b/buffet/privet/privet_handler.h
new file mode 100644
index 0000000..bb86c0a
--- /dev/null
+++ b/buffet/privet/privet_handler.h
@@ -0,0 +1,139 @@
+// 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_PRIVET_PRIVET_HANDLER_H_
+#define BUFFET_PRIVET_PRIVET_HANDLER_H_
+
+#include <map>
+#include <string>
+#include <utility>
+
+#include <base/macros.h>
+#include <base/memory/weak_ptr.h>
+#include <base/scoped_observer.h>
+
+#include "buffet/privet/cloud_delegate.h"
+
+namespace base {
+class Value;
+class DictionaryValue;
+}  // namespace base
+
+namespace privetd {
+
+class DeviceDelegate;
+class IdentityDelegate;
+class SecurityDelegate;
+class WifiDelegate;
+
+enum class AuthScope;
+
+// Privet V3 HTTP/HTTPS requests handler.
+// API details at https://developers.google.com/cloud-devices/
+class PrivetHandler : public CloudDelegate::Observer {
+ public:
+  // Callback to handle requests asynchronously.
+  // |status| is HTTP status code.
+  // |output| is result returned in HTTP response. Contains result of
+  // successfully request of information about error.
+  using RequestCallback =
+      base::Callback<void(int status, const base::DictionaryValue& output)>;
+
+  PrivetHandler(CloudDelegate* cloud,
+                DeviceDelegate* device,
+                SecurityDelegate* pairing,
+                WifiDelegate* wifi,
+                IdentityDelegate* identity);
+  ~PrivetHandler() override;
+
+  void OnCommandDefsChanged() override;
+  void OnStateChanged() override;
+
+  // Handles HTTP/HTTPS Privet request.
+  // |api| is the path from the HTTP request, e.g /privet/info.
+  // |auth_header| is the Authentication header from HTTP request.
+  // |input| is the the POST data from HTTP request. If nullptr, data format is
+  // not valid JSON.
+  // |callback| will be called exactly once during or after |HandleRequest|
+  // call.
+  void HandleRequest(const std::string& api,
+                     const std::string& auth_header,
+                     const base::DictionaryValue* input,
+                     const RequestCallback& callback);
+
+ private:
+  using ApiHandler = void (PrivetHandler::*)(const base::DictionaryValue&,
+                                             const UserInfo&,
+                                             const RequestCallback&);
+
+  void AddHandler(const std::string& path, ApiHandler handler, AuthScope scope);
+
+  void HandleInfo(const base::DictionaryValue&,
+                  const UserInfo& user_info,
+                  const RequestCallback& callback);
+  void HandlePairingStart(const base::DictionaryValue& input,
+                          const UserInfo& user_info,
+                          const RequestCallback& callback);
+  void HandlePairingConfirm(const base::DictionaryValue& input,
+                            const UserInfo& user_info,
+                            const RequestCallback& callback);
+  void HandlePairingCancel(const base::DictionaryValue& input,
+                           const UserInfo& user_info,
+                           const RequestCallback& callback);
+  void HandleAuth(const base::DictionaryValue& input,
+                  const UserInfo& user_info,
+                  const RequestCallback& callback);
+  void HandleSetupStart(const base::DictionaryValue& input,
+                        const UserInfo& user_info,
+                        const RequestCallback& callback);
+  void HandleSetupStatus(const base::DictionaryValue&,
+                         const UserInfo& user_info,
+                         const RequestCallback& callback);
+  void HandleState(const base::DictionaryValue& input,
+                   const UserInfo& user_info,
+                   const RequestCallback& callback);
+  void HandleCommandDefs(const base::DictionaryValue& input,
+                         const UserInfo& user_info,
+                         const RequestCallback& callback);
+  void HandleCommandsExecute(const base::DictionaryValue& input,
+                             const UserInfo& user_info,
+                             const RequestCallback& callback);
+  void HandleCommandsStatus(const base::DictionaryValue& input,
+                            const UserInfo& user_info,
+                            const RequestCallback& callback);
+  void HandleCommandsList(const base::DictionaryValue& input,
+                          const UserInfo& user_info,
+                          const RequestCallback& callback);
+  void HandleCommandsCancel(const base::DictionaryValue& input,
+                            const UserInfo& user_info,
+                            const RequestCallback& callback);
+
+  void OnUpdateDeviceInfoDone(const std::string& ssid,
+                              const std::string& passphrase,
+                              const std::string& ticket,
+                              const std::string& user,
+                              const RequestCallback& callback) const;
+  void ReplyWithSetupStatus(const RequestCallback& callback) const;
+
+  CloudDelegate* cloud_ = nullptr;
+  DeviceDelegate* device_ = nullptr;
+  SecurityDelegate* security_ = nullptr;
+  WifiDelegate* wifi_ = nullptr;
+  IdentityDelegate* identity_ = nullptr;
+
+  std::map<std::string, std::pair<AuthScope, ApiHandler>> handlers_;
+
+  uint64_t last_user_id_{0};
+  int state_fingerprint_{0};
+  int command_defs_fingerprint_{0};
+  ScopedObserver<CloudDelegate, CloudDelegate::Observer> cloud_observer_{this};
+
+  base::WeakPtrFactory<PrivetHandler> weak_ptr_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(PrivetHandler);
+};
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_PRIVET_HANDLER_H_
diff --git a/buffet/privet/privet_handler_unittest.cc b/buffet/privet/privet_handler_unittest.cc
new file mode 100644
index 0000000..e2dcfd3
--- /dev/null
+++ b/buffet/privet/privet_handler_unittest.cc
@@ -0,0 +1,724 @@
+// 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/privet/privet_handler.h"
+
+#include <set>
+#include <string>
+#include <utility>
+
+#include <base/bind.h>
+#include <base/json/json_reader.h>
+#include <base/json/json_writer.h>
+#include <base/run_loop.h>
+#include <base/strings/string_util.h>
+#include <base/values.h>
+#include <chromeos/http/http_request.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "buffet/privet/constants.h"
+#include "buffet/privet/mock_delegates.h"
+
+using testing::_;
+using testing::DoAll;
+using testing::Invoke;
+using testing::Return;
+using testing::SetArgPointee;
+
+namespace privetd {
+
+namespace {
+
+void LoadTestJson(const std::string& test_json,
+                  base::DictionaryValue* dictionary) {
+  std::string json = test_json;
+  base::ReplaceChars(json, "'", "\"", &json);
+  int error = 0;
+  std::string message;
+  std::unique_ptr<base::Value> value(base::JSONReader::ReadAndReturnError(
+      json, base::JSON_PARSE_RFC, &error, &message));
+  EXPECT_TRUE(value.get()) << "\nError: " << message << "\n" << json;
+  base::DictionaryValue* dictionary_ptr = nullptr;
+  if (value->GetAsDictionary(&dictionary_ptr))
+    dictionary->MergeDictionary(dictionary_ptr);
+}
+
+bool IsEqualValue(const base::Value& val1, const base::Value& val2) {
+  return val1.Equals(&val2);
+}
+
+struct CodeWithReason {
+  CodeWithReason(int code_in, const std::string& reason_in)
+      : code(code_in), reason(reason_in) {}
+  int code;
+  std::string reason;
+};
+
+std::ostream& operator<<(std::ostream& stream, const CodeWithReason& error) {
+  return stream << "{" << error.code << ", " << error.reason << "}";
+}
+
+bool IsEqualError(const CodeWithReason& expected,
+                  const base::DictionaryValue& dictionary) {
+  std::string reason;
+  int code = 0;
+  return dictionary.GetInteger("error.http_status", &code) &&
+         code == expected.code && dictionary.GetString("error.code", &reason) &&
+         reason == expected.reason;
+}
+
+bool IsEqualDictionary(const base::DictionaryValue& dictionary1,
+                       const base::DictionaryValue& dictionary2) {
+  base::DictionaryValue::Iterator it1(dictionary1);
+  base::DictionaryValue::Iterator it2(dictionary2);
+  for (; !it1.IsAtEnd() && !it2.IsAtEnd(); it1.Advance(), it2.Advance()) {
+    // Output mismatched keys.
+    EXPECT_EQ(it1.key(), it2.key());
+    if (it1.key() != it2.key())
+      return false;
+
+    if (it1.key() == "error") {
+      std::string code1;
+      std::string code2;
+      const char kCodeKey[] = "error.code";
+      if (!dictionary1.GetString(kCodeKey, &code1) ||
+          !dictionary2.GetString(kCodeKey, &code2) || code1 != code2) {
+        return false;
+      }
+      continue;
+    }
+
+    const base::DictionaryValue* d1{nullptr};
+    const base::DictionaryValue* d2{nullptr};
+    if (it1.value().GetAsDictionary(&d1) && it2.value().GetAsDictionary(&d2)) {
+      if (!IsEqualDictionary(*d1, *d2))
+        return false;
+      continue;
+    }
+
+    // Output mismatched values.
+    EXPECT_PRED2(IsEqualValue, it1.value(), it2.value());
+    if (!IsEqualValue(it1.value(), it2.value()))
+      return false;
+  }
+
+  return it1.IsAtEnd() && it2.IsAtEnd();
+}
+
+bool IsEqualJson(const std::string& test_json,
+                 const base::DictionaryValue& dictionary) {
+  base::DictionaryValue dictionary2;
+  LoadTestJson(test_json, &dictionary2);
+  return IsEqualDictionary(dictionary2, dictionary);
+}
+
+}  // namespace
+
+class PrivetHandlerTest : public testing::Test {
+ public:
+  PrivetHandlerTest() {}
+
+ protected:
+  void SetUp() override {
+    auth_header_ = "Privet anonymous";
+    handler_.reset(
+        new PrivetHandler(&cloud_, &device_, &security_, &wifi_, &identity_));
+  }
+
+  const base::DictionaryValue& HandleRequest(
+      const std::string& api,
+      const base::DictionaryValue* input) {
+    output_.Clear();
+    handler_->HandleRequest(api, auth_header_, input,
+                            base::Bind(&PrivetHandlerTest::HandlerCallback,
+                                       base::Unretained(this)));
+    base::RunLoop().RunUntilIdle();
+    return output_;
+  }
+
+  const base::DictionaryValue& HandleRequest(const std::string& api,
+                                             const std::string& json_input) {
+    base::DictionaryValue dictionary;
+    LoadTestJson(json_input, &dictionary);
+    return HandleRequest(api, &dictionary);
+  }
+
+  void HandleUnknownRequest(const std::string& api) {
+    output_.Clear();
+    base::DictionaryValue dictionary;
+    handler_->HandleRequest(api, auth_header_, &dictionary,
+                            base::Bind(&PrivetHandlerTest::HandlerNoFound));
+    base::RunLoop().RunUntilIdle();
+  }
+
+  void SetNoWifiAndGcd() {
+    handler_.reset(
+        new PrivetHandler(&cloud_, &device_, &security_, nullptr, &identity_));
+    EXPECT_CALL(cloud_, GetCloudId()).WillRepeatedly(Return(""));
+    EXPECT_CALL(cloud_, GetConnectionState())
+        .WillRepeatedly(ReturnRef(gcd_disabled_state_));
+    auto set_error =
+        [](const std::string&, const std::string&, chromeos::ErrorPtr* error) {
+          chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                                 "setupUnavailable", "");
+        };
+    EXPECT_CALL(cloud_, Setup(_, _, _))
+        .WillRepeatedly(DoAll(Invoke(set_error), Return(false)));
+  }
+
+  testing::StrictMock<MockCloudDelegate> cloud_;
+  testing::StrictMock<MockDeviceDelegate> device_;
+  testing::StrictMock<MockSecurityDelegate> security_;
+  testing::StrictMock<MockWifiDelegate> wifi_;
+  testing::StrictMock<MockIdentityDelegate> identity_;
+  std::string auth_header_;
+
+ private:
+  void HandlerCallback(int status, const base::DictionaryValue& output) {
+    output_.MergeDictionary(&output);
+    if (!output_.HasKey("error")) {
+      EXPECT_EQ(chromeos::http::status_code::Ok, status);
+      return;
+    }
+    EXPECT_NE(chromeos::http::status_code::Ok, status);
+    output_.SetInteger("error.http_status", status);
+  }
+
+  static void HandlerNoFound(int status, const base::DictionaryValue&) {
+    EXPECT_EQ(status, 404);
+  }
+
+  base::MessageLoop message_loop_;
+  std::unique_ptr<PrivetHandler> handler_;
+  base::DictionaryValue output_;
+  ConnectionState gcd_disabled_state_{ConnectionState::kDisabled};
+};
+
+TEST_F(PrivetHandlerTest, UnknownApi) {
+  HandleUnknownRequest("/privet/foo");
+}
+
+TEST_F(PrivetHandlerTest, InvalidFormat) {
+  auth_header_ = "";
+  EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidFormat"),
+               HandleRequest("/privet/info", nullptr));
+}
+
+TEST_F(PrivetHandlerTest, MissingAuth) {
+  auth_header_ = "";
+  EXPECT_PRED2(IsEqualError, CodeWithReason(401, "missingAuthorization"),
+               HandleRequest("/privet/info", "{}"));
+}
+
+TEST_F(PrivetHandlerTest, InvalidAuth) {
+  auth_header_ = "foo";
+  EXPECT_PRED2(IsEqualError, CodeWithReason(401, "invalidAuthorization"),
+               HandleRequest("/privet/info", "{}"));
+}
+
+TEST_F(PrivetHandlerTest, ExpiredAuth) {
+  auth_header_ = "Privet 123";
+  EXPECT_CALL(security_, ParseAccessToken(_, _))
+      .WillRepeatedly(DoAll(SetArgPointee<1>(base::Time()),
+                            Return(UserInfo{AuthScope::kOwner, 1})));
+  EXPECT_PRED2(IsEqualError, CodeWithReason(403, "authorizationExpired"),
+               HandleRequest("/privet/info", "{}"));
+}
+
+TEST_F(PrivetHandlerTest, InvalidAuthScope) {
+  EXPECT_PRED2(IsEqualError, CodeWithReason(403, "invalidAuthorizationScope"),
+               HandleRequest("/privet/v3/setup/start", "{}"));
+}
+
+TEST_F(PrivetHandlerTest, InfoMinimal) {
+  SetNoWifiAndGcd();
+  EXPECT_CALL(security_, GetPairingTypes())
+      .WillRepeatedly(Return(std::set<PairingType>{}));
+  EXPECT_CALL(security_, GetCryptoTypes())
+      .WillRepeatedly(Return(std::set<CryptoType>{}));
+
+  const char kExpected[] = R"({
+    'version': '3.0',
+    'id': 'TestId',
+    'name': 'TestDevice',
+    'services': [],
+    'modelManifestId': "ABMID",
+    'basicModelManifest': {
+      'uiDeviceKind': 'developmentBoard',
+      'oemName': 'Chromium',
+      'modelName': 'Brillo'
+    },
+    'endpoints': {
+      'httpPort': 0,
+      'httpUpdatesPort': 0,
+      'httpsPort': 0,
+      'httpsUpdatesPort': 0
+    },
+    'authentication': {
+      'anonymousMaxScope': 'user',
+      'mode': [
+        'anonymous',
+        'pairing'
+      ],
+      'pairing': [
+      ],
+      'crypto': [
+      ]
+    },
+    'gcd': {
+      'id': '',
+      'status': 'disabled'
+    },
+    'uptime': 3600
+  })";
+  EXPECT_PRED2(IsEqualJson, kExpected, HandleRequest("/privet/info", "{}"));
+}
+
+TEST_F(PrivetHandlerTest, Info) {
+  EXPECT_CALL(cloud_, GetDescription())
+      .WillRepeatedly(Return("TestDescription"));
+  EXPECT_CALL(cloud_, GetLocation()).WillRepeatedly(Return("TestLocation"));
+  EXPECT_CALL(cloud_, GetServices())
+      .WillRepeatedly(Return(std::set<std::string>{"service1", "service2"}));
+  EXPECT_CALL(device_, GetHttpEnpoint())
+      .WillRepeatedly(Return(std::make_pair(80, 10080)));
+  EXPECT_CALL(device_, GetHttpsEnpoint())
+      .WillRepeatedly(Return(std::make_pair(443, 10443)));
+  EXPECT_CALL(wifi_, GetHostedSsid())
+      .WillRepeatedly(Return("Test_device.BBABCLAprv"));
+
+  const char kExpected[] = R"({
+    'version': '3.0',
+    'id': 'TestId',
+    'name': 'TestDevice',
+    'description': 'TestDescription',
+    'location': 'TestLocation',
+    'services': [
+      "service1",
+      "service2"
+    ],
+    'modelManifestId': "ABMID",
+    'basicModelManifest': {
+      'uiDeviceKind': 'developmentBoard',
+      'oemName': 'Chromium',
+      'modelName': 'Brillo'
+    },
+    'endpoints': {
+      'httpPort': 80,
+      'httpUpdatesPort': 10080,
+      'httpsPort': 443,
+      'httpsUpdatesPort': 10443
+    },
+    'authentication': {
+      'anonymousMaxScope': 'none',
+      'mode': [
+        'anonymous',
+        'pairing'
+      ],
+      'pairing': [
+        'pinCode',
+        'embeddedCode',
+        'ultrasound32',
+        'audible32'
+      ],
+      'crypto': [
+        'p224_spake2',
+        'p256_spake2'
+      ]
+    },
+    'wifi': {
+      'capabilities': [
+        '2.4GHz'
+      ],
+      'ssid': 'TestSsid',
+      'hostedSsid': 'Test_device.BBABCLAprv',
+      'status': 'offline'
+    },
+    'gcd': {
+      'id': 'TestCloudId',
+      'status': 'online'
+    },
+    'uptime': 3600
+  })";
+  EXPECT_PRED2(IsEqualJson, kExpected, HandleRequest("/privet/info", "{}"));
+}
+
+TEST_F(PrivetHandlerTest, PairingStartInvalidParams) {
+  EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidParams"),
+               HandleRequest("/privet/v3/pairing/start",
+                             "{'pairing':'embeddedCode','crypto':'crypto'}"));
+
+  EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidParams"),
+               HandleRequest("/privet/v3/pairing/start",
+                             "{'pairing':'code','crypto':'p256_spake2'}"));
+}
+
+TEST_F(PrivetHandlerTest, PairingStart) {
+  EXPECT_PRED2(
+      IsEqualJson,
+      "{'deviceCommitment': 'testCommitment', 'sessionId': 'testSession'}",
+      HandleRequest("/privet/v3/pairing/start",
+                    "{'pairing': 'embeddedCode', 'crypto': 'p256_spake2'}"));
+}
+
+TEST_F(PrivetHandlerTest, PairingConfirm) {
+  EXPECT_PRED2(
+      IsEqualJson,
+      "{'certFingerprint':'testFingerprint','certSignature':'testSignature'}",
+      HandleRequest(
+          "/privet/v3/pairing/confirm",
+          "{'sessionId':'testSession','clientCommitment':'testCommitment'}"));
+}
+
+TEST_F(PrivetHandlerTest, PairingCancel) {
+  EXPECT_PRED2(IsEqualJson, "{}",
+               HandleRequest("/privet/v3/pairing/cancel",
+                             "{'sessionId': 'testSession'}"));
+}
+
+TEST_F(PrivetHandlerTest, AuthErrorNoType) {
+  EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidAuthMode"),
+               HandleRequest("/privet/v3/auth", "{}"));
+}
+
+TEST_F(PrivetHandlerTest, AuthErrorInvalidType) {
+  EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidAuthMode"),
+               HandleRequest("/privet/v3/auth", "{'mode':'unknown'}"));
+}
+
+TEST_F(PrivetHandlerTest, AuthErrorNoScope) {
+  EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidRequestedScope"),
+               HandleRequest("/privet/v3/auth", "{'mode':'anonymous'}"));
+}
+
+TEST_F(PrivetHandlerTest, AuthErrorInvalidScope) {
+  EXPECT_PRED2(
+      IsEqualError, CodeWithReason(400, "invalidRequestedScope"),
+      HandleRequest("/privet/v3/auth",
+                    "{'mode':'anonymous','requestedScope':'unknown'}"));
+}
+
+TEST_F(PrivetHandlerTest, AuthErrorAccessDenied) {
+  EXPECT_PRED2(IsEqualError, CodeWithReason(403, "accessDenied"),
+               HandleRequest("/privet/v3/auth",
+                             "{'mode':'anonymous','requestedScope':'owner'}"));
+}
+
+TEST_F(PrivetHandlerTest, AuthErrorInvalidAuthCode) {
+  EXPECT_CALL(security_, IsValidPairingCode("testToken"))
+      .WillRepeatedly(Return(false));
+  const char kInput[] = R"({
+    'mode': 'pairing',
+    'requestedScope': 'user',
+    'authCode': 'testToken'
+  })";
+  EXPECT_PRED2(IsEqualError, CodeWithReason(403, "invalidAuthCode"),
+               HandleRequest("/privet/v3/auth", kInput));
+}
+
+TEST_F(PrivetHandlerTest, AuthAnonymous) {
+  const char kExpected[] = R"({
+    'accessToken': 'GuestAccessToken',
+    'expiresIn': 3600,
+    'scope': 'user',
+    'tokenType': 'Privet'
+  })";
+  EXPECT_PRED2(IsEqualJson, kExpected,
+               HandleRequest("/privet/v3/auth",
+                             "{'mode':'anonymous','requestedScope':'auto'}"));
+}
+
+TEST_F(PrivetHandlerTest, AuthPairing) {
+  EXPECT_CALL(security_, IsValidPairingCode("testToken"))
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(security_, CreateAccessToken(_, _))
+      .WillRepeatedly(Return("OwnerAccessToken"));
+  const char kInput[] = R"({
+    'mode': 'pairing',
+    'requestedScope': 'owner',
+    'authCode': 'testToken'
+  })";
+  const char kExpected[] = R"({
+    'accessToken': 'OwnerAccessToken',
+    'expiresIn': 3600,
+    'scope': 'owner',
+    'tokenType': 'Privet'
+  })";
+  EXPECT_PRED2(
+      IsEqualJson, kExpected, HandleRequest("/privet/v3/auth", kInput));
+}
+
+class PrivetHandlerSetupTest : public PrivetHandlerTest {
+ public:
+  void SetUp() override {
+    PrivetHandlerTest::SetUp();
+    auth_header_ = "Privet 123";
+    EXPECT_CALL(security_, ParseAccessToken(_, _))
+        .WillRepeatedly(DoAll(SetArgPointee<1>(base::Time::Now()),
+                              Return(UserInfo{AuthScope::kOwner, 1})));
+  }
+};
+
+TEST_F(PrivetHandlerSetupTest, StatusEmpty) {
+  SetNoWifiAndGcd();
+  EXPECT_PRED2(
+      IsEqualJson, "{}", HandleRequest("/privet/v3/setup/status", "{}"));
+}
+
+TEST_F(PrivetHandlerSetupTest, StatusWifi) {
+  wifi_.setup_state_ = SetupState{SetupState::kSuccess};
+
+  const char kExpected[] = R"({
+    'wifi': {
+        'ssid': 'TestSsid',
+        'status': 'success'
+     }
+  })";
+  EXPECT_PRED2(
+      IsEqualJson, kExpected, HandleRequest("/privet/v3/setup/status", "{}"));
+}
+
+TEST_F(PrivetHandlerSetupTest, StatusWifiError) {
+  chromeos::ErrorPtr error;
+  chromeos::Error::AddTo(&error, FROM_HERE, "test", "invalidPassphrase", "");
+  wifi_.setup_state_ = SetupState{std::move(error)};
+
+  const char kExpected[] = R"({
+    'wifi': {
+        'status': 'error',
+        'error': {
+          'code': 'invalidPassphrase'
+        }
+     }
+  })";
+  EXPECT_PRED2(
+      IsEqualJson, kExpected, HandleRequest("/privet/v3/setup/status", "{}"));
+}
+
+TEST_F(PrivetHandlerSetupTest, StatusGcd) {
+  cloud_.setup_state_ = SetupState{SetupState::kSuccess};
+
+  const char kExpected[] = R"({
+    'gcd': {
+        'id': 'TestCloudId',
+        'status': 'success'
+     }
+  })";
+  EXPECT_PRED2(
+      IsEqualJson, kExpected, HandleRequest("/privet/v3/setup/status", "{}"));
+}
+
+TEST_F(PrivetHandlerSetupTest, StatusGcdError) {
+  chromeos::ErrorPtr error;
+  chromeos::Error::AddTo(&error, FROM_HERE, "test", "invalidTicket", "");
+  cloud_.setup_state_ = SetupState{std::move(error)};
+
+  const char kExpected[] = R"({
+    'gcd': {
+        'status': 'error',
+        'error': {
+          'code': 'invalidTicket'
+        }
+     }
+  })";
+  EXPECT_PRED2(
+      IsEqualJson, kExpected, HandleRequest("/privet/v3/setup/status", "{}"));
+}
+
+TEST_F(PrivetHandlerSetupTest, SetupNameDescriptionLocation) {
+  EXPECT_CALL(cloud_, UpdateDeviceInfo("testName", "testDescription",
+                                       "testLocation", _, _)).Times(1);
+  const char kInput[] = R"({
+    'name': 'testName',
+    'description': 'testDescription',
+    'location': 'testLocation'
+  })";
+  EXPECT_PRED2(IsEqualJson, "{}",
+               HandleRequest("/privet/v3/setup/start", kInput));
+}
+
+TEST_F(PrivetHandlerSetupTest, InvalidParams) {
+  const char kInputWifi[] = R"({
+    'wifi': {
+      'ssid': ''
+    }
+  })";
+  EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidParams"),
+               HandleRequest("/privet/v3/setup/start", kInputWifi));
+
+  const char kInputRegistration[] = R"({
+    'gcd': {
+      'ticketId': ''
+    }
+  })";
+  EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidParams"),
+               HandleRequest("/privet/v3/setup/start", kInputRegistration));
+}
+
+TEST_F(PrivetHandlerSetupTest, WifiSetupUnavailable) {
+  SetNoWifiAndGcd();
+  EXPECT_PRED2(IsEqualError, CodeWithReason(400, "setupUnavailable"),
+               HandleRequest("/privet/v3/setup/start", "{'wifi': {}}"));
+}
+
+TEST_F(PrivetHandlerSetupTest, WifiSetup) {
+  const char kInput[] = R"({
+    'wifi': {
+      'ssid': 'testSsid',
+      'passphrase': 'testPass'
+    }
+  })";
+  auto set_error = [](const std::string&, const std::string&,
+                      chromeos::ErrorPtr* error) {
+    chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain, "deviceBusy", "");
+  };
+  EXPECT_CALL(wifi_, ConfigureCredentials(_, _, _))
+      .WillOnce(DoAll(Invoke(set_error), Return(false)));
+  EXPECT_PRED2(IsEqualError, CodeWithReason(503, "deviceBusy"),
+               HandleRequest("/privet/v3/setup/start", kInput));
+
+  const char kExpected[] = R"({
+    'wifi': {
+      'status': 'inProgress'
+    }
+  })";
+  wifi_.setup_state_ = SetupState{SetupState::kInProgress};
+  EXPECT_CALL(wifi_, ConfigureCredentials("testSsid", "testPass", _))
+      .WillOnce(Return(true));
+  EXPECT_PRED2(IsEqualJson, kExpected,
+               HandleRequest("/privet/v3/setup/start", kInput));
+}
+
+TEST_F(PrivetHandlerSetupTest, GcdSetupUnavailable) {
+  SetNoWifiAndGcd();
+  const char kInput[] = R"({
+    'gcd': {
+      'ticketId': 'testTicket',
+      'user': 'testUser'
+    }
+  })";
+
+  EXPECT_PRED2(IsEqualError, CodeWithReason(400, "setupUnavailable"),
+               HandleRequest("/privet/v3/setup/start", kInput));
+}
+
+TEST_F(PrivetHandlerSetupTest, GcdSetup) {
+  const char kInput[] = R"({
+    'gcd': {
+      'ticketId': 'testTicket',
+      'user': 'testUser'
+    }
+  })";
+
+  auto set_error = [](const std::string&, const std::string&,
+                      chromeos::ErrorPtr* error) {
+    chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain, "deviceBusy", "");
+  };
+  EXPECT_CALL(cloud_, Setup(_, _, _))
+      .WillOnce(DoAll(Invoke(set_error), Return(false)));
+  EXPECT_PRED2(IsEqualError, CodeWithReason(503, "deviceBusy"),
+               HandleRequest("/privet/v3/setup/start", kInput));
+
+  const char kExpected[] = R"({
+    'gcd': {
+      'status': 'inProgress'
+    }
+  })";
+  cloud_.setup_state_ = SetupState{SetupState::kInProgress};
+  EXPECT_CALL(cloud_, Setup("testTicket", "testUser", _))
+      .WillOnce(Return(true));
+  EXPECT_PRED2(IsEqualJson, kExpected,
+               HandleRequest("/privet/v3/setup/start", kInput));
+}
+
+TEST_F(PrivetHandlerSetupTest, State) {
+  EXPECT_PRED2(IsEqualJson, "{'state': {'test': {}}, 'fingerprint': '0'}",
+               HandleRequest("/privet/v3/state", "{}"));
+
+  cloud_.NotifyOnStateChanged();
+
+  EXPECT_PRED2(IsEqualJson, "{'state': {'test': {}}, 'fingerprint': '1'}",
+               HandleRequest("/privet/v3/state", "{}"));
+}
+
+TEST_F(PrivetHandlerSetupTest, CommandsDefs) {
+  EXPECT_PRED2(IsEqualJson, "{'commands': {'test':{}}, 'fingerprint': '0'}",
+               HandleRequest("/privet/v3/commandDefs", "{}"));
+
+  cloud_.NotifyOnCommandDefsChanged();
+
+  EXPECT_PRED2(IsEqualJson, "{'commands': {'test':{}}, 'fingerprint': '1'}",
+               HandleRequest("/privet/v3/commandDefs", "{}"));
+}
+
+TEST_F(PrivetHandlerSetupTest, CommandsExecute) {
+  const char kInput[] = "{'name': 'test'}";
+  base::DictionaryValue command;
+  LoadTestJson(kInput, &command);
+  LoadTestJson("{'id':'5'}", &command);
+  EXPECT_CALL(cloud_, AddCommand(_, _, _, _))
+      .WillOnce(RunCallback<2, const base::DictionaryValue&>(command));
+
+  EXPECT_PRED2(IsEqualJson, "{'name':'test', 'id':'5'}",
+               HandleRequest("/privet/v3/commands/execute", kInput));
+}
+
+TEST_F(PrivetHandlerSetupTest, CommandsStatus) {
+  const char kInput[] = "{'id': '5'}";
+  base::DictionaryValue command;
+  LoadTestJson(kInput, &command);
+  LoadTestJson("{'name':'test'}", &command);
+  EXPECT_CALL(cloud_, GetCommand(_, _, _, _))
+      .WillOnce(RunCallback<2, const base::DictionaryValue&>(command));
+
+  EXPECT_PRED2(IsEqualJson, "{'name':'test', 'id':'5'}",
+               HandleRequest("/privet/v3/commands/status", kInput));
+
+  chromeos::ErrorPtr error;
+  chromeos::Error::AddTo(&error, FROM_HERE, errors::kDomain, "notFound", "");
+  EXPECT_CALL(cloud_, GetCommand(_, _, _, _))
+      .WillOnce(RunCallback<3>(error.get()));
+
+  EXPECT_PRED2(IsEqualError, CodeWithReason(404, "notFound"),
+               HandleRequest("/privet/v3/commands/status", "{'id': '15'}"));
+}
+
+TEST_F(PrivetHandlerSetupTest, CommandsCancel) {
+  const char kExpected[] = "{'id': '5', 'name':'test', 'state':'cancelled'}";
+  base::DictionaryValue command;
+  LoadTestJson(kExpected, &command);
+  EXPECT_CALL(cloud_, CancelCommand(_, _, _, _))
+      .WillOnce(RunCallback<2, const base::DictionaryValue&>(command));
+
+  EXPECT_PRED2(IsEqualJson, kExpected,
+               HandleRequest("/privet/v3/commands/cancel", "{'id': '8'}"));
+
+  chromeos::ErrorPtr error;
+  chromeos::Error::AddTo(&error, FROM_HERE, errors::kDomain, "notFound", "");
+  EXPECT_CALL(cloud_, CancelCommand(_, _, _, _))
+      .WillOnce(RunCallback<3>(error.get()));
+
+  EXPECT_PRED2(IsEqualError, CodeWithReason(404, "notFound"),
+               HandleRequest("/privet/v3/commands/cancel", "{'id': '11'}"));
+}
+
+TEST_F(PrivetHandlerSetupTest, CommandsList) {
+  const char kExpected[] = R"({
+    'commands' : [
+        {'id':'5', 'state':'cancelled'},
+        {'id':'15', 'state':'inProgress'}
+     ]})";
+
+  base::DictionaryValue commands;
+  LoadTestJson(kExpected, &commands);
+
+  EXPECT_CALL(cloud_, ListCommands(_, _, _))
+      .WillOnce(RunCallback<1, const base::DictionaryValue&>(commands));
+
+  EXPECT_PRED2(IsEqualJson, kExpected,
+               HandleRequest("/privet/v3/commands/list", "{}"));
+}
+
+}  // namespace privetd
diff --git a/buffet/privet/privet_types.h b/buffet/privet/privet_types.h
new file mode 100644
index 0000000..c1e2ba0
--- /dev/null
+++ b/buffet/privet/privet_types.h
@@ -0,0 +1,98 @@
+// 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_PRIVET_PRIVET_TYPES_H_
+#define BUFFET_PRIVET_PRIVET_TYPES_H_
+
+#include <string>
+
+#include <chromeos/errors/error.h>
+
+namespace privetd {
+
+// Scopes in order of increasing privileges.
+enum class AuthScope {
+  kNone,
+  kViewer,
+  kUser,
+  kOwner,
+};
+
+class UserInfo {
+ public:
+  explicit UserInfo(AuthScope scope = AuthScope::kNone, uint64_t user_id = 0)
+      : scope_{scope}, user_id_{scope == AuthScope::kNone ? 0 : user_id} {}
+  AuthScope scope() const { return scope_; }
+  uint64_t user_id() const { return user_id_; }
+
+ private:
+  AuthScope scope_;
+  uint64_t user_id_;
+};
+
+class ConnectionState final {
+ public:
+  enum Status {
+    kDisabled,
+    kUnconfigured,
+    kConnecting,
+    kOnline,
+    kOffline,
+  };
+
+  explicit ConnectionState(Status status) : status_(status) {}
+  explicit ConnectionState(chromeos::ErrorPtr error)
+      : status_(kOffline), error_(std::move(error)) {}
+
+  Status status() const {
+    CHECK(!error_);
+    return status_;
+  }
+
+  bool IsStatusEqual(Status status) const {
+    if (error_)
+      return false;
+    return status_ == status;
+  }
+
+  const chromeos::Error* error() const { return error_.get(); }
+
+ private:
+  Status status_;
+  chromeos::ErrorPtr error_;
+};
+
+class SetupState final {
+ public:
+  enum Status {
+    kNone,
+    kInProgress,
+    kSuccess,
+  };
+
+  explicit SetupState(Status status) : status_(status) {}
+  explicit SetupState(chromeos::ErrorPtr error)
+      : status_(kNone), error_(std::move(error)) {}
+
+  Status status() const {
+    CHECK(!error_);
+    return status_;
+  }
+
+  bool IsStatusEqual(Status status) const {
+    if (error_)
+      return false;
+    return status_ == status;
+  }
+
+  const chromeos::Error* error() const { return error_.get(); }
+
+ private:
+  Status status_;
+  chromeos::ErrorPtr error_;
+};
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_PRIVET_TYPES_H_
diff --git a/buffet/privet/privetd_conf_parser.cc b/buffet/privet/privetd_conf_parser.cc
new file mode 100644
index 0000000..6ebe155
--- /dev/null
+++ b/buffet/privet/privetd_conf_parser.cc
@@ -0,0 +1,146 @@
+// 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/privet/privetd_conf_parser.h"
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <chromeos/strings/string_utils.h>
+
+#include "buffet/privet/security_delegate.h"
+
+namespace privetd {
+
+namespace {
+
+const char kWiFiBootstrapMode[] = "wifi_bootstrapping_mode";
+const char kGcdBootstrapMode[] = "gcd_bootstrapping_mode";
+const char kConnectTimeout[] = "connect_timeout_seconds";
+const char kBootstrapTimeout[] = "bootstrap_timeout_seconds";
+const char kMonitorTimeout[] = "monitor_timeout_seconds";
+const char kPairingModes[] = "pairing_modes";
+const char kEmbeddedCodePath[] = "embedded_code_path";
+
+const char kBootstrapModeOff[] = "off";
+const char kBootstrapModeAutomatic[] = "automatic";
+const char kBootstrapModeManual[] = "manual";
+
+}  // namespace
+
+const char kWiFiBootstrapInterfaces[] = "automatic_mode_interfaces";
+
+PrivetdConfigParser::PrivetdConfigParser()
+    : wifi_bootstrap_mode_{WiFiBootstrapMode::kDisabled},
+      gcd_bootstrap_mode_{GcdBootstrapMode::kDisabled},
+      connect_timeout_seconds_{60u},
+      bootstrap_timeout_seconds_{600u},
+      monitor_timeout_seconds_{120u},
+      pairing_modes_{PairingType::kPinCode} {
+}
+
+bool PrivetdConfigParser::Parse(const chromeos::KeyValueStore& config_store) {
+  std::string wifi_bootstrap_mode_str;
+  if (config_store.GetString(kWiFiBootstrapMode, &wifi_bootstrap_mode_str)) {
+    if (wifi_bootstrap_mode_str.compare(kBootstrapModeOff) == 0) {
+      wifi_bootstrap_mode_ = WiFiBootstrapMode::kDisabled;
+    } else if (wifi_bootstrap_mode_str.compare(kBootstrapModeAutomatic) == 0) {
+      wifi_bootstrap_mode_ = WiFiBootstrapMode::kAutomatic;
+    } else if (wifi_bootstrap_mode_str.compare(kBootstrapModeManual) == 0) {
+      LOG(ERROR) << "Manual WiFi bootstrapping mode is unsupported.";
+      return false;
+    } else {
+      LOG(ERROR) << "Unrecognized WiFi bootstrapping mode: "
+                 << wifi_bootstrap_mode_str;
+      return false;
+    }
+  }
+
+  std::string gcd_bootstrap_mode_str;
+  if (config_store.GetString(kGcdBootstrapMode, &gcd_bootstrap_mode_str)) {
+    if (gcd_bootstrap_mode_str.compare(kBootstrapModeOff) == 0) {
+      gcd_bootstrap_mode_ = GcdBootstrapMode::kDisabled;
+    } else if (gcd_bootstrap_mode_str.compare(kBootstrapModeAutomatic) == 0) {
+      gcd_bootstrap_mode_ = GcdBootstrapMode::kAutomatic;
+    } else if (gcd_bootstrap_mode_str.compare(kBootstrapModeManual) == 0) {
+      LOG(ERROR) << "Manual GCD bootstrapping mode is unsupported.";
+      return false;
+    } else {
+      LOG(ERROR) << "Unrecognized GCD bootstrapping mode: "
+                 << gcd_bootstrap_mode_str;
+      return false;
+    }
+  }
+
+  std::string wifi_interface_list_str;
+  if (config_store.GetString(kWiFiBootstrapInterfaces,
+                             &wifi_interface_list_str)) {
+    auto interfaces =
+        chromeos::string_utils::Split(wifi_interface_list_str, ",", true, true);
+    automatic_wifi_interfaces_.insert(interfaces.begin(), interfaces.end());
+  }
+
+  auto parse_timeout = [](const std::string& input, uint32_t* parsed_value) {
+    uint32_t temp{0};
+    // Tragically, this will set |temp| even for invalid parsings.
+    if (base::StringToUint(input, &temp)) {
+      *parsed_value = temp;  // Parse worked, use that value.
+      return true;
+    }
+    return false;
+  };
+
+  std::string connect_timeout_seconds_str;
+  if (config_store.GetString(kConnectTimeout, &connect_timeout_seconds_str) &&
+      !parse_timeout(connect_timeout_seconds_str, &connect_timeout_seconds_)) {
+    LOG(ERROR) << "Invalid string given for connect timeout: "
+               << connect_timeout_seconds_str;
+    return false;
+  }
+
+  std::string bootstrap_timeout_seconds_str;
+  if (config_store.GetString(kBootstrapTimeout,
+                             &bootstrap_timeout_seconds_str) &&
+      !parse_timeout(bootstrap_timeout_seconds_str,
+                     &bootstrap_timeout_seconds_)) {
+    LOG(ERROR) << "Invalid string given for bootstrap timeout: "
+               << bootstrap_timeout_seconds_str;
+    return false;
+  }
+
+  std::string monitor_timeout_seconds_str;
+  if (config_store.GetString(kMonitorTimeout, &monitor_timeout_seconds_str) &&
+      !parse_timeout(monitor_timeout_seconds_str, &monitor_timeout_seconds_)) {
+    LOG(ERROR) << "Invalid string given for monitor timeout: "
+               << monitor_timeout_seconds_str;
+    return false;
+  }
+
+  std::set<PairingType> pairing_modes;
+  std::string embedded_code_path;
+  if (config_store.GetString(kEmbeddedCodePath, &embedded_code_path)) {
+    embedded_code_path_ = base::FilePath(embedded_code_path);
+    if (!embedded_code_path_.empty())
+      pairing_modes.insert(PairingType::kEmbeddedCode);
+  }
+
+  std::string modes_str;
+  if (config_store.GetString(kPairingModes, &modes_str)) {
+    for (const std::string& mode :
+         chromeos::string_utils::Split(modes_str, ",", true, true)) {
+      PairingType pairing_mode;
+      if (!StringToPairingType(mode, &pairing_mode)) {
+        LOG(ERROR) << "Invalid pairing mode : " << mode;
+        return false;
+      }
+      pairing_modes.insert(pairing_mode);
+    }
+  }
+
+  if (!pairing_modes.empty())
+    pairing_modes_ = std::move(pairing_modes);
+
+  return true;
+}
+
+}  // namespace privetd
diff --git a/buffet/privet/privetd_conf_parser.h b/buffet/privet/privetd_conf_parser.h
new file mode 100644
index 0000000..846342f
--- /dev/null
+++ b/buffet/privet/privetd_conf_parser.h
@@ -0,0 +1,65 @@
+// 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_PRIVET_PRIVETD_CONF_PARSER_H_
+#define BUFFET_PRIVET_PRIVETD_CONF_PARSER_H_
+
+#include <set>
+#include <string>
+
+#include <chromeos/key_value_store.h>
+
+namespace privetd {
+
+extern const char kWiFiBootstrapInterfaces[];
+
+enum class WiFiBootstrapMode {
+  kDisabled,
+  kManual,
+  kAutomatic,
+};
+
+enum class GcdBootstrapMode {
+  kDisabled,
+  kManual,
+  kAutomatic,
+};
+
+enum class PairingType;
+
+class PrivetdConfigParser final {
+ public:
+  PrivetdConfigParser();
+
+  bool Parse(const chromeos::KeyValueStore& config_store);
+
+  WiFiBootstrapMode wifi_bootstrap_mode() const { return wifi_bootstrap_mode_; }
+  GcdBootstrapMode gcd_bootstrap_mode() const { return gcd_bootstrap_mode_; }
+  const std::set<std::string>& automatic_wifi_interfaces() const {
+    return automatic_wifi_interfaces_;
+  }
+  uint32_t connect_timeout_seconds() const { return connect_timeout_seconds_; }
+  uint32_t bootstrap_timeout_seconds() const {
+    return bootstrap_timeout_seconds_;
+  }
+  uint32_t monitor_timeout_seconds() const { return monitor_timeout_seconds_; }
+  const std::set<PairingType>& pairing_modes() { return pairing_modes_; }
+  const base::FilePath& embedded_code_path() const {
+    return embedded_code_path_;
+  }
+
+ private:
+  WiFiBootstrapMode wifi_bootstrap_mode_;
+  GcdBootstrapMode gcd_bootstrap_mode_;
+  std::set<std::string> automatic_wifi_interfaces_;
+  uint32_t connect_timeout_seconds_;
+  uint32_t bootstrap_timeout_seconds_;
+  uint32_t monitor_timeout_seconds_;
+  std::set<PairingType> pairing_modes_;
+  base::FilePath embedded_code_path_;
+};
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_PRIVETD_CONF_PARSER_H_
diff --git a/buffet/privet/privetd_conf_parser_unittest.cc b/buffet/privet/privetd_conf_parser_unittest.cc
new file mode 100644
index 0000000..8060dd7
--- /dev/null
+++ b/buffet/privet/privetd_conf_parser_unittest.cc
@@ -0,0 +1,138 @@
+// 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/privet/privetd_conf_parser.h"
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/logging.h>
+#include <chromeos/strings/string_utils.h>
+#include <gtest/gtest.h>
+
+#include "buffet/privet/security_delegate.h"
+
+using chromeos::string_utils::Join;
+using chromeos::KeyValueStore;
+using std::map;
+using std::string;
+
+namespace privetd {
+
+namespace {
+
+const char kWiFiBootstrapMode[] = "wifi_bootstrapping_mode";
+const char kGcdBootstrapMode[] = "gcd_bootstrapping_mode";
+const char kConnectTimeout[] = "connect_timeout_seconds";
+const char kBootstrapTimeout[] = "bootstrap_timeout_seconds";
+const char kMonitorTimeout[] = "monitor_timeout_seconds";
+const char kPairingModes[] = "pairing_modes";
+const char kEmbeddedCodePath[] = "embedded_code_path";
+
+}  // namespace
+
+class PrivetdConfParserTest : public testing::Test {
+ public:
+  using ConfDict = map<string, string>;
+
+  void SetUp() override {
+    CHECK(temp_dir_.CreateUniqueTempDir());
+    temp_file_ = temp_dir_.path().Append("temp.conf");
+  }
+
+  bool IsValid(const ConfDict& conf_dict) {
+    KeyValueStore store;
+    FillKeyValueStore(conf_dict, &store);
+    PrivetdConfigParser config;
+    return config.Parse(store);
+  }
+
+  void FillKeyValueStore(const ConfDict& conf_dict, KeyValueStore* store) {
+    std::vector<string> file_pieces;
+    for (const auto& it : conf_dict) {
+      file_pieces.push_back(Join("=", it.first, it.second));
+    }
+    string blob{Join("\n", file_pieces)};
+    int expected_len = blob.length();
+    CHECK(expected_len == base::WriteFile(temp_file_,
+                                          blob.c_str(),
+                                          expected_len));
+    CHECK(store->Load(temp_file_));
+  }
+
+ private:
+  base::FilePath temp_file_;
+  base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(PrivetdConfParserTest, ShouldRejectInvalidTimeouts) {
+  EXPECT_FALSE(IsValid({{kConnectTimeout, "-1"}}));
+  EXPECT_FALSE(IsValid({{kConnectTimeout, "a"}}));
+  EXPECT_FALSE(IsValid({{kConnectTimeout, ""}}));
+  EXPECT_FALSE(IsValid({{kConnectTimeout, "30 430"}}));
+}
+
+TEST_F(PrivetdConfParserTest, ShouldRejectInvalidWiFiBootstrapModes) {
+  EXPECT_FALSE(IsValid({{kWiFiBootstrapMode, ""}}));
+  EXPECT_FALSE(IsValid({{kWiFiBootstrapMode, "clown_shoes"}}));
+  EXPECT_FALSE(IsValid({{kWiFiBootstrapMode, "off is invalid"}}));
+  EXPECT_FALSE(IsValid({{kWiFiBootstrapMode, "30"}}));
+}
+
+TEST_F(PrivetdConfParserTest, ShouldRejectInvalidGcdBootstrapModes) {
+  EXPECT_FALSE(IsValid({{kGcdBootstrapMode, ""}}));
+  EXPECT_FALSE(IsValid({{kGcdBootstrapMode, "clown_shoes"}}));
+  EXPECT_FALSE(IsValid({{kGcdBootstrapMode, "off is invalid"}}));
+  EXPECT_FALSE(IsValid({{kGcdBootstrapMode, "30"}}));
+}
+
+TEST_F(PrivetdConfParserTest, ShouldParseSettings) {
+  const std::set<std::string> kExpectedWiFiInterfaces{"eth1", "clown shoes"};
+  const uint32_t kExpectedConnectTimeout{1};
+  const uint32_t kExpectedBootstrapTimeout{2};
+  const uint32_t kExpectedMonitorTimeout{3};
+  const std::set<PairingType> kExpectedPairingModes{PairingType::kEmbeddedCode,
+                                                    PairingType::kPinCode};
+  static const char kExpectedEmbeddedCodePath[]{"123ABC"};
+  const ConfDict conf_dict{
+      {kWiFiBootstrapMode, "automatic"},
+      {kGcdBootstrapMode, "automatic"},
+      {kWiFiBootstrapInterfaces, Join(",", kExpectedWiFiInterfaces)},
+      {kConnectTimeout, std::to_string(kExpectedConnectTimeout)},
+      {kBootstrapTimeout, std::to_string(kExpectedBootstrapTimeout)},
+      {kMonitorTimeout, std::to_string(kExpectedMonitorTimeout)},
+      {kPairingModes, "pinCode"},
+      {kEmbeddedCodePath, kExpectedEmbeddedCodePath},
+  };
+  KeyValueStore store;
+  FillKeyValueStore(conf_dict, &store);
+  PrivetdConfigParser parser;
+  EXPECT_TRUE(parser.Parse(store));
+  EXPECT_EQ(WiFiBootstrapMode::kAutomatic, parser.wifi_bootstrap_mode());
+  EXPECT_EQ(GcdBootstrapMode::kAutomatic, parser.gcd_bootstrap_mode());
+  EXPECT_EQ(kExpectedWiFiInterfaces, parser.automatic_wifi_interfaces());
+  EXPECT_EQ(kExpectedConnectTimeout, parser.connect_timeout_seconds());
+  EXPECT_EQ(kExpectedBootstrapTimeout, parser.bootstrap_timeout_seconds());
+  EXPECT_EQ(kExpectedMonitorTimeout, parser.monitor_timeout_seconds());
+  EXPECT_EQ(kExpectedPairingModes, parser.pairing_modes());
+  EXPECT_EQ(kExpectedEmbeddedCodePath, parser.embedded_code_path().value());
+}
+
+TEST_F(PrivetdConfParserTest, CriticalDefaults) {
+  PrivetdConfigParser parser;
+  EXPECT_EQ(WiFiBootstrapMode::kDisabled, parser.wifi_bootstrap_mode());
+  EXPECT_EQ(GcdBootstrapMode::kDisabled, parser.gcd_bootstrap_mode());
+  EXPECT_GT(parser.connect_timeout_seconds(), 0);
+  EXPECT_GT(parser.bootstrap_timeout_seconds(), 0);
+  EXPECT_GT(parser.monitor_timeout_seconds(), 0);
+  EXPECT_EQ(std::set<PairingType>{PairingType::kPinCode},
+            parser.pairing_modes());
+  EXPECT_TRUE(parser.embedded_code_path().empty());
+}
+
+}  // namespace privetd
diff --git a/buffet/privet/security_delegate.h b/buffet/privet/security_delegate.h
new file mode 100644
index 0000000..f69ffb2
--- /dev/null
+++ b/buffet/privet/security_delegate.h
@@ -0,0 +1,79 @@
+// 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_PRIVET_SECURITY_DELEGATE_H_
+#define BUFFET_PRIVET_SECURITY_DELEGATE_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include <base/time/time.h>
+#include <chromeos/secure_blob.h>
+
+#include "buffet/privet/privet_types.h"
+
+namespace privetd {
+
+enum class PairingType {
+  kPinCode,
+  kEmbeddedCode,
+  kUltrasound32,
+  kAudible32,
+};
+
+enum class CryptoType {
+  kNone,
+  kSpake_p224,
+  kSpake_p256,
+};
+
+// Interface to provide Security related logic for |PrivetHandler|.
+class SecurityDelegate {
+ public:
+  virtual ~SecurityDelegate() = default;
+
+  // Creates access token for the given scope, user id and |time|.
+  virtual std::string CreateAccessToken(const UserInfo& user_info,
+                                        const base::Time& time) = 0;
+
+  // Validates |token| and returns scope and user id parsed from that.
+  virtual UserInfo ParseAccessToken(const std::string& token,
+                                    base::Time* time) const = 0;
+
+  // Returns list of pairing methods by device.
+  virtual std::set<PairingType> GetPairingTypes() const = 0;
+
+  // Returns list of crypto methods supported by devices.
+  virtual std::set<CryptoType> GetCryptoTypes() const = 0;
+
+  // Returns true if |auth_code| provided by client is valid. Client should
+  // obtain |auth_code| during pairing process.
+  virtual bool IsValidPairingCode(const std::string& auth_code) const = 0;
+
+  virtual bool StartPairing(PairingType mode,
+                            CryptoType crypto,
+                            std::string* session_id,
+                            std::string* device_commitment,
+                            chromeos::ErrorPtr* error) = 0;
+
+  virtual bool ConfirmPairing(const std::string& session_id,
+                              const std::string& client_commitment,
+                              std::string* fingerprint,
+                              std::string* signature,
+                              chromeos::ErrorPtr* error) = 0;
+
+  virtual bool CancelPairing(const std::string& session_id,
+                             chromeos::ErrorPtr* error) = 0;
+};
+
+bool StringToPairingType(const std::string& mode, PairingType* id);
+std::string PairingTypeToString(PairingType id);
+
+bool StringToAuthScope(const std::string& scope, AuthScope* id);
+std::string AuthScopeToString(AuthScope id);
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_SECURITY_DELEGATE_H_
diff --git a/buffet/privet/security_manager.cc b/buffet/privet/security_manager.cc
new file mode 100644
index 0000000..5dc3b0c
--- /dev/null
+++ b/buffet/privet/security_manager.cc
@@ -0,0 +1,415 @@
+// 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/privet/security_manager.h"
+
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <set>
+
+#include <base/bind.h>
+#include <base/guid.h>
+#include <base/logging.h>
+#include <base/message_loop/message_loop.h>
+#include <base/rand_util.h>
+#include <base/stl_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <chromeos/data_encoding.h>
+#include <chromeos/key_value_store.h>
+#include <chromeos/strings/string_utils.h>
+#include <crypto/p224_spake.h>
+
+#include "buffet/privet/constants.h"
+#include "buffet/privet/openssl_utils.h"
+
+namespace privetd {
+
+namespace {
+
+const char kTokenDelimeter[] = ":";
+const int kSessionExpirationTimeMinutes = 5;
+const int kPairingExpirationTimeMinutes = 5;
+const int kMaxAllowedPairingAttemts = 3;
+const int kPairingBlockingTimeMinutes = 1;
+
+const char kEmbeddedCode[] = "embedded_code";
+
+// Returns "scope:id:time".
+std::string CreateTokenData(const UserInfo& user_info, const base::Time& time) {
+  return base::IntToString(static_cast<int>(user_info.scope())) +
+         kTokenDelimeter + base::Uint64ToString(user_info.user_id()) +
+         kTokenDelimeter + base::Int64ToString(time.ToTimeT());
+}
+
+// Splits string of "scope:id:time" format.
+UserInfo SplitTokenData(const std::string& token, base::Time* time) {
+  const UserInfo kNone;
+  auto parts = chromeos::string_utils::Split(token, kTokenDelimeter);
+  if (parts.size() != 3)
+    return kNone;
+  int scope = 0;
+  if (!base::StringToInt(parts[0], &scope) ||
+      scope < static_cast<int>(AuthScope::kNone) ||
+      scope > static_cast<int>(AuthScope::kOwner)) {
+    return kNone;
+  }
+
+  uint64_t id{0};
+  if (!base::StringToUint64(parts[1], &id))
+    return kNone;
+
+  int64_t timestamp{0};
+  if (!base::StringToInt64(parts[2], &timestamp))
+    return kNone;
+  *time = base::Time::FromTimeT(timestamp);
+  return UserInfo{static_cast<AuthScope>(scope), id};
+}
+
+std::string LoadEmbeddedCode(const base::FilePath& path) {
+  std::string code;
+  chromeos::KeyValueStore store;
+  if (store.Load(path))
+    store.GetString(kEmbeddedCode, &code);
+  return code;
+}
+
+class Spakep224Exchanger : public SecurityManager::KeyExchanger {
+ public:
+  explicit Spakep224Exchanger(const std::string& password)
+      : spake_(crypto::P224EncryptedKeyExchange::kPeerTypeServer, password) {}
+  ~Spakep224Exchanger() override = default;
+
+  // SecurityManager::KeyExchanger methods.
+  const std::string& GetMessage() override { return spake_.GetNextMessage(); }
+
+  bool ProcessMessage(const std::string& message,
+                      chromeos::ErrorPtr* error) override {
+    switch (spake_.ProcessMessage(message)) {
+      case crypto::P224EncryptedKeyExchange::kResultPending:
+        return true;
+      case crypto::P224EncryptedKeyExchange::kResultFailed:
+        chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                               errors::kInvalidClientCommitment,
+                               spake_.error());
+        return false;
+      default:
+        LOG(FATAL) << "SecurityManager uses only one round trip";
+    }
+    return false;
+  }
+
+  const std::string& GetKey() const override {
+    return spake_.GetUnverifiedKey();
+  }
+
+ private:
+  crypto::P224EncryptedKeyExchange spake_;
+};
+
+class UnsecureKeyExchanger : public SecurityManager::KeyExchanger {
+ public:
+  explicit UnsecureKeyExchanger(const std::string& password)
+      : password_(password) {}
+  ~UnsecureKeyExchanger() override = default;
+
+  // SecurityManager::KeyExchanger methods.
+  const std::string& GetMessage() override { return password_; }
+
+  bool ProcessMessage(const std::string& message,
+                      chromeos::ErrorPtr* error) override {
+    return true;
+  }
+
+  const std::string& GetKey() const override { return password_; }
+
+ private:
+  std::string password_;
+};
+
+}  // namespace
+
+SecurityManager::SecurityManager(const std::set<PairingType>& pairing_modes,
+                                 const base::FilePath& embedded_code_path,
+                                 bool disable_security)
+    : is_security_disabled_(disable_security),
+      pairing_modes_(pairing_modes),
+      embedded_code_path_(embedded_code_path),
+      secret_(kSha256OutputSize) {
+  base::RandBytes(secret_.data(), kSha256OutputSize);
+
+  CHECK_EQ(embedded_code_path_.empty(),
+           std::find(pairing_modes_.begin(), pairing_modes_.end(),
+                     PairingType::kEmbeddedCode) == pairing_modes_.end());
+}
+
+SecurityManager::~SecurityManager() {
+  while (!pending_sessions_.empty())
+    ClosePendingSession(pending_sessions_.begin()->first);
+}
+
+// Returns "base64([hmac]scope:id:time)".
+std::string SecurityManager::CreateAccessToken(const UserInfo& user_info,
+                                               const base::Time& time) {
+  chromeos::SecureBlob data(CreateTokenData(user_info, time));
+  chromeos::Blob hash(HmacSha256(secret_, data));
+  return chromeos::data_encoding::Base64Encode(chromeos::SecureBlob::Combine(
+      chromeos::SecureBlob(hash.begin(), hash.end()), data));
+}
+
+// Parses "base64([hmac]scope:id:time)".
+UserInfo SecurityManager::ParseAccessToken(const std::string& token,
+                                           base::Time* time) const {
+  chromeos::Blob decoded;
+  if (!chromeos::data_encoding::Base64Decode(token, &decoded) ||
+      decoded.size() <= kSha256OutputSize) {
+    return UserInfo{};
+  }
+  chromeos::SecureBlob data(decoded.begin() + kSha256OutputSize, decoded.end());
+  decoded.resize(kSha256OutputSize);
+  if (decoded != HmacSha256(secret_, data))
+    return UserInfo{};
+  return SplitTokenData(data.to_string(), time);
+}
+
+std::set<PairingType> SecurityManager::GetPairingTypes() const {
+  return pairing_modes_;
+}
+
+std::set<CryptoType> SecurityManager::GetCryptoTypes() const {
+  std::set<CryptoType> result{CryptoType::kSpake_p224};
+  if (is_security_disabled_)
+    result.insert(CryptoType::kNone);
+  return result;
+}
+
+bool SecurityManager::IsValidPairingCode(const std::string& auth_code) const {
+  if (is_security_disabled_)
+    return true;
+  chromeos::Blob auth_decoded;
+  if (!chromeos::data_encoding::Base64Decode(auth_code, &auth_decoded))
+    return false;
+  for (const auto& session : confirmed_sessions_) {
+    if (auth_decoded ==
+        HmacSha256(chromeos::SecureBlob{session.second->GetKey()},
+                   chromeos::SecureBlob{session.first})) {
+      pairing_attemts_ = 0;
+      block_pairing_until_ = base::Time{};
+      return true;
+    }
+  }
+  LOG(ERROR) << "Attempt to authenticate with invalide code.";
+  return false;
+}
+
+bool SecurityManager::StartPairing(PairingType mode,
+                                   CryptoType crypto,
+                                   std::string* session_id,
+                                   std::string* device_commitment,
+                                   chromeos::ErrorPtr* error) {
+  if (!CheckIfPairingAllowed(error))
+    return false;
+
+  if (std::find(pairing_modes_.begin(), pairing_modes_.end(), mode) ==
+      pairing_modes_.end()) {
+    chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                           errors::kInvalidParams,
+                           "Pairing mode is not enabled");
+    return false;
+  }
+
+  std::string code;
+  switch (mode) {
+    case PairingType::kEmbeddedCode:
+      CHECK(!embedded_code_path_.empty());
+
+      if (embedded_code_.empty())
+        embedded_code_ = LoadEmbeddedCode(embedded_code_path_);
+
+      if (embedded_code_.empty()) {  // File is not created yet.
+        chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                               errors::kDeviceBusy,
+                               "Embedded code is not ready");
+        return false;
+      }
+
+      code = embedded_code_;
+      break;
+    case PairingType::kUltrasound32:
+    case PairingType::kAudible32: {
+      code = base::RandBytesAsString(4);
+      break;
+    }
+    case PairingType::kPinCode:
+      code = base::StringPrintf("%04i", base::RandInt(0, 9999));
+      break;
+    default:
+      chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                             errors::kInvalidParams,
+                             "Unsupported pairing mode");
+      return false;
+  }
+
+  std::unique_ptr<KeyExchanger> spake;
+  switch (crypto) {
+    case CryptoType::kSpake_p224:
+      spake.reset(new Spakep224Exchanger(code));
+      break;
+    case CryptoType::kNone:
+      if (is_security_disabled_) {
+        spake.reset(new UnsecureKeyExchanger(code));
+        break;
+      }
+    // Fall through...
+    default:
+      chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                             errors::kInvalidParams, "Unsupported crypto");
+      return false;
+  }
+
+  // Allow only a single session at a time for now.
+  while (!pending_sessions_.empty())
+    ClosePendingSession(pending_sessions_.begin()->first);
+
+  std::string session;
+  do {
+    session = base::GenerateGUID();
+  } while (confirmed_sessions_.find(session) != confirmed_sessions_.end() ||
+           pending_sessions_.find(session) != pending_sessions_.end());
+  std::string commitment = spake->GetMessage();
+  pending_sessions_.emplace(session, std::move(spake));
+
+  base::MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(base::IgnoreResult(&SecurityManager::ClosePendingSession),
+                 weak_ptr_factory_.GetWeakPtr(), session),
+      base::TimeDelta::FromMinutes(kPairingExpirationTimeMinutes));
+
+  *session_id = session;
+  *device_commitment = chromeos::data_encoding::Base64Encode(commitment);
+  LOG(INFO) << "Pairing code for session " << *session_id << " is " << code;
+  // TODO(vitalybuka): Handle case when device can't start multiple pairing
+  // simultaneously and implement throttling to avoid brute force attack.
+  if (!on_start_.is_null()) {
+    on_start_.Run(session, mode,
+                  chromeos::string_utils::GetStringAsBytes(code));
+  }
+
+  return true;
+}
+
+bool SecurityManager::ConfirmPairing(const std::string& session_id,
+                                     const std::string& client_commitment,
+                                     std::string* fingerprint,
+                                     std::string* signature,
+                                     chromeos::ErrorPtr* error) {
+  auto session = pending_sessions_.find(session_id);
+  if (session == pending_sessions_.end()) {
+    chromeos::Error::AddToPrintf(
+        error, FROM_HERE, errors::kDomain, errors::kUnknownSession,
+        "Unknown session id: '%s'", session_id.c_str());
+    return false;
+  }
+  CHECK(!certificate_fingerprint_.empty());
+
+  chromeos::Blob commitment;
+  if (!chromeos::data_encoding::Base64Decode(client_commitment, &commitment)) {
+    ClosePendingSession(session_id);
+    chromeos::Error::AddToPrintf(
+        error, FROM_HERE, errors::kDomain, errors::kInvalidFormat,
+        "Invalid commitment string: '%s'", client_commitment.c_str());
+    return false;
+  }
+
+  if (!session->second->ProcessMessage(
+          std::string(commitment.begin(), commitment.end()), error)) {
+    ClosePendingSession(session_id);
+    chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                           errors::kCommitmentMismatch,
+                           "Pairing code or crypto implementation mismatch");
+    return false;
+  }
+
+  std::string key = session->second->GetKey();
+  VLOG(3) << "KEY " << base::HexEncode(key.data(), key.size());
+
+  *fingerprint =
+      chromeos::data_encoding::Base64Encode(certificate_fingerprint_);
+  chromeos::Blob cert_hmac =
+      HmacSha256(chromeos::SecureBlob(session->second->GetKey()),
+                 certificate_fingerprint_);
+  *signature = chromeos::data_encoding::Base64Encode(cert_hmac);
+  confirmed_sessions_.emplace(session->first, std::move(session->second));
+  base::MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(base::IgnoreResult(&SecurityManager::CloseConfirmedSession),
+                 weak_ptr_factory_.GetWeakPtr(), session_id),
+      base::TimeDelta::FromMinutes(kSessionExpirationTimeMinutes));
+  ClosePendingSession(session_id);
+  return true;
+}
+
+bool SecurityManager::CancelPairing(const std::string& session_id,
+                                    chromeos::ErrorPtr* error) {
+  bool confirmed = CloseConfirmedSession(session_id);
+  bool pending = ClosePendingSession(session_id);
+  if (pending) {
+    CHECK_GE(pairing_attemts_, 1);
+    --pairing_attemts_;
+  }
+  CHECK(!confirmed || !pending);
+  if (confirmed || pending)
+    return true;
+  chromeos::Error::AddToPrintf(error, FROM_HERE, errors::kDomain,
+                               errors::kUnknownSession,
+                               "Unknown session id: '%s'", session_id.c_str());
+  return false;
+}
+
+void SecurityManager::RegisterPairingListeners(
+    const PairingStartListener& on_start,
+    const PairingEndListener& on_end) {
+  CHECK(on_start_.is_null() && on_end_.is_null());
+  on_start_ = on_start;
+  on_end_  = on_end;
+}
+
+bool SecurityManager::CheckIfPairingAllowed(chromeos::ErrorPtr* error) {
+  if (is_security_disabled_)
+    return true;
+
+  if (block_pairing_until_ > base::Time::Now()) {
+    chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain,
+                           errors::kDeviceBusy, "Too many pairing attempts");
+    return false;
+  }
+
+  if (++pairing_attemts_ >= kMaxAllowedPairingAttemts) {
+    LOG(INFO) << "Pairing blocked for" << kPairingBlockingTimeMinutes
+              << "minutes.";
+    block_pairing_until_ = base::Time::Now();
+    block_pairing_until_ +=
+        base::TimeDelta::FromMinutes(kPairingBlockingTimeMinutes);
+  }
+
+  return true;
+}
+
+bool SecurityManager::ClosePendingSession(const std::string& session_id) {
+  // The most common source of these session_id values is the map containing
+  // the sessions, which we're about to clear out.  Make a local copy.
+  const std::string safe_session_id{session_id};
+  const size_t num_erased = pending_sessions_.erase(safe_session_id);
+  if (num_erased > 0 && !on_end_.is_null())
+    on_end_.Run(safe_session_id);
+  return num_erased != 0;
+}
+
+bool SecurityManager::CloseConfirmedSession(const std::string& session_id) {
+  return confirmed_sessions_.erase(session_id) != 0;
+}
+
+}  // namespace privetd
diff --git a/buffet/privet/security_manager.h b/buffet/privet/security_manager.h
new file mode 100644
index 0000000..04a942e
--- /dev/null
+++ b/buffet/privet/security_manager.h
@@ -0,0 +1,110 @@
+// 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_PRIVET_SECURITY_MANAGER_H_
+#define BUFFET_PRIVET_SECURITY_MANAGER_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <base/memory/weak_ptr.h>
+#include <chromeos/errors/error.h>
+#include <chromeos/secure_blob.h>
+
+#include "buffet/privet/security_delegate.h"
+
+namespace crypto {
+class P224EncryptedKeyExchange;
+}  // namespace crypto
+
+namespace privetd {
+
+class SecurityManager : public SecurityDelegate {
+ public:
+  using PairingStartListener =
+      base::Callback<void(const std::string& session_id,
+                          PairingType pairing_type,
+                          const std::vector<uint8_t>& code)>;
+  using PairingEndListener =
+      base::Callback<void(const std::string& session_id)>;
+
+  class KeyExchanger {
+   public:
+    virtual ~KeyExchanger() = default;
+
+    virtual const std::string& GetMessage() = 0;
+    virtual bool ProcessMessage(const std::string& message,
+                                chromeos::ErrorPtr* error) = 0;
+    virtual const std::string& GetKey() const = 0;
+  };
+
+  SecurityManager(const std::set<PairingType>& pairing_modes,
+                  const base::FilePath& embedded_code_path,
+                  bool disable_security = false);
+  ~SecurityManager() override;
+
+  // SecurityDelegate methods
+  std::string CreateAccessToken(const UserInfo& user_info,
+                                const base::Time& time) override;
+  UserInfo ParseAccessToken(const std::string& token,
+                            base::Time* time) const override;
+  std::set<PairingType> GetPairingTypes() const override;
+  std::set<CryptoType> GetCryptoTypes() const override;
+  bool IsValidPairingCode(const std::string& auth_code) const override;
+
+  bool StartPairing(PairingType mode,
+                    CryptoType crypto,
+                    std::string* session_id,
+                    std::string* device_commitment,
+                    chromeos::ErrorPtr* error) override;
+
+  bool ConfirmPairing(const std::string& session_id,
+                      const std::string& client_commitment,
+                      std::string* fingerprint,
+                      std::string* signature,
+                      chromeos::ErrorPtr* error) override;
+  bool CancelPairing(const std::string& session_id,
+                     chromeos::ErrorPtr* error) override;
+
+  void RegisterPairingListeners(const PairingStartListener& on_start,
+                                const PairingEndListener& on_end);
+
+  void SetCertificateFingerprint(const chromeos::Blob& fingerprint) {
+    certificate_fingerprint_ = fingerprint;
+  }
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(SecurityManagerTest, ThrottlePairing);
+  // Allows limited number of new sessions without successful authorization.
+  bool CheckIfPairingAllowed(chromeos::ErrorPtr* error);
+  bool ClosePendingSession(const std::string& session_id);
+  bool CloseConfirmedSession(const std::string& session_id);
+
+  // If true allows unencrypted pairing and accepts any access code.
+  bool is_security_disabled_{false};
+  std::set<PairingType> pairing_modes_;
+  const base::FilePath embedded_code_path_;
+  std::string embedded_code_;
+  std::map<std::string, std::unique_ptr<KeyExchanger>> pending_sessions_;
+  std::map<std::string, std::unique_ptr<KeyExchanger>> confirmed_sessions_;
+  mutable int pairing_attemts_{0};
+  mutable base::Time block_pairing_until_;
+  chromeos::SecureBlob secret_;
+  chromeos::Blob certificate_fingerprint_;
+  PairingStartListener on_start_;
+  PairingEndListener on_end_;
+
+  base::WeakPtrFactory<SecurityManager> weak_ptr_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(SecurityManager);
+};
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_SECURITY_MANAGER_H_
diff --git a/buffet/privet/security_manager_unittest.cc b/buffet/privet/security_manager_unittest.cc
new file mode 100644
index 0000000..32b7463
--- /dev/null
+++ b/buffet/privet/security_manager_unittest.cc
@@ -0,0 +1,316 @@
+// 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/privet/security_manager.h"
+
+#include <algorithm>
+#include <cctype>
+#include <functional>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/message_loop/message_loop.h>
+#include <base/rand_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <chromeos/data_encoding.h>
+#include <chromeos/key_value_store.h>
+#include <chromeos/strings/string_utils.h>
+#include <crypto/p224_spake.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "buffet/privet/openssl_utils.h"
+
+using testing::Eq;
+using testing::_;
+
+namespace privetd {
+
+namespace {
+
+bool IsBase64Char(char c) {
+  return isalnum(c) || (c == '+') || (c == '/') || (c == '=');
+}
+
+bool IsBase64(const std::string& text) {
+  return !text.empty() &&
+         !std::any_of(text.begin(), text.end(),
+                      std::not1(std::ref(IsBase64Char)));
+}
+
+class MockPairingCallbacks {
+ public:
+  MOCK_METHOD3(OnPairingStart,
+               void(const std::string& session_id,
+                    PairingType pairing_type,
+                    const std::vector<uint8_t>& code));
+  MOCK_METHOD1(OnPairingEnd, void(const std::string& session_id));
+};
+
+base::FilePath GetTempFilePath() {
+  base::FilePath file_path;
+  EXPECT_TRUE(base::CreateTemporaryFile(&file_path));
+  return file_path;
+}
+
+}  // namespace
+
+class SecurityManagerTest : public testing::Test {
+ public:
+  void SetUp() override {
+    chromeos::Blob fingerprint;
+    fingerprint.resize(256 / 8);
+    base::RandBytes(fingerprint.data(), fingerprint.size());
+    security_.SetCertificateFingerprint(fingerprint);
+
+    chromeos::KeyValueStore store;
+    store.SetString("embedded_code", "1234");
+    EXPECT_TRUE(store.Save(embedded_code_path_));
+  }
+
+  void TearDown() override { base::DeleteFile(embedded_code_path_, false); }
+
+ protected:
+  void PairAndAuthenticate(std::string* fingerprint, std::string* signature) {
+    std::string session_id;
+    std::string device_commitment_base64;
+
+    EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
+                                       CryptoType::kSpake_p224, &session_id,
+                                       &device_commitment_base64, nullptr));
+    EXPECT_FALSE(session_id.empty());
+    EXPECT_FALSE(device_commitment_base64.empty());
+
+    crypto::P224EncryptedKeyExchange spake{
+        crypto::P224EncryptedKeyExchange::kPeerTypeClient, "1234"};
+
+    std::string client_commitment_base64{
+        chromeos::data_encoding::Base64Encode(spake.GetNextMessage())};
+
+    EXPECT_TRUE(security_.ConfirmPairing(session_id, client_commitment_base64,
+                                         fingerprint, signature, nullptr));
+    EXPECT_TRUE(IsBase64(*fingerprint));
+    EXPECT_TRUE(IsBase64(*signature));
+
+    chromeos::Blob device_commitment;
+    ASSERT_TRUE(chromeos::data_encoding::Base64Decode(device_commitment_base64,
+                                                      &device_commitment));
+    spake.ProcessMessage(
+        chromeos::string_utils::GetBytesAsString(device_commitment));
+
+    chromeos::Blob auth_code{
+        HmacSha256(chromeos::SecureBlob{spake.GetUnverifiedKey()},
+                   chromeos::SecureBlob{session_id})};
+
+    std::string auth_code_base64{
+        chromeos::data_encoding::Base64Encode(auth_code)};
+
+    EXPECT_TRUE(security_.IsValidPairingCode(auth_code_base64));
+  }
+
+  const base::Time time_ = base::Time::FromTimeT(1410000000);
+  base::MessageLoop message_loop_;
+  base::FilePath embedded_code_path_{GetTempFilePath()};
+  SecurityManager security_{{PairingType::kEmbeddedCode}, embedded_code_path_};
+};
+
+TEST_F(SecurityManagerTest, IsBase64) {
+  EXPECT_TRUE(IsBase64(
+      security_.CreateAccessToken(UserInfo{AuthScope::kUser, 7}, time_)));
+}
+
+TEST_F(SecurityManagerTest, CreateSameToken) {
+  EXPECT_EQ(
+      security_.CreateAccessToken(UserInfo{AuthScope::kViewer, 555}, time_),
+      security_.CreateAccessToken(UserInfo{AuthScope::kViewer, 555}, time_));
+}
+
+TEST_F(SecurityManagerTest, CreateTokenDifferentScope) {
+  EXPECT_NE(
+      security_.CreateAccessToken(UserInfo{AuthScope::kViewer, 456}, time_),
+      security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 456}, time_));
+}
+
+TEST_F(SecurityManagerTest, CreateTokenDifferentUser) {
+  EXPECT_NE(
+      security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 456}, time_),
+      security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 789}, time_));
+}
+
+TEST_F(SecurityManagerTest, CreateTokenDifferentTime) {
+  EXPECT_NE(
+      security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 567}, time_),
+      security_.CreateAccessToken(UserInfo{AuthScope::kOwner, 567},
+                                  base::Time::FromTimeT(1400000000)));
+}
+
+TEST_F(SecurityManagerTest, CreateTokenDifferentInstance) {
+  EXPECT_NE(security_.CreateAccessToken(UserInfo{AuthScope::kUser, 123}, time_),
+            SecurityManager({}, base::FilePath{})
+                .CreateAccessToken(UserInfo{AuthScope::kUser, 123}, time_));
+}
+
+TEST_F(SecurityManagerTest, ParseAccessToken) {
+  // Multiple attempts with random secrets.
+  for (size_t i = 0; i < 1000; ++i) {
+    SecurityManager security{{}, base::FilePath{}};
+
+    std::string token =
+        security.CreateAccessToken(UserInfo{AuthScope::kUser, 5}, time_);
+    base::Time time2;
+    EXPECT_EQ(AuthScope::kUser,
+              security.ParseAccessToken(token, &time2).scope());
+    EXPECT_EQ(5u, security.ParseAccessToken(token, &time2).user_id());
+    // Token timestamp resolution is one second.
+    EXPECT_GE(1, std::abs((time_ - time2).InSeconds()));
+  }
+}
+
+TEST_F(SecurityManagerTest, PairingNoSession) {
+  std::string fingerprint;
+  std::string signature;
+  chromeos::ErrorPtr error;
+  ASSERT_FALSE(
+      security_.ConfirmPairing("123", "345", &fingerprint, &signature, &error));
+  EXPECT_EQ("unknownSession", error->GetCode());
+}
+
+TEST_F(SecurityManagerTest, Pairing) {
+  std::vector<std::pair<std::string, std::string> > fingerprints(2);
+  for (auto& it : fingerprints) {
+    PairAndAuthenticate(&it.first, &it.second);
+  }
+
+  // Same certificate.
+  EXPECT_EQ(fingerprints.front().first, fingerprints.back().first);
+
+  // Signed with different secret.
+  EXPECT_NE(fingerprints.front().second, fingerprints.back().second);
+}
+
+TEST_F(SecurityManagerTest, NotifiesListenersOfSessionStartAndEnd) {
+  testing::StrictMock<MockPairingCallbacks> callbacks;
+  security_.RegisterPairingListeners(
+      base::Bind(&MockPairingCallbacks::OnPairingStart,
+                 base::Unretained(&callbacks)),
+      base::Bind(&MockPairingCallbacks::OnPairingEnd,
+                 base::Unretained(&callbacks)));
+  for (auto commitment_suffix :
+       std::vector<std::string>{"", "invalid_commitment"}) {
+    // StartPairing should notify us that a new session has begun.
+    std::string session_id;
+    std::string device_commitment;
+    EXPECT_CALL(callbacks, OnPairingStart(_, PairingType::kEmbeddedCode, _));
+    EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
+                                       CryptoType::kSpake_p224, &session_id,
+                                       &device_commitment, nullptr));
+    EXPECT_FALSE(session_id.empty());
+    EXPECT_FALSE(device_commitment.empty());
+    testing::Mock::VerifyAndClearExpectations(&callbacks);
+
+    // ConfirmPairing should notify us that the session has ended.
+    EXPECT_CALL(callbacks, OnPairingEnd(Eq(session_id)));
+    crypto::P224EncryptedKeyExchange spake{
+        crypto::P224EncryptedKeyExchange::kPeerTypeServer, "1234"};
+    std::string client_commitment =
+        chromeos::data_encoding::Base64Encode(spake.GetNextMessage());
+    std::string fingerprint, signature;
+    // Regardless of whether the commitment is valid or not, we should get a
+    // callback indicating that the pairing session is gone.
+    security_.ConfirmPairing(session_id, client_commitment + commitment_suffix,
+                             &fingerprint, &signature, nullptr);
+    testing::Mock::VerifyAndClearExpectations(&callbacks);
+  }
+}
+
+TEST_F(SecurityManagerTest, CancelPairing) {
+  testing::StrictMock<MockPairingCallbacks> callbacks;
+  security_.RegisterPairingListeners(
+      base::Bind(&MockPairingCallbacks::OnPairingStart,
+                 base::Unretained(&callbacks)),
+      base::Bind(&MockPairingCallbacks::OnPairingEnd,
+                 base::Unretained(&callbacks)));
+  std::string session_id;
+  std::string device_commitment;
+  EXPECT_CALL(callbacks, OnPairingStart(_, PairingType::kEmbeddedCode, _));
+  EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
+                                     CryptoType::kSpake_p224, &session_id,
+                                     &device_commitment, nullptr));
+  EXPECT_CALL(callbacks, OnPairingEnd(Eq(session_id)));
+  EXPECT_TRUE(security_.CancelPairing(session_id, nullptr));
+}
+
+TEST_F(SecurityManagerTest, ThrottlePairing) {
+  auto pair = [this]() {
+    std::string session_id;
+    std::string device_commitment;
+    chromeos::ErrorPtr error;
+    bool result = security_.StartPairing(PairingType::kEmbeddedCode,
+                                         CryptoType::kSpake_p224, &session_id,
+                                         &device_commitment, &error);
+    EXPECT_TRUE(result || error->GetCode() == "deviceBusy");
+    return result;
+  };
+
+  EXPECT_TRUE(pair());
+  EXPECT_TRUE(pair());
+  EXPECT_TRUE(pair());
+  EXPECT_FALSE(pair());
+  EXPECT_GT(security_.block_pairing_until_, base::Time::Now());
+  EXPECT_LE(security_.block_pairing_until_,
+            base::Time::Now() + base::TimeDelta::FromMinutes(15));
+
+  // Wait timeout.
+  security_.block_pairing_until_ =
+      base::Time::Now() - base::TimeDelta::FromMinutes(1);
+
+  // Allow exactly one attempt.
+  EXPECT_TRUE(pair());
+  EXPECT_FALSE(pair());
+
+  // Wait timeout.
+  security_.block_pairing_until_ =
+      base::Time::Now() - base::TimeDelta::FromMinutes(1);
+
+  // Completely unblock by successfully pairing.
+  std::string fingerprint;
+  std::string signature;
+  PairAndAuthenticate(&fingerprint, &signature);
+
+  // Now we have 3 attempts again.
+  EXPECT_TRUE(pair());
+  EXPECT_TRUE(pair());
+  EXPECT_TRUE(pair());
+  EXPECT_FALSE(pair());
+}
+
+TEST_F(SecurityManagerTest, DontBlockForCanceledSessions) {
+  for (int i = 0; i < 20; ++i) {
+    std::string session_id;
+    std::string device_commitment;
+    EXPECT_TRUE(security_.StartPairing(PairingType::kEmbeddedCode,
+                                       CryptoType::kSpake_p224, &session_id,
+                                       &device_commitment, nullptr));
+    EXPECT_TRUE(security_.CancelPairing(session_id, nullptr));
+  }
+}
+
+TEST_F(SecurityManagerTest, EmbeddedCodeNotReady) {
+  std::string session_id;
+  std::string device_commitment;
+  base::DeleteFile(embedded_code_path_, false);
+  chromeos::ErrorPtr error;
+  ASSERT_FALSE(security_.StartPairing(PairingType::kEmbeddedCode,
+                                      CryptoType::kSpake_p224, &session_id,
+                                      &device_commitment, &error));
+  EXPECT_EQ("deviceBusy", error->GetCode());
+}
+
+}  // namespace privetd
diff --git a/buffet/privet/shill_client.cc b/buffet/privet/shill_client.cc
new file mode 100644
index 0000000..335d568
--- /dev/null
+++ b/buffet/privet/shill_client.cc
@@ -0,0 +1,515 @@
+// 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/privet/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>
+
+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 privetd {
+
+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;
+}
+
+ServiceState ShillServiceStateToServiceState(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 ServiceState::kConnected;
+  }
+  if ((state.compare(shill::kStateAssociation) == 0) ||
+      (state.compare(shill::kStateConfiguration) == 0)) {
+    return ServiceState::kConnecting;
+  }
+  if ((state.compare(shill::kStateFailure) == 0) ||
+      (state.compare(shill::kStateActivationFailure) == 0)) {
+    // TODO(wiley) Get error information off the service object.
+    return ServiceState::kFailure;
+  }
+  if ((state.compare(shill::kStateIdle) == 0) ||
+      (state.compare(shill::kStateOffline) == 0) ||
+      (state.compare(shill::kStateDisconnect) == 0)) {
+    return ServiceState::kOffline;
+  }
+  LOG(WARNING) << "Unknown state found: '" << state << "'";
+  return ServiceState::kOffline;
+}
+
+}  // namespace
+
+std::string ServiceStateToString(ServiceState state) {
+  switch (state) {
+    case ServiceState::kOffline:
+      return "offline";
+    case ServiceState::kFailure:
+      return "failure";
+    case ServiceState::kConnecting:
+      return "connecting";
+    case ServiceState::kConnected:
+      return "connected";
+  }
+  LOG(ERROR) << "Unknown ServiceState value!";
+  return "unknown";
+}
+
+ShillClient::ShillClient(const scoped_refptr<dbus::Bus>& bus,
+                         const set<string>& device_whitelist)
+    : bus_{bus},
+      manager_proxy_{bus_, ObjectPath{"/"}},
+      device_whitelist_{device_whitelist} {
+  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);
+}
+
+void ShillClient::Init() {
+  VLOG(2) << "ShillClient::Init();";
+  CleanupConnectingService(false);
+  devices_.clear();
+  connectivity_state_ = ServiceState::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;
+}
+
+ServiceState ShillClient::GetConnectionState() const {
+  return connectivity_state_;
+}
+
+bool ShillClient::AmOnline() const {
+  return connectivity_state_ == ServiceState::kConnected;
+}
+
+void ShillClient::RegisterConnectivityListener(
+    const ConnectivityListener& 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_ = ServiceState::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 = ServiceState::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 = ShillServiceStateToServiceState(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 ||
+      ShillServiceStateToServiceState(state) != ServiceState::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 = ShillServiceStateToServiceState(state);
+      UpdateConnectivityState();
+      return;
+    }
+  }
+}
+
+void ShillClient::UpdateConnectivityState() {
+  // Update the connectivity state of the device by picking the
+  // state of the currently most connected selected service.
+  ServiceState new_connectivity_state{ServiceState::kOffline};
+  for (const auto& kv : devices_) {
+    if (kv.second.service_state > new_connectivity_state) {
+      new_connectivity_state = kv.second.service_state;
+    }
+  }
+  VLOG(3) << "New connectivity state is "
+          << ServiceStateToString(new_connectivity_state);
+  if (new_connectivity_state != connectivity_state_) {
+    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(), AmOnline()));
+  }
+}
+
+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 privetd
diff --git a/buffet/privet/shill_client.h b/buffet/privet/shill_client.h
new file mode 100644
index 0000000..d8e6959
--- /dev/null
+++ b/buffet/privet/shill_client.h
@@ -0,0 +1,134 @@
+// 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_PRIVET_SHILL_CLIENT_H_
+#define BUFFET_PRIVET_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 "shill/dbus-proxies.h"
+
+namespace privetd {
+
+enum class ServiceState {
+  kOffline = 0,
+  kFailure,
+  kConnecting,
+  kConnected,
+};
+
+std::string ServiceStateToString(ServiceState state);
+
+class ShillClient final {
+ public:
+  // A callback that interested parties can register to be notified of
+  // transitions from online to offline and vice versa.  The boolean
+  // parameter will be true if we're online, and false if we're offline.
+  using ConnectivityListener = base::Callback<void(bool)>;
+
+  ShillClient(const scoped_refptr<dbus::Bus>& bus,
+              const std::set<std::string>& device_whitelist);
+  ~ShillClient() = default;
+
+
+  void Init();
+  void RegisterConnectivityListener(const ConnectivityListener& listener);
+  // Causes shill to attempt to connect to the given network with the given
+  // passphrase.  This is accomplished by:
+  //  1) Configuring a service through the Manager with the SSID and passphrase.
+  //  2) Calling Connect() on the service.
+  //  2) Monitoring the returned Service object until we reach an online state,
+  //     an error state, or another call to ConnectToService() occurs.
+  // Returns false on immediate failures with some descriptive codes in |error|.
+  bool ConnectToService(const std::string& ssid,
+                        const std::string& passphrase,
+                        const base::Closure& on_success,
+                        chromeos::ErrorPtr* error);
+  ServiceState GetConnectionState() const;
+  bool AmOnline() const;
+
+ 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;
+    ServiceState service_state{ServiceState::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<ConnectivityListener> 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_;
+  ServiceState connectivity_state_{ServiceState::kOffline};
+
+  base::WeakPtrFactory<ShillClient> weak_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(ShillClient);
+};
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_SHILL_CLIENT_H_
diff --git a/buffet/privet/wifi_bootstrap_manager.cc b/buffet/privet/wifi_bootstrap_manager.cc
new file mode 100644
index 0000000..6c2bc4f
--- /dev/null
+++ b/buffet/privet/wifi_bootstrap_manager.cc
@@ -0,0 +1,293 @@
+// 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/privet/wifi_bootstrap_manager.h"
+
+#include <base/logging.h>
+#include <base/memory/weak_ptr.h>
+#include <base/message_loop/message_loop.h>
+#include <chromeos/bind_lambda.h>
+#include <chromeos/key_value_store.h>
+
+#include "buffet/privet/ap_manager_client.h"
+#include "buffet/privet/constants.h"
+#include "buffet/privet/shill_client.h"
+
+namespace privetd {
+
+WifiBootstrapManager::WifiBootstrapManager(DaemonState* state_store,
+                                           ShillClient* shill_client,
+                                           ApManagerClient* ap_manager_client,
+                                           CloudDelegate* gcd,
+                                           uint32_t connect_timeout_seconds,
+                                           uint32_t bootstrap_timeout_seconds,
+                                           uint32_t monitor_timeout_seconds)
+    : state_store_(state_store),
+      shill_client_(shill_client),
+      ap_manager_client_(ap_manager_client),
+      ssid_generator_(gcd, this),
+      connect_timeout_seconds_(connect_timeout_seconds),
+      bootstrap_timeout_seconds_(bootstrap_timeout_seconds),
+      monitor_timeout_seconds_(monitor_timeout_seconds) {
+  cloud_observer_.Add(gcd);
+}
+
+void WifiBootstrapManager::Init() {
+  CHECK(!is_initialized_);
+  std::string ssid = ssid_generator_.GenerateSsid();
+  if (ssid.empty())
+    return;  // Delay initialization until ssid_generator_ is ready.
+  chromeos::KeyValueStore state_store;
+  if (!state_store_->GetBoolean(state_key::kWifiHasBeenBootstrapped,
+                                &have_ever_been_bootstrapped_) ||
+      !state_store_->GetString(state_key::kWifiLastConfiguredSSID,
+                               &last_configured_ssid_)) {
+    have_ever_been_bootstrapped_ = false;
+  }
+  UpdateConnectionState();
+  shill_client_->RegisterConnectivityListener(
+      base::Bind(&WifiBootstrapManager::OnConnectivityChange,
+                 lifetime_weak_factory_.GetWeakPtr()));
+  if (!have_ever_been_bootstrapped_) {
+    StartBootstrapping();
+  } else {
+    StartMonitoring();
+  }
+  is_initialized_ = true;
+}
+
+void WifiBootstrapManager::RegisterStateListener(
+    const StateListener& listener) {
+  // Notify about current state.
+  listener.Run(state_);
+  state_listeners_.push_back(listener);
+}
+
+void WifiBootstrapManager::StartBootstrapping() {
+  if (shill_client_->AmOnline()) {
+    // If one of the devices we monitor for connectivity is online, we need not
+    // start an AP.  For most devices, this is a situation which happens in
+    // testing when we have an ethernet connection.  If you need to always
+    // start an AP to bootstrap WiFi credentials, then add your WiFi interface
+    // to the device whitelist.
+    StartMonitoring();
+    return;
+  }
+
+  UpdateState(kBootstrapping);
+  if (have_ever_been_bootstrapped_) {
+    // If we have been configured before, we'd like to periodically take down
+    // our AP and find out if we can connect again.  Many kinds of failures are
+    // transient, and having an AP up prohibits us from connecting as a client.
+    base::MessageLoop::current()->PostDelayedTask(
+        FROM_HERE, base::Bind(&WifiBootstrapManager::OnBootstrapTimeout,
+                              tasks_weak_factory_.GetWeakPtr()),
+        base::TimeDelta::FromSeconds(bootstrap_timeout_seconds_));
+  }
+  // TODO(vitalybuka): Add SSID probing.
+  std::string ssid = ssid_generator_.GenerateSsid();
+  CHECK(!ssid.empty());
+  ap_manager_client_->Start(ssid);
+}
+
+void WifiBootstrapManager::EndBootstrapping() {
+  ap_manager_client_->Stop();
+}
+
+void WifiBootstrapManager::StartConnecting(const std::string& ssid,
+                                           const std::string& passphrase) {
+  VLOG(1) << "WiFi is attempting to connect. (ssid=" << ssid
+          << ", pass=" << passphrase << ").";
+  UpdateState(kConnecting);
+  base::MessageLoop::current()->PostDelayedTask(
+      FROM_HERE, base::Bind(&WifiBootstrapManager::OnConnectTimeout,
+                            tasks_weak_factory_.GetWeakPtr()),
+      base::TimeDelta::FromSeconds(connect_timeout_seconds_));
+  shill_client_->ConnectToService(
+      ssid, passphrase, base::Bind(&WifiBootstrapManager::OnConnectSuccess,
+                                   tasks_weak_factory_.GetWeakPtr(), ssid),
+      nullptr);
+}
+
+void WifiBootstrapManager::EndConnecting() {
+}
+
+void WifiBootstrapManager::StartMonitoring() {
+  VLOG(1) << "Monitoring connectivity.";
+  // We already have a callback in place with |shill_client_| to update our
+  // connectivity state.  See OnConnectivityChange().
+  UpdateState(kMonitoring);
+}
+
+void WifiBootstrapManager::EndMonitoring() {
+}
+
+void WifiBootstrapManager::UpdateState(State new_state) {
+  VLOG(3) << "Switching state from " << state_ << " to " << new_state;
+  // Abort irrelevant tasks.
+  tasks_weak_factory_.InvalidateWeakPtrs();
+
+  switch (state_) {
+    case kDisabled:
+      break;
+    case kBootstrapping:
+      EndBootstrapping();
+      break;
+    case kMonitoring:
+      EndMonitoring();
+      break;
+    case kConnecting:
+      EndConnecting();
+      break;
+  }
+
+  if (new_state != state_) {
+    state_ = new_state;
+    // Post with weak ptr to avoid notification after this object destroyed.
+    base::MessageLoop::current()->PostTask(
+        FROM_HERE, base::Bind(&WifiBootstrapManager::NotifyStateListeners,
+                              lifetime_weak_factory_.GetWeakPtr(), new_state));
+  } else {
+    VLOG(3) << "Not notifying listeners of state change, "
+            << "because the states are the same.";
+  }
+}
+
+void WifiBootstrapManager::NotifyStateListeners(State new_state) const {
+  for (const StateListener& listener : state_listeners_)
+    listener.Run(new_state);
+}
+
+const ConnectionState& WifiBootstrapManager::GetConnectionState() const {
+  return connection_state_;
+}
+
+const SetupState& WifiBootstrapManager::GetSetupState() const {
+  return setup_state_;
+}
+
+bool WifiBootstrapManager::ConfigureCredentials(const std::string& ssid,
+                                                const std::string& passphrase,
+                                                chromeos::ErrorPtr* error) {
+  setup_state_ = SetupState{SetupState::kInProgress};
+  // TODO(vitalybuka): Find more reliable way to finish request or move delay
+  // into PrivetHandler as it's very HTTP specific.
+  base::MessageLoop::current()->PostDelayedTask(
+      FROM_HERE, base::Bind(&WifiBootstrapManager::StartConnecting,
+                            tasks_weak_factory_.GetWeakPtr(), ssid, passphrase),
+      base::TimeDelta::FromSeconds(kSetupDelaySeconds));
+  return true;
+}
+
+std::string WifiBootstrapManager::GetCurrentlyConnectedSsid() const {
+  // TODO(vitalybuka): Get from shill, if possible.
+  return last_configured_ssid_;
+}
+
+std::string WifiBootstrapManager::GetHostedSsid() const {
+  return ap_manager_client_->GetSsid();
+}
+
+std::set<WifiType> WifiBootstrapManager::GetTypes() const {
+  // TODO(wiley) This should do some system work to figure this out.
+  return {WifiType::kWifi24};
+}
+
+void WifiBootstrapManager::OnDeviceInfoChanged() {
+  // Initialization was delayed until buffet is ready.
+  if (!is_initialized_)
+    Init();
+}
+
+void WifiBootstrapManager::OnConnectSuccess(const std::string& ssid) {
+  VLOG(1) << "Wifi was connected successfully";
+  have_ever_been_bootstrapped_ = true;
+  last_configured_ssid_ = ssid;
+  state_store_->SetBoolean(state_key::kWifiHasBeenBootstrapped,
+                           have_ever_been_bootstrapped_);
+  state_store_->SetString(state_key::kWifiLastConfiguredSSID,
+                          last_configured_ssid_);
+  state_store_->Save();
+  setup_state_ = SetupState{SetupState::kSuccess};
+  StartMonitoring();
+}
+
+void WifiBootstrapManager::OnBootstrapTimeout() {
+  VLOG(1) << "Bootstrapping has timed out.";
+  StartMonitoring();
+}
+
+void WifiBootstrapManager::OnConnectTimeout() {
+  VLOG(1) << "Wifi timed out while connecting";
+  chromeos::ErrorPtr error;
+  chromeos::Error::AddTo(&error, FROM_HERE, errors::kDomain,
+                         errors::kInvalidState,
+                         "Failed to connect to provided network");
+  setup_state_ = SetupState{std::move(error)};
+  StartBootstrapping();
+}
+
+void WifiBootstrapManager::OnConnectivityChange(bool is_connected) {
+  VLOG(3) << "ConnectivityChanged: " << is_connected;
+  UpdateConnectionState();
+
+  if (state_ == kBootstrapping) {
+    StartMonitoring();
+    return;
+  }
+  if (state_ == kMonitoring) {
+    if (is_connected) {
+      tasks_weak_factory_.InvalidateWeakPtrs();
+    } else {
+      // Tasks queue may have more than one OnMonitorTimeout enqueued. The
+      // first one could be executed as it would change the state and abort the
+      // rest.
+      base::MessageLoop::current()->PostDelayedTask(
+          FROM_HERE, base::Bind(&WifiBootstrapManager::OnMonitorTimeout,
+                                tasks_weak_factory_.GetWeakPtr()),
+          base::TimeDelta::FromSeconds(monitor_timeout_seconds_));
+    }
+  }
+}
+
+void WifiBootstrapManager::OnMonitorTimeout() {
+  VLOG(1) << "Spent too long offline.  Entering bootstrap mode.";
+  // TODO(wiley) Retrieve relevant errors from shill.
+  StartBootstrapping();
+}
+
+void WifiBootstrapManager::UpdateConnectionState() {
+  connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
+  if (!have_ever_been_bootstrapped_) {
+    return;
+  }
+  ServiceState service_state{shill_client_->GetConnectionState()};
+  switch (service_state) {
+    case ServiceState::kOffline:
+      connection_state_ = ConnectionState{ConnectionState::kOffline};
+      return;
+    case ServiceState::kFailure: {
+      // TODO(wiley) Pull error information from somewhere.
+      chromeos::ErrorPtr error;
+      chromeos::Error::AddTo(&error, FROM_HERE, errors::kDomain,
+                             errors::kInvalidState, "Unknown WiFi error");
+      connection_state_ = ConnectionState{std::move(error)};
+      return;
+    }
+    case ServiceState::kConnecting:
+      connection_state_ = ConnectionState{ConnectionState::kConnecting};
+      return;
+    case ServiceState::kConnected:
+      connection_state_ = ConnectionState{ConnectionState::kOnline};
+      return;
+  }
+  chromeos::ErrorPtr error;
+  chromeos::Error::AddToPrintf(
+      &error, FROM_HERE, errors::kDomain, errors::kInvalidState,
+      "Unknown state returned from ShillClient: %s",
+      ServiceStateToString(service_state).c_str());
+  connection_state_ = ConnectionState{std::move(error)};
+}
+
+}  // namespace privetd
diff --git a/buffet/privet/wifi_bootstrap_manager.h b/buffet/privet/wifi_bootstrap_manager.h
new file mode 100644
index 0000000..bb7d3ea
--- /dev/null
+++ b/buffet/privet/wifi_bootstrap_manager.h
@@ -0,0 +1,130 @@
+// 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_PRIVET_WIFI_BOOTSTRAP_MANAGER_H_
+#define BUFFET_PRIVET_WIFI_BOOTSTRAP_MANAGER_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <base/memory/weak_ptr.h>
+#include <base/scoped_observer.h>
+
+#include "buffet/privet/cloud_delegate.h"
+#include "buffet/privet/daemon_state.h"
+#include "buffet/privet/privet_types.h"
+#include "buffet/privet/wifi_delegate.h"
+#include "buffet/privet/wifi_ssid_generator.h"
+
+namespace privetd {
+
+class ApManagerClient;
+class CloudDelegate;
+class DeviceDelegate;
+class ShillClient;
+
+class WifiBootstrapManager : public WifiDelegate,
+                             public CloudDelegate::Observer {
+ public:
+  enum State {
+    kDisabled,
+    kBootstrapping,
+    kMonitoring,
+    kConnecting,
+  };
+
+  using StateListener = base::Callback<void(State)>;
+
+  WifiBootstrapManager(DaemonState* state_store,
+                       ShillClient* shill_client,
+                       ApManagerClient* ap_manager_client,
+                       CloudDelegate* gcd,
+                       uint32_t connect_timeout_seconds,
+                       uint32_t bootstrap_timeout_seconds,
+                       uint32_t monitor_timeout_seconds);
+  ~WifiBootstrapManager() override = default;
+  virtual void Init();
+  void RegisterStateListener(const StateListener& listener);
+
+  // Overrides from WifiDelegate.
+  const ConnectionState& GetConnectionState() const override;
+  const SetupState& GetSetupState() const override;
+  bool ConfigureCredentials(const std::string& ssid,
+                            const std::string& passphrase,
+                            chromeos::ErrorPtr* error) override;
+  std::string GetCurrentlyConnectedSsid() const override;
+  std::string GetHostedSsid() const override;
+  std::set<WifiType> GetTypes() const override;
+
+  // Overrides from CloudDelegate::Observer.
+  void OnDeviceInfoChanged() override;
+
+ private:
+  // These Start* tasks:
+  //   1) Do state appropriate work for entering the indicated state.
+  //   2) Update the state variable to reflect that we're in a new state
+  //   3) Call StateListeners to notify that we've transitioned.
+  // These End* tasks perform cleanup on leaving indicated state.
+  void StartBootstrapping();
+  void EndBootstrapping();
+
+  void StartConnecting(const std::string& ssid, const std::string& passphrase);
+  void EndConnecting();
+
+  void StartMonitoring();
+  void EndMonitoring();
+
+  // Update the current state, post tasks to notify listeners accordingly to
+  // the MessageLoop.
+  void UpdateState(State new_state);
+  void NotifyStateListeners(State new_state) const;
+
+  // If we've been bootstrapped successfully before, and we're bootstrapping
+  // again because we slipped offline for a sufficiently longtime, we want
+  // to return to monitoring mode periodically in case our connectivity issues
+  // were temporary.
+  void OnBootstrapTimeout();
+  void OnConnectTimeout();
+  void OnConnectSuccess(const std::string& ssid);
+  void OnConnectivityChange(bool is_connected);
+  void OnMonitorTimeout();
+  void UpdateConnectionState();
+
+  // Initialization could be delayed if ssid_generator_ is not ready.
+  bool is_initialized_{false};
+  State state_{kDisabled};
+  // Setup state is the temporal state of the most recent bootstrapping attempt.
+  // It is not persisted to disk.
+  SetupState setup_state_{SetupState::kNone};
+  ConnectionState connection_state_{ConnectionState::kDisabled};
+  DaemonState* state_store_;
+  ShillClient* shill_client_;
+  ApManagerClient* ap_manager_client_;
+  WifiSsidGenerator ssid_generator_;
+
+  uint32_t connect_timeout_seconds_;
+  uint32_t bootstrap_timeout_seconds_;
+  uint32_t monitor_timeout_seconds_;
+  std::vector<StateListener> state_listeners_;
+  bool have_ever_been_bootstrapped_{false};
+  bool currently_online_{false};
+  std::string last_configured_ssid_;
+
+  ScopedObserver<CloudDelegate, CloudDelegate::Observer> cloud_observer_{this};
+
+  // Helps to reset irrelevant tasks switching state.
+  base::WeakPtrFactory<WifiBootstrapManager> tasks_weak_factory_{this};
+
+  base::WeakPtrFactory<WifiBootstrapManager> lifetime_weak_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(WifiBootstrapManager);
+};
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_WIFI_BOOTSTRAP_MANAGER_H_
diff --git a/buffet/privet/wifi_delegate.h b/buffet/privet/wifi_delegate.h
new file mode 100644
index 0000000..f350652
--- /dev/null
+++ b/buffet/privet/wifi_delegate.h
@@ -0,0 +1,55 @@
+// 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_PRIVET_WIFI_DELEGATE_H_
+#define BUFFET_PRIVET_WIFI_DELEGATE_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "buffet/privet/privet_types.h"
+
+namespace privetd {
+
+enum class WifiType {
+  kWifi24,
+  kWifi50,
+};
+
+// Interface to provide WiFi functionality for PrivetHandler.
+class WifiDelegate {
+ public:
+  WifiDelegate() = default;
+  virtual ~WifiDelegate() = default;
+
+  // Returns status of the WiFi connection.
+  virtual const ConnectionState& GetConnectionState() const = 0;
+
+  // Returns status of the last WiFi setup.
+  virtual const SetupState& GetSetupState() const = 0;
+
+  // Starts WiFi setup. Device should try to connect to provided SSID and
+  // password and store them on success. Result of setup should be available
+  // using GetSetupState().
+  // Final setup state can be retrieved with GetSetupState().
+  virtual bool ConfigureCredentials(const std::string& ssid,
+                                    const std::string& password,
+                                    chromeos::ErrorPtr* error) = 0;
+
+  // Returns SSID of the currently configured WiFi network. Empty string, if
+  // WiFi has not been configured yet.
+  virtual std::string GetCurrentlyConnectedSsid() const = 0;
+
+  // Returns SSID of the WiFi network hosted by this device. Empty if device is
+  // not in setup or P2P modes.
+  virtual std::string GetHostedSsid() const = 0;
+
+  // Returns list of supported WiFi types. Currently it's just frequencies.
+  virtual std::set<WifiType> GetTypes() const = 0;
+};
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_WIFI_DELEGATE_H_
diff --git a/buffet/privet/wifi_ssid_generator.cc b/buffet/privet/wifi_ssid_generator.cc
new file mode 100644
index 0000000..0fa0c7c
--- /dev/null
+++ b/buffet/privet/wifi_ssid_generator.cc
@@ -0,0 +1,92 @@
+// 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/privet/wifi_ssid_generator.h"
+
+#include <bitset>
+
+#include <base/bind.h>
+#include <base/rand_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+
+#include "buffet/privet/cloud_delegate.h"
+#include "buffet/privet/device_delegate.h"
+#include "buffet/privet/wifi_delegate.h"
+
+namespace privetd {
+
+namespace {
+
+const int kDeviceNameSize = 20;
+// [DeviceName+Idx <= 20].[modelID == 5][flags == 2]prv
+const char kSsidFormat[] = "%s %s.%5.5s%2.2sprv";
+
+const char base64chars[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+bool IsSetupNeeded(const ConnectionState& state) {
+  if (state.error())
+    return true;
+  switch (state.status()) {
+    case ConnectionState::kUnconfigured:
+      return true;
+    case ConnectionState::kDisabled:
+    case ConnectionState::kConnecting:
+    case ConnectionState::kOnline:
+    case ConnectionState::kOffline:
+      return false;
+  }
+  CHECK(false);
+  return false;
+}
+
+}  // namespace
+
+WifiSsidGenerator::WifiSsidGenerator(const CloudDelegate* cloud,
+                                     const WifiDelegate* wifi)
+    : gcd_(cloud), wifi_(wifi), get_random_(base::Bind(&base::RandInt, 0, 99)) {
+  CHECK(gcd_);
+}
+
+std::string WifiSsidGenerator::GenerateFlags() const {
+  std::bitset<6> flags1;
+  // Device needs WiFi configuration.
+  flags1[0] = wifi_ && IsSetupNeeded(wifi_->GetConnectionState());
+  // Device needs GCD registration.
+  flags1[1] = IsSetupNeeded(gcd_->GetConnectionState());
+
+  std::bitset<6> flags2;
+  // Device is discoverable over WiFi.
+  flags2[0] = true;
+
+  std::string result{2, base64chars[0]};
+  result[0] = base64chars[flags1.to_ulong()];
+  result[1] = base64chars[flags2.to_ulong()];
+  return result;
+}
+
+std::string WifiSsidGenerator::GenerateSsid() const {
+  std::string name;
+  std::string model_id;
+  if (!gcd_ || !gcd_->GetName(&name, nullptr) ||
+      !gcd_->GetModelId(&model_id, nullptr)) {
+    return std::string();
+  }
+  std::string idx{base::IntToString(get_random_.Run())};
+  name = name.substr(0, kDeviceNameSize - idx.size() - 1);
+  CHECK_EQ(5u, model_id.size());
+
+  std::string result =
+      base::StringPrintf(kSsidFormat, name.c_str(), idx.c_str(),
+                         model_id.c_str(), GenerateFlags().c_str());
+  CHECK_EQ(result[result.size() - 11], '.');
+  return result;
+}
+
+void WifiSsidGenerator::SetRandomForTests(int n) {
+  get_random_ = base::Bind(&base::RandInt, n, n);
+}
+
+}  // namespace privetd
diff --git a/buffet/privet/wifi_ssid_generator.h b/buffet/privet/wifi_ssid_generator.h
new file mode 100644
index 0000000..33e9628
--- /dev/null
+++ b/buffet/privet/wifi_ssid_generator.h
@@ -0,0 +1,43 @@
+// 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_PRIVET_WIFI_SSID_GENERATOR_H_
+#define BUFFET_PRIVET_WIFI_SSID_GENERATOR_H_
+
+#include <string>
+
+#include <base/callback.h>
+
+namespace privetd {
+
+class CloudDelegate;
+class WifiDelegate;
+
+class WifiSsidGenerator final {
+ public:
+  WifiSsidGenerator(const CloudDelegate* gcd, const WifiDelegate* wifi);
+  ~WifiSsidGenerator() = default;
+
+  std::string GenerateFlags() const;
+
+  // Can return empty string if CloudDelegate is not ready.
+  std::string GenerateSsid() const;
+
+ private:
+  friend class WifiSsidGeneratorTest;
+
+  // Sets object to use |n| instead of random number for SSID generation.
+  void SetRandomForTests(int n);
+
+  const CloudDelegate* gcd_{nullptr};
+  const WifiDelegate* wifi_{nullptr};
+
+  base::Callback<int(void)> get_random_;
+
+  DISALLOW_COPY_AND_ASSIGN(WifiSsidGenerator);
+};
+
+}  // namespace privetd
+
+#endif  // BUFFET_PRIVET_WIFI_SSID_GENERATOR_H_
diff --git a/buffet/privet/wifi_ssid_generator_unittest.cc b/buffet/privet/wifi_ssid_generator_unittest.cc
new file mode 100644
index 0000000..16c4475
--- /dev/null
+++ b/buffet/privet/wifi_ssid_generator_unittest.cc
@@ -0,0 +1,68 @@
+// 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/privet/wifi_ssid_generator.h"
+
+#include <base/strings/utf_string_conversions.h>
+#include <gtest/gtest.h>
+
+#include "buffet/privet/mock_delegates.h"
+#include "buffet/privet/openssl_utils.h"
+
+namespace privetd {
+
+class WifiSsidGeneratorTest : public testing::Test {
+ protected:
+  void SetRandomForTests(int n) { ssid_generator_.SetRandomForTests(n); }
+
+  testing::StrictMock<MockCloudDelegate> gcd_;
+  testing::StrictMock<MockWifiDelegate> wifi_;
+
+  WifiSsidGenerator ssid_generator_{&gcd_, &wifi_};
+};
+
+TEST_F(WifiSsidGeneratorTest, GenerateFlags) {
+  EXPECT_EQ(ssid_generator_.GenerateFlags().size(), 2);
+
+  wifi_.connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
+  gcd_.connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
+  EXPECT_EQ("DB", ssid_generator_.GenerateFlags());
+
+  wifi_.connection_state_ = ConnectionState{ConnectionState::kOnline};
+  EXPECT_EQ("CB", ssid_generator_.GenerateFlags());
+
+  gcd_.connection_state_ = ConnectionState{ConnectionState::kOffline};
+  EXPECT_EQ("AB", ssid_generator_.GenerateFlags());
+
+  wifi_.connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
+  EXPECT_EQ("BB", ssid_generator_.GenerateFlags());
+}
+
+TEST_F(WifiSsidGeneratorTest, GenerateSsid31orLess) {
+  EXPECT_LE(ssid_generator_.GenerateSsid().size(), 31);
+}
+
+TEST_F(WifiSsidGeneratorTest, GenerateSsidValue) {
+  SetRandomForTests(47);
+  EXPECT_EQ("TestDevice 47.ABMIDABprv", ssid_generator_.GenerateSsid());
+
+  SetRandomForTests(9);
+  EXPECT_EQ("TestDevice 9.ABMIDABprv", ssid_generator_.GenerateSsid());
+}
+
+TEST_F(WifiSsidGeneratorTest, GenerateSsidLongName) {
+  SetRandomForTests(99);
+  EXPECT_CALL(gcd_, GetName(_, _))
+      .WillRepeatedly(
+          DoAll(SetArgPointee<0>("Very Long Device Name"), Return(true)));
+  EXPECT_EQ("Very Long Device  99.ABMIDABprv", ssid_generator_.GenerateSsid());
+}
+
+TEST_F(WifiSsidGeneratorTest, GenerateSsidNoName) {
+  SetRandomForTests(99);
+  EXPECT_CALL(gcd_, GetName(_, _)).WillRepeatedly(Return(false));
+  EXPECT_EQ("", ssid_generator_.GenerateSsid());
+}
+
+}  // namespace privetd