diff --git a/components/protocomm/CMakeLists.txt b/components/protocomm/CMakeLists.txt new file mode 100644 index 000000000..a6609e5aa --- /dev/null +++ b/components/protocomm/CMakeLists.txt @@ -0,0 +1,25 @@ +set(COMPONENT_ADD_INCLUDEDIRS include/common + include/security + include/transports) +set(COMPONENT_PRIV_INCLUDEDIRS proto-c src/common src/simple_ble) +set(COMPONENT_SRCS "src/common/protocomm.c" + "src/security/security0.c" + "src/security/security1.c" + "proto-c/constants.pb-c.c" + "proto-c/sec0.pb-c.c" + "proto-c/sec1.pb-c.c" + "proto-c/session.pb-c.c" + "src/transports/protocomm_console.c" + "src/transports/protocomm_httpd.c") + +set(COMPONENT_PRIV_REQUIRES protobuf-c mbedtls console http_server bt) + +if(CONFIG_BT_ENABLED) + if(CONFIG_BLUEDROID_ENABLED) + list(APPEND COMPONENT_SRCS + "src/simple_ble/simple_ble.c" + "src/transports/protocomm_ble.c") + endif() +endif() + +register_component() diff --git a/components/protocomm/component.mk b/components/protocomm/component.mk index 7941a8e3b..3ed61cbc1 100644 --- a/components/protocomm/component.mk +++ b/components/protocomm/component.mk @@ -1,3 +1,7 @@ -COMPONENT_ADD_INCLUDEDIRS := include/common include/security proto-c -COMPONENT_PRIV_INCLUDEDIRS := src/common -COMPONENT_SRCDIRS := src/common src/security proto-c +COMPONENT_ADD_INCLUDEDIRS := include/common include/security include/transports +COMPONENT_PRIV_INCLUDEDIRS := proto-c src/common src/simple_ble +COMPONENT_SRCDIRS := src/common src/security proto-c src/simple_ble src/transports + +ifneq ($(filter y, $(CONFIG_BT_ENABLED) $(CONFIG_BLUEDROID_ENABLED)),y y) + COMPONENT_OBJEXCLUDE := src/simple_ble/simple_ble.o src/transports/protocomm_ble.o +endif diff --git a/components/protocomm/include/transports/protocomm_ble.h b/components/protocomm/include/transports/protocomm_ble.h new file mode 100644 index 000000000..b86059e57 --- /dev/null +++ b/components/protocomm/include/transports/protocomm_ble.h @@ -0,0 +1,92 @@ +// Copyright 2018 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 + +#include + +/** + * BLE device name cannot be larger than this value + */ +#define MAX_BLE_DEVNAME_LEN 13 + +/** + * @brief This structure maps handler required by protocomm layer to + * UUIDs which are used to uniquely identify BLE characteristics + * from a smartphone or a similar client device. + */ +typedef struct name_uuid { + /** + * Name of the handler, which is passed to protocomm layer + */ + char *name; + + /** + * UUID to be assigned to the BLE characteristic which is + * mapped to the handler + */ + uint16_t uuid; +} protocomm_ble_name_uuid_t; + +/** + * @brief Config parameters for protocomm BLE service + */ +typedef struct { + /** + * BLE device name being broadcast at the time of provisioning + */ + char device_name[MAX_BLE_DEVNAME_LEN]; + uint8_t service_uuid[16]; /*!< SSID of the provisioning service */ + ssize_t nu_lookup_count; /*!< Number of entries in the Name-UUID lookup table */ + + /** + * Pointer to the Name-UUID lookup table + */ + protocomm_ble_name_uuid_t *nu_lookup; +} protocomm_ble_config_t; + +/** + * @brief Start Bluetooth Low Energy based transport layer for provisioning + * + * Initialize and start required BLE service for provisioning. This includes + * the initialization for characteristics/service for BLE. + * + * @param[in] pc Protocomm instance pointer obtained from protocomm_new() + * @param[in] config Pointer to config structure for initialising BLE + * + * @return + * - ESP_OK : if successful + * - ESP_FAIL : Simple BLE start error + * - ESP_ERR_NO_MEM : Error allocating memory for internal resources + * - ESP_ERR_INVALID_STATE : Error in ble config + * - ESP_ERR_INVALID_ARG : Null arguments + */ +esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *config); + +/** + * @brief Stop Bluetooth Low Energy based transport layer for provisioning + * + * Stops service/task responsible for BLE based interactions for provisioning + * + * @note You might want to optionally reclaim memory from Bluetooth. + * Refer to the documentation of `esp_bt_mem_release` in that case. + * + * @param[in] pc Same protocomm instance that was passed to protocomm_ble_start() + * + * @return + * - ESP_OK : For success or appropriate error code + * - ESP_FAIL : Simple BLE stop error + * - ESP_ERR_INVALID_ARG : Null / incorrect protocomm instance + */ +esp_err_t protocomm_ble_stop(protocomm_t *pc); diff --git a/components/protocomm/include/transports/protocomm_console.h b/components/protocomm/include/transports/protocomm_console.h new file mode 100644 index 000000000..b7004d02b --- /dev/null +++ b/components/protocomm/include/transports/protocomm_console.h @@ -0,0 +1,59 @@ +// Copyright 2018 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 + +#include + +#define PROTOCOMM_CONSOLE_DEFAULT_CONFIG() { \ + .stack_size = 4096, \ + .task_priority = tskIDLE_PRIORITY + 3, \ +} + +/** + * @brief Config parameters for protocomm console + */ +typedef struct { + size_t stack_size; /*!< Stack size of console taks */ + unsigned task_priority; /*!< Priority of console task */ +} protocomm_console_config_t; + +/** + * @brief Start console based protocomm transport + * + * @note This is a singleton. ie. Protocomm can have multiple instances, but only + * one instance can be bound to a console based transport layer. + * + * @param[in] pc Protocomm instance pointer obtained from protocomm_new() + * @param[in] config Config param structure for protocomm console + * + * @return + * - ESP_OK : Server started succefully + * - ESP_ERR_INVALID_ARG : Null arguments + * - ESP_ERR_NOT_SUPPORTED : Transport layer bound to another protocomm instance + * - ESP_ERR_INVALID_STATE : Transport layer already bound to this protocomm instance + * - ESP_FAIL : Failed to start console thread + */ +esp_err_t protocomm_console_start(protocomm_t *pc, const protocomm_console_config_t *config); + +/** + * @brief Stop console protocomm transport + * + * @param[in] pc Same protocomm instance that was passed to protocomm_console_start() + * + * @return + * - ESP_OK : Server stopped succefully + * - ESP_ERR_INVALID_ARG : Null / incorrect protocomm instance pointer + */ +esp_err_t protocomm_console_stop(protocomm_t *pc); diff --git a/components/protocomm/include/transports/protocomm_httpd.h b/components/protocomm/include/transports/protocomm_httpd.h new file mode 100644 index 000000000..701fa9683 --- /dev/null +++ b/components/protocomm/include/transports/protocomm_httpd.h @@ -0,0 +1,73 @@ +// Copyright 2018 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 + +#include + +#define PROTOCOMM_HTTPD_DEFAULT_CONFIG() { \ + .port = 80, \ + .stack_size = 4096, \ + .task_priority = tskIDLE_PRIORITY + 5, \ +} + +/** + * @brief Config parameters for protocomm HTTP server + */ +typedef struct { + uint16_t port; /*!< Port on which the http server will listen */ + + /** + * Stack size of server task, adjusted depending + * upon stack usage of endpoint handler + */ + size_t stack_size; + unsigned task_priority; /*!< Priority of server task */ +} protocomm_httpd_config_t; + +/** + * @brief Start HTTPD protocomm transport + * + * This API internally creates a framework to allow endpoint registration and security + * configuration for the protocomm. + * + * @note This is a singleton. ie. Protocomm can have multiple instances, but only + * one instance can be bound to an HTTP transport layer. + * + * @param[in] pc Protocomm instance pointer obtained from protocomm_new() + * @param[in] config Pointer to config structure for initialising HTTP server + * + * @return + * - ESP_OK : Server started succefully + * - ESP_ERR_INVALID_ARG : Null arguments + * - ESP_ERR_NOT_SUPPORTED : Transport layer bound to another protocomm instance + * - ESP_ERR_INVALID_STATE : Transport layer already bound to this protocomm instance + * - ESP_ERR_NO_MEM : Memory allocation for server resource failed + * - ESP_ERR_HTTPD_* : HTTP server error on start + */ +esp_err_t protocomm_httpd_start(protocomm_t *pc, const protocomm_httpd_config_t *config); + +/** + * @brief Stop HTTPD protocomm transport + * + * This API cleans up the HTTPD transport protocomm and frees all the handlers registered + * with the protocomm. + * + * @param[in] pc Same protocomm instance that was passed to protocomm_httpd_start() + * + * @return + * - ESP_OK : Server stopped succefully + * - ESP_ERR_INVALID_ARG : Null / incorrect protocomm instance pointer + */ +esp_err_t protocomm_httpd_stop(protocomm_t *pc); diff --git a/components/protocomm/src/simple_ble/simple_ble.c b/components/protocomm/src/simple_ble/simple_ble.c new file mode 100644 index 000000000..e52be0053 --- /dev/null +++ b/components/protocomm/src/simple_ble/simple_ble.c @@ -0,0 +1,268 @@ +// Copyright 2015-2018 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 +#include +#include +#include +#include +#include +#include +#include + +#include "simple_ble.h" + +#define SIMPLE_BLE_MAX_GATT_TABLE_SIZE 20 + +static const char *TAG = "simple_ble"; + +static simple_ble_cfg_t *g_ble_cfg_p; +static uint16_t g_gatt_table_map[SIMPLE_BLE_MAX_GATT_TABLE_SIZE]; + +uint16_t simple_ble_get_uuid(uint16_t handle) +{ + uint16_t *uuid_ptr; + + for (int i = 0; i < SIMPLE_BLE_MAX_GATT_TABLE_SIZE; i++) { + if (g_gatt_table_map[i] == handle) { + uuid_ptr = (uint16_t *) g_ble_cfg_p->gatt_db[i].att_desc.uuid_p; + return *uuid_ptr; + } + } + return -1; +} + +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + switch (event) { + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: + esp_ble_gap_start_advertising(&g_ble_cfg_p->adv_params); + + break; + default: + break; + } +} + +static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t p_gatts_if, esp_ble_gatts_cb_param_t *param) +{ + static esp_gatt_if_t gatts_if = ESP_GATT_IF_NONE; + esp_err_t ret; + uint8_t service_instance_id = 0; + if (event == ESP_GATTS_REG_EVT) { + if (param->reg.status == ESP_GATT_OK) { + gatts_if = p_gatts_if; + } else { + ESP_LOGE(TAG, "reg app failed, app_id 0x0x%x, status %d", + param->reg.app_id, + param->reg.status); + return; + } + } + + if (gatts_if != ESP_GATT_IF_NONE && gatts_if != p_gatts_if) { + return; + } + + switch (event) { + case ESP_GATTS_REG_EVT: + ret = esp_ble_gatts_create_attr_tab(g_ble_cfg_p->gatt_db, gatts_if, g_ble_cfg_p->gatt_db_count, service_instance_id); + if (ret) { + ESP_LOGE(TAG, "create attr table failed, error code = 0x%x", ret); + return; + } + ret = esp_ble_gap_set_device_name(g_ble_cfg_p->device_name); + if (ret) { + ESP_LOGE(TAG, "set device name failed, error code = 0x%x", ret); + return; + } + ret = esp_ble_gap_config_adv_data(&g_ble_cfg_p->adv_data); + if (ret) { + ESP_LOGE(TAG, "config adv data failed, error code = 0x%x", ret); + return; + } + break; + case ESP_GATTS_READ_EVT: + g_ble_cfg_p->read_fn(event, gatts_if, param); + break; + case ESP_GATTS_WRITE_EVT: + g_ble_cfg_p->write_fn(event, gatts_if, param); + break; + case ESP_GATTS_EXEC_WRITE_EVT: + g_ble_cfg_p->exec_write_fn(event, gatts_if, param); + break; + case ESP_GATTS_MTU_EVT: + ESP_LOGV(TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu); + if (g_ble_cfg_p->set_mtu_fn) { + g_ble_cfg_p->set_mtu_fn(event, gatts_if, param); + } + break; + case ESP_GATTS_CONF_EVT: + ESP_LOGD(TAG, "ESP_GATTS_CONF_EVT, status = %d", param->conf.status); + break; + case ESP_GATTS_START_EVT: + ESP_LOGD(TAG, "SERVICE_START_EVT, status %d, service_handle %d", param->start.status, param->start.service_handle); + break; + case ESP_GATTS_CONNECT_EVT: + ESP_LOGD(TAG, "ESP_GATTS_CONNECT_EVT, conn_id = %d", param->connect.conn_id); + g_ble_cfg_p->connect_fn(event, gatts_if, param); + esp_ble_conn_update_params_t conn_params = {0}; + memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); + /* For the iOS system, please refer the official Apple documents about BLE connection parameters restrictions. */ + conn_params.latency = 0; + conn_params.max_int = 0x20; // max_int = 0x20*1.25ms = 40ms + conn_params.min_int = 0x10; // min_int = 0x10*1.25ms = 20ms + conn_params.timeout = 400; // timeout = 400*10ms = 4000ms + esp_ble_gap_update_conn_params(&conn_params); + break; + case ESP_GATTS_DISCONNECT_EVT: + ESP_LOGD(TAG, "ESP_GATTS_DISCONNECT_EVT, reason = %d", param->disconnect.reason); + g_ble_cfg_p->disconnect_fn(event, gatts_if, param); + esp_ble_gap_start_advertising(&g_ble_cfg_p->adv_params); + break; + case ESP_GATTS_CREAT_ATTR_TAB_EVT: { + if (param->add_attr_tab.status != ESP_GATT_OK) { + ESP_LOGE(TAG, "creating the attribute table failed, error code=0x%x", param->add_attr_tab.status); + } else if (param->add_attr_tab.num_handle != g_ble_cfg_p->gatt_db_count) { + ESP_LOGE(TAG, "created attribute table abnormally "); + } else { + ESP_LOGD(TAG, "created attribute table successfully, the number handle = %d", param->add_attr_tab.num_handle); + memcpy(g_gatt_table_map, param->add_attr_tab.handles, param->add_attr_tab.num_handle * sizeof(g_gatt_table_map[0])); + /* We assume, for now, that the first entry is always the index to the 'service' definition */ + esp_ble_gatts_start_service(g_gatt_table_map[0]); + } + break; + } + case ESP_GATTS_STOP_EVT: + case ESP_GATTS_OPEN_EVT: + case ESP_GATTS_CANCEL_OPEN_EVT: + case ESP_GATTS_CLOSE_EVT: + case ESP_GATTS_LISTEN_EVT: + case ESP_GATTS_CONGEST_EVT: + case ESP_GATTS_UNREG_EVT: + case ESP_GATTS_DELETE_EVT: + default: + break; + } +} + +simple_ble_cfg_t *simple_ble_init() +{ + simple_ble_cfg_t *ble_cfg_p = (simple_ble_cfg_t *) malloc(sizeof(simple_ble_cfg_t)); + if (ble_cfg_p == NULL) { + ESP_LOGE(TAG, "No memory for simple_ble_cfg_t"); + return NULL; + } + return ble_cfg_p; +} + +esp_err_t simple_ble_deinit() +{ + free(g_ble_cfg_p->gatt_db); + free(g_ble_cfg_p); + g_ble_cfg_p = NULL; + return ESP_OK; +} + +/* Expects the pointer stays valid throughout */ +esp_err_t simple_ble_start(simple_ble_cfg_t *cfg) +{ + g_ble_cfg_p = cfg; + ESP_LOGD(TAG, "Free mem at start of simple_ble_init %d", esp_get_free_heap_size()); + esp_err_t ret; + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(TAG, "%s enable controller failed %d", __func__, ret); + return ret; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(TAG, "%s enable controller failed %d", __func__, ret); + return ret; + } + + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(TAG, "%s init bluetooth failed %d", __func__, ret); + return ret; + } + + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(TAG, "%s enable bluetooth failed %d", __func__, ret); + return ret; + } + + ret = esp_ble_gatts_register_callback(gatts_profile_event_handler); + if (ret) { + ESP_LOGE(TAG, "gatts register error, error code = 0x%x", ret); + return ret; + } + + ret = esp_ble_gap_register_callback(gap_event_handler); + if (ret) { + ESP_LOGE(TAG, "gap register error, error code = 0x%x", ret); + return ret; + } + + uint16_t app_id = 0x55; + ret = esp_ble_gatts_app_register(app_id); + if (ret) { + ESP_LOGE(TAG, "gatts app register error, error code = 0x%x", ret); + return ret; + } + + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); + if (local_mtu_ret) { + ESP_LOGE(TAG, "set local MTU failed, error code = 0x%x", local_mtu_ret); + } + ESP_LOGD(TAG, "Free mem at end of simple_ble_init %d", esp_get_free_heap_size()); + return ESP_OK; +} + +esp_err_t simple_ble_stop() +{ + esp_err_t err; + ESP_LOGD(TAG, "Free mem at start of simple_ble_stop %d", esp_get_free_heap_size()); + err = esp_bluedroid_disable(); + if (err != ESP_OK) { + return ESP_FAIL; + } + ESP_LOGD(TAG, "esp_bluedroid_disable called successfully"); + err = esp_bluedroid_deinit(); + if (err != ESP_OK) { + return err; + } + ESP_LOGD(TAG, "esp_bluedroid_deinit called successfully"); + err = esp_bt_controller_disable(); + if (err != ESP_OK) { + return ESP_FAIL; + } + + /* The API `esp_bt_controller_deinit` will have to be removed when we add support for + * `reset to provisioning` + */ + ESP_LOGD(TAG, "esp_bt_controller_disable called successfully"); + err = esp_bt_controller_deinit(); + if (err != ESP_OK) { + return ESP_FAIL; + } + ESP_LOGD(TAG, "esp_bt_controller_deinit called successfully"); + + ESP_LOGD(TAG, "Free mem at end of simple_ble_stop %d", esp_get_free_heap_size()); + return ESP_OK; +} diff --git a/components/protocomm/src/simple_ble/simple_ble.h b/components/protocomm/src/simple_ble/simple_ble.h new file mode 100644 index 000000000..892cd4d55 --- /dev/null +++ b/components/protocomm/src/simple_ble/simple_ble.h @@ -0,0 +1,107 @@ +// Copyright 2015-2018 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. +#ifndef _SIMPLE_BLE_ +#define _SIMPLE_BLE_ + + +#include +#include +#include + +#include +#include + +typedef void (simple_ble_cb_t)(esp_gatts_cb_event_t event, esp_gatt_if_t p_gatts_if, esp_ble_gatts_cb_param_t *param); + +/** + * This structure is populated with the details required + * to create an instance of BLE easily. It requires function + * pointers, advertising parameters and gatt description table + */ +typedef struct { + /** Name to be displayed to devices scanning for ESP32 */ + const char *device_name; + /** Advertising data content, according to "Supplement to the Bluetooth Core Specification" */ + esp_ble_adv_data_t adv_data; + /** Parameters to configure the nature of advertising */ + esp_ble_adv_params_t adv_params; + /** Descriptor table which consists the configuration required by services and characteristics */ + esp_gatts_attr_db_t *gatt_db; + /** Number of entries in the gatt_db descriptor table */ + ssize_t gatt_db_count; + /** BLE read callback */ + simple_ble_cb_t *read_fn; + /** BLE write callback */ + simple_ble_cb_t *write_fn; + /** BLE exec write callback */ + simple_ble_cb_t *exec_write_fn; + /** Client disconnect callback */ + simple_ble_cb_t *disconnect_fn; + /** Client connect callback */ + simple_ble_cb_t *connect_fn; + /** MTU set callback */ + simple_ble_cb_t *set_mtu_fn; +} simple_ble_cfg_t; + + +/** Initialise a simple ble connection + * + * This function allocates memory and returns a pointer to the + * configuration structure. + * + * @return simple_ble_cfg_t* Pointer to configuration structure + */ +simple_ble_cfg_t *simple_ble_init(); + +/** Deallocates memory + * + * This function deallocate memory of the configuration structure. + * + * @return ESP_OK + */ +esp_err_t simple_ble_deinit(); + +/** Starts BLE service + * + * This function makes calls to the GATT and GAP APIs + * to initialize the BLE service as per parameters stored + * in the config structure. At the end of this function, + * one should be able to scan and connect to the ESP32 device + * using BLE. + * This API sets the MTU size to 500 (this is not part of the config structure) + * + * @return ESP_OK on success, and appropriate error code for failure + */ +esp_err_t simple_ble_start(simple_ble_cfg_t *cfg); + +/** Stops the BLE service + * + * This API is called to stop the BLE service. + * This includes calls to disable and deinit bluedroid and bt controller. + * + * @return ESP_OK on success, and appropriate error code for failure + */ +esp_err_t simple_ble_stop(); + +/** Convert handle to UUID of characteristic + * + * This function can be easily used to get the corresponding + * UUID for a characteristic that has been created, and the one for + * which we only have the handle for. + * + * @return uuid the UUID of the handle, -1 in case of invalid handle + */ +uint16_t simple_ble_get_uuid(uint16_t handle); + +#endif /* _SIMPLE_BLE_ */ diff --git a/components/protocomm/src/transports/protocomm_ble.c b/components/protocomm/src/transports/protocomm_ble.c new file mode 100644 index 000000000..a4b1927d0 --- /dev/null +++ b/components/protocomm/src/transports/protocomm_ble.c @@ -0,0 +1,510 @@ +// Copyright 2018 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 +#include +#include + +#include +#include + +#include "protocomm_priv.h" +#include "simple_ble.h" + +#define CHAR_VAL_LEN_MAX (256 + 1) +#define PREPARE_BUF_MAX_SIZE CHAR_VAL_LEN_MAX + +static const char *TAG = "protocomm_ble"; + +/* BLE specific configuration parameters */ +const uint16_t GATTS_SERVICE_UUID_PROV = 0xFFFF; +const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE; +const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE; +const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE; + +typedef struct { + uint8_t *prepare_buf; + int prepare_len; + uint16_t handle; +} prepare_type_env_t; + +static prepare_type_env_t prepare_write_env; + +typedef struct _protocomm_ble { + protocomm_t *pc_ble; + protocomm_ble_name_uuid_t *g_nu_lookup; + ssize_t g_nu_lookup_count; + uint16_t gatt_mtu; +} _protocomm_ble_internal_t; + +static _protocomm_ble_internal_t *protoble_internal; + +static esp_ble_adv_params_t adv_params = { + .adv_int_min = 0x100, + .adv_int_max = 0x100, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; + +static char* protocomm_ble_device_name = NULL; + +/* The length of adv data must be less than 31 bytes */ +static esp_ble_adv_data_t adv_data = { + .set_scan_rsp = false, + .include_name = true, + .include_txpower = true, + .min_interval = 0x100, + .max_interval = 0x100, + .appearance = ESP_BLE_APPEARANCE_UNKNOWN, + .manufacturer_len = 0, + .p_manufacturer_data = NULL, + .service_data_len = 0, + .p_service_data = NULL, + .service_uuid_len = 0, + .p_service_uuid = NULL, + .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), +}; + +static void hexdump(const char *msg, uint8_t *buf, int len) +{ + ESP_LOGD(TAG, "%s:", msg); + ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, len, ESP_LOG_DEBUG); +} + +static const char *handle_to_handler(uint16_t handle) +{ + uint16_t uuid = simple_ble_get_uuid(handle); + for (int i = 0; i < protoble_internal->g_nu_lookup_count; i++) { + if (protoble_internal->g_nu_lookup[i].uuid == uuid ) { + return protoble_internal->g_nu_lookup[i].name; + } + } + return NULL; +} + +static void transport_simple_ble_read(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + static const uint8_t *read_buf = NULL; + static uint16_t read_len = 0; + esp_gatt_status_t status = ESP_OK; + + ESP_LOGD(TAG, "Inside read w/ session - %d on param %d %d", + param->read.conn_id, param->read.handle, read_len); + if (!read_len) { + ESP_LOGD(TAG, "Reading attr value first time"); + status = esp_ble_gatts_get_attr_value(param->read.handle, &read_len, &read_buf); + } else { + ESP_LOGD(TAG, "Subsequent read request for attr value"); + } + + esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *) malloc(sizeof(esp_gatt_rsp_t)); + if (gatt_rsp != NULL) { + gatt_rsp->attr_value.len = MIN(read_len, (protoble_internal->gatt_mtu - 1)); + if (read_len && read_buf) { + memcpy(gatt_rsp->attr_value.value, + read_buf + param->read.offset, + gatt_rsp->attr_value.len); + } + read_len -= gatt_rsp->attr_value.len; + } else { + ESP_LOGE(TAG, "%s, malloc failed", __func__); + return; + } + esp_err_t err = esp_ble_gatts_send_response(gatts_if, param->read.conn_id, + param->read.trans_id, status, gatt_rsp); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Send response error in read"); + } + free(gatt_rsp); +} + +static esp_err_t prepare_write_event_env(esp_gatt_if_t gatts_if, + esp_ble_gatts_cb_param_t *param) +{ + ESP_LOGD(TAG, "prepare write, handle = %d, value len = %d", + param->write.handle, param->write.len); + esp_gatt_status_t status = ESP_GATT_OK; + if (prepare_write_env.prepare_buf == NULL) { + prepare_write_env.prepare_buf = (uint8_t *) malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t)); + if (prepare_write_env.prepare_buf == NULL) { + ESP_LOGE(TAG, "%s , failed tp allocate preparebuf", __func__); + status = ESP_GATT_NO_RESOURCES; + } + /* prepare_write_env.prepare_len = 0; */ + } else { + if (param->write.offset > PREPARE_BUF_MAX_SIZE) { + status = ESP_GATT_INVALID_OFFSET; + } else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) { + status = ESP_GATT_INVALID_ATTR_LEN; + } + } + memcpy(prepare_write_env.prepare_buf + param->write.offset, + param->write.value, + param->write.len); + prepare_write_env.prepare_len += param->write.len; + prepare_write_env.handle = param->write.handle; + if (param->write.need_rsp) { + esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *) malloc(sizeof(esp_gatt_rsp_t)); + if (gatt_rsp != NULL) { + gatt_rsp->attr_value.len = param->write.len; + gatt_rsp->attr_value.handle = param->write.handle; + gatt_rsp->attr_value.offset = param->write.offset; + gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; + memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len); + esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, + param->write.trans_id, status, + gatt_rsp); + if (response_err != ESP_OK) { + ESP_LOGE(TAG, "Send response error in prep write"); + } + free(gatt_rsp); + } else { + ESP_LOGE(TAG, "%s, malloc failed", __func__); + } + } + if (status != ESP_GATT_OK) { + if (prepare_write_env.prepare_buf) { + free(prepare_write_env.prepare_buf); + prepare_write_env.prepare_buf = NULL; + prepare_write_env.prepare_len = 0; + } + return ESP_FAIL; + } + return ESP_OK; +} + +static void transport_simple_ble_write(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + esp_err_t ret; + + ESP_LOGD(TAG, "Inside write with session - %d on attr handle - %d \nLen -%d IS Prep - %d", + param->write.conn_id, param->write.handle, param->write.len, param->write.is_prep); + + if (param->write.is_prep) { + ret = prepare_write_event_env(gatts_if, param); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error appending to prepare buffer"); + } + return; + } else { + ESP_LOGD(TAG, "is_prep not set"); + } + + ret = protocomm_req_handle(protoble_internal->pc_ble, + handle_to_handler(param->write.handle), + param->exec_write.conn_id, + param->write.value, + param->write.len, + &outbuf, &outlen); + if (ret == ESP_OK) { + ret = esp_ble_gatts_set_attr_value(param->write.handle, outlen, outbuf); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to set the session attribute value"); + } + ret = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, + param->write.trans_id, ESP_GATT_OK, NULL); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Send response error in write"); + } + hexdump("Response from write", outbuf, outlen); + + } else { + ESP_LOGE(TAG, "Invalid content received, killing connection"); + esp_ble_gatts_close(gatts_if, param->write.conn_id); + } + if (outbuf) { + free(outbuf); + } +} + + +static void transport_simple_ble_exec_write(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + esp_err_t err; + uint8_t *outbuf = NULL; + ssize_t outlen = 0; + ESP_LOGD(TAG, "Inside exec_write w/ session - %d", param->exec_write.conn_id); + + if ((param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) + && + prepare_write_env.prepare_buf) { + err = protocomm_req_handle(protoble_internal->pc_ble, + handle_to_handler(prepare_write_env.handle), + param->exec_write.conn_id, + prepare_write_env.prepare_buf, + prepare_write_env.prepare_len, + &outbuf, &outlen); + + if (err != ESP_OK) { + ESP_LOGE(TAG, "Invalid content received, killing connection"); + esp_ble_gatts_close(gatts_if, param->write.conn_id); + } else { + hexdump("Response from exec write", outbuf, outlen); + esp_ble_gatts_set_attr_value(prepare_write_env.handle, outlen, outbuf); + } + } + if (prepare_write_env.prepare_buf) { + free(prepare_write_env.prepare_buf); + prepare_write_env.prepare_buf = NULL; + prepare_write_env.prepare_len = 0; + } + + err = esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, param->exec_write.trans_id, ESP_GATT_OK, NULL); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Send response error in exec write"); + } + if (outbuf) { + free(outbuf); + } +} + +static void transport_simple_ble_disconnect(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + esp_err_t ret; + ESP_LOGV(TAG, "Inside disconnect w/ session - %d", param->disconnect.conn_id); + if (protoble_internal->pc_ble->sec && + protoble_internal->pc_ble->sec->close_transport_session) { + ret = protoble_internal->pc_ble->sec->close_transport_session(param->disconnect.conn_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "error closing the session after disconnect"); + } + } + protoble_internal->gatt_mtu = ESP_GATT_DEF_BLE_MTU_SIZE; +} + +static void transport_simple_ble_connect(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + esp_err_t ret; + ESP_LOGV(TAG, "Inside BLE connect w/ conn_id - %d", param->connect.conn_id); + if (protoble_internal->pc_ble->sec && + protoble_internal->pc_ble->sec->new_transport_session) { + ret = protoble_internal->pc_ble->sec->new_transport_session(param->connect.conn_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "error creating the session"); + } + } +} + +static void transport_simple_ble_set_mtu(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + protoble_internal->gatt_mtu = param->mtu.mtu; + return; +} + +static esp_err_t protocomm_ble_add_endpoint(const char *ep_name, + protocomm_req_handler_t req_handler, + void *priv_data) +{ + /* Endpoint UUID already added when protocomm_ble_start() was called */ + return ESP_OK; +} + +static esp_err_t protocomm_ble_remove_endpoint(const char *ep_name) +{ + /* Endpoint UUID will be removed when protocomm_ble_stop() is called */ + return ESP_OK; +} + + +static ssize_t populate_gatt_db(esp_gatts_attr_db_t **gatt_db_generated) +{ + int i; + /* We need esp_gatts_attr_db_t of size 2 * number of handlers + 1 for service */ + ssize_t gatt_db_generated_entries = 2 * protoble_internal->g_nu_lookup_count + 1; + + *gatt_db_generated = (esp_gatts_attr_db_t *) malloc(sizeof(esp_gatts_attr_db_t) * + (gatt_db_generated_entries)); + if ((*gatt_db_generated) == NULL) { + ESP_LOGE(TAG, "Failed to assign memory to gatt_db"); + return -1; + } + /* Declare service */ + (*gatt_db_generated)[0].attr_control.auto_rsp = ESP_GATT_RSP_BY_APP; + + (*gatt_db_generated)[0].att_desc.uuid_length = ESP_UUID_LEN_16; + (*gatt_db_generated)[0].att_desc.uuid_p = (uint8_t *) &primary_service_uuid; + (*gatt_db_generated)[0].att_desc.perm = ESP_GATT_PERM_READ; + (*gatt_db_generated)[0].att_desc.max_length = sizeof(uint16_t); + (*gatt_db_generated)[0].att_desc.length = sizeof(GATTS_SERVICE_UUID_PROV); + (*gatt_db_generated)[0].att_desc.value = (uint8_t *) &GATTS_SERVICE_UUID_PROV; + + /* Declare characteristics */ + for (i = 1 ; i < gatt_db_generated_entries ; i++) { + (*gatt_db_generated)[i].attr_control.auto_rsp = ESP_GATT_RSP_BY_APP; + + (*gatt_db_generated)[i].att_desc.uuid_length = ESP_UUID_LEN_16; + (*gatt_db_generated)[i].att_desc.perm = ESP_GATT_PERM_READ | + ESP_GATT_PERM_WRITE; + + if (i % 2 == 1) { /* Char Declaration */ + (*gatt_db_generated)[i].att_desc.uuid_p = (uint8_t *) &character_declaration_uuid; + (*gatt_db_generated)[i].att_desc.max_length = sizeof(uint8_t); + (*gatt_db_generated)[i].att_desc.length = sizeof(uint8_t); + (*gatt_db_generated)[i].att_desc.value = (uint8_t *) &char_prop_read_write; + } else { /* Char Value */ + (*gatt_db_generated)[i].att_desc.uuid_p = (uint8_t *)&protoble_internal->g_nu_lookup[i / 2 - 1].uuid; + (*gatt_db_generated)[i].att_desc.max_length = CHAR_VAL_LEN_MAX; + (*gatt_db_generated)[i].att_desc.length = 0; + (*gatt_db_generated)[i].att_desc.value = NULL; + } + } + return gatt_db_generated_entries; +} + +static void protocomm_ble_cleanup(void) +{ + if (protoble_internal) { + if (protoble_internal->g_nu_lookup) { + for (unsigned i = 0; i < protoble_internal->g_nu_lookup_count; i++) { + if (protoble_internal->g_nu_lookup[i].name) { + free(protoble_internal->g_nu_lookup[i].name); + } + } + free(protoble_internal->g_nu_lookup); + } + free(protoble_internal); + protoble_internal = NULL; + } + if (protocomm_ble_device_name) { + free(protocomm_ble_device_name); + protocomm_ble_device_name = NULL; + } + if (adv_data.p_service_uuid) { + free(adv_data.p_service_uuid); + adv_data.p_service_uuid = NULL; + adv_data.service_uuid_len = 0; + } +} + +esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *config) +{ + if (!pc || !config || !config->device_name || !config->nu_lookup) { + return ESP_ERR_INVALID_ARG; + } + + if (protoble_internal) { + ESP_LOGE(TAG, "Protocomm BLE already started"); + return ESP_FAIL; + } + + /* Store service UUID internally */ + adv_data.service_uuid_len = sizeof(config->service_uuid); + adv_data.p_service_uuid = malloc(sizeof(config->service_uuid)); + if (adv_data.p_service_uuid == NULL) { + ESP_LOGE(TAG, "Error allocating memory for storing service UUID"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + memcpy(adv_data.p_service_uuid, config->service_uuid, adv_data.service_uuid_len); + + /* Store BLE device name internally */ + protocomm_ble_device_name = strdup(config->device_name); + if (protocomm_ble_device_name == NULL) { + ESP_LOGE(TAG, "Error allocating memory for storing BLE device name"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + + protoble_internal = (_protocomm_ble_internal_t *) calloc(1, sizeof(_protocomm_ble_internal_t)); + if (protoble_internal == NULL) { + ESP_LOGE(TAG, "Error allocating internal protocomm structure"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + + protoble_internal->g_nu_lookup_count = config->nu_lookup_count; + protoble_internal->g_nu_lookup = malloc(config->nu_lookup_count * sizeof(protocomm_ble_name_uuid_t)); + if (protoble_internal->g_nu_lookup == NULL) { + ESP_LOGE(TAG, "Error allocating internal name UUID table"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + + for (unsigned i = 0; i < protoble_internal->g_nu_lookup_count; i++) { + protoble_internal->g_nu_lookup[i].uuid = config->nu_lookup[i].uuid; + protoble_internal->g_nu_lookup[i].name = strdup(config->nu_lookup[i].name); + if (protoble_internal->g_nu_lookup[i].name == NULL) { + ESP_LOGE(TAG, "Error allocating internal name UUID entry"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + } + + pc->add_endpoint = protocomm_ble_add_endpoint; + pc->remove_endpoint = protocomm_ble_remove_endpoint; + protoble_internal->pc_ble = pc; + protoble_internal->gatt_mtu = ESP_GATT_DEF_BLE_MTU_SIZE; + + simple_ble_cfg_t *ble_config = simple_ble_init(); + if (ble_config == NULL) { + ESP_LOGE(TAG, "Ran out of memory for BLE config"); + protocomm_ble_cleanup(); + return ESP_ERR_NO_MEM; + } + + /* Set function pointers required for simple BLE layer */ + ble_config->read_fn = transport_simple_ble_read; + ble_config->write_fn = transport_simple_ble_write; + ble_config->exec_write_fn = transport_simple_ble_exec_write; + ble_config->disconnect_fn = transport_simple_ble_disconnect; + ble_config->connect_fn = transport_simple_ble_connect; + ble_config->set_mtu_fn = transport_simple_ble_set_mtu; + + /* Set parameters required for advertising */ + ble_config->adv_data = adv_data; + ble_config->adv_params = adv_params; + + ble_config->device_name = protocomm_ble_device_name; + ble_config->gatt_db_count = populate_gatt_db(&ble_config->gatt_db); + + if (ble_config->gatt_db_count == -1) { + ESP_LOGE(TAG, "Invalid GATT database count"); + simple_ble_deinit(); + protocomm_ble_cleanup(); + return ESP_ERR_INVALID_STATE; + } + + esp_err_t err = simple_ble_start(ble_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "simple_ble_start failed w/ error code 0x%x", err); + simple_ble_deinit(); + protocomm_ble_cleanup(); + return err; + } + + prepare_write_env.prepare_buf = NULL; + ESP_LOGV(TAG, "Waiting for client to connect ......"); + return ESP_OK; +} + +esp_err_t protocomm_ble_stop(protocomm_t *pc) +{ + if ((pc != NULL) && + (protoble_internal != NULL ) && + (pc == protoble_internal->pc_ble)) { + esp_err_t ret = ESP_OK; + ret = simple_ble_stop(); + if (ret) { + ESP_LOGE(TAG, "BLE stop failed"); + } + simple_ble_deinit(); + protocomm_ble_cleanup(); + return ret; + } + return ESP_ERR_INVALID_ARG; +} diff --git a/components/protocomm/src/transports/protocomm_console.c b/components/protocomm/src/transports/protocomm_console.c new file mode 100644 index 000000000..859c3395a --- /dev/null +++ b/components/protocomm/src/transports/protocomm_console.c @@ -0,0 +1,224 @@ +// Copyright 2018 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "protocomm_priv.h" + +static const char *TAG = "protocomm_console"; + +static uint32_t session_id = PROTOCOMM_NO_SESSION_ID; +static protocomm_t *pc_console = NULL; /* The global protocomm instance for console */ +static TaskHandle_t console_task = NULL; + +esp_err_t protocomm_console_stop(protocomm_t *pc) +{ + if (pc != pc_console) { + ESP_LOGE(TAG, "Incorrect stop request"); + return ESP_ERR_INVALID_ARG; + } + + ESP_LOGI(TAG, "Stopping console..."); + xTaskNotifyGive(console_task); + return ESP_OK; +} + +static ssize_t hex2bin(const char *hexstr, uint8_t *bytes) +{ + size_t hexstrLen = strlen(hexstr); + ssize_t bytesLen = hexstrLen / 2; + + int count = 0; + const char *pos = hexstr; + + for(count = 0; count < bytesLen; count++) { + sscanf(pos, "%2hhx", &bytes[count]); + pos += 2; + } + + return bytesLen; +} + +static bool stopped(void) +{ + uint32_t flag = 0; + xTaskNotifyWait(0, 0, &flag, (TickType_t) 10/portTICK_RATE_MS); + return (flag != 0); +} + +static void protocomm_console_task(void *arg) +{ + int uart_num = (int) arg; + uint8_t linebuf[256]; + int i, cmd_ret; + esp_err_t ret; + QueueHandle_t uart_queue; + uart_event_t event; + + ESP_LOGV(TAG, "Initialising UART on port %d", uart_num); + uart_driver_install(uart_num, 256, 0, 8, &uart_queue, 0); + /* Initialize the console */ + esp_console_config_t console_config = { + .max_cmdline_args = 8, + .max_cmdline_length = 256, + }; + + esp_console_init(&console_config); + esp_console_register_help_command(); + + while (!stopped()) { + uart_write_bytes(uart_num, "\n>> ", 4); + memset(linebuf, 0, sizeof(linebuf)); + i = 0; + do { + ret = xQueueReceive(uart_queue, (void * )&event, (TickType_t) 10/portTICK_RATE_MS); + if (ret != pdPASS) { + if (stopped()) { + break; + } else { + continue; + } + } + if (event.type == UART_DATA) { + while (uart_read_bytes(uart_num, (uint8_t *) &linebuf[i], 1, 0)) { + if (linebuf[i] == '\r') { + uart_write_bytes(uart_num, "\r\n", 2); + } else { + uart_write_bytes(uart_num, (char *) &linebuf[i], 1); + } + i++; + } + } + } while ((i < 255) && linebuf[i-1] != '\r'); + if (stopped()) { + break; + } + ret = esp_console_run((char *) linebuf, &cmd_ret); + if (ret < 0) { + ESP_LOGE(TAG, "Console dispatcher error\n"); + break; + } + } + + if (pc_console->sec && pc_console->sec->cleanup) { + pc_console->sec->cleanup(); + } + + pc_console = NULL; + esp_console_deinit(); + + ESP_LOGI(TAG, "Console stopped"); + vTaskDelete(NULL); +} + +static int common_cmd_handler(int argc, char** argv) +{ + int i, ret; + + uint32_t cur_session_id = atoi(argv[1]); + + uint8_t *buf = (uint8_t *) malloc(strlen(argv[2])); + uint8_t *outbuf; + ssize_t outlen; + ssize_t len = hex2bin(argv[2], buf); + + if (cur_session_id != session_id) { + if (pc_console->sec && pc_console->sec->new_transport_session) { + ret = pc_console->sec->new_transport_session(cur_session_id); + if (ret == ESP_OK) { + session_id = cur_session_id; + } + } + } + + ret = protocomm_req_handle(pc_console, argv[0], cur_session_id, buf, len, &outbuf, &outlen); + free(buf); + + if (ret == ESP_OK) { + printf("\r\n"); + for (i = 0; i < outlen; i++) { + printf("%02x", outbuf[i]); + } + printf("\r\n"); + + /* Transport is responsible for freeing the transmit buffer */ + free(outbuf); + + return ESP_OK; + } else { + return ret; + } +} + +static esp_err_t protocomm_console_add_endpoint(const char *ep_name, protocomm_req_handler_t req_handler, void *priv_data) +{ + (void) req_handler; + (void) priv_data; + + esp_err_t ret; + esp_console_cmd_t cmd; + memset(&cmd, 0, sizeof(cmd)); + + cmd.command = ep_name; + cmd.help = ""; + cmd.func = common_cmd_handler; + + ret = esp_console_cmd_register(&cmd); + + return ret; +} + +static esp_err_t protocomm_console_remove_endpoint(const char *ep_name) +{ + /* Command deletion happens internally in esp_console_deinit function */ + + return ESP_OK; +} + +esp_err_t protocomm_console_start(protocomm_t *pc, const protocomm_console_config_t *config) +{ + if (pc == NULL) { + return ESP_ERR_INVALID_ARG; + } + + if (pc_console != NULL) { + if (pc_console == pc) { + return ESP_ERR_INVALID_STATE; + } + else { + return ESP_ERR_NOT_SUPPORTED; + } + } + + + if (xTaskCreate(protocomm_console_task, "protocomm_console", + config->stack_size, NULL, config->task_priority, &console_task) != pdPASS) { + return ESP_FAIL; + } + + pc->add_endpoint = protocomm_console_add_endpoint; + pc->remove_endpoint = protocomm_console_remove_endpoint; + pc_console = pc; + return ESP_OK; +} diff --git a/components/protocomm/src/transports/protocomm_httpd.c b/components/protocomm/src/transports/protocomm_httpd.c new file mode 100644 index 000000000..e8d246603 --- /dev/null +++ b/components/protocomm/src/transports/protocomm_httpd.c @@ -0,0 +1,249 @@ +// Copyright 2018 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 +#include +#include +#include + +#include + +#include +#include + +#include "protocomm_priv.h" + +static const char *TAG = "protocomm_httpd"; +static protocomm_t *pc_httpd; /* The global protocomm instance for HTTPD */ +static uint32_t session_id = PROTOCOMM_NO_SESSION_ID; + +#define MAX_REQ_BODY_LEN 4096 + +static esp_err_t common_post_handler(httpd_req_t *req) +{ + esp_err_t ret; + uint8_t *outbuf = NULL; + char *req_body = NULL; + const char *ep_name = NULL; + ssize_t outlen; + + int cur_session_id = httpd_req_to_sockfd(req); + + if (cur_session_id != session_id) { + /* Initialise new security session */ + if (session_id != PROTOCOMM_NO_SESSION_ID) { + ESP_LOGV(TAG, "Closing session with ID: %d", session_id); + /* Presently HTTP server doesn't support callback on socket closure so + * previous session can only be closed when new session is requested */ + if (pc_httpd->sec && pc_httpd->sec->close_transport_session) { + ret = pc_httpd->sec->close_transport_session(session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to close session with ID: %d", session_id); + ret = ESP_FAIL; + goto out; + } + } + session_id = PROTOCOMM_NO_SESSION_ID; + } + if (pc_httpd->sec && pc_httpd->sec->new_transport_session) { + ret = pc_httpd->sec->new_transport_session(cur_session_id); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to launch new session with ID: %d", cur_session_id); + ret = ESP_FAIL; + goto out; + } + } + session_id = cur_session_id; + ESP_LOGV(TAG, "New session with ID: %d", cur_session_id); + } + + if (req->content_len <= 0) { + ESP_LOGE(TAG, "Content length not found"); + ret = ESP_FAIL; + goto out; + } else if (req->content_len > MAX_REQ_BODY_LEN) { + ESP_LOGE(TAG, "Request content length should be less than 4kb"); + ret = ESP_FAIL; + goto out; + } + + req_body = (char *) malloc(req->content_len); + if (!req_body) { + ESP_LOGE(TAG, "Unable to allocate for request length %d", req->content_len); + ret = ESP_ERR_NO_MEM; + goto out; + } + + size_t recv_size = 0; + while (recv_size < req->content_len) { + ret = httpd_req_recv(req, req_body + recv_size, req->content_len - recv_size); + if (ret < 0) { + ret = ESP_FAIL; + goto out; + } + recv_size += ret; + } + + /* Extract the endpoint name from URI string of type "/ep_name" */ + ep_name = req->uri + 1; + + ret = protocomm_req_handle(pc_httpd, ep_name, session_id, + (uint8_t *)req_body, recv_size, &outbuf, &outlen); + + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Data handler failed"); + ret = ESP_FAIL; + goto out; + } + + ret = httpd_resp_send(req, (char *)outbuf, outlen); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "HTTP send failed"); + ret = ESP_FAIL; + goto out; + } + ret = ESP_OK; +out: + if (req_body) { + free(req_body); + } + if (outbuf) { + free(outbuf); + } + return ret; +} + +esp_err_t protocomm_httpd_add_endpoint(const char *ep_name, + protocomm_req_handler_t req_handler, + void *priv_data) +{ + if (pc_httpd == NULL) { + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGV(TAG, "Adding endpoint : %s", ep_name); + + /* Construct URI name by prepending '/' to ep_name */ + char* ep_uri = calloc(1, strlen(ep_name) + 2); + if (!ep_uri) { + ESP_LOGE(TAG, "Malloc failed for ep uri"); + return ESP_ERR_NO_MEM; + } + + /* Create URI handler structure */ + sprintf(ep_uri, "/%s", ep_name); + httpd_uri_t config_handler = { + .uri = ep_uri, + .method = HTTP_POST, + .handler = common_post_handler, + .user_ctx = NULL + }; + + /* Register URI handler */ + esp_err_t err; + httpd_handle_t *server = (httpd_handle_t *) pc_httpd->priv; + if ((err = httpd_register_uri_handler(*server, &config_handler)) != ESP_OK) { + ESP_LOGE(TAG, "Uri handler register failed: %s", esp_err_to_name(err)); + free(ep_uri); + return ESP_FAIL; + } + + free(ep_uri); + return ESP_OK; +} + +static esp_err_t protocomm_httpd_remove_endpoint(const char *ep_name) +{ + if (pc_httpd == NULL) { + return ESP_ERR_INVALID_STATE; + } + + ESP_LOGV(TAG, "Removing endpoint : %s", ep_name); + + /* Construct URI name by prepending '/' to ep_name */ + char* ep_uri = calloc(1, strlen(ep_name) + 2); + if (!ep_uri) { + ESP_LOGE(TAG, "Malloc failed for ep uri"); + return ESP_ERR_NO_MEM; + } + sprintf(ep_uri, "/%s", ep_name); + + /* Unregister URI handler */ + esp_err_t err; + httpd_handle_t *server = (httpd_handle_t *) pc_httpd->priv; + if ((err = httpd_unregister_uri_handler(*server, ep_uri, HTTP_POST)) != ESP_OK) { + ESP_LOGE(TAG, "Uri handler de-register failed: %s", esp_err_to_name(err)); + free(ep_uri); + return ESP_FAIL; + } + + free(ep_uri); + return ESP_OK; +} + +esp_err_t protocomm_httpd_start(protocomm_t *pc, const protocomm_httpd_config_t *config) +{ + if (!pc || !config) { + return ESP_ERR_INVALID_ARG; + } + + if (pc_httpd) { + if (pc == pc_httpd) { + ESP_LOGE(TAG, "HTTP server already running for this protocomm instance"); + return ESP_ERR_INVALID_STATE; + } + ESP_LOGE(TAG, "HTTP server started for another protocomm instance"); + return ESP_ERR_NOT_SUPPORTED; + } + + /* Private data will point to the HTTP server handle */ + pc->priv = calloc(1, sizeof(httpd_handle_t)); + if (!pc->priv) { + ESP_LOGE(TAG, "Malloc failed for HTTP server handle"); + return ESP_ERR_NO_MEM; + } + + /* Configure the HTTP server */ + httpd_config_t server_config = HTTPD_DEFAULT_CONFIG(); + server_config.server_port = config->port; + server_config.stack_size = config->stack_size; + server_config.task_priority = config->task_priority; + server_config.lru_purge_enable = true; + server_config.max_open_sockets = 1; + + esp_err_t err; + if ((err = httpd_start((httpd_handle_t *)pc->priv, &server_config)) != ESP_OK) { + ESP_LOGE(TAG, "Failed to start http server: %s", esp_err_to_name(err)); + free(pc->priv); + return err; + } + + pc->add_endpoint = protocomm_httpd_add_endpoint; + pc->remove_endpoint = protocomm_httpd_remove_endpoint; + pc_httpd = pc; + return ESP_OK; +} + +esp_err_t protocomm_httpd_stop(protocomm_t *pc) +{ + if ((pc != NULL) && (pc == pc_httpd)) { + httpd_handle_t *server_handle = (httpd_handle_t *) pc_httpd->priv; + httpd_stop(*server_handle); + free(server_handle); + pc_httpd->priv = NULL; + pc_httpd = NULL; + return ESP_OK; + } + return ESP_ERR_INVALID_ARG; +}