]> granicus.if.org Git - esp-idf/commitdiff
Protocomm : Added support for choice of transport - WiFi (SoftAP+HTTPD), BLE, Console...
authorAmey Inamdar <amey@espressif.com>
Mon, 30 Jul 2018 16:06:48 +0000 (21:36 +0530)
committerAnurag Kar <anurag.kar@espressif.com>
Tue, 2 Oct 2018 13:37:28 +0000 (19:07 +0530)
Co-Authored-By: Amey Inamdar <amey@espressif.com>
Co-Authored-By: Anurag Kar <anurag.kar@espressif.com>
components/protocomm/CMakeLists.txt [new file with mode: 0644]
components/protocomm/component.mk
components/protocomm/include/transports/protocomm_ble.h [new file with mode: 0644]
components/protocomm/include/transports/protocomm_console.h [new file with mode: 0644]
components/protocomm/include/transports/protocomm_httpd.h [new file with mode: 0644]
components/protocomm/src/simple_ble/simple_ble.c [new file with mode: 0644]
components/protocomm/src/simple_ble/simple_ble.h [new file with mode: 0644]
components/protocomm/src/transports/protocomm_ble.c [new file with mode: 0644]
components/protocomm/src/transports/protocomm_console.c [new file with mode: 0644]
components/protocomm/src/transports/protocomm_httpd.c [new file with mode: 0644]

diff --git a/components/protocomm/CMakeLists.txt b/components/protocomm/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a6609e5
--- /dev/null
@@ -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()
index 7941a8e3bae7bc3e6a2c8c5fdee2b4727c8c625a..3ed61cbc19cb1781523f46cf30ca9d6fb8bfb344 100644 (file)
@@ -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 (file)
index 0000000..b86059e
--- /dev/null
@@ -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 <protocomm.h>
+
+/**
+ * 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 (file)
index 0000000..b7004d0
--- /dev/null
@@ -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 <protocomm.h>
+
+#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 (file)
index 0000000..701fa96
--- /dev/null
@@ -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 <protocomm.h>
+
+#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 (file)
index 0000000..e52be00
--- /dev/null
@@ -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 <freertos/FreeRTOS.h>
+#include <esp_system.h>
+#include <esp_log.h>
+#include <esp_bt.h>
+#include <esp_gap_ble_api.h>
+#include <esp_gatts_api.h>
+#include <esp_bt_main.h>
+#include <esp_gatt_common_api.h>
+
+#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 (file)
index 0000000..892cd4d
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <esp_gap_ble_api.h>
+#include <esp_gatts_api.h>
+
+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 (file)
index 0000000..a4b1927
--- /dev/null
@@ -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 <sys/param.h>
+#include <esp_log.h>
+#include <esp_gatt_common_api.h>
+
+#include <protocomm.h>
+#include <protocomm_ble.h>
+
+#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 (file)
index 0000000..859c339
--- /dev/null
@@ -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 <stdio.h>
+#include <string.h>
+#include <esp_log.h>
+#include <esp_console.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <freertos/queue.h>
+#include <driver/uart.h>
+
+#include <protocomm.h>
+#include <protocomm_console.h>
+
+#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 (file)
index 0000000..e8d2466
--- /dev/null
@@ -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 <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_log.h>
+#include <esp_err.h>
+
+#include <http_server.h>
+
+#include <protocomm.h>
+#include <protocomm_httpd.h>
+
+#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;
+}