rainmaker: Cluster revision update

- Renamed attribute "challenge" to "challenge_response"
- Added a new attribute "challenge" (writable) to write the challenge.
 Apps can either use this or the older command to write RainMaker challenge
This commit is contained in:
Shubham Patil
2023-10-09 21:04:18 +05:30
committed by Hrishikesh Dhayagude
parent 1cacb8b2fc
commit 462e58a8d4
2 changed files with 173 additions and 48 deletions
@@ -20,36 +20,53 @@
#include <esp_matter_rainmaker.h> #include <esp_matter_rainmaker.h>
#include <esp_rmaker_core.h> #include <esp_rmaker_core.h>
#include <esp_rmaker_user_mapping.h> #include <esp_rmaker_user_mapping.h>
#include <app/util/attribute-storage.h>
#include <app/AttributeAccessInterface.h>
#define ESP_MATTER_RAINMAKER_COMMAND_LIMIT 5 /* This command can be called 5 times per reboot */ #define ESP_MATTER_RAINMAKER_COMMAND_LIMIT 5 /* This command can be called 5 times per reboot */
#define ESP_MATTER_RAINMAKER_MAX_DATA_LEN 40 #define ESP_MATTER_RAINMAKER_MAX_DATA_LEN 40
#define ESP_MATTER_RAINMAKER_MAX_SIGN_DATA_LEN 40 #define ESP_MATTER_RAINMAKER_MAX_CHALLENGE_LEN 40
#define ESP_MATTER_RAINMAKER_MAX_NODE_ID_LEN 40 #define ESP_MATTER_RAINMAKER_MAX_NODE_ID_LEN 40
#define ESP_MATTER_RAINMAKER_MAX_CHALLENGE_LEN 150 #define ESP_MATTER_RAINMAKER_MAX_CHALLENGE_RESPONSE_LEN 150
using namespace chip::app;
using namespace chip::app::Clusters; using namespace chip::app::Clusters;
#define RAINMAKER_CLUSTER_REVISION 2
static const char *TAG = "esp_matter_rainmaker"; static const char *TAG = "esp_matter_rainmaker";
namespace esp_matter { namespace esp_matter {
// Rainmaker cluster data model definition
namespace cluster { namespace cluster {
namespace rainmaker { namespace rainmaker {
static constexpr chip::EndpointId endpoint_id = 0x00000000; /* Same as root node endpoint. This will always be static constexpr chip::EndpointId endpoint_id = 0x00000000; /* Same as root node endpoint. This will always be
endpoint_id 0. */ endpoint_id 0. */
static constexpr chip::ClusterId Id = 0x131BFC00; /* 0x131B == manufacturer code. static constexpr chip::ClusterId Id = 0x131BFC00; /* 0x131B == manufacturer code.
0xFCOO == start of manufacturer specific cluster_id */ 0xFCOO == start of manufacturer specific cluster_id */
namespace attribute { namespace attribute {
namespace status { namespace status {
static constexpr chip::AttributeId Id = 0x00000000; static constexpr chip::AttributeId Id = 0x00000000;
} /* status */ } /* status */
namespace node_id {
namespace rmaker_node_id {
static constexpr chip::AttributeId Id = 0x00000001; static constexpr chip::AttributeId Id = 0x00000001;
} /* node_id */ } /* rmaker_node_id */
namespace challenge {
// This attribute is of access type read-only.
namespace challenge_response {
static constexpr chip::AttributeId Id = 0x00000002; static constexpr chip::AttributeId Id = 0x00000002;
} /* challenge_response */
// This attribute is of access type write.
// When a client writes to this attribute, it signs the incoming payload and
// subsequently stores that signature in the challenge_response attribute.
namespace challenge {
static constexpr chip::AttributeId Id = 0x00000003;
} /* challenge */ } /* challenge */
} /* attribute */ } /* attribute */
namespace command { namespace command {
@@ -128,24 +145,24 @@ static esp_err_t status_attribute_update(bool status)
return attribute::update(endpoint_id, cluster_id, attribute_id, &val); return attribute::update(endpoint_id, cluster_id, attribute_id, &val);
} }
static esp_err_t node_id_attribute_update(char *node_id) static esp_err_t rmaker_node_id_attribute_update(char *rmaker_node_id)
{ {
if (!node_id) { if (!rmaker_node_id) {
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
} }
uint16_t endpoint_id = cluster::rainmaker::endpoint_id; uint16_t endpoint_id = cluster::rainmaker::endpoint_id;
uint32_t cluster_id = cluster::rainmaker::Id; uint32_t cluster_id = cluster::rainmaker::Id;
uint32_t attribute_id = cluster::rainmaker::attribute::node_id::Id; uint32_t attribute_id = cluster::rainmaker::attribute::rmaker_node_id::Id;
esp_matter_attr_val_t val = esp_matter_char_str(node_id, strlen(node_id)); esp_matter_attr_val_t val = esp_matter_char_str(rmaker_node_id, strlen(rmaker_node_id));
return attribute::update(endpoint_id, cluster_id, attribute_id, &val); return attribute::update(endpoint_id, cluster_id, attribute_id, &val);
} }
static esp_err_t challenge_attribute_update(char *challenge) static esp_err_t challenge_response_attribute_update(char *challenge_response)
{ {
uint16_t endpoint_id = cluster::rainmaker::endpoint_id; uint16_t endpoint_id = cluster::rainmaker::endpoint_id;
uint32_t cluster_id = cluster::rainmaker::Id; uint32_t cluster_id = cluster::rainmaker::Id;
uint32_t attribute_id = cluster::rainmaker::attribute::challenge::Id; uint32_t attribute_id = cluster::rainmaker::attribute::challenge_response::Id;
esp_matter_attr_val_t val = esp_matter_char_str(challenge, strlen(challenge)); esp_matter_attr_val_t val = esp_matter_char_str(challenge_response, strlen(challenge_response));
return attribute::update(endpoint_id, cluster_id, attribute_id, &val); return attribute::update(endpoint_id, cluster_id, attribute_id, &val);
} }
@@ -187,7 +204,7 @@ static esp_err_t command_callback(const ConcreteCommandPath &command_path, TLVRe
/* Parse the tlv data */ /* Parse the tlv data */
chip::CharSpan config_value; chip::CharSpan config_value;
chip::app::DataModel::Decode(tlv_data, config_value); DataModel::Decode(tlv_data, config_value);
const char *data = config_value.data(); const char *data = config_value.data();
int size = config_value.size(); int size = config_value.size();
if (!data || size <= 0) { if (!data || size <= 0) {
@@ -235,6 +252,34 @@ static esp_err_t command_callback(const ConcreteCommandPath &command_path, TLVRe
return ESP_OK; return ESP_OK;
} }
static esp_err_t sign_and_update_challenge_response(chip::CharSpan challenge_span)
{
if (challenge_span.size() > ESP_MATTER_RAINMAKER_MAX_CHALLENGE_LEN) {
return ESP_ERR_INVALID_ARG;
}
/* Copy the data. This is done to make the strings NULL terminated. */
char challenge[ESP_MATTER_RAINMAKER_MAX_CHALLENGE_LEN] = {0};
uint8_t bytes_to_copy = std::min(sizeof(challenge), challenge_span.size());
strncpy(challenge, challenge_span.data(), bytes_to_copy);
challenge[bytes_to_copy] = 0;
ESP_LOGI(TAG, "challenge: %s", challenge);
// sign the data here
char *challenge_response = NULL;
size_t outlen = 0;
esp_err_t err = esp_rmaker_node_auth_sign_msg((void *)challenge, challenge_span.size(), (void **)&challenge_response, &outlen);
if (err != ESP_OK) {
return err;
}
err = challenge_response_attribute_update(challenge_response);
free(challenge_response);
return err;
}
static esp_err_t sign_data_command_callback(const ConcreteCommandPath &command_path, TLVReader &tlv_data, void *opaque_ptr) static esp_err_t sign_data_command_callback(const ConcreteCommandPath &command_path, TLVReader &tlv_data, void *opaque_ptr)
{ {
/* Get ids */ /* Get ids */
@@ -256,35 +301,21 @@ static esp_err_t sign_data_command_callback(const ConcreteCommandPath &command_p
} }
command_count--; command_count--;
// In an invoke interaction, the payload should be enclosed within an anonymous struct,
// and each argument must be specified using a context-specific tag.
// The following code parses the data without context-specific tags, which is incorrect and needs correction.
// TODO: CON-820
/* Parse the tlv data */ /* Parse the tlv data */
chip::CharSpan config_value; chip::CharSpan config_value;
chip::app::DataModel::Decode(tlv_data, config_value); DataModel::Decode(tlv_data, config_value);
if (!config_value.data() || config_value.size() <= 0) { if (!config_value.data() || config_value.size() <= 0) {
ESP_LOGE(TAG, "Command data not found or was not decoded correctly. The expected data is a string or the" ESP_LOGE(TAG, "Command data not found or was not decoded correctly. The expected data is a string or the"
"format is \"<data>\""); "format is \"<data>\"");
return ESP_FAIL; return ESP_FAIL;
} }
/* Copy the data. This is done to make the strings NULL terminated. */ return sign_and_update_challenge_response(config_value);
char challenge[ESP_MATTER_RAINMAKER_MAX_SIGN_DATA_LEN] = {0};
strncpy(challenge, config_value.data(), config_value.size());
challenge[config_value.size()] = '\0';
ESP_LOGI(TAG, "challenge: %s", challenge);
// sign the data here
char *challenge_response = NULL;
size_t outlen = 0;
esp_err_t err = esp_rmaker_node_auth_sign_msg((void *)challenge, config_value.size(), (void **)&challenge_response, &outlen);
// Return if challenge response is NULL
if (err != ESP_OK) {
return err;
}
challenge_attribute_update(challenge_response);
free(challenge_response);
return err;
} }
static esp_err_t custom_cluster_create() static esp_err_t custom_cluster_create()
@@ -295,22 +326,27 @@ static esp_err_t custom_cluster_create()
/* Create custom rainmaker cluster */ /* Create custom rainmaker cluster */
cluster_t *cluster = esp_matter::cluster::create(endpoint, cluster::rainmaker::Id, CLUSTER_FLAG_SERVER); cluster_t *cluster = esp_matter::cluster::create(endpoint, cluster::rainmaker::Id, CLUSTER_FLAG_SERVER);
attribute::create(cluster, Globals::Attributes::ClusterRevision::Id, ATTRIBUTE_FLAG_NONE, esp_matter_uint16(1)); attribute::create(cluster, Globals::Attributes::ClusterRevision::Id, ATTRIBUTE_FLAG_NONE, esp_matter_uint16(RAINMAKER_CLUSTER_REVISION));
/* Create custom status attribute */ /* Create custom status attribute */
/* Update the value of the attribute after esp_rmaker_node_init() is done */ /* Update the value of the attribute after esp_rmaker_node_init() is done */
attribute::create(cluster, cluster::rainmaker::attribute::status::Id, ATTRIBUTE_FLAG_NONE, esp_matter_bool(false)); attribute::create(cluster, cluster::rainmaker::attribute::status::Id, ATTRIBUTE_FLAG_NONE, esp_matter_bool(false));
/* Create custom node_id attribute */ /* Create custom rmaker_node_id attribute */
/* Update the value of the attribute after esp_rmaker_node_init() is done */ /* Update the value of the attribute after esp_rmaker_node_init() is done */
char node_id[ESP_MATTER_RAINMAKER_MAX_NODE_ID_LEN] = {0}; char rmaker_node_id[ESP_MATTER_RAINMAKER_MAX_NODE_ID_LEN] = {0};
attribute::create(cluster, cluster::rainmaker::attribute::node_id::Id, ATTRIBUTE_FLAG_NONE, attribute::create(cluster, cluster::rainmaker::attribute::rmaker_node_id::Id, ATTRIBUTE_FLAG_NONE,
esp_matter_char_str(node_id, sizeof(node_id))); esp_matter_char_str(rmaker_node_id, sizeof(rmaker_node_id)));
/* Create custom challenge_response attribute */
/* Update the value of the attribute after sign_data command is called */
char challenge_response[ESP_MATTER_RAINMAKER_MAX_CHALLENGE_RESPONSE_LEN] = {0};
attribute::create(cluster, cluster::rainmaker::attribute::challenge_response::Id, ATTRIBUTE_FLAG_NONE,
esp_matter_char_str(challenge_response, sizeof(challenge_response)));
/* Create custom challenge attribute */ /* Create custom challenge attribute */
/* Update the value of the attribute after sign_data command is called */
char challenge[ESP_MATTER_RAINMAKER_MAX_CHALLENGE_LEN] = {0}; char challenge[ESP_MATTER_RAINMAKER_MAX_CHALLENGE_LEN] = {0};
attribute::create(cluster, cluster::rainmaker::attribute::challenge::Id, ATTRIBUTE_FLAG_NONE, attribute::create(cluster, cluster::rainmaker::attribute::challenge::Id, ATTRIBUTE_FLAG_WRITABLE,
esp_matter_char_str(challenge, sizeof(challenge))); esp_matter_char_str(challenge, sizeof(challenge)));
/* Create custom configuration command */ /* Create custom configuration command */
@@ -324,13 +360,56 @@ static esp_err_t custom_cluster_create()
return ESP_OK; return ESP_OK;
} }
class RainmakerAttrAccess : public AttributeAccessInterface
{
public:
// Register for the Rainmaker cluster on endpoint 0.
RainmakerAttrAccess() : AttributeAccessInterface(chip::Optional<chip::EndpointId>(cluster::rainmaker::endpoint_id),
cluster::rainmaker::Id) {}
CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override
{
return CHIP_NO_ERROR;
}
CHIP_ERROR Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) override
{
if (aPath == ConcreteDataAttributePath(cluster::rainmaker::endpoint_id, cluster::rainmaker::Id, cluster::rainmaker::attribute::challenge::Id))
{
chip::CharSpan challenge;
CHIP_ERROR c_err = aDecoder.Decode(challenge);
if (c_err != CHIP_NO_ERROR)
{
ESP_LOGE(TAG, "Failed to decode challenge, err:%" CHIP_ERROR_FORMAT, c_err.Format());
return c_err;
}
return (ESP_OK == sign_and_update_challenge_response(challenge)) ? CHIP_NO_ERROR : CHIP_ERROR_INCORRECT_STATE;
}
else
{
return CHIP_NO_ERROR;
}
}
};
RainmakerAttrAccess gAttrAccess;
esp_err_t init() esp_err_t init()
{ {
/* Add custom rainmaker cluster */ /* Add custom rainmaker cluster */
#if CONFIG_ENABLE_CHIP_SHELL #if CONFIG_ENABLE_CHIP_SHELL
register_commands(); register_commands();
#endif #endif
return custom_cluster_create(); esp_err_t err = custom_cluster_create();
if (ESP_OK != err) {
ESP_LOGE(TAG, "Failed to create rainmaker cluster");
return err;
}
registerAttributeAccessOverride(&gAttrAccess);
return ESP_OK;
} }
esp_err_t start() esp_err_t start()
@@ -346,8 +425,8 @@ esp_err_t start()
esp_event_handler_register(RMAKER_EVENT, RMAKER_EVENT_USER_NODE_MAPPING_RESET, esp_event_handler_register(RMAKER_EVENT, RMAKER_EVENT_USER_NODE_MAPPING_RESET,
&user_node_association_event_handler, NULL); &user_node_association_event_handler, NULL);
/* Update node_id */ /* Update rmaker_node_id */
return node_id_attribute_update(esp_rmaker_get_node_id()); return rmaker_node_id_attribute_update(esp_rmaker_get_node_id());
} }
} /* rainmaker */ } /* rainmaker */
@@ -0,0 +1,46 @@
<?xml version="1.0"?>
<!--
Copyright (c) 2023 Project CHIP Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<configurator>
<domain name="CHIP"/>
<cluster>
<name>Rainmaker</name>
<domain>CHIP</domain>
<code>0x131BFC00</code>
<define>RAINMAKER_CLUSTER</define>
<description>Attributes and commands for rainmaker cluster.</description>
<globalAttribute side="either" code="0xFFFD" value="2"/>
<attribute side="server" code="0x0000" define="STATUS" type="boolean" default="0">Status</attribute>
<attribute side="server" code="0x0001" define="RMAKER_NODE_ID" type="char_string" length="40">RmakerNodeId</attribute>
<attribute side="server" code="0x0002" define="CHALLENGE_RESPONSE" type="char_string" length="150">ChallengeResponse</attribute>
<attribute side="server" code="0x0003" define="CHALLENGE" type="char_string" length="40" writable="true">Challenge</attribute>
<command source="client" code="0x00" name="Configuration" optional="false">
<description>Configuration command.</description>
<arg name="ConfigurationArg" type="char_string"/>
</command>
<command source="client" code="0x01" name="SignData" optional="false">
<description>SignData command.</description>
<arg name="SignDataArg" type="char_string"/>
</command>
</cluster>
</configurator>