| // 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/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 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::kDomain, 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_->GetMutableConfig()->AddOnChangedCallback(base::Bind( | |
| &CloudDelegateImpl::OnConfigChanged, weak_factory_.GetWeakPtr())); | |
| device_->AddGcdStateChangedCallback(base::Bind( | |
| &CloudDelegateImpl::OnRegistrationChanged, weak_factory_.GetWeakPtr())); | |
| component_manager_->AddTraitDefChangedCallback( | |
| base::Bind(&CloudDelegateImpl::NotifyOnTraitDefsChanged, | |
| weak_factory_.GetWeakPtr())); | |
| component_manager_->AddCommandAddedCallback(base::Bind( | |
| &CloudDelegateImpl::OnCommandAdded, weak_factory_.GetWeakPtr())); | |
| component_manager_->AddCommandRemovedCallback(base::Bind( | |
| &CloudDelegateImpl::OnCommandRemoved, weak_factory_.GetWeakPtr())); | |
| component_manager_->AddStateChangedCallback(base::Bind( | |
| &CloudDelegateImpl::NotifyOnStateChanged, weak_factory_.GetWeakPtr())); | |
| component_manager_->AddComponentTreeChangedCallback( | |
| base::Bind(&CloudDelegateImpl::NotifyOnComponentTreeChanged, | |
| 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 std::string& ticket_id, | |
| const std::string& user, | |
| ErrorPtr* error) override { | |
| VLOG(1) << "GCD Setup started. ticket_id: " << ticket_id | |
| << ", user:" << user; | |
| // Set (or reset) the retry counter, since we are starting a new | |
| // registration process. | |
| registation_retry_count_ = kMaxDeviceRegistrationRetries; | |
| ticket_id_ = ticket_id; | |
| 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 | |
| : ""; | |
| } | |
| const base::DictionaryValue& GetLegacyCommandDef() const override { | |
| return component_manager_->GetLegacyCommandDefinitions(); | |
| } | |
| const base::DictionaryValue& GetLegacyState() const override { | |
| return component_manager_->GetLegacyState(); | |
| } | |
| const base::DictionaryValue& GetComponents() const override { | |
| return component_manager_->GetComponents(); | |
| } | |
| 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_NE(user_info.user_id(), 0u); | |
| ErrorPtr error; | |
| UserRole role; | |
| std::string str_scope = EnumToString(user_info.scope()); | |
| if (!StringToEnum(str_scope, &role)) { | |
| Error::AddToPrintf(&error, FROM_HERE, errors::kDomain, | |
| 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.user_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().release()); | |
| } | |
| } | |
| base::DictionaryValue commands_json; | |
| commands_json.Set("commands", list_value.DeepCopy()); | |
| callback.Run(commands_json, nullptr); | |
| } | |
| private: | |
| void OnCommandAdded(Command* command) { | |
| // Set to 0 for any new unknown command. | |
| command_owners_.insert(std::make_pair(command->GetID(), 0)); | |
| } | |
| void OnCommandRemoved(Command* command) { | |
| CHECK(command_owners_.erase(command->GetID())); | |
| } | |
| void OnConfigChanged(const Settings&) { NotifyOnDeviceInfoChanged(); } | |
| 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::kDomain, errors::kInvalidState, | |
| "Unexpected registration status: %s", EnumToString(status).c_str()); | |
| connection_state_ = ConnectionState{std::move(error)}; | |
| } | |
| NotifyOnDeviceInfoChanged(); | |
| } | |
| 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::kDomain, errors::kInvalidState, | |
| "Failed to register device"); | |
| setup_state_ = SetupState{std::move(error)}; | |
| return; | |
| } | |
| device_->RegisterDevice(ticket_id_, | |
| 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::kOwner) { | |
| 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(uint64_t owner_id, | |
| const UserInfo& user_info, | |
| ErrorPtr* error) const { | |
| CHECK(user_info.scope() != AuthScope::kNone); | |
| CHECK_NE(user_info.user_id(), 0u); | |
| if (user_info.scope() == AuthScope::kOwner || | |
| owner_id == user_info.user_id()) { | |
| return true; | |
| } | |
| Error::AddTo(error, FROM_HERE, errors::kDomain, errors::kAccessDenied, | |
| "Need to be owner of the command."); | |
| return false; | |
| } | |
| 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}; | |
| // Ticket ID for registering the device. | |
| std::string ticket_id_; | |
| // Number of remaining retries for device registration process. | |
| int registation_retry_count_{0}; | |
| // Map of command IDs to user IDs. | |
| std::map<std::string, uint64_t> 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}}; | |
| } | |
| void CloudDelegate::NotifyOnDeviceInfoChanged() { | |
| FOR_EACH_OBSERVER(Observer, observer_list_, OnDeviceInfoChanged()); | |
| } | |
| void CloudDelegate::NotifyOnTraitDefsChanged() { | |
| FOR_EACH_OBSERVER(Observer, observer_list_, OnTraitDefsChanged()); | |
| } | |
| void CloudDelegate::NotifyOnComponentTreeChanged() { | |
| FOR_EACH_OBSERVER(Observer, observer_list_, OnComponentTreeChanged()); | |
| } | |
| void CloudDelegate::NotifyOnStateChanged() { | |
| FOR_EACH_OBSERVER(Observer, observer_list_, OnStateChanged()); | |
| } | |
| } // namespace privet | |
| } // namespace weave |