1 // Copyright 2015 The Weave Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/privet/cloud_delegate.h"
6
7 #include <map>
8 #include <vector>
9
10 #include <base/bind.h>
11 #include <base/logging.h>
12 #include <base/memory/weak_ptr.h>
13 #include <base/values.h>
14 #include <weave/error.h>
15 #include <weave/provider/task_runner.h>
16
17 #include "src/backoff_entry.h"
18 #include "src/component_manager.h"
19 #include "src/config.h"
20 #include "src/device_registration_info.h"
21 #include "src/privet/constants.h"
22
23 namespace weave {
24 namespace privet {
25
26 namespace {
27
28 const BackoffEntry::Policy register_backoff_policy = {0, 1000, 2.0, 0.2,
29 5000, -1, false};
30
31 const int kMaxDeviceRegistrationRetries = 100; // ~ 8 minutes @5s retries.
32
ReturnNotFound(const std::string & command_id,ErrorPtr * error)33 CommandInstance* ReturnNotFound(const std::string& command_id,
34 ErrorPtr* error) {
35 Error::AddToPrintf(error, FROM_HERE, errors::kNotFound,
36 "Command not found, ID='%s'", command_id.c_str());
37 return nullptr;
38 }
39
40 class CloudDelegateImpl : public CloudDelegate {
41 public:
CloudDelegateImpl(provider::TaskRunner * task_runner,DeviceRegistrationInfo * device,ComponentManager * component_manager)42 CloudDelegateImpl(provider::TaskRunner* task_runner,
43 DeviceRegistrationInfo* device,
44 ComponentManager* component_manager)
45 : task_runner_{task_runner},
46 device_{device},
47 component_manager_{component_manager} {
48 device_->GetMutableConfig()->AddOnChangedCallback(base::Bind(
49 &CloudDelegateImpl::OnConfigChanged, weak_factory_.GetWeakPtr()));
50 device_->AddGcdStateChangedCallback(base::Bind(
51 &CloudDelegateImpl::OnRegistrationChanged, weak_factory_.GetWeakPtr()));
52
53 component_manager_->AddTraitDefChangedCallback(
54 base::Bind(&CloudDelegateImpl::NotifyOnTraitDefsChanged,
55 weak_factory_.GetWeakPtr()));
56 component_manager_->AddCommandAddedCallback(base::Bind(
57 &CloudDelegateImpl::OnCommandAdded, weak_factory_.GetWeakPtr()));
58 component_manager_->AddCommandRemovedCallback(base::Bind(
59 &CloudDelegateImpl::OnCommandRemoved, weak_factory_.GetWeakPtr()));
60 component_manager_->AddStateChangedCallback(base::Bind(
61 &CloudDelegateImpl::NotifyOnStateChanged, weak_factory_.GetWeakPtr()));
62 component_manager_->AddComponentTreeChangedCallback(
63 base::Bind(&CloudDelegateImpl::NotifyOnComponentTreeChanged,
64 weak_factory_.GetWeakPtr()));
65 }
66
67 ~CloudDelegateImpl() override = default;
68
GetDeviceId() const69 std::string GetDeviceId() const override {
70 return device_->GetSettings().device_id;
71 }
72
GetModelId() const73 std::string GetModelId() const override {
74 CHECK_EQ(5u, device_->GetSettings().model_id.size());
75 return device_->GetSettings().model_id;
76 }
77
GetName() const78 std::string GetName() const override { return device_->GetSettings().name; }
79
GetDescription() const80 std::string GetDescription() const override {
81 return device_->GetSettings().description;
82 }
83
GetLocation() const84 std::string GetLocation() const override {
85 return device_->GetSettings().location;
86 }
87
UpdateDeviceInfo(const std::string & name,const std::string & description,const std::string & location)88 void UpdateDeviceInfo(const std::string& name,
89 const std::string& description,
90 const std::string& location) override {
91 device_->UpdateDeviceInfo(name, description, location);
92 }
93
GetOemName() const94 std::string GetOemName() const override {
95 return device_->GetSettings().oem_name;
96 }
97
GetModelName() const98 std::string GetModelName() const override {
99 return device_->GetSettings().model_name;
100 }
101
GetAnonymousMaxScope() const102 AuthScope GetAnonymousMaxScope() const override {
103 return device_->GetSettings().local_anonymous_access_role;
104 }
105
GetConnectionState() const106 const ConnectionState& GetConnectionState() const override {
107 return connection_state_;
108 }
109
GetSetupState() const110 const SetupState& GetSetupState() const override { return setup_state_; }
111
Setup(const std::string & ticket_id,const std::string & user,ErrorPtr * error)112 bool Setup(const std::string& ticket_id,
113 const std::string& user,
114 ErrorPtr* error) override {
115 VLOG(1) << "GCD Setup started. ticket_id: " << ticket_id
116 << ", user:" << user;
117 // Set (or reset) the retry counter, since we are starting a new
118 // registration process.
119 registation_retry_count_ = kMaxDeviceRegistrationRetries;
120 ticket_id_ = ticket_id;
121 if (setup_state_.IsStatusEqual(SetupState::kInProgress)) {
122 // Another registration is in progress. In case it fails, we will use
123 // the new ticket ID when retrying the request.
124 return true;
125 }
126 setup_state_ = SetupState(SetupState::kInProgress);
127 setup_weak_factory_.InvalidateWeakPtrs();
128 backoff_entry_.Reset();
129 task_runner_->PostDelayedTask(
130 FROM_HERE, base::Bind(&CloudDelegateImpl::CallManagerRegisterDevice,
131 setup_weak_factory_.GetWeakPtr()),
132 {});
133 // Return true because we initiated setup.
134 return true;
135 }
136
GetCloudId() const137 std::string GetCloudId() const override {
138 return connection_state_.status() > ConnectionState::kUnconfigured
139 ? device_->GetSettings().cloud_id
140 : "";
141 }
142
GetLegacyCommandDef() const143 const base::DictionaryValue& GetLegacyCommandDef() const override {
144 return component_manager_->GetLegacyCommandDefinitions();
145 }
146
GetLegacyState() const147 const base::DictionaryValue& GetLegacyState() const override {
148 return component_manager_->GetLegacyState();
149 }
150
GetComponents() const151 const base::DictionaryValue& GetComponents() const override {
152 return component_manager_->GetComponents();
153 }
154
FindComponent(const std::string & path,ErrorPtr * error) const155 const base::DictionaryValue* FindComponent(const std::string& path,
156 ErrorPtr* error) const override {
157 return component_manager_->FindComponent(path, error);
158 }
159
GetTraits() const160 const base::DictionaryValue& GetTraits() const override {
161 return component_manager_->GetTraits();
162 }
163
AddCommand(const base::DictionaryValue & command,const UserInfo & user_info,const CommandDoneCallback & callback)164 void AddCommand(const base::DictionaryValue& command,
165 const UserInfo& user_info,
166 const CommandDoneCallback& callback) override {
167 CHECK(user_info.scope() != AuthScope::kNone);
168 CHECK(!user_info.id().IsEmpty());
169
170 ErrorPtr error;
171 UserRole role;
172 std::string str_scope = EnumToString(user_info.scope());
173 if (!StringToEnum(str_scope, &role)) {
174 Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidParams,
175 "Invalid role: '%s'", str_scope.c_str());
176 return callback.Run({}, std::move(error));
177 }
178
179 std::string id;
180 auto command_instance = component_manager_->ParseCommandInstance(
181 command, Command::Origin::kLocal, role, &id, &error);
182 if (!command_instance)
183 return callback.Run({}, std::move(error));
184 component_manager_->AddCommand(std::move(command_instance));
185 command_owners_[id] = user_info.id();
186 callback.Run(*component_manager_->FindCommand(id)->ToJson(), nullptr);
187 }
188
GetCommand(const std::string & id,const UserInfo & user_info,const CommandDoneCallback & callback)189 void GetCommand(const std::string& id,
190 const UserInfo& user_info,
191 const CommandDoneCallback& callback) override {
192 CHECK(user_info.scope() != AuthScope::kNone);
193 ErrorPtr error;
194 auto command = GetCommandInternal(id, user_info, &error);
195 if (!command)
196 return callback.Run({}, std::move(error));
197 callback.Run(*command->ToJson(), nullptr);
198 }
199
CancelCommand(const std::string & id,const UserInfo & user_info,const CommandDoneCallback & callback)200 void CancelCommand(const std::string& id,
201 const UserInfo& user_info,
202 const CommandDoneCallback& callback) override {
203 CHECK(user_info.scope() != AuthScope::kNone);
204 ErrorPtr error;
205 auto command = GetCommandInternal(id, user_info, &error);
206 if (!command || !command->Cancel(&error))
207 return callback.Run({}, std::move(error));
208 callback.Run(*command->ToJson(), nullptr);
209 }
210
ListCommands(const UserInfo & user_info,const CommandDoneCallback & callback)211 void ListCommands(const UserInfo& user_info,
212 const CommandDoneCallback& callback) override {
213 CHECK(user_info.scope() != AuthScope::kNone);
214
215 base::ListValue list_value;
216
217 for (const auto& it : command_owners_) {
218 if (CanAccessCommand(it.second, user_info, nullptr)) {
219 list_value.Append(
220 component_manager_->FindCommand(it.first)->ToJson().release());
221 }
222 }
223
224 base::DictionaryValue commands_json;
225 commands_json.Set("commands", list_value.DeepCopy());
226
227 callback.Run(commands_json, nullptr);
228 }
229
230 private:
OnCommandAdded(Command * command)231 void OnCommandAdded(Command* command) {
232 // Set to "" for any new unknown command.
233 command_owners_.insert(std::make_pair(command->GetID(), UserAppId{}));
234 }
235
OnCommandRemoved(Command * command)236 void OnCommandRemoved(Command* command) {
237 CHECK(command_owners_.erase(command->GetID()));
238 }
239
OnConfigChanged(const Settings &)240 void OnConfigChanged(const Settings&) { NotifyOnDeviceInfoChanged(); }
241
OnRegistrationChanged(GcdState status)242 void OnRegistrationChanged(GcdState status) {
243 if (status == GcdState::kUnconfigured ||
244 status == GcdState::kInvalidCredentials) {
245 connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
246 } else if (status == GcdState::kConnecting) {
247 // TODO(vitalybuka): Find conditions for kOffline.
248 connection_state_ = ConnectionState{ConnectionState::kConnecting};
249 } else if (status == GcdState::kConnected) {
250 connection_state_ = ConnectionState{ConnectionState::kOnline};
251 } else {
252 ErrorPtr error;
253 Error::AddToPrintf(&error, FROM_HERE, errors::kInvalidState,
254 "Unexpected registration status: %s",
255 EnumToString(status).c_str());
256 connection_state_ = ConnectionState{std::move(error)};
257 }
258 NotifyOnDeviceInfoChanged();
259 }
260
OnRegisterSuccess(const std::string & cloud_id)261 void OnRegisterSuccess(const std::string& cloud_id) {
262 VLOG(1) << "Device registered: " << cloud_id;
263 setup_state_ = SetupState(SetupState::kSuccess);
264 }
265
CallManagerRegisterDevice()266 void CallManagerRegisterDevice() {
267 ErrorPtr error;
268 CHECK_GE(registation_retry_count_, 0);
269 if (registation_retry_count_-- == 0) {
270 Error::AddTo(&error, FROM_HERE, errors::kInvalidState,
271 "Failed to register device");
272 setup_state_ = SetupState{std::move(error)};
273 return;
274 }
275
276 device_->RegisterDevice(ticket_id_,
277 base::Bind(&CloudDelegateImpl::RegisterDeviceDone,
278 setup_weak_factory_.GetWeakPtr()));
279 }
280
RegisterDeviceDone(ErrorPtr error)281 void RegisterDeviceDone(ErrorPtr error) {
282 if (error) {
283 // Registration failed. Retry with backoff.
284 backoff_entry_.InformOfRequest(false);
285 return task_runner_->PostDelayedTask(
286 FROM_HERE, base::Bind(&CloudDelegateImpl::CallManagerRegisterDevice,
287 setup_weak_factory_.GetWeakPtr()),
288 backoff_entry_.GetTimeUntilRelease());
289 }
290 backoff_entry_.InformOfRequest(true);
291 setup_state_ = SetupState(SetupState::kSuccess);
292 }
293
GetCommandInternal(const std::string & command_id,const UserInfo & user_info,ErrorPtr * error) const294 CommandInstance* GetCommandInternal(const std::string& command_id,
295 const UserInfo& user_info,
296 ErrorPtr* error) const {
297 if (user_info.scope() < AuthScope::kManager) {
298 auto it = command_owners_.find(command_id);
299 if (it == command_owners_.end())
300 return ReturnNotFound(command_id, error);
301 if (CanAccessCommand(it->second, user_info, error))
302 return nullptr;
303 }
304
305 auto command = component_manager_->FindCommand(command_id);
306 if (!command)
307 return ReturnNotFound(command_id, error);
308
309 return command;
310 }
311
CanAccessCommand(const UserAppId & owner,const UserInfo & user_info,ErrorPtr * error) const312 bool CanAccessCommand(const UserAppId& owner,
313 const UserInfo& user_info,
314 ErrorPtr* error) const {
315 CHECK(user_info.scope() != AuthScope::kNone);
316 CHECK(!user_info.id().IsEmpty());
317
318 if (user_info.scope() == AuthScope::kManager ||
319 (owner.type == user_info.id().type &&
320 owner.user == user_info.id().user &&
321 (user_info.id().app.empty() || // Token is not restricted to the app.
322 owner.app == user_info.id().app))) {
323 return true;
324 }
325
326 return Error::AddTo(error, FROM_HERE, errors::kAccessDenied,
327 "Need to be owner of the command.");
328 }
329
330 provider::TaskRunner* task_runner_{nullptr};
331 DeviceRegistrationInfo* device_{nullptr};
332 ComponentManager* component_manager_{nullptr};
333
334 // Primary state of GCD.
335 ConnectionState connection_state_{ConnectionState::kDisabled};
336
337 // State of the current or last setup.
338 SetupState setup_state_{SetupState::kNone};
339
340 // Ticket ID for registering the device.
341 std::string ticket_id_;
342
343 // Number of remaining retries for device registration process.
344 int registation_retry_count_{0};
345
346 // Map of command IDs to user IDs.
347 std::map<std::string, UserAppId> command_owners_;
348
349 // Backoff entry for retrying device registration.
350 BackoffEntry backoff_entry_{®ister_backoff_policy};
351
352 // |setup_weak_factory_| tracks the lifetime of callbacks used in connection
353 // with a particular invocation of Setup().
354 base::WeakPtrFactory<CloudDelegateImpl> setup_weak_factory_{this};
355 // |weak_factory_| tracks the lifetime of |this|.
356 base::WeakPtrFactory<CloudDelegateImpl> weak_factory_{this};
357 };
358
359 } // namespace
360
CloudDelegate()361 CloudDelegate::CloudDelegate() {}
362
~CloudDelegate()363 CloudDelegate::~CloudDelegate() {}
364
365 // static
CreateDefault(provider::TaskRunner * task_runner,DeviceRegistrationInfo * device,ComponentManager * component_manager)366 std::unique_ptr<CloudDelegate> CloudDelegate::CreateDefault(
367 provider::TaskRunner* task_runner,
368 DeviceRegistrationInfo* device,
369 ComponentManager* component_manager) {
370 return std::unique_ptr<CloudDelegateImpl>{
371 new CloudDelegateImpl{task_runner, device, component_manager}};
372 }
373
NotifyOnDeviceInfoChanged()374 void CloudDelegate::NotifyOnDeviceInfoChanged() {
375 FOR_EACH_OBSERVER(Observer, observer_list_, OnDeviceInfoChanged());
376 }
377
NotifyOnTraitDefsChanged()378 void CloudDelegate::NotifyOnTraitDefsChanged() {
379 FOR_EACH_OBSERVER(Observer, observer_list_, OnTraitDefsChanged());
380 }
381
NotifyOnComponentTreeChanged()382 void CloudDelegate::NotifyOnComponentTreeChanged() {
383 FOR_EACH_OBSERVER(Observer, observer_list_, OnComponentTreeChanged());
384 }
385
NotifyOnStateChanged()386 void CloudDelegate::NotifyOnStateChanged() {
387 FOR_EACH_OBSERVER(Observer, observer_list_, OnStateChanged());
388 }
389
390 } // namespace privet
391 } // namespace weave
392