chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:04:25 +08:00
commit 548b49ebc0
20937 changed files with 5455372 additions and 0 deletions
@@ -0,0 +1,464 @@
/*
* SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <esp_err.h>
#include <esp_log.h>
#include <protocomm.h>
#include <protocomm_security0.h>
#include <protocomm_security1.h>
#include <protocomm_security2.h>
#include <esp_local_ctrl.h>
#include "esp_local_ctrl_priv.h"
#include "esp_local_ctrl.pb-c.h"
#define ESP_LOCAL_CTRL_VERSION "v1.0"
/* JSON format string for version endpoint */
#define ESP_LOCAL_CTRL_VER_FMT_STR "{\"local_ctrl\":{\"ver\":\"%s\",\"sec_ver\":%d,\"sec_patch_ver\":%d}}"
struct inst_ctx {
protocomm_t *pc;
esp_local_ctrl_config_t config;
esp_local_ctrl_prop_t **props;
size_t props_count;
};
struct inst_ctx *local_ctrl_inst_ctx;
static const char *TAG = "esp_local_ctrl";
esp_err_t esp_local_ctrl_start(const esp_local_ctrl_config_t *config)
{
esp_err_t ret;
if (!config) {
ESP_LOGE(TAG, "NULL configuration provided");
return ESP_ERR_INVALID_ARG;
}
if (!config->transport) {
ESP_LOGE(TAG, "No transport provided");
return ESP_ERR_INVALID_ARG;
}
if (config->max_properties == 0) {
ESP_LOGE(TAG, "max_properties must be greater than 0");
return ESP_ERR_INVALID_ARG;
}
if (!config->handlers.get_prop_values ||
!config->handlers.set_prop_values) {
ESP_LOGE(TAG, "Handlers cannot be null");
return ESP_ERR_INVALID_ARG;
}
if (local_ctrl_inst_ctx) {
ESP_LOGW(TAG, "Service already active");
return ESP_ERR_INVALID_STATE;
}
local_ctrl_inst_ctx = calloc(1, sizeof(struct inst_ctx));
if (!local_ctrl_inst_ctx) {
ESP_LOGE(TAG, "Failed to allocate memory for instance");
return ESP_ERR_NO_MEM;
}
memcpy(&local_ctrl_inst_ctx->config, config, sizeof(local_ctrl_inst_ctx->config));
local_ctrl_inst_ctx->props = calloc(local_ctrl_inst_ctx->config.max_properties,
sizeof(esp_local_ctrl_prop_t *));
if (!local_ctrl_inst_ctx->props) {
ESP_LOGE(TAG, "Failed to allocate memory for properties");
free(local_ctrl_inst_ctx);
local_ctrl_inst_ctx = NULL;
return ESP_ERR_NO_MEM;
}
/* Since the config structure will be different for different transport modes, each transport may
* implement a `copy_config()` function, which accepts a configuration structure as input and
* creates a copy of that, which can be kept in the context structure of the `esp_local_ctrl` instance.
* This copy can be later be freed using `free_config()` */
if (config->transport->copy_config) {
ret = config->transport->copy_config(&local_ctrl_inst_ctx->config.transport_config,
&config->transport_config);
if (ret != ESP_OK) {
esp_local_ctrl_stop();
return ret;
}
}
/* For a selected transport mode, endpoints may need to be declared prior to starting the
* `esp_local_ctrl` service, e.g. in case of BLE. By declaration it means that the transport layer
* allocates some resources for an endpoint, and later, after service has started, a handler
* is assigned for that endpoint */
if (config->transport->declare_ep) {
/* UUIDs are 16bit unique IDs for each endpoint. This may or may not be relevant for
* a chosen transport. We reserve all values from FF50 to FFFF for the internal endpoints.
* The remaining endpoints can be used by the application for its own custom endpoints */
uint16_t start_uuid = 0xFF50;
ret = config->transport->declare_ep(&local_ctrl_inst_ctx->config.transport_config,
"esp_local_ctrl/version", start_uuid++);
if (ret != ESP_OK) {
esp_local_ctrl_stop();
return ret;
}
ret = config->transport->declare_ep(&local_ctrl_inst_ctx->config.transport_config,
"esp_local_ctrl/session", start_uuid++);
if (ret != ESP_OK) {
esp_local_ctrl_stop();
return ret;
}
ret = config->transport->declare_ep(&local_ctrl_inst_ctx->config.transport_config,
"esp_local_ctrl/control", start_uuid++);
if (ret != ESP_OK) {
esp_local_ctrl_stop();
return ret;
}
}
local_ctrl_inst_ctx->pc = protocomm_new();
if (!local_ctrl_inst_ctx->pc) {
ESP_LOGE(TAG, "Failed to create new protocomm instance");
esp_local_ctrl_stop();
return ESP_FAIL;
}
if (config->transport->start_service) {
ret = config->transport->start_service(local_ctrl_inst_ctx->pc,
&local_ctrl_inst_ctx->config.transport_config);
if (ret != ESP_OK) {
esp_local_ctrl_stop();
return ret;
}
}
protocomm_security_t *proto_sec_handle = NULL;
switch (local_ctrl_inst_ctx->config.proto_sec.version) {
case PROTOCOM_SEC_CUSTOM:
proto_sec_handle = local_ctrl_inst_ctx->config.proto_sec.custom_handle;
break;
case PROTOCOM_SEC1:
#ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1
proto_sec_handle = (protocomm_security_t *) &protocomm_security1;
#else
// Enable SECURITY_VERSION_1 in Protocomm configuration menu
return ESP_ERR_NOT_SUPPORTED;
#endif
break;
case PROTOCOM_SEC2:
#ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2
proto_sec_handle = (protocomm_security_t *) &protocomm_security2;
break;
#else
// Enable SECURITY_VERSION_2 in Protocomm configuration menu
return ESP_ERR_NOT_SUPPORTED;
#endif
case PROTOCOM_SEC0:
default:
#ifdef CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0
proto_sec_handle = (protocomm_security_t *) &protocomm_security0;
#else
// Enable SECURITY_VERSION_0 in Protocomm configuration menu
return ESP_ERR_NOT_SUPPORTED;
#endif
break;
}
ret = protocomm_set_security(local_ctrl_inst_ctx->pc, "esp_local_ctrl/session",
proto_sec_handle, local_ctrl_inst_ctx->config.proto_sec.sec_params);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to set session endpoint");
esp_local_ctrl_stop();
return ret;
}
int sec_ver = 0;
uint8_t sec_patch_ver = 0;
protocomm_get_sec_version(local_ctrl_inst_ctx->pc, &sec_ver, &sec_patch_ver);
const int rsize = snprintf(NULL, 0, ESP_LOCAL_CTRL_VER_FMT_STR, ESP_LOCAL_CTRL_VERSION, sec_ver, sec_patch_ver) + 1;
char *ver_str = malloc(rsize);
if (!ver_str) {
ESP_LOGE(TAG, "Failed to allocate memory for version string");
esp_local_ctrl_stop();
return ESP_ERR_NO_MEM;
}
snprintf(ver_str, rsize, ESP_LOCAL_CTRL_VER_FMT_STR, ESP_LOCAL_CTRL_VERSION, sec_ver, sec_patch_ver);
ESP_LOGD(TAG, "ver_str: %s", ver_str);
ret = protocomm_set_version(local_ctrl_inst_ctx->pc, "esp_local_ctrl/version",
ver_str);
free(ver_str);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to set version endpoint");
esp_local_ctrl_stop();
return ret;
}
ret = protocomm_add_endpoint(local_ctrl_inst_ctx->pc, "esp_local_ctrl/control",
esp_local_ctrl_data_handler, NULL);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to set control endpoint");
esp_local_ctrl_stop();
return ret;
}
return ESP_OK;
}
esp_err_t esp_local_ctrl_stop(void)
{
if (local_ctrl_inst_ctx) {
if (local_ctrl_inst_ctx->config.transport->free_config) {
local_ctrl_inst_ctx->config.transport->free_config(&local_ctrl_inst_ctx->config.transport_config);
}
if (local_ctrl_inst_ctx->pc) {
if (local_ctrl_inst_ctx->config.transport->stop_service) {
local_ctrl_inst_ctx->config.transport->stop_service(local_ctrl_inst_ctx->pc);
}
protocomm_delete(local_ctrl_inst_ctx->pc);
}
if (local_ctrl_inst_ctx->config.handlers.usr_ctx_free_fn) {
local_ctrl_inst_ctx->config.handlers.usr_ctx_free_fn(
local_ctrl_inst_ctx->config.handlers.usr_ctx);
}
/* Iterate through all properties one by one and free them */
for (uint32_t i = 0; i < local_ctrl_inst_ctx->config.max_properties; i++) {
if (local_ctrl_inst_ctx->props[i] == NULL) {
continue;
}
/* Release memory allocated for property data */
free(local_ctrl_inst_ctx->props[i]->name);
if (local_ctrl_inst_ctx->props[i]->ctx_free_fn) {
local_ctrl_inst_ctx->props[i]->ctx_free_fn(local_ctrl_inst_ctx->props[i]->ctx);
}
free(local_ctrl_inst_ctx->props[i]);
}
free(local_ctrl_inst_ctx->props);
free(local_ctrl_inst_ctx);
local_ctrl_inst_ctx = NULL;
}
return ESP_OK;
}
static int esp_local_ctrl_get_property_index(const char *name)
{
if (!local_ctrl_inst_ctx || !name) {
return -1;
}
/* Iterate through all properties one by one
* and find the one with matching name */
for (uint32_t i = 0; i < local_ctrl_inst_ctx->props_count; i++) {
if (strcmp(local_ctrl_inst_ctx->props[i]->name, name) == 0) {
return i;
}
}
return -1;
}
esp_err_t esp_local_ctrl_add_property(const esp_local_ctrl_prop_t *prop)
{
if (!local_ctrl_inst_ctx) {
ESP_LOGE(TAG, "Service not running");
return ESP_ERR_INVALID_STATE;
}
if (!prop || !prop->name) {
return ESP_ERR_INVALID_ARG;
}
if (esp_local_ctrl_get_property_index(prop->name) >= 0) {
ESP_LOGE(TAG, "Property with name %s exists", prop->name);
return ESP_ERR_INVALID_STATE;
}
if (local_ctrl_inst_ctx->config.max_properties
== local_ctrl_inst_ctx->props_count) {
ESP_LOGE(TAG, "Max properties limit reached. Cannot add property %s", prop->name);
return ESP_ERR_NO_MEM;
}
uint32_t i = local_ctrl_inst_ctx->props_count;
local_ctrl_inst_ctx->props[i] = calloc(1, sizeof(esp_local_ctrl_prop_t));
if (!local_ctrl_inst_ctx->props[i]) {
ESP_LOGE(TAG, "Failed to allocate memory for new property %s", prop->name);
return ESP_ERR_NO_MEM;
}
local_ctrl_inst_ctx->props[i]->name = strdup(prop->name);
if (!local_ctrl_inst_ctx->props[i]->name) {
ESP_LOGE(TAG, "Failed to allocate memory for property data %s", prop->name);
free(local_ctrl_inst_ctx->props[i]);
local_ctrl_inst_ctx->props[i] = NULL;
return ESP_ERR_NO_MEM;
}
local_ctrl_inst_ctx->props[i]->type = prop->type;
local_ctrl_inst_ctx->props[i]->size = prop->size;
local_ctrl_inst_ctx->props[i]->flags = prop->flags;
local_ctrl_inst_ctx->props[i]->ctx = prop->ctx;
local_ctrl_inst_ctx->props[i]->ctx_free_fn = prop->ctx_free_fn;
local_ctrl_inst_ctx->props_count++;
return ESP_OK;
}
esp_err_t esp_local_ctrl_remove_property(const char *name)
{
int idx = esp_local_ctrl_get_property_index(name);
if (idx < 0) {
ESP_LOGE(TAG, "Property %s not found", name);
return ESP_ERR_NOT_FOUND;
}
/* Release memory allocated for property data */
if (local_ctrl_inst_ctx->props[idx]->ctx_free_fn) {
local_ctrl_inst_ctx->props[idx]->ctx_free_fn(
local_ctrl_inst_ctx->props[idx]->ctx);
}
free(local_ctrl_inst_ctx->props[idx]->name);
free(local_ctrl_inst_ctx->props[idx]);
local_ctrl_inst_ctx->props[idx++] = NULL;
/* Move the following properties forward, so that there is
* no empty space between two properties */
for (uint32_t i = idx; i < local_ctrl_inst_ctx->props_count; i++) {
if (local_ctrl_inst_ctx->props[i] == NULL) {
break;
}
local_ctrl_inst_ctx->props[i-1] = local_ctrl_inst_ctx->props[i];
}
/* Clear the stale pointer left in the last slot after compaction to
* prevent a double-free if esp_local_ctrl_stop() is called before the
* slot is overwritten by a subsequent add_property(). */
local_ctrl_inst_ctx->props[local_ctrl_inst_ctx->props_count - 1] = NULL;
local_ctrl_inst_ctx->props_count--;
return ESP_OK;
}
const esp_local_ctrl_prop_t *esp_local_ctrl_get_property(const char *name)
{
int idx = esp_local_ctrl_get_property_index(name);
if (idx < 0) {
ESP_LOGE(TAG, "Property %s not found", name);
return NULL;
}
return local_ctrl_inst_ctx->props[idx];
}
esp_err_t esp_local_ctrl_get_prop_count(size_t *count)
{
if (!local_ctrl_inst_ctx) {
ESP_LOGE(TAG, "Service not running");
return ESP_ERR_INVALID_STATE;
}
if (!count) {
return ESP_ERR_INVALID_ARG;
}
*count = local_ctrl_inst_ctx->props_count;
return ESP_OK;
}
esp_err_t esp_local_ctrl_get_prop_values(size_t total_indices, uint32_t *indices,
esp_local_ctrl_prop_t *props,
esp_local_ctrl_prop_val_t *values)
{
if (!local_ctrl_inst_ctx) {
ESP_LOGE(TAG, "Service not running");
return ESP_ERR_INVALID_STATE;
}
if (!indices || !props || !values) {
return ESP_ERR_INVALID_ARG;
}
/* Convert indices to names */
for (size_t i = 0; i < total_indices; i++) {
if (indices[i] >= local_ctrl_inst_ctx->props_count) {
ESP_LOGE(TAG, "Invalid property index %" PRId32, indices[i]);
return ESP_ERR_INVALID_ARG;
}
props[i].name = local_ctrl_inst_ctx->props[indices[i]]->name;
props[i].type = local_ctrl_inst_ctx->props[indices[i]]->type;
props[i].flags = local_ctrl_inst_ctx->props[indices[i]]->flags;
props[i].size = local_ctrl_inst_ctx->props[indices[i]]->size;
props[i].ctx = local_ctrl_inst_ctx->props[indices[i]]->ctx;
}
esp_local_ctrl_handlers_t *h = &local_ctrl_inst_ctx->config.handlers;
esp_err_t ret = h->get_prop_values(total_indices, props, values, h->usr_ctx);
/* Properties with fixed sizes need to be checked */
for (size_t i = 0; i < total_indices; i++) {
if (local_ctrl_inst_ctx->props[indices[i]]->size != 0) {
values[i].size = local_ctrl_inst_ctx->props[indices[i]]->size;
}
}
return ret;
}
esp_err_t esp_local_ctrl_set_prop_values(size_t total_indices, uint32_t *indices,
const esp_local_ctrl_prop_val_t *values)
{
if (!local_ctrl_inst_ctx) {
ESP_LOGE(TAG, "Service not running");
return ESP_ERR_INVALID_STATE;
}
if (!indices || !values) {
return ESP_ERR_INVALID_ARG;
}
esp_local_ctrl_prop_t *props = calloc(total_indices,
sizeof(esp_local_ctrl_prop_t));
if (!props) {
ESP_LOGE(TAG, "Unable to allocate memory for properties array");
return ESP_ERR_NO_MEM;
}
for (size_t i = 0; i < total_indices; i++) {
if (indices[i] >= local_ctrl_inst_ctx->props_count) {
ESP_LOGE(TAG, "Invalid property index %" PRId32, indices[i]);
free(props);
return ESP_ERR_INVALID_ARG;
}
/* Properties with fixed sizes need to be checked */
if ((local_ctrl_inst_ctx->props[indices[i]]->size != values[i].size) &&
(local_ctrl_inst_ctx->props[indices[i]]->size != 0)) {
ESP_LOGE(TAG, "Invalid property size %d. Expected %d",
values[i].size, local_ctrl_inst_ctx->props[indices[i]]->size);
free(props);
return ESP_ERR_INVALID_ARG;
}
props[i].name = local_ctrl_inst_ctx->props[indices[i]]->name;
props[i].type = local_ctrl_inst_ctx->props[indices[i]]->type;
props[i].flags = local_ctrl_inst_ctx->props[indices[i]]->flags;
props[i].size = local_ctrl_inst_ctx->props[indices[i]]->size;
props[i].ctx = local_ctrl_inst_ctx->props[indices[i]]->ctx;
}
esp_local_ctrl_handlers_t *h = &local_ctrl_inst_ctx->config.handlers;
esp_err_t ret = h->set_prop_values(total_indices, props, values, h->usr_ctx);
free(props);
return ret;
}
esp_err_t esp_local_ctrl_set_handler(const char *ep_name,
protocomm_req_handler_t handler,
void *priv_data)
{
esp_err_t ret = ESP_ERR_INVALID_STATE;
if (local_ctrl_inst_ctx) {
ret = protocomm_add_endpoint(local_ctrl_inst_ctx->pc, ep_name,
handler, priv_data);
}
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to register endpoint handler");
}
return ret;
}
@@ -0,0 +1,302 @@
/*
* SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <esp_err.h>
#include <esp_log.h>
#include "esp_compiler.h"
#include "esp_local_ctrl.h"
#include "esp_local_ctrl_priv.h"
#include "esp_local_ctrl.pb-c.h"
#define SAFE_ALLOCATION(type, var) \
type *var = (type *) malloc(sizeof(type)); \
if (!var) { \
ESP_LOGE(TAG, "Error allocating memory"); \
return ESP_ERR_NO_MEM; \
}
static const char* TAG = "esp_local_ctrl_handler";
typedef struct esp_local_ctrl_cmd {
int cmd_num;
int expected_payload_case;
esp_err_t (*command_handler)(LocalCtrlMessage *req,
LocalCtrlMessage *resp, void **ctx);
} esp_local_ctrl_cmd_t;
static esp_err_t cmd_get_prop_count_handler(LocalCtrlMessage *req,
LocalCtrlMessage *resp, void **ctx);
static esp_err_t cmd_get_prop_vals_handler(LocalCtrlMessage *req,
LocalCtrlMessage *resp, void **ctx);
static esp_err_t cmd_set_prop_vals_handler(LocalCtrlMessage *req,
LocalCtrlMessage *resp, void **ctx);
static esp_local_ctrl_cmd_t cmd_table[] = {
{
.cmd_num = LOCAL_CTRL_MSG_TYPE__TypeCmdGetPropertyCount,
.expected_payload_case = LOCAL_CTRL_MESSAGE__PAYLOAD_CMD_GET_PROP_COUNT,
.command_handler = cmd_get_prop_count_handler
},
{
.cmd_num = LOCAL_CTRL_MSG_TYPE__TypeCmdGetPropertyValues,
.expected_payload_case = LOCAL_CTRL_MESSAGE__PAYLOAD_CMD_GET_PROP_VALS,
.command_handler = cmd_get_prop_vals_handler
},
{
.cmd_num = LOCAL_CTRL_MSG_TYPE__TypeCmdSetPropertyValues,
.expected_payload_case = LOCAL_CTRL_MESSAGE__PAYLOAD_CMD_SET_PROP_VALS,
.command_handler = cmd_set_prop_vals_handler
}
};
static uint16_t err_to_status(esp_err_t err)
{
uint16_t status;
switch (err) {
case ESP_OK:
status = STATUS__Success;
break;
case ESP_ERR_INVALID_ARG:
status = STATUS__InvalidArgument;
break;
case ESP_ERR_INVALID_STATE:
status = STATUS__InvalidProto;
break;
default:
status = STATUS__InternalError;
}
return status;
}
static esp_err_t cmd_get_prop_count_handler(LocalCtrlMessage *req,
LocalCtrlMessage *resp, void **ctx)
{
SAFE_ALLOCATION(RespGetPropertyCount, resp_payload);
resp_get_property_count__init(resp_payload);
size_t prop_count = 0;
resp_payload->status = err_to_status(esp_local_ctrl_get_prop_count(&prop_count));
resp_payload->count = prop_count;
resp->payload_case = LOCAL_CTRL_MESSAGE__PAYLOAD_RESP_GET_PROP_COUNT;
resp->resp_get_prop_count = resp_payload;
ESP_LOGD(TAG, "Got properties count %d", prop_count);
return ESP_OK;
}
typedef void (*prop_val_free_fn_t)(void *val);
static esp_err_t cmd_get_prop_vals_handler(LocalCtrlMessage *req,
LocalCtrlMessage *resp, void **ctx)
{
SAFE_ALLOCATION(RespGetPropertyValues, resp_payload);
resp_get_property_values__init(resp_payload);
esp_local_ctrl_prop_val_t *vals = calloc(req->cmd_get_prop_vals->n_indices,
sizeof(esp_local_ctrl_prop_val_t));
esp_local_ctrl_prop_t *descs = calloc(req->cmd_get_prop_vals->n_indices,
sizeof(esp_local_ctrl_prop_t));
prop_val_free_fn_t *free_fns = calloc(req->cmd_get_prop_vals->n_indices,
sizeof(prop_val_free_fn_t));
resp_payload->props = calloc(req->cmd_get_prop_vals->n_indices,
sizeof(PropertyInfo *));
if (!vals || !descs || !free_fns || !resp_payload->props) {
ESP_LOGE(TAG, "Failed to allocate memory for getting values");
free(vals);
free(descs);
free(free_fns);
free(resp_payload->props);
free(resp_payload);
return ESP_ERR_NO_MEM;
}
esp_err_t ret = esp_local_ctrl_get_prop_values(req->cmd_get_prop_vals->n_indices,
req->cmd_get_prop_vals->indices,
descs, vals);
resp_payload->status = err_to_status(ret);
if (ret == ESP_OK) {
resp_payload->n_props = 0;
for (size_t i = 0; i < req->cmd_get_prop_vals->n_indices; i++) {
ESP_COMPILER_DIAGNOSTIC_PUSH_IGNORE("-Wanalyzer-malloc-leak") // False-positive detection. TODO GCC-366
resp_payload->props[i] = malloc(sizeof(PropertyInfo));
if (!resp_payload->props[i]) {
resp_payload->status = STATUS__InternalError;
break;
}
ESP_COMPILER_DIAGNOSTIC_POP("-Wanalyzer-malloc-leak")
resp_payload->n_props++;
property_info__init(resp_payload->props[i]);
resp_payload->props[i]->name = descs[i].name;
resp_payload->props[i]->type = descs[i].type;
resp_payload->props[i]->flags = descs[i].flags;
resp_payload->props[i]->value.data = vals[i].data;
resp_payload->props[i]->value.len = vals[i].size;
free_fns[i] = vals[i].free_fn;
}
}
resp->payload_case = LOCAL_CTRL_MESSAGE__PAYLOAD_RESP_GET_PROP_VALS;
resp->resp_get_prop_vals = resp_payload;
(*ctx) = (void *)free_fns;
free(vals);
free(descs);
/* Unless it's a fatal error, always return ESP_OK, otherwise
* the underlying connection will be closed by protocomm */
return ESP_OK;
}
static esp_err_t cmd_set_prop_vals_handler(LocalCtrlMessage *req,
LocalCtrlMessage *resp, void **ctx)
{
SAFE_ALLOCATION(RespSetPropertyValues, resp_payload);
resp_set_property_values__init(resp_payload);
uint32_t *idxs = calloc(req->cmd_set_prop_vals->n_props, sizeof(uint32_t));
esp_local_ctrl_prop_val_t *vals = calloc(req->cmd_set_prop_vals->n_props,
sizeof(esp_local_ctrl_prop_val_t));
if (!idxs || !vals) {
ESP_LOGE(TAG, "Failed to allocate memory for setting values");
free(idxs);
free(vals);
free(resp_payload);
return ESP_ERR_NO_MEM;
}
for (size_t i = 0; i < req->cmd_set_prop_vals->n_props; i++) {
idxs[i] = req->cmd_set_prop_vals->props[i]->index;
vals[i].data = req->cmd_set_prop_vals->props[i]->value.data;
vals[i].size = req->cmd_set_prop_vals->props[i]->value.len;
}
esp_err_t ret = esp_local_ctrl_set_prop_values(req->cmd_set_prop_vals->n_props,
idxs, vals);
resp_payload->status = err_to_status(ret);
resp->payload_case = LOCAL_CTRL_MESSAGE__PAYLOAD_RESP_SET_PROP_VALS;
resp->resp_set_prop_vals = resp_payload;
free(idxs);
free(vals);
/* Unless it's a fatal error, always return ESP_OK, otherwise
* the underlying connection will be closed by protocomm */
return ESP_OK;
}
static int lookup_cmd_handler(int cmd_id)
{
for (size_t i = 0; i < sizeof(cmd_table)/sizeof(esp_local_ctrl_cmd_t); i++) {
if (cmd_table[i].cmd_num == cmd_id) {
return i;
}
}
return -1;
}
static void esp_local_ctrl_command_cleanup(LocalCtrlMessage *resp, void **ctx)
{
if (!resp) {
return;
}
switch (resp->msg) {
case LOCAL_CTRL_MSG_TYPE__TypeRespGetPropertyCount:
free(resp->resp_get_prop_count);
break;
case LOCAL_CTRL_MSG_TYPE__TypeRespGetPropertyValues: {
if (resp->resp_get_prop_vals) {
prop_val_free_fn_t *free_fns = (prop_val_free_fn_t *)(*ctx);
for (size_t i = 0; i < resp->resp_get_prop_vals->n_props; i++) {
if (free_fns && free_fns[i]) {
free_fns[i](resp->resp_get_prop_vals->props[i]->value.data);
}
free(resp->resp_get_prop_vals->props[i]);
}
free(free_fns);
free(resp->resp_get_prop_vals->props);
free(resp->resp_get_prop_vals);
}
}
break;
case LOCAL_CTRL_MSG_TYPE__TypeRespSetPropertyValues:
free(resp->resp_set_prop_vals);
break;
default:
ESP_LOGE(TAG, "Unsupported response type in cleanup_handler");
}
return;
}
static esp_err_t esp_local_ctrl_command_dispatcher(LocalCtrlMessage *req,
LocalCtrlMessage *resp,
void **ctx)
{
int cmd_index = lookup_cmd_handler(req->msg);
if (cmd_index < 0) {
ESP_LOGE(TAG, "Invalid command handler lookup");
return ESP_ERR_INVALID_ARG;
}
if (req->payload_case != cmd_table[cmd_index].expected_payload_case) {
ESP_LOGE(TAG, "Payload type mismatch: msg_type %d expects payload %d, got %d",
req->msg, cmd_table[cmd_index].expected_payload_case, req->payload_case);
return ESP_ERR_INVALID_ARG;
}
esp_err_t ret = cmd_table[cmd_index].command_handler(req, resp, ctx);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Error executing command handler");
return ret;
}
return ESP_OK;
}
esp_err_t esp_local_ctrl_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen,
uint8_t **outbuf, ssize_t *outlen, void *priv_data)
{
void *temp_ctx = NULL;
LocalCtrlMessage *req = local_ctrl_message__unpack(NULL, inlen, inbuf);
if (!req) {
ESP_LOGE(TAG, "Unable to unpack payload data");
return ESP_ERR_INVALID_ARG;
}
LocalCtrlMessage resp;
local_ctrl_message__init(&resp);
resp.msg = req->msg + 1; /* Response is request + 1 */
esp_err_t ret = esp_local_ctrl_command_dispatcher(req, &resp, &temp_ctx);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "command dispatcher failed");
esp_local_ctrl_command_cleanup(&resp, &temp_ctx);
local_ctrl_message__free_unpacked(req, NULL);
return ESP_FAIL;
}
local_ctrl_message__free_unpacked(req, NULL);
*outlen = local_ctrl_message__get_packed_size(&resp);
if (*outlen <= 0) {
ESP_LOGE(TAG, "Invalid encoding for response");
esp_local_ctrl_command_cleanup(&resp, &temp_ctx);
return ESP_FAIL;
}
*outbuf = (uint8_t *) malloc(*outlen);
if (!*outbuf) {
ESP_LOGE(TAG, "System out of memory");
esp_local_ctrl_command_cleanup(&resp, &temp_ctx);
return ESP_ERR_NO_MEM;
}
local_ctrl_message__pack(&resp, *outbuf);
esp_local_ctrl_command_cleanup(&resp, &temp_ctx);
ESP_LOG_BUFFER_HEX_LEVEL(TAG, *outbuf, *outlen, ESP_LOG_DEBUG);
return ESP_OK;
}
@@ -0,0 +1,153 @@
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
//
// 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.
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <esp_err.h>
#include <protocomm.h>
#include <esp_local_ctrl.h>
/**
* @brief `esp_local_ctrl` transport specific structure
*
* Every supported transport layer should have the following functions
* implemented for starting, stopping and configuring a protocomm service
*/
struct esp_local_ctrl_transport {
/**
* Handler for starting a protocomm service as per specified configuration
*/
esp_err_t (*start_service) (protocomm_t *pc,
const esp_local_ctrl_transport_config_t *config);
/**
* Handler for stopping a protocomm service
*/
void (*stop_service) (protocomm_t *pc);
/**
* Handler for creating a copy of the transport specific configuration
*/
esp_err_t (*copy_config) (esp_local_ctrl_transport_config_t *dest_config,
const esp_local_ctrl_transport_config_t *src_config);
/**
* Handler for allocating resources corresponding to a protocomm endpoint.
* Usually when adding a new endpoint `protocomm_endpoint_add()` API is used,
* but the transport layer may need to perform resource allocation for
* each endpoint, prior to starting the protocomm instance. This handler
* is useful in that case, as it is called before `start_service()`.
*/
esp_err_t (*declare_ep) (esp_local_ctrl_transport_config_t *config,
const char *ep_name, uint16_t ep_uuid);
/**
* Handler for freeing a transport specific configuration
*/
void (*free_config) (esp_local_ctrl_transport_config_t *config);
};
/**
* @brief Protocomm handler for `esp_local_ctrl`
*
* This is the handler which is responsible for processing incoming requests
* over a protocomm channel, then invokes one of the following functions
* depending upon the request type:
* - `esp_local_ctrl_get_prop_count()`
* - `esp_local_ctrl_get_prop_values()`
* -` esp_local_ctrl_set_prop_values()`
* The output of the above functions are used to form the response messages
* corresponding to request types. The formed response messages are packed and
* sent back via the protocomm channel.
*
* @param[in] session_id A number to identify an ongoing session between
* device and client
* @param[in] inbuf Buffer which holds serialized / packed request data
* @param[in] inlen Length of input buffer
* @param[out] outbuf Buffer which holds serialized / packed response data
* @param[out] outlen Length of output buffer
* @param[in] priv_data Private data associated with `esp_local_ctrl` endpoint
*
* @return
* - ESP_OK : Success
* - ESP_FAIL : Failure
*/
esp_err_t esp_local_ctrl_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen,
uint8_t **outbuf, ssize_t *outlen, void *priv_data);
/**
* @brief Use this for obtaining total number of properties registered
* with `esp_local_ctrl` service
*
* @param[out] count Pointer to variable where the result is to be stored
*
* @return
* - ESP_OK : Success
* - ESP_FAIL : Failure
*/
esp_err_t esp_local_ctrl_get_prop_count(size_t *count);
/**
* @brief Get descriptions and values of multiple properties at the same time.
* The properties are requested by indices. This internally calls the
* `get_prop_values` handler specified in the `esp_local_ctrl_handlers_t`
* structure. Since `get_prop_values` accepts property structure, the
* indices are first converted to the corresponding `esp_local_ctrl_prop_t`
* internally.
*
* @param[in] total_indices The number of elements in the `indices` array argument
* @param[in] indices An array of indices, that specify which properties to get
* @param[out] props A pre-allocated array of empty property structures, elements of
* which are to be populated with names, types and flags of those
* properties which correspond to the provided indices
* @param[out] values A pre-allocated array of empty value structures, elements of
* which are to be populated with values and sizes of those
* properties which correspond to the provided indices
*
* @return
* - ESP_OK : Success
* - ESP_FAIL : Failure
*/
esp_err_t esp_local_ctrl_get_prop_values(size_t total_indices, uint32_t *indices,
esp_local_ctrl_prop_t *props,
esp_local_ctrl_prop_val_t *values);
/**
* @brief Set values of multiple properties at the same time. The properties to
* set are specified by indices. This internally calls the `set_prop_values`
* handler specified in the `esp_local_ctrl_handlers_t` structure. Since
* `set_prop_values` accepts property structures, the indices are first
* converted to the corresponding `esp_local_ctrl_prop_t` internally.
*
* @param[in] total_indices The number of elements in the `indices` array argument
* @param[in] indices An array of indices, that specify which properties to set
* @param[in] values A array of values. Every value should have the correct
* size, if it is for setting a fixed size property, else
* error will be generated and none of the properties will
* be set to any of the given values
*
* @return
* - ESP_OK : Success
* - ESP_FAIL : Failure
*/
esp_err_t esp_local_ctrl_set_prop_values(size_t total_indices, uint32_t *indices,
const esp_local_ctrl_prop_val_t *values);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,148 @@
/*
* SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <esp_err.h>
#include <esp_log.h>
#include <stdio.h>
#include <string.h>
#include <esp_local_ctrl.h>
#include <protocomm_ble.h>
#include "esp_local_ctrl_priv.h"
#define LOCAL_CTRL_VERSION "v1.0"
static const char *TAG = "esp_local_ctrl_transport_ble";
static esp_err_t
start_ble_transport(protocomm_t *pc,
const esp_local_ctrl_transport_config_t *config)
{
if (!config || !config->ble) {
ESP_LOGE(TAG, "NULL configuration provided");
return ESP_ERR_INVALID_ARG;
}
return protocomm_ble_start(pc, config->ble);
}
static void stop_ble_transport(protocomm_t *pc)
{
protocomm_ble_stop(pc);
}
static esp_err_t
copy_ble_config(esp_local_ctrl_transport_config_t *dest_config,
const esp_local_ctrl_transport_config_t *src_config)
{
esp_err_t ret = ESP_OK;
if (!dest_config || !src_config || !src_config->ble) {
ESP_LOGE(TAG, "NULL arguments provided");
return ESP_ERR_INVALID_ARG;
}
dest_config->ble = calloc(1, sizeof(protocomm_ble_config_t));
if (!dest_config->ble) {
ESP_LOGE(TAG, "Failed to allocate memory for BLE transport config");
return ESP_ERR_NO_MEM;
}
/* Copy BLE device name */
memcpy(dest_config->ble->device_name, src_config->ble->device_name,
sizeof(src_config->ble->device_name));
/* Copy Service UUID */
memcpy(dest_config->ble->service_uuid, src_config->ble->service_uuid,
sizeof(src_config->ble->service_uuid));
dest_config->ble->nu_lookup_count = 0;
if (src_config->ble->nu_lookup_count) {
/* Copy any provided name-uuid lookup table */
dest_config->ble->nu_lookup = calloc(src_config->ble->nu_lookup_count,
sizeof(protocomm_ble_name_uuid_t));
if (!dest_config->ble->nu_lookup) {
ESP_LOGE(TAG, "Failed to allocate memory for BLE characteristic names");
free(dest_config->ble);
return ESP_ERR_NO_MEM;
}
for (uint16_t i = 0; i < src_config->ble->nu_lookup_count; i++) {
dest_config->ble->nu_lookup[i].uuid = src_config->ble->nu_lookup[i].uuid;
if (!src_config->ble->nu_lookup[i].name) {
ESP_LOGE(TAG, "Endpoint name cannot be null");
ret = ESP_ERR_INVALID_ARG;
goto err_free_nu_lookup;
}
dest_config->ble->nu_lookup[i].name =
strdup(src_config->ble->nu_lookup[i].name);
if (!dest_config->ble->nu_lookup[i].name) {
ESP_LOGE(TAG, "Failed to allocate memory for endpoint name");
ret = ESP_ERR_NO_MEM;
goto err_free_nu_lookup;
}
dest_config->ble->nu_lookup_count++;
}
}
return ESP_OK;
err_free_nu_lookup:
for (uint16_t i = 0; i < dest_config->ble->nu_lookup_count; i++) {
free((void *)dest_config->ble->nu_lookup[i].name);
}
free(dest_config->ble->nu_lookup);
free(dest_config->ble);
dest_config->ble = NULL;
return ret;
}
static esp_err_t declare_endpoint(esp_local_ctrl_transport_config_t *config,
const char *ep_name, uint16_t ep_uuid)
{
if (!config || !config->ble) {
ESP_LOGE(TAG, "NULL configuration provided");
return ESP_ERR_INVALID_ARG;
}
protocomm_ble_name_uuid_t *nu_lookup =
realloc(config->ble->nu_lookup, (config->ble->nu_lookup_count + 1) *
sizeof(protocomm_ble_name_uuid_t));
if (!nu_lookup) {
ESP_LOGE(TAG, "Failed to allocate memory for new endpoint entry");
return ESP_ERR_NO_MEM;
}
config->ble->nu_lookup = nu_lookup;
nu_lookup[config->ble->nu_lookup_count].uuid = ep_uuid;
nu_lookup[config->ble->nu_lookup_count].name = strdup(ep_name);
if (!nu_lookup[config->ble->nu_lookup_count].name) {
ESP_LOGE(TAG, "Failed to allocate memory for new endpoint name");
return ESP_ERR_NO_MEM;
}
config->ble->nu_lookup_count++;
return ESP_OK;
}
static void free_config(esp_local_ctrl_transport_config_t *config)
{
if (config && config->ble) {
for (unsigned int i = 0; i < config->ble->nu_lookup_count; i++) {
free((void *)config->ble->nu_lookup[i].name);
}
free(config->ble->nu_lookup);
free(config->ble);
config->ble = NULL;
}
}
const esp_local_ctrl_transport_t *esp_local_ctrl_get_transport_ble(void)
{
static const esp_local_ctrl_transport_t tp = {
.start_service = start_ble_transport,
.stop_service = stop_ble_transport,
.copy_config = copy_ble_config,
.declare_ep = declare_endpoint,
.free_config = free_config
};
return &tp;
};
@@ -0,0 +1,151 @@
/*
* SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <esp_err.h>
#include <esp_log.h>
#if defined __has_include
# if __has_include("mdns.h")
# define WITH_MDNS
# include "mdns.h"
# endif
#endif
#include <esp_netif.h>
#include <protocomm_httpd.h>
#include <esp_local_ctrl.h>
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
#include <esp_https_server.h>
#else
#include <esp_http_server.h>
#endif
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
#define esp_local_ctrl_httpd_start httpd_ssl_start
#define esp_local_ctrl_httpd_stop httpd_ssl_stop
#else
#define esp_local_ctrl_httpd_start httpd_start
#define esp_local_ctrl_httpd_stop httpd_stop
#endif
#include "esp_local_ctrl_priv.h"
#define LOCAL_CTRL_VERSION "v1.0"
static const char *TAG = "esp_local_ctrl_transport_httpd";
static httpd_handle_t server_handle = NULL;
static esp_err_t start_httpd_transport(protocomm_t *pc, const esp_local_ctrl_transport_config_t *config)
{
if (!config || !config->httpd) {
ESP_LOGE(TAG, "NULL configuration provided");
return ESP_ERR_INVALID_ARG;
}
esp_err_t err;
#ifdef WITH_MDNS
uint16_t port = 0;
#ifdef CONFIG_ESP_HTTPS_SERVER_ENABLE
/* Extract configured port */
port = (
config->httpd->transport_mode == HTTPD_SSL_TRANSPORT_SECURE ?
config->httpd->port_secure :
config->httpd->port_insecure
);
#else
port = config->httpd->server_port;
#endif
err = mdns_service_add("Local Control Service", "_esp_local_ctrl",
"_tcp", port, NULL, 0);
if (err != ESP_OK) {
/* mDNS is not mandatory for provisioning to work,
* so print warning and return without failure */
ESP_LOGW(TAG, "Error adding mDNS service! Check if mDNS is running");
} else {
/* Information to identify the roles of the various
* protocomm endpoint URIs provided by the service */
err |= mdns_service_txt_item_set("_esp_local_ctrl", "_tcp",
"version_endpoint", "/esp_local_ctrl/version");
err |= mdns_service_txt_item_set("_esp_local_ctrl", "_tcp",
"session_endpoint", "/esp_local_ctrl/session");
err |= mdns_service_txt_item_set("_esp_local_ctrl", "_tcp",
"control_endpoint", "/esp_local_ctrl/control");
if (err != ESP_OK) {
ESP_LOGE(TAG, "Error adding mDNS service text item");
}
}
#endif
err = esp_local_ctrl_httpd_start(&server_handle, config->httpd);
if (ESP_OK != err) {
ESP_LOGE(TAG, "Error starting HTTP/S service!");
#ifdef WITH_MDNS
mdns_service_remove("_esp_local_ctrl", "_tcp");
#endif
return err;
}
protocomm_httpd_config_t pc_conf = {
.ext_handle_provided = true,
.data = {
.handle = &server_handle
}
};
return protocomm_httpd_start(pc, &pc_conf);
}
static void stop_httpd_transport(protocomm_t *pc)
{
#ifdef WITH_MDNS
mdns_service_remove("_esp_local_ctrl", "_tcp");
#endif
protocomm_httpd_stop(pc);
if (esp_local_ctrl_httpd_stop(server_handle) == ESP_OK) {
server_handle = NULL;
}
}
static esp_err_t copy_httpd_config(esp_local_ctrl_transport_config_t *dest_config, const esp_local_ctrl_transport_config_t *src_config)
{
if (!dest_config || !src_config || !src_config->httpd) {
ESP_LOGE(TAG, "NULL configuration provided");
return ESP_ERR_INVALID_ARG;
}
dest_config->httpd = calloc(1, sizeof(esp_local_ctrl_transport_config_httpd_t));
if (!dest_config->httpd) {
ESP_LOGE(TAG, "Failed to allocate memory for HTTPD transport config");
return ESP_ERR_NO_MEM;
}
memcpy(dest_config->httpd,
src_config->httpd,
sizeof(esp_local_ctrl_transport_config_httpd_t));
return ESP_OK;
}
static void free_config(esp_local_ctrl_transport_config_t *config)
{
if (config && config->httpd) {
free(config->httpd);
config->httpd = NULL;
}
}
const esp_local_ctrl_transport_t *esp_local_ctrl_get_transport_httpd(void)
{
static const esp_local_ctrl_transport_t tp = {
.start_service = start_httpd_transport,
.stop_service = stop_httpd_transport,
.copy_config = copy_httpd_config,
.declare_ep = NULL,
.free_config = free_config
};
return &tp;
};