Merge commit '03cd192eceb46f936ad89e6ae04dac130202e18c' into HEAD
* commit '03cd192eceb46f936ad89e6ae04dac130202e18c':
Add support for the custom colorXY trait
Persist kInvalidCredentials state
Fixed headers parsing in CurlHttpClient
Replace clouddevices with weave in documentation and tests Removed OAuth scope parameter as optional.
Change-Id: If6f63c188d28af515714633d7058fc34c5f006f0
diff --git a/examples/daemon/light/light.cc b/examples/daemon/light/light.cc
index 484b9e1..d54de93 100644
--- a/examples/daemon/light/light.cc
+++ b/examples/daemon/light/light.cc
@@ -19,12 +19,44 @@
device->AddStateDefinitionsFromJson(R"({
"onOff": {"state": ["on", "standby"]},
- "brightness": {"brightness": "integer"}
+ "brightness": {"brightness": "integer"},
+ "colorXY": {
+ "colorSetting": {
+ "properties": {
+ "colorX": {"minimum": 0.0, "maximum": 1.0},
+ "colorY": {"minimum": 0.0, "maximum": 1.0}
+ }
+ },
+ "colorCapRed": {
+ "properties": {
+ "colorX": {"minimum": 0.0, "maximum": 1.0},
+ "colorY": {"minimum": 0.0, "maximum": 1.0}
+ }
+ },
+ "colorCapGreen": {
+ "properties": {
+ "colorX": {"minimum": 0.0, "maximum": 1.0},
+ "colorY": {"minimum": 0.0, "maximum": 1.0}
+ }
+ },
+ "colorCapBlue": {
+ "properties": {
+ "colorX": {"minimum": 0.0, "maximum": 1.0},
+ "colorY": {"minimum": 0.0, "maximum": 1.0}
+ }
+ }
+ }
})");
device->SetStatePropertiesFromJson(R"({
"onOff":{"state": "standby"},
- "brightness":{"brightness": 0}
+ "brightness":{"brightness": 0},
+ "colorXY": {
+ "colorSetting": {"colorX": 0, "colorY": 0},
+ "colorCapRed": {"colorX": 0.674, "colorY": 0.322},
+ "colorCapGreen":{"colorX": 0.408, "colorY": 0.517},
+ "colorCapBlue": {"colorX": 0.168, "colorY": 0.041}
+ }
})",
nullptr);
@@ -46,6 +78,28 @@
}
}
}
+ },
+ "_colorXY": {
+ "_setConfig": {
+ "minimalRole": "user",
+ "parameters": {
+ "_colorSetting": {
+ "type": "object",
+ "properties": {
+ "_colorX": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ },
+ "_colorY": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ }
+ }
+ }
+ }
+ }
}
})");
device->AddCommandHandler("onOff.setConfig",
@@ -54,6 +108,9 @@
device->AddCommandHandler("brightness.setConfig",
base::Bind(&LightHandler::OnBrightnessSetConfig,
weak_ptr_factory_.GetWeakPtr()));
+ device->AddCommandHandler("_colorXY._setConfig",
+ base::Bind(&LightHandler::OnColorXYSetConfig,
+ weak_ptr_factory_.GetWeakPtr()));
}
private:
@@ -105,11 +162,51 @@
cmd->Abort(error.get(), nullptr);
}
+ void OnColorXYSetConfig(const std::weak_ptr<weave::Command>& command) {
+ auto cmd = command.lock();
+ if (!cmd)
+ return;
+ LOG(INFO) << "received command: " << cmd->GetName();
+ auto params = cmd->GetParameters();
+ base::DictionaryValue* colorXY = nullptr;
+ if (params->GetDictionary("_colorSetting", &colorXY)) {
+ bool updateState = false;
+ double X = 0.0;
+ double Y = 0.0;
+ if (colorXY->GetDouble("_colorX", &X)) {
+ color_X_ = X;
+ updateState = true;
+ }
+
+ if (colorXY->GetDouble("_colorY", &Y)) {
+ color_Y_ = Y;
+ updateState = true;
+ }
+
+ if (updateState)
+ UpdateLightState();
+
+ cmd->Complete({}, nullptr);
+ return;
+ }
+
+ weave::ErrorPtr error;
+ weave::Error::AddTo(&error, FROM_HERE, "example", "invalid_parameter_value",
+ "Invalid parameters");
+ cmd->Abort(error.get(), nullptr);
+ }
+
void UpdateLightState() {
base::DictionaryValue state;
state.SetString("onOff.state", light_status_ ? "on" : "standby");
state.SetInteger("brightness.brightness", brightness_state_);
+
+ std::unique_ptr<base::DictionaryValue> colorXY(new base::DictionaryValue());
+ colorXY->SetDouble("colorX", color_X_);
+ colorXY->SetDouble("colorY", color_Y_);
+ state.Set("colorXY.colorSetting", colorXY.get());
device_->SetStateProperties(state, nullptr);
+ colorXY.release();
}
weave::Device* device_{nullptr};
@@ -117,6 +214,8 @@
// Simulate the state of the light.
bool light_status_;
int32_t brightness_state_;
+ double color_X_{0.0};
+ double color_Y_{0.0};
base::WeakPtrFactory<LightHandler> weak_ptr_factory_{this};
};
diff --git a/examples/provider/curl_http_client.cc b/examples/provider/curl_http_client.cc
index 32aa4af..774c07b 100644
--- a/examples/provider/curl_http_client.cc
+++ b/examples/provider/curl_http_client.cc
@@ -34,6 +34,27 @@
return size * nmemb;
}
+size_t HeaderFunction(void* contents, size_t size, size_t nmemb, void* userp) {
+ std::string header(static_cast<const char*>(contents), size * nmemb);
+ auto pos = header.find(':');
+ if (pos != std::string::npos) {
+ std::pair<std::string, std::string> header_pair;
+
+ static const char kSpaces[] = " \t\r\n";
+ header_pair.first = header.substr(0, pos);
+ pos = header.find_first_not_of(kSpaces, pos + 1);
+ if (pos != std::string::npos) {
+ auto last_non_space = header.find_last_not_of(kSpaces);
+ if (last_non_space >= pos)
+ header_pair.second = header.substr(pos, last_non_space - pos + 1);
+ }
+
+ static_cast<provider::HttpClient::Headers*>(userp)
+ ->emplace_back(std::move(header_pair));
+ }
+ return size * nmemb;
+}
+
std::pair<std::unique_ptr<CurlHttpClient::Response>, ErrorPtr>
SendRequestBlocking(CurlHttpClient::Method method,
const std::string& url,
@@ -76,9 +97,10 @@
CHECK_EQ(CURLE_OK,
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response->data));
CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HEADERFUNCTION,
- &WriteFunction));
- CHECK_EQ(CURLE_OK, curl_easy_setopt(curl.get(), CURLOPT_HEADERDATA,
- &response->content_type));
+ &HeaderFunction));
+ provider::HttpClient::Headers response_headers;
+ CHECK_EQ(CURLE_OK,
+ curl_easy_setopt(curl.get(), CURLOPT_HEADERDATA, &response_headers));
CURLcode res = curl_easy_perform(curl.get());
if (chunk)
@@ -91,20 +113,16 @@
return {nullptr, std::move(error)};
}
- const std::string kContentType = "\r\nContent-Type:";
- auto pos = response->content_type.find(kContentType);
- if (pos == std::string::npos) {
+ for (const auto& header : response_headers) {
+ if (header.first == "Content-Type")
+ response->content_type = header.second;
+ }
+
+ if (response->content_type.empty()) {
Error::AddTo(&error, FROM_HERE, "curl", "no_content_header",
"Content-Type header is missing");
return {nullptr, std::move(error)};
}
- pos += kContentType.size();
- auto pos_end = response->content_type.find("\r\n", pos);
- if (pos_end == std::string::npos) {
- pos_end = response->content_type.size();
- }
-
- response->content_type = response->content_type.substr(pos, pos_end);
CHECK_EQ(CURLE_OK, curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE,
&response->status));
diff --git a/include/weave/device.h b/include/weave/device.h
index e761e0c..19012b5 100644
--- a/include/weave/device.h
+++ b/include/weave/device.h
@@ -24,7 +24,7 @@
// States of Gcd connection.
enum class GcdState {
- kUnconfigured, // We have no credentials.
+ kUnconfigured, // Device was not registered.
kConnecting, // We have credentials but not yet connected.
kConnected, // We're registered and connected to the cloud.
kInvalidCredentials, // Our registration has been revoked.
diff --git a/src/device_registration_info.cc b/src/device_registration_info.cc
index d2b10a9..751e530 100644
--- a/src/device_registration_info.cc
+++ b/src/device_registration_info.cc
@@ -201,9 +201,9 @@
SplitAtFirst(response.GetContentType(), ";", true).first;
if (content_type != http::kJson && content_type != http::kPlain) {
- Error::AddTo(error, FROM_HERE, errors::json::kDomain,
- "non_json_content_type",
- "Unexpected response content type: " + content_type);
+ Error::AddTo(
+ error, FROM_HERE, errors::json::kDomain, "non_json_content_type",
+ "Unexpected content type: \'" + response.GetContentType() + "\'");
return std::unique_ptr<base::DictionaryValue>();
}
@@ -262,6 +262,11 @@
cloud_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
oauth2_backoff_entry_.reset(new BackoffEntry{cloud_backoff_policy_.get()});
+ bool revoked =
+ !GetSettings().cloud_id.empty() && !HaveRegistrationCredentials();
+ gcd_state_ =
+ revoked ? GcdState::kInvalidCredentials : GcdState::kUnconfigured;
+
command_manager_->AddCommandDefChanged(
base::Bind(&DeviceRegistrationInfo::OnCommandDefsChanged,
weak_factory_.GetWeakPtr()));
@@ -511,9 +516,8 @@
void DeviceRegistrationInfo::GetDeviceInfo(
const CloudRequestDoneCallback& callback) {
ErrorPtr error;
- if (!VerifyRegistrationCredentials(&error)) {
+ if (!VerifyRegistrationCredentials(&error))
return callback.Run({}, std::move(error));
- }
DoCloudRequest(HttpClient::Method::kGet, GetDeviceURL(), nullptr, callback);
}
@@ -675,9 +679,8 @@
// TODO(antonm): Add support for device removal.
ErrorPtr error;
- if (!VerifyRegistrationCredentials(&error)) {
+ if (!VerifyRegistrationCredentials(&error))
return data->callback.Run({}, std::move(error));
- }
if (cloud_backoff_entry_->ShouldRejectRequest()) {
VLOG(1) << "Cloud request delayed for "
@@ -762,13 +765,13 @@
void DeviceRegistrationInfo::CheckAccessTokenError(ErrorPtr error) {
if (error && error->HasError(kErrorDomainOAuth2, "invalid_grant"))
- MarkDeviceUnregistered();
+ RemoveCredentials();
}
void DeviceRegistrationInfo::ConnectToCloud(ErrorPtr error) {
if (error) {
if (error->HasError(kErrorDomainOAuth2, "invalid_grant"))
- MarkDeviceUnregistered();
+ RemoveCredentials();
return;
}
@@ -1270,10 +1273,10 @@
<< cloud_id << "'";
return;
}
- MarkDeviceUnregistered();
+ RemoveCredentials();
}
-void DeviceRegistrationInfo::MarkDeviceUnregistered() {
+void DeviceRegistrationInfo::RemoveCredentials() {
if (!HaveRegistrationCredentials())
return;
@@ -1281,7 +1284,7 @@
LOG(INFO) << "Device is unregistered from the cloud. Deleting credentials";
Config::Transaction change{config_.get()};
- change.set_cloud_id("");
+ // Keep cloud_id to switch to detect kInvalidCredentials after restart.
change.set_robot_account("");
change.set_refresh_token("");
change.Commit();
diff --git a/src/device_registration_info.h b/src/device_registration_info.h
index 9d9d21f..1399b37 100644
--- a/src/device_registration_info.h
+++ b/src/device_registration_info.h
@@ -110,12 +110,6 @@
// Starts GCD device if credentials available.
void Start();
- // 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;
-
// Updates a command (override from CloudCommandUpdateInterface).
void UpdateCommand(const std::string& command_id,
const base::DictionaryValue& command_patch,
@@ -134,6 +128,12 @@
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);
@@ -266,7 +266,7 @@
void OnDeviceDeleted(const std::string& cloud_id) override;
// Wipes out the device registration information and stops server connections.
- void MarkDeviceUnregistered();
+ void RemoveCredentials();
void RegisterDeviceError(const DoneCallback& callback, ErrorPtr error);
void RegisterDeviceOnTicketSent(
diff --git a/src/device_registration_info_unittest.cc b/src/device_registration_info_unittest.cc
index 9edd396..df4a438 100644
--- a/src/device_registration_info_unittest.cc
+++ b/src/device_registration_info_unittest.cc
@@ -186,6 +186,10 @@
GcdState GetGcdState() const { return dev_reg_->GetGcdState(); }
+ bool HaveRegistrationCredentials() const {
+ return dev_reg_->HaveRegistrationCredentials();
+ }
+
provider::test::FakeTaskRunner task_runner_;
provider::test::MockConfigStore config_store_;
StrictMock<MockHttpClient> http_client_;
@@ -222,14 +226,13 @@
url += "client_id=";
url += test_data::kClientId;
EXPECT_EQ(url, dev_reg_->GetOAuthURL(
- "auth",
- {{"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
- {"response_type", "code"},
- {"client_id", test_data::kClientId}}));
+ "auth", {{"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
+ {"response_type", "code"},
+ {"client_id", test_data::kClientId}}));
}
TEST_F(DeviceRegistrationInfoTest, HaveRegistrationCredentials) {
- EXPECT_FALSE(dev_reg_->HaveRegistrationCredentials());
+ EXPECT_FALSE(HaveRegistrationCredentials());
ReloadSettings();
EXPECT_CALL(
@@ -254,7 +257,7 @@
})));
EXPECT_TRUE(RefreshAccessToken(nullptr));
- EXPECT_TRUE(dev_reg_->HaveRegistrationCredentials());
+ EXPECT_TRUE(HaveRegistrationCredentials());
}
TEST_F(DeviceRegistrationInfoTest, CheckAuthenticationFailure) {
@@ -313,6 +316,7 @@
EXPECT_FALSE(RefreshAccessToken(&error));
EXPECT_TRUE(error->HasError(kErrorDomainOAuth2, "invalid_grant"));
EXPECT_EQ(GcdState::kInvalidCredentials, GetGcdState());
+ EXPECT_EQ(test_data::kDeviceId, dev_reg_->GetSettings().cloud_id);
}
TEST_F(DeviceRegistrationInfoTest, GetDeviceInfo) {
diff --git a/src/privet/cloud_delegate.cc b/src/privet/cloud_delegate.cc
index b0eea41..d612668 100644
--- a/src/privet/cloud_delegate.cc
+++ b/src/privet/cloud_delegate.cc
@@ -223,7 +223,8 @@
void OnConfigChanged(const Settings&) { NotifyOnDeviceInfoChanged(); }
void OnRegistrationChanged(GcdState status) {
- if (status == GcdState::kUnconfigured) {
+ if (status == GcdState::kUnconfigured ||
+ status == GcdState::kInvalidCredentials) {
connection_state_ = ConnectionState{ConnectionState::kUnconfigured};
} else if (status == GcdState::kConnecting) {
// TODO(vitalybuka): Find conditions for kOffline.