b75f8b1b20
List of changes: * New component esp_local_ctrl added * Example added under examples/protocols/esp_local_ctrl * Documentation added under protocols/esp_local_ctrl * Demo client side app esp_local_ctrl.py added under examples/protocols/esp_local_ctrl/scripts * protocomm_ble : protocomm_ble_config_t given struct name for allowing forward declaration * esp_prov/transport_softap renamed to transport_http * transport_http module supports verification of server certificate * transport_http module performs name resolution before connection
417 lines
15 KiB
C
417 lines
15 KiB
C
// 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.
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <esp_err.h>
|
|
#include <esp_log.h>
|
|
|
|
#include <protocomm.h>
|
|
#include <protocomm_security0.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"
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
ret = protocomm_set_version(local_ctrl_inst_ctx->pc, "esp_local_ctrl/version",
|
|
ESP_LOCAL_CTRL_VERSION);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "Failed to set version endpoint");
|
|
esp_local_ctrl_stop();
|
|
return ret;
|
|
}
|
|
|
|
ret = protocomm_set_security(local_ctrl_inst_ctx->pc, "esp_local_ctrl/session",
|
|
&protocomm_security0, NULL);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "Failed to set session 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];
|
|
}
|
|
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 %d", 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 %d", 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;
|
|
}
|