|  | // Copyright 2015 The Weave 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 "src/privet/cloud_delegate.h" | 
|  |  | 
|  | #include <map> | 
|  | #include <vector> | 
|  |  | 
|  | #include <base/bind.h> | 
|  | #include <base/logging.h> | 
|  | #include <base/memory/weak_ptr.h> | 
|  | #include <base/values.h> | 
|  | #include <weave/error.h> | 
|  | #include <weave/device.h> | 
|  | #include <weave/provider/task_runner.h> | 
|  |  | 
|  | #include "src/backoff_entry.h" | 
|  | #include "src/component_manager.h" | 
|  | #include "src/config.h" | 
|  | #include "src/device_registration_info.h" | 
|  | #include "src/privet/constants.h" | 
|  |  | 
|  | namespace weave { | 
|  | namespace privet { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const char kErrorAlreayRegistered[] = "already_registered"; | 
|  |  | 
|  | const BackoffEntry::Policy register_backoff_policy = {0,    1000, 2.0,  0.2, | 
|  | 5000, -1,   false}; | 
|  |  | 
|  | const int kMaxDeviceRegistrationRetries = 100;  // ~ 8 minutes @5s retries. | 
|  |  | 
|  | CommandInstance* ReturnNotFound(const std::string& command_id, | 
|  | ErrorPtr* error) { | 
|  | Error::AddToPrintf(error, FROM_HERE, errors::kNotFound, | 
|  | "Command not found, ID='%s'", command_id.c_str()); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | class CloudDelegateImpl : public CloudDelegate { | 
|  | public: | 
|  | CloudDelegateImpl(provider::TaskRunner* task_runner, | 
|  | DeviceRegistrationInfo* device, | 
|  | ComponentManager* component_manager) | 
|  | : task_runner_{task_runner}, | 
|  | device_{device}, | 
|  | component_manager_{component_manager} { | 
|  | device_->AddGcdStateChangedCallback(base::Bind( | 
|  | &CloudDelegateImpl::OnRegistrationChanged, weak_factory_.GetWeakPtr())); | 
|  |  | 
|  | component_manager_->AddCommandAddedCallback(base::Bind( | 
|  | &CloudDelegateImpl::OnCommandAdded, weak_factory_.GetWeakPtr())); | 
|  | component_manager_->AddCommandRemovedCallback(base::Bind( | 
|  | &CloudDelegateImpl::OnCommandRemoved, weak_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | ~CloudDelegateImpl() override = default; | 
|  |  | 
|  | std::string GetDeviceId() const override { | 
|  | return device_->GetSettings().device_id; | 
|  | } | 
|  |  | 
|  | std::string GetModelId() const override { | 
|  | CHECK_EQ(5u, device_->GetSettings().model_id.size()); | 
|  | return device_->GetSettings().model_id; | 
|  | } | 
|  |  | 
|  | std::string GetName() const override { return device_->GetSettings().name; } | 
|  |  | 
|  | std::string GetDescription() const override { | 
|  | return device_->GetSettings().description; | 
|  | } | 
|  |  | 
|  | std::string GetLocation() const override { | 
|  | return device_->GetSettings().location; | 
|  | } | 
|  |  | 
|  | void UpdateDeviceInfo(const std::string& name, | 
|  | const std::string& description, | 
|  | const std::string& location) override { | 
|  | device_->UpdateDeviceInfo(name, description, location); | 
|  | } | 
|  |  | 
|  | std::string GetOemName() const override { | 
|  | return device_->GetSettings().oem_name; | 
|  | } | 
|  |  | 
|  | std::string GetModelName() const override { | 
|  | return device_->GetSettings().model_name; | 
|  | } | 
|  |  | 
|  | AuthScope GetAnonymousMaxScope() const override { | 
|  | return device_->GetSettings().local_anonymous_access_role; | 
|  | } | 
|  |  | 
|  | const ConnectionState& GetConnectionState() const override { | 
|  | return connection_state_; | 
|  | } | 
|  |  | 
|  | const SetupState& GetSetupState() const override { return setup_state_; } | 
|  |  | 
|  | bool Setup(const RegistrationData& registration_data, | 
|  | ErrorPtr* error) override { | 
|  | VLOG(1) << "GCD Setup started. "; | 
|  | if (device_->HaveRegistrationCredentials()) { | 
|  | Error::AddTo(error, FROM_HERE, kErrorAlreayRegistered, | 
|  | "Unable to register already registered device"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Set (or reset) the retry counter, since we are starting a new | 
|  | // registration process. | 
|  | registation_retry_count_ = kMaxDeviceRegistrationRetries; | 
|  | registration_data_ = registration_data; | 
|  | if (setup_state_.IsStatusEqual(SetupState::kInProgress)) { | 
|  | // Another registration is in progress. In case it fails, we will use | 
|  | // the new ticket ID when retrying the request. | 
|  | return true; | 
|  | } | 
|  | setup_state_ = SetupState(SetupState::kInProgress); | 
|  | setup_weak_factory_.InvalidateWeakPtrs(); | 
|  | backoff_entry_.Reset(); | 
|  | task_runner_->PostDelayedTask( | 
|  | FROM_HERE, base::Bind(&CloudDelegateImpl::CallManagerRegisterDevice, | 
|  | setup_weak_factory_.GetWeakPtr()), | 
|  | {}); | 
|  | // Return true because we initiated setup. | 
|  | return true; | 
|  | } | 
|  |  | 
|  | std::string GetCloudId() const override { | 
|  | return connection_state_.status() > ConnectionState::kUnconfigured | 
|  | ? device_->GetSettings().cloud_id | 
|  | : ""; | 
|  | } | 
|  |  | 
|  | std::string GetOAuthUrl() const override { | 
|  | return device_->GetSettings().oauth_url; | 
|  | } | 
|  |  | 
|  | std::string GetServiceUrl() const override { | 
|  | return device_->GetSettings().service_url; | 
|  | } | 
|  |  | 
|  | std::string GetXmppEndpoint() const override { | 
|  | return device_->GetSettings().xmpp_endpoint; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> GetComponentsForUser( | 
|  | const UserInfo& user_info) const override { | 
|  | UserRole role; | 
|  | std::string str_scope = EnumToString(user_info.scope()); | 
|  | CHECK(StringToEnum(str_scope, &role)); | 
|  | return component_manager_->GetComponentsForUserRole(role); | 
|  | } | 
|  |  | 
|  | const base::DictionaryValue* FindComponent(const std::string& path, | 
|  | ErrorPtr* error) const override { | 
|  | return component_manager_->FindComponent(path, error); | 
|  | } | 
|  |  | 
|  | const base::DictionaryValue& GetTraits() const override { | 
|  | return component_manager_->GetTraits(); | 
|  | } | 
|  |  | 
|  | void AddCommand(const base::DictionaryValue& command, | 
|  | const UserInfo& user_info, | 
|  | const CommandDoneCallback& callback) override { | 
|  | CHECK(user_info.scope() != AuthScope::kNone); | 
|  | CHECK(!user_info.id().IsEmpty()); | 
|  |  | 
|  | ErrorPtr error; | 
|  | UserRole role; | 
|  | std::string str_scope = EnumToString(user_info.scope()); | 
|  | if (!StringToEnum(str_scope, &role)) { | 
|  | Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidParams, | 
|  | "Invalid role: '%s'", str_scope.c_str()); | 
|  | return callback.Run({}, std::move(error)); | 
|  | } | 
|  |  | 
|  | std::string id; | 
|  | auto command_instance = component_manager_->ParseCommandInstance( | 
|  | command, Command::Origin::kLocal, role, &id, &error); | 
|  | if (!command_instance) | 
|  | return callback.Run({}, std::move(error)); | 
|  | component_manager_->AddCommand(std::move(command_instance)); | 
|  | command_owners_[id] = user_info.id(); | 
|  | callback.Run(*component_manager_->FindCommand(id)->ToJson(), nullptr); | 
|  | } | 
|  |  | 
|  | void GetCommand(const std::string& id, | 
|  | const UserInfo& user_info, | 
|  | const CommandDoneCallback& callback) override { | 
|  | CHECK(user_info.scope() != AuthScope::kNone); | 
|  | ErrorPtr error; | 
|  | auto command = GetCommandInternal(id, user_info, &error); | 
|  | if (!command) | 
|  | return callback.Run({}, std::move(error)); | 
|  | callback.Run(*command->ToJson(), nullptr); | 
|  | } | 
|  |  | 
|  | void CancelCommand(const std::string& id, | 
|  | const UserInfo& user_info, | 
|  | const CommandDoneCallback& callback) override { | 
|  | CHECK(user_info.scope() != AuthScope::kNone); | 
|  | ErrorPtr error; | 
|  | auto command = GetCommandInternal(id, user_info, &error); | 
|  | if (!command || !command->Cancel(&error)) | 
|  | return callback.Run({}, std::move(error)); | 
|  | callback.Run(*command->ToJson(), nullptr); | 
|  | } | 
|  |  | 
|  | void ListCommands(const UserInfo& user_info, | 
|  | const CommandDoneCallback& callback) override { | 
|  | CHECK(user_info.scope() != AuthScope::kNone); | 
|  |  | 
|  | base::ListValue list_value; | 
|  |  | 
|  | for (const auto& it : command_owners_) { | 
|  | if (CanAccessCommand(it.second, user_info, nullptr)) | 
|  | list_value.Append(component_manager_->FindCommand(it.first)->ToJson()); | 
|  | } | 
|  |  | 
|  | base::DictionaryValue commands_json; | 
|  | commands_json.Set("commands", list_value.CreateDeepCopy()); | 
|  |  | 
|  | callback.Run(commands_json, nullptr); | 
|  | } | 
|  |  | 
|  | void AddOnTraitsChangedCallback(const base::Closure& callback) override { | 
|  | component_manager_->AddTraitDefChangedCallback(callback); | 
|  | } | 
|  |  | 
|  | void AddOnStateChangedCallback(const base::Closure& callback) override { | 
|  | component_manager_->AddStateChangedCallback(callback); | 
|  | } | 
|  |  | 
|  | void AddOnComponentsChangeCallback(const base::Closure& callback) override { | 
|  | component_manager_->AddComponentTreeChangedCallback(callback); | 
|  | } | 
|  |  | 
|  | private: | 
|  | void OnCommandAdded(Command* command) { | 
|  | // Set to "" for any new unknown command. | 
|  | command_owners_.insert(std::make_pair(command->GetID(), UserAppId{})); | 
|  | } | 
|  |  | 
|  | void OnCommandRemoved(Command* command) { | 
|  | CHECK(command_owners_.erase(command->GetID())); | 
|  | } | 
|  |  | 
|  | void OnRegistrationChanged(GcdState status) { | 
|  | if (status == GcdState::kUnconfigured || | 
|  | status == GcdState::kInvalidCredentials) { | 
|  | connection_state_ = ConnectionState{ConnectionState::kUnconfigured}; | 
|  | } else if (status == GcdState::kConnecting) { | 
|  | // TODO(vitalybuka): Find conditions for kOffline. | 
|  | connection_state_ = ConnectionState{ConnectionState::kConnecting}; | 
|  | } else if (status == GcdState::kConnected) { | 
|  | connection_state_ = ConnectionState{ConnectionState::kOnline}; | 
|  | } else { | 
|  | ErrorPtr error; | 
|  | Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidState, | 
|  | "Unexpected registration status: %s", | 
|  | EnumToString(status).c_str()); | 
|  | connection_state_ = ConnectionState{std::move(error)}; | 
|  | } | 
|  | } | 
|  |  | 
|  | void OnRegisterSuccess(const std::string& cloud_id) { | 
|  | VLOG(1) << "Device registered: " << cloud_id; | 
|  | setup_state_ = SetupState(SetupState::kSuccess); | 
|  | } | 
|  |  | 
|  | void CallManagerRegisterDevice() { | 
|  | ErrorPtr error; | 
|  | CHECK_GE(registation_retry_count_, 0); | 
|  | if (registation_retry_count_-- == 0) { | 
|  | Error::AddTo(&error, FROM_HERE, errors::kInvalidState, | 
|  | "Failed to register device"); | 
|  | setup_state_ = SetupState{std::move(error)}; | 
|  | return; | 
|  | } | 
|  |  | 
|  | device_->RegisterDevice(registration_data_, | 
|  | base::Bind(&CloudDelegateImpl::RegisterDeviceDone, | 
|  | setup_weak_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | void RegisterDeviceDone(ErrorPtr error) { | 
|  | if (error) { | 
|  | // Registration failed. Retry with backoff. | 
|  | backoff_entry_.InformOfRequest(false); | 
|  | return task_runner_->PostDelayedTask( | 
|  | FROM_HERE, base::Bind(&CloudDelegateImpl::CallManagerRegisterDevice, | 
|  | setup_weak_factory_.GetWeakPtr()), | 
|  | backoff_entry_.GetTimeUntilRelease()); | 
|  | } | 
|  | backoff_entry_.InformOfRequest(true); | 
|  | setup_state_ = SetupState(SetupState::kSuccess); | 
|  | } | 
|  |  | 
|  | CommandInstance* GetCommandInternal(const std::string& command_id, | 
|  | const UserInfo& user_info, | 
|  | ErrorPtr* error) const { | 
|  | if (user_info.scope() < AuthScope::kManager) { | 
|  | auto it = command_owners_.find(command_id); | 
|  | if (it == command_owners_.end()) | 
|  | return ReturnNotFound(command_id, error); | 
|  | if (CanAccessCommand(it->second, user_info, error)) | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | auto command = component_manager_->FindCommand(command_id); | 
|  | if (!command) | 
|  | return ReturnNotFound(command_id, error); | 
|  |  | 
|  | return command; | 
|  | } | 
|  |  | 
|  | bool CanAccessCommand(const UserAppId& owner, | 
|  | const UserInfo& user_info, | 
|  | ErrorPtr* error) const { | 
|  | CHECK(user_info.scope() != AuthScope::kNone); | 
|  | CHECK(!user_info.id().IsEmpty()); | 
|  |  | 
|  | if (user_info.scope() == AuthScope::kManager || | 
|  | (owner.type == user_info.id().type && | 
|  | owner.user == user_info.id().user && | 
|  | (user_info.id().app.empty() ||  // Token is not restricted to the app. | 
|  | owner.app == user_info.id().app))) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return Error::AddTo(error, FROM_HERE, errors::kAccessDenied, | 
|  | "Need to be owner of the command."); | 
|  | } | 
|  |  | 
|  | provider::TaskRunner* task_runner_{nullptr}; | 
|  | DeviceRegistrationInfo* device_{nullptr}; | 
|  | ComponentManager* component_manager_{nullptr}; | 
|  |  | 
|  | // Primary state of GCD. | 
|  | ConnectionState connection_state_{ConnectionState::kDisabled}; | 
|  |  | 
|  | // State of the current or last setup. | 
|  | SetupState setup_state_{SetupState::kNone}; | 
|  |  | 
|  | // Registration data for current registration process. | 
|  | RegistrationData registration_data_; | 
|  |  | 
|  | // Number of remaining retries for device registration process. | 
|  | int registation_retry_count_{0}; | 
|  |  | 
|  | // Map of command IDs to user IDs. | 
|  | std::map<std::string, UserAppId> command_owners_; | 
|  |  | 
|  | // Backoff entry for retrying device registration. | 
|  | BackoffEntry backoff_entry_{®ister_backoff_policy}; | 
|  |  | 
|  | // |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( | 
|  | provider::TaskRunner* task_runner, | 
|  | DeviceRegistrationInfo* device, | 
|  | ComponentManager* component_manager) { | 
|  | return std::unique_ptr<CloudDelegateImpl>{ | 
|  | new CloudDelegateImpl{task_runner, device, component_manager}}; | 
|  | } | 
|  |  | 
|  | }  // namespace privet | 
|  | }  // namespace weave |