blob: aee43d82389c1ab4b576e856a9394f66b39ce190 [file] [log] [blame]
// 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 <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/callback.h>
#include <base/macros.h>
#include <base/memory/weak_ptr.h>
#include <base/time/time.h>
#include <weave/device.h>
#include <weave/error.h>
#include <weave/provider/http_client.h>
#include "src/backoff_entry.h"
#include "src/commands/cloud_command_update_interface.h"
#include "src/commands/command_manager.h"
#include "src/config.h"
#include "src/data_encoding.h"
#include "src/notification/notification_channel.h"
#include "src/notification/notification_delegate.h"
#include "src/notification/pull_channel.h"
#include "src/states/state_change_queue_interface.h"
namespace base {
class DictionaryValue;
} // namespace base
namespace weave {
class StateManager;
namespace provider {
class Network;
class TaskRunner;
extern const char kErrorDomainOAuth2[];
extern const char kErrorDomainGCD[];
extern const char kErrorDomainGCDServer[];
// The DeviceRegistrationInfo class represents device registration information.
class DeviceRegistrationInfo : public NotificationDelegate,
public CloudCommandUpdateInterface {
using CloudRequestDoneCallback =
base::Callback<void(const base::DictionaryValue& response,
ErrorPtr error)>;
DeviceRegistrationInfo(const std::shared_ptr<CommandManager>& command_manager,
const std::shared_ptr<StateManager>& state_manager,
std::unique_ptr<Config> config,
provider::TaskRunner* task_runner,
provider::HttpClient* http_client,
provider::Network* network);
~DeviceRegistrationInfo() override;
void AddGcdStateChangedCallback(
const Device::GcdStateChangedCallback& callback);
void RegisterDevice(const std::string& ticket_id,
const DoneCallback& callback);
void UpdateDeviceInfo(const std::string& name,
const std::string& description,
const std::string& location);
void UpdateBaseConfig(AuthScope anonymous_access_role,
bool local_discovery_enabled,
bool local_pairing_enabled);
bool UpdateServiceConfig(const std::string& client_id,
const std::string& client_secret,
const std::string& api_key,
const std::string& oauth_url,
const std::string& service_url,
ErrorPtr* error);
void GetDeviceInfo(const CloudRequestDoneCallback& callback);
// Returns the GCD service request URL. If |subpath| is specified, it is
// appended to the base URL which is normally
// If |params| are specified, each key-value pair is formatted using
// WebParamsEncode() and appended to URL as a query
// string.
// So, calling:
// GetServiceURL("ticket", {{"key","apiKey"}})
// will return something like:
std::string GetServiceURL(const std::string& subpath = {},
const WebParamList& params = {}) const;
// Returns a service URL to access the registered device on GCD server.
// The base URL used to construct the full URL looks like this:
std::string GetDeviceURL(const std::string& subpath = {},
const WebParamList& params = {}) const;
// Similar to GetServiceURL, GetOAuthURL() returns a URL of OAuth 2.0 server.
// The base URL used is
std::string GetOAuthURL(const std::string& subpath = {},
const WebParamList& params = {}) const;
// Starts GCD device if credentials available.
void Start();
void SwitchXmppChannel(const std::string& channel);
// Updates a command (override from CloudCommandUpdateInterface).
void UpdateCommand(const std::string& command_id,
const base::DictionaryValue& command_patch,
const DoneCallback& callback) override;
// TODO(vitalybuka): remove getters and pass config to dependent code.
const Config::Settings& GetSettings() const { return config_->GetSettings(); }
Config* GetMutableConfig() { return config_.get(); }
GcdState GetGcdState() const { return gcd_state_; }
friend class DeviceRegistrationInfoTest;
base::WeakPtr<DeviceRegistrationInfo> AsWeakPtr() {
return weak_factory_.GetWeakPtr();
// Checks whether we have credentials generated during registration.
bool HaveRegistrationCredentials() const;
// Calls HaveRegistrationCredentials() and logs an error if no credentials
// are available.
bool VerifyRegistrationCredentials(ErrorPtr* error) const;
// Cause DeviceRegistrationInfo to attempt to connect to cloud server on
// its own later.
void ScheduleCloudConnection(const base::TimeDelta& delay);
// Initiates the connection to the cloud server.
// Device will do required start up chores and then start to listen
// to new commands.
void ConnectToCloud(ErrorPtr error);
// Notification called when ConnectToCloud() succeeds.
void OnConnectedToCloud(ErrorPtr error);
// Forcibly refreshes the access token.
void RefreshAccessToken(const DoneCallback& callback);
// Callbacks for RefreshAccessToken().
void OnRefreshAccessTokenDone(
const DoneCallback& callback,
std::unique_ptr<provider::HttpClient::Response> response,
ErrorPtr error);
// Parse the OAuth response, and sets registration status to
// kInvalidCredentials if our registration is no longer valid.
std::unique_ptr<base::DictionaryValue> ParseOAuthResponse(
const provider::HttpClient::Response& response,
ErrorPtr* error);
// This attempts to open a notification channel. The channel needs to be
// restarted anytime the access_token is refreshed.
void StartNotificationChannel();
// Do a HTTPS request to cloud services.
// Handles many cases like reauthorization, 5xx HTTP response codes
// and device removal. It is a recommended way to do cloud API
// requests.
// TODO(antonm): Consider moving into some other class.
void DoCloudRequest(provider::HttpClient::Method method,
const std::string& url,
const base::DictionaryValue* body,
const CloudRequestDoneCallback& callback);
// Helper for DoCloudRequest().
struct CloudRequestData {
provider::HttpClient::Method method;
std::string url;
std::string body;
CloudRequestDoneCallback callback;
void SendCloudRequest(const std::shared_ptr<const CloudRequestData>& data);
void OnCloudRequestDone(
const std::shared_ptr<const CloudRequestData>& data,
std::unique_ptr<provider::HttpClient::Response> response,
ErrorPtr error);
void RetryCloudRequest(const std::shared_ptr<const CloudRequestData>& data);
void OnAccessTokenRefreshed(
const std::shared_ptr<const CloudRequestData>& data,
ErrorPtr error);
void CheckAccessTokenError(ErrorPtr error);
void UpdateDeviceResource(const DoneCallback& callback);
void StartQueuedUpdateDeviceResource();
void OnUpdateDeviceResourceDone(const base::DictionaryValue& device_info,
ErrorPtr error);
void OnUpdateDeviceResourceError(ErrorPtr error);
// Callback from GetDeviceInfo() to retrieve the device resource timestamp
// and retry UpdateDeviceResource() call.
void OnDeviceInfoRetrieved(const base::DictionaryValue& device_info,
ErrorPtr error);
// Extracts the timestamp from the device resource and sets it to
// |last_device_resource_updated_timestamp_|.
// Returns false if the "lastUpdateTimeMs" field is not found in the device
// resource or it is invalid.
bool UpdateDeviceInfoTimestamp(const base::DictionaryValue& device_info);
void FetchCommands(
const base::Callback<void(const base::ListValue&, ErrorPtr)>& callback,
const std::string& reason);
void OnFetchCommandsDone(
const base::Callback<void(const base::ListValue&, ErrorPtr)>& callback,
const base::DictionaryValue& json,
// Called when FetchCommands completes (with either success or error).
// This method reschedules any pending/queued fetch requests.
void OnFetchCommandsReturned();
// Processes the command list that is fetched from the server on connection.
// Aborts commands which are in transitional states and publishes queued
// commands which are queued.
void ProcessInitialCommandList(const base::ListValue& commands,
ErrorPtr error);
void PublishCommands(const base::ListValue& commands, ErrorPtr error);
void PublishCommand(const base::DictionaryValue& command);
// Helper function to pull the pending command list from the server using
// FetchCommands() and make them available on D-Bus with PublishCommands().
// |backup_fetch| is set to true when performing backup ("just-in-case")
// command fetch while XMPP channel is up and running.
void FetchAndPublishCommands(const std::string& reason);
void PublishStateUpdates();
void OnPublishStateDone(StateChangeQueueInterface::UpdateID update_id,
const base::DictionaryValue& reply,
ErrorPtr error);
void OnPublishStateError(ErrorPtr error);
// If unrecoverable error occurred (e.g. error parsing command instance),
// notify the server that the command is aborted by the device.
void NotifyCommandAborted(const std::string& command_id, ErrorPtr error);
// Builds Cloud API devices collection REST resource which matches
// current state of the device including command definitions
// for all supported commands and current device state.
std::unique_ptr<base::DictionaryValue> BuildDeviceResource(ErrorPtr* error);
void SetGcdState(GcdState new_state);
void SetDeviceId(const std::string& cloud_id);
// Callback called when command definitions are changed to re-publish new CDD.
void OnCommandDefsChanged();
void OnStateChanged();
// Callback for network connectivity changes.
void OnNetworkStateChanged();
// Overrides from NotificationDelegate.
void OnConnected(const std::string& channel_name) override;
void OnDisconnected() override;
void OnPermanentFailure() override;
void OnCommandCreated(const base::DictionaryValue& command,
const std::string& channel_name) override;
void OnDeviceDeleted(const std::string& cloud_id) override;
// Wipes out the device registration information and stops server connections.
void RemoveCredentials();
void RegisterDeviceError(const DoneCallback& callback, ErrorPtr error);
void RegisterDeviceOnTicketSent(
const std::string& ticket_id,
const DoneCallback& callback,
std::unique_ptr<provider::HttpClient::Response> response,
ErrorPtr error);
void RegisterDeviceOnTicketFinalized(
const DoneCallback& callback,
std::unique_ptr<provider::HttpClient::Response> response,
ErrorPtr error);
void RegisterDeviceOnAuthCodeSent(
const std::string& cloud_id,
const std::string& robot_account,
const DoneCallback& callback,
std::unique_ptr<provider::HttpClient::Response> response,
ErrorPtr error);
// Transient data
std::string access_token_;
base::Time access_token_expiration_;
// The time stamp of last device resource update on the server.
std::string last_device_resource_updated_timestamp_;
// Set to true if the device has connected to the cloud server correctly.
// At this point, normal state and command updates can be dispatched to the
// server.
bool connected_to_cloud_{false};
// HTTP transport used for communications.
provider::HttpClient* http_client_{nullptr};
provider::TaskRunner* task_runner_{nullptr};
// Global command manager.
std::shared_ptr<CommandManager> command_manager_;
// Device state manager.
std::shared_ptr<StateManager> state_manager_;
std::unique_ptr<Config> config_;
// Backoff manager for DoCloudRequest() method.
std::unique_ptr<BackoffEntry::Policy> cloud_backoff_policy_;
std::unique_ptr<BackoffEntry> cloud_backoff_entry_;
std::unique_ptr<BackoffEntry> oauth2_backoff_entry_;
// Flag set to true while a device state update patch request is in flight
// to the cloud server.
bool device_state_update_pending_{false};
// Set to true when command queue fetch request is in flight to the server.
bool fetch_commands_request_sent_{false};
// Set to true when another command queue fetch request is queued while
// another one was in flight.
bool fetch_commands_request_queued_{false};
// Specifies the reason for queued command fetch request.
std::string queued_fetch_reason_;
using ResourceUpdateCallbackList = std::vector<DoneCallback>;
// Callbacks for device resource update request currently in flight to the
// cloud server.
ResourceUpdateCallbackList in_progress_resource_update_callbacks_;
// Callbacks for device resource update requests queued while another request
// is in flight to the cloud server.
ResourceUpdateCallbackList queued_resource_update_callbacks_;
std::unique_ptr<NotificationChannel> primary_notification_channel_;
std::unique_ptr<PullChannel> pull_channel_;
NotificationChannel* current_notification_channel_{nullptr};
bool notification_channel_starting_{false};
provider::Network* network_{nullptr};
std::string xmpp_channel_;
// Tracks our GCD state.
GcdState gcd_state_{GcdState::kUnconfigured};
std::vector<Device::GcdStateChangedCallback> gcd_state_changed_callbacks_;
std::vector<std::shared_ptr<const CloudRequestData>> offline_cloud_requests_;
base::WeakPtrFactory<DeviceRegistrationInfo> weak_factory_{this};
} // namespace weave