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 = ∅
+ } 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, ®istration)) {
+ 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], ×tamp))
+ 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