// 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::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(!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(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::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}; // 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, 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