| // 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 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_->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 RegistrationData& registration_data, |
| ErrorPtr* error) override { |
| VLOG(1) << "GCD Setup started. "; |
| // 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; |
| } |
| |
| 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(!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().release()); |
| } |
| } |
| |
| base::DictionaryValue commands_json; |
| commands_json.Set("commands", list_value.DeepCopy()); |
| |
| callback.Run(commands_json, nullptr); |
| } |
| |
| 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 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::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::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}}; |
| } |
| |
| 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 |