Files
esp-idf/components/protocomm/src/transports/protocomm_nimble.c
Isha Pardikar 2cfcae8d41 Merge branch 'feature/rainmaker_BT_configurable' into 'master'
Doc: Made bluetooth configurable after wifi provisioning is completed

See merge request espressif/esp-idf!18014
2022-07-26 15:21:27 +05:30

983 lines
34 KiB
C

/*
* SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sys/param.h>
#include <esp_log.h>
#include <string.h>
#include <protocomm.h>
#include <protocomm_ble.h>
#include "protocomm_priv.h"
/* NimBLE */
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
static const char *TAG = "protocomm_nimble";
int ble_uuid_flat(const ble_uuid_t *, void *);
static uint8_t ble_uuid_base[BLE_UUID128_VAL_LENGTH];
static int num_chr_dsc;
static uint16_t s_cached_conn_handle;
/* Standard 16 bit UUID for characteristic User Description*/
#define BLE_GATT_UUID_CHAR_DSC 0x2901
/********************************************************
* Maintain database for Attribute specific data *
********************************************************/
struct data_mbuf {
SLIST_ENTRY(data_mbuf) node;
uint8_t *outbuf;
ssize_t outlen;
uint16_t attr_handle;
};
static SLIST_HEAD(data_mbuf_head, data_mbuf) data_mbuf_list =
SLIST_HEAD_INITIALIZER(data_mbuf_list);
static struct data_mbuf *find_attr_with_handle(uint16_t attr_handle)
{
struct data_mbuf *cur;
SLIST_FOREACH(cur, &data_mbuf_list, node) {
if (cur->attr_handle == attr_handle) {
return cur;
}
}
return NULL;
}
/**************************************************************
* Initialize GAP, protocomm parameters *
**************************************************************/
static int simple_ble_gap_event(struct ble_gap_event *event, void *arg);
static uint8_t own_addr_type;
void ble_store_config_init(void);
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 struct ble_gap_adv_params adv_params;
static char *protocomm_ble_device_name;
static struct ble_hs_adv_fields adv_data, resp_data;
static uint8_t *protocomm_ble_mfg_data;
static size_t protocomm_ble_mfg_data_len;
/**********************************************************************
* Maintain database of uuid_name addresses to free memory afterwards *
**********************************************************************/
struct uuid128_name_buf {
SLIST_ENTRY(uuid128_name_buf) link;
ble_uuid128_t *uuid128_name_table;
};
static SLIST_HEAD(uuid128_name_buf_head, uuid128_name_buf) uuid128_name_list =
SLIST_HEAD_INITIALIZER(uuid128_name_list);
/**********************************************************************
* Initialize simple BLE parameters, advertisement, scan response etc *
**********************************************************************/
static int
gatt_svr_chr_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg);
static int
gatt_svr_dsc_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg);
void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg);
typedef void (simple_ble_cb_t)(struct ble_gap_event *event, void *arg);
static void transport_simple_ble_connect(struct ble_gap_event *event, void *arg);
static void transport_simple_ble_disconnect(struct ble_gap_event *event, void *arg);
static void transport_simple_ble_set_mtu(struct ble_gap_event *event, void *arg);
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" */
struct ble_hs_adv_fields adv_data;
/** Parameters to configure the nature of advertising */
struct ble_gap_adv_params adv_params;
/** Descriptor table which consists the configuration required by services and characteristics */
struct ble_gatt_svc_def *gatt_db;
/** 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;
/** BLE bonding */
unsigned ble_bonding:1;
/** BLE Secure Connection flag */
unsigned ble_sm_sc:1;
} simple_ble_cfg_t;
static simple_ble_cfg_t *ble_cfg_p;
/************************************************************
* Functions to set and get attr value based on attr Handle *
************************************************************/
static int simple_ble_gatts_set_attr_value(uint16_t attr_handle, ssize_t outlen,
uint8_t *outbuf)
{
struct data_mbuf *attr_mbuf = find_attr_with_handle(attr_handle);
if (!attr_mbuf) {
attr_mbuf = calloc(1, sizeof(struct data_mbuf));
if (!attr_mbuf) {
ESP_LOGE(TAG, "Failed to allocate memory for storing outbuf and outlen");
return ESP_ERR_NO_MEM;
}
SLIST_INSERT_HEAD(&data_mbuf_list, attr_mbuf, node);
attr_mbuf->attr_handle = attr_handle;
} else {
free(attr_mbuf->outbuf);
}
attr_mbuf->outbuf = outbuf;
attr_mbuf->outlen = outlen;
return ESP_OK;
}
static int simple_ble_gatts_get_attr_value(uint16_t attr_handle, ssize_t
*outlen, uint8_t **outbuf)
{
struct data_mbuf *attr_mbuf = find_attr_with_handle(attr_handle);
if (!attr_mbuf) {
ESP_LOGE(TAG, "Outbuf with handle %d not found", attr_handle);
return ESP_ERR_NOT_FOUND;
}
*outbuf = attr_mbuf->outbuf;
*outlen = attr_mbuf->outlen;
return ESP_OK;
}
/*****************************************************************************************/
/* SIMPLE BLE INTEGRATION */
/*****************************************************************************************/
static void
simple_ble_advertise(void)
{
int rc;
adv_data.flags = (BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP);
adv_data.num_uuids128 = 1;
adv_data.uuids128_is_complete = 1;
rc = ble_gap_adv_set_fields(&adv_data);
if (rc != 0) {
ESP_LOGE(TAG, "Error setting advertisement data; rc = %d", rc);
return;
}
rc = ble_gap_adv_rsp_set_fields((const struct ble_hs_adv_fields *) &resp_data);
if (rc != 0) {
ESP_LOGE(TAG, "Error in setting scan response; rc = %d", rc);
return;
}
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
adv_params.itvl_min = 0x100;
adv_params.itvl_max = 0x100;
rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER,
&adv_params, simple_ble_gap_event, NULL);
if (rc != 0) {
/* If BLE Host is disabled, it probably means device is already
* provisioned in previous session. Avoid error prints for this case.*/
if (rc == BLE_HS_EDISABLED) {
ESP_LOGD(TAG, "BLE Host is disabled !!");
} else {
ESP_LOGE(TAG, "Error enabling advertisement; rc = %d", rc);
}
return;
}
/* Take note of free heap space */
ESP_LOGD(TAG, "Minimum free heap size = %d, free Heap size = %d",
esp_get_minimum_free_heap_size(), esp_get_free_heap_size());
}
static int
simple_ble_gap_event(struct ble_gap_event *event, void *arg)
{
struct ble_gap_conn_desc desc;
int rc;
switch (event->type) {
case BLE_GAP_EVENT_CONNECT:
/* A new connection was established or a connection attempt failed. */
if (event->connect.status == 0) {
transport_simple_ble_connect(event, arg);
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
if (rc != 0) {
ESP_LOGE(TAG, "No open connection with the specified handle");
return rc;
}
s_cached_conn_handle = event->connect.conn_handle;
} else {
/* Connection failed; resume advertising. */
simple_ble_advertise();
}
return 0;
case BLE_GAP_EVENT_DISCONNECT:
ESP_LOGD(TAG, "disconnect; reason=%d ", event->disconnect.reason);
transport_simple_ble_disconnect(event, arg);
s_cached_conn_handle = 0; /* Clear conn_handle value */
/* Connection terminated; resume advertising. */
simple_ble_advertise();
return 0;
case BLE_GAP_EVENT_ADV_COMPLETE:
simple_ble_advertise();
return 0;
case BLE_GAP_EVENT_MTU:
ESP_LOGI(TAG, "mtu update event; conn_handle=%d cid=%d mtu=%d\n",
event->mtu.conn_handle,
event->mtu.channel_id,
event->mtu.value);
transport_simple_ble_set_mtu(event, arg);
return 0;
}
return 0;
}
/* Gets `g_nu_lookup name handler` from 128 bit UUID */
static const char *uuid128_to_handler(uint8_t *uuid)
{
/* Use it to convert 128 bit UUID to 16 bit UUID.*/
uint8_t *uuid16 = uuid + 12;
for (int i = 0; i < protoble_internal->g_nu_lookup_count; i++) {
if (protoble_internal->g_nu_lookup[i].uuid == *(uint16_t *)uuid16 ) {
ESP_LOGD(TAG, "UUID (0x%x) matched with proto-name = %s", *uuid16, protoble_internal->g_nu_lookup[i].name);
return protoble_internal->g_nu_lookup[i].name;
} else {
ESP_LOGD(TAG, "UUID did not match... %x", *uuid16);
}
}
return NULL;
}
/* Callback to handle GATT characteristic descriptor read */
static int
gatt_svr_dsc_access(uint16_t conn_handle, uint16_t attr_handle, struct
ble_gatt_access_ctxt *ctxt, void *arg)
{
if (ctxt->op != BLE_GATT_ACCESS_OP_READ_DSC) {
ESP_LOGE(TAG, "Invalid operation on Read-only Descriptor");
return BLE_ATT_ERR_UNLIKELY;
}
int rc;
ssize_t temp_outlen = strlen(ctxt->dsc->arg);
rc = os_mbuf_append(ctxt->om, ctxt->dsc->arg, temp_outlen);
return rc;
}
/* Callback to handle GATT characteristic value Read & Write */
static int
gatt_svr_chr_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg)
{
int rc;
esp_err_t ret;
char buf[BLE_UUID_STR_LEN];
ssize_t temp_outlen = 0;
uint8_t *temp_outbuf = NULL;
uint8_t *uuid = NULL;
uint8_t *data_buf = NULL;
uint16_t data_len = 0;
uint16_t data_buf_len = 0;
switch (ctxt->op) {
case BLE_GATT_ACCESS_OP_READ_CHR:
ESP_LOGD(TAG, "Read attempted for characteristic UUID = %s, attr_handle = %d",
ble_uuid_to_str(ctxt->chr->uuid, buf), attr_handle);
rc = simple_ble_gatts_get_attr_value(attr_handle, &temp_outlen,
&temp_outbuf);
if (rc != 0) {
ESP_LOGE(TAG, "Failed to read characteristic with attr_handle = %d", attr_handle);
return rc;
}
rc = os_mbuf_append(ctxt->om, temp_outbuf, temp_outlen);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
case BLE_GATT_ACCESS_OP_WRITE_CHR:
/* If empty packet is received, return */
if (ctxt->om->om_len == 0) {
ESP_LOGD(TAG,"Empty packet");
return 0;
}
uuid = (uint8_t *) calloc(BLE_UUID128_VAL_LENGTH, sizeof(uint8_t));
if (!uuid) {
ESP_LOGE(TAG, "Error allocating memory for 128 bit UUID");
return BLE_ATT_ERR_INSUFFICIENT_RES;
}
rc = ble_uuid_flat(ctxt->chr->uuid, uuid);
if (rc != 0) {
free(uuid);
ESP_LOGE(TAG, "Error fetching Characteristic UUID128");
return rc;
}
/* Save the length of entire data */
data_len = OS_MBUF_PKTLEN(ctxt->om);
ESP_LOGD(TAG, "Write attempt for uuid = %s, attr_handle = %d, data_len = %d",
ble_uuid_to_str(ctxt->chr->uuid, buf), attr_handle, data_len);
data_buf = calloc(1, data_len);
if (data_buf == NULL) {
ESP_LOGE(TAG, "Error allocating memory for characteristic value");
return BLE_ATT_ERR_INSUFFICIENT_RES;
}
rc = ble_hs_mbuf_to_flat(ctxt->om, data_buf, data_len, &data_buf_len);
if (rc != 0) {
ESP_LOGE(TAG, "Error getting data from memory buffers");
return BLE_ATT_ERR_UNLIKELY;
}
ret = protocomm_req_handle(protoble_internal->pc_ble,
uuid128_to_handler(uuid),
conn_handle,
data_buf,
data_buf_len,
&temp_outbuf, &temp_outlen);
/* Release the 16 bytes allocated for uuid*/
free(uuid);
free(data_buf);
if (ret == ESP_OK) {
/* Save data address and length outbuf and outlen internally */
rc = simple_ble_gatts_set_attr_value(attr_handle, temp_outlen,
temp_outbuf);
if (rc != 0) {
ESP_LOGE(TAG, "Failed to set outbuf for characteristic with attr_handle = %d",
attr_handle);
free(temp_outbuf);
}
return rc;
} else {
ESP_LOGE(TAG, "Invalid content received, killing connection");
return BLE_ATT_ERR_INVALID_PDU;
}
default:
return BLE_ATT_ERR_UNLIKELY;
}
}
void
gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
{
char buf[BLE_UUID_STR_LEN];
switch (ctxt->op) {
case BLE_GATT_REGISTER_OP_SVC:
ESP_LOGD(TAG, "registering service %s with handle=%d TYPE =%d",
ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf),
ctxt->svc.handle, ctxt->svc.svc_def->uuid->type);
break;
case BLE_GATT_REGISTER_OP_CHR:
ESP_LOGD(TAG, "registering characteristic %s with "
"def_handle=%d val_handle=%d , TYPE = %d",
ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
ctxt->chr.def_handle,
ctxt->chr.val_handle, ctxt->chr.chr_def->uuid->type);
break;
case BLE_GATT_REGISTER_OP_DSC:
ESP_LOGD(TAG, "registering descriptor %s with handle=%d",
ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf),
ctxt->dsc.handle);
break;
default:
assert(0);
break;
}
}
int
gatt_svr_init(const simple_ble_cfg_t *config)
{
int rc;
rc = ble_gatts_count_cfg(config->gatt_db);
if (rc != 0) {
return rc;
}
rc = ble_gatts_add_svcs(config->gatt_db);
if (rc != 0) {
return rc;
}
return 0;
}
static void
simple_ble_on_reset(int reason)
{
ESP_LOGE(TAG, "Resetting state; reason=%d\n", reason);
}
static void
simple_ble_on_sync(void)
{
int rc;
rc = ble_hs_util_ensure_addr(0);
if (rc != 0) {
ESP_LOGE(TAG, "Error loading address");
return;
}
/* Figure out address to use while advertising (no privacy for now) */
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
ESP_LOGE(TAG, "error determining address type; rc=%d\n", rc);
return;
}
/* Begin advertising. */
simple_ble_advertise();
}
void
nimble_host_task(void *param)
{
/* This function will return only when nimble_port_stop() is executed */
ESP_LOGI(TAG, "BLE Host Task Started");
nimble_port_run();
nimble_port_freertos_deinit();
}
static int simple_ble_start(const simple_ble_cfg_t *cfg)
{
ble_cfg_p = (void *)cfg;
int rc;
ESP_LOGD(TAG, "Free memory at start of simple_ble_init %d", esp_get_free_heap_size());
nimble_port_init();
/* Initialize the NimBLE host configuration. */
ble_hs_cfg.reset_cb = simple_ble_on_reset;
ble_hs_cfg.sync_cb = simple_ble_on_sync;
ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
/* Initialize security manager configuration in NimBLE host */
ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_NO_IO; /* Just Works */
ble_hs_cfg.sm_bonding = cfg->ble_bonding;
ble_hs_cfg.sm_mitm = 1;
ble_hs_cfg.sm_sc = cfg->ble_sm_sc;
/* Distribute LTK and IRK */
ble_hs_cfg.sm_our_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
ble_hs_cfg.sm_their_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
rc = gatt_svr_init(cfg);
if (rc != 0) {
ESP_LOGE(TAG, "Error initializing GATT server");
return rc;
}
/* Set device name, configure response data to be sent while advertising */
rc = ble_svc_gap_device_name_set(cfg->device_name);
if (rc != 0) {
ESP_LOGE(TAG, "Error setting device name");
return rc;
}
resp_data.name = (void *) ble_svc_gap_device_name();
if (resp_data.name != NULL) {
resp_data.name_len = strlen(ble_svc_gap_device_name());
resp_data.name_is_complete = 1;
}
/* Set manufacturer data if protocomm_ble_mfg_data points to valid data */
if (protocomm_ble_mfg_data != NULL) {
resp_data.mfg_data = protocomm_ble_mfg_data;
resp_data.mfg_data_len = protocomm_ble_mfg_data_len;
ESP_LOGD(TAG, "Custom manufacturer data length = %d", protocomm_ble_mfg_data_len);
}
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(nimble_host_task);
return 0;
}
/* transport_simple BLE Fn */
static void transport_simple_ble_disconnect(struct ble_gap_event *event, void *arg)
{
esp_err_t ret;
ESP_LOGD(TAG, "Inside disconnect w/ session - %d",
event->disconnect.conn.conn_handle);
#ifdef CONFIG_WIFI_PROV_KEEP_BLE_ON_AFTER_PROV
/* Ignore BLE events received after protocomm layer is stopped */
if (protoble_internal == NULL) {
ESP_LOGI(TAG,"Protocomm layer has already stopped");
return;
}
#endif
if (protoble_internal->pc_ble->sec &&
protoble_internal->pc_ble->sec->close_transport_session) {
ret =
protoble_internal->pc_ble->sec->close_transport_session(protoble_internal->pc_ble->sec_inst, event->disconnect.conn.conn_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "error closing the session after disconnect");
}
}
protoble_internal->gatt_mtu = BLE_ATT_MTU_DFLT;
}
static void transport_simple_ble_connect(struct ble_gap_event *event, void *arg)
{
esp_err_t ret;
ESP_LOGD(TAG, "Inside BLE connect w/ conn_id - %d", event->connect.conn_handle);
if (protoble_internal->pc_ble->sec &&
protoble_internal->pc_ble->sec->new_transport_session) {
ret =
protoble_internal->pc_ble->sec->new_transport_session(protoble_internal->pc_ble->sec_inst, event->connect.conn_handle);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "error creating the session");
}
}
}
static void transport_simple_ble_set_mtu(struct ble_gap_event *event, void *arg)
{
protoble_internal->gatt_mtu = event->mtu.value;
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;
}
/* Function to add descriptor to characteristic. The value of descriptor is
* filled with corresponding protocomm endpoint names. Characteristic address,
* its serial no. and XXX 16 bit standard UUID for descriptor to be provided as
* input parameters. Returns 0 on success and returns ESP_ERR_NO_MEM on
* failure. */
static int
ble_gatt_add_char_dsc(struct ble_gatt_chr_def *characteristics, int idx, uint16_t dsc_uuid)
{
ble_uuid_t *uuid16 = BLE_UUID16_DECLARE(dsc_uuid);
/* Allocate memory for 2 descriptors, the 2nd descriptor shall be all NULLs
* to indicate End of Descriptors. */
(characteristics + idx)->descriptors = (struct ble_gatt_dsc_def *) calloc(2,
sizeof(struct ble_gatt_dsc_def));
if ((characteristics + idx)->descriptors == NULL) {
ESP_LOGE(TAG, "Error while allocating memory for characteristic descriptor");
return ESP_ERR_NO_MEM;
}
(characteristics + idx)->descriptors[0].uuid = (ble_uuid_t *) calloc(1,
sizeof(ble_uuid16_t));
if ((characteristics + idx)->descriptors[0].uuid == NULL) {
ESP_LOGE(TAG, "Error while allocating memory for characteristic descriptor");
return ESP_ERR_NO_MEM;
}
memcpy((void *)(characteristics + idx)->descriptors[0].uuid, uuid16,
sizeof(ble_uuid16_t));
(characteristics + idx)->descriptors[0].att_flags = BLE_ATT_F_READ;
(characteristics + idx)->descriptors[0].access_cb = gatt_svr_dsc_access;
(characteristics + idx)->descriptors[0].arg = (void *)
protoble_internal->g_nu_lookup[idx].name;
return 0;
}
/* Function to add characteristics to the service. For simplicity the
* flags and access callbacks are same for all the characteristics. The Fn
* requires pointer to characteristic of service and index of characteristic,
* depending upon the index no. individual characteristics can be handled in
* future. The fn also assumes that the required memory for all characteristics
* is already allocated while adding corresponding service. Returns 0 on
* success and returns ESP_ERR_NO_MEM on failure to add characteristic. */
static int
ble_gatt_add_characteristics(struct ble_gatt_chr_def *characteristics, int idx)
{
/* Prepare 128 bit UUID of characteristics using custom base 128
* bit UUID and replacing byte 12 and 13 with corresponding protocom
* endpoint 16 bit UUID value. */
ble_uuid128_t temp_uuid128_name = {0};
temp_uuid128_name.u.type = BLE_UUID_TYPE_128;
memcpy(temp_uuid128_name.value, ble_uuid_base, BLE_UUID128_VAL_LENGTH);
memcpy(&temp_uuid128_name.value[12], &protoble_internal->g_nu_lookup[idx].uuid, 2);
(characteristics + idx)->flags = BLE_GATT_CHR_F_READ |
BLE_GATT_CHR_F_WRITE ;
#if defined(CONFIG_WIFI_PROV_BLE_FORCE_ENCRYPTION)
(characteristics + idx)->flags |= BLE_GATT_CHR_F_READ_ENC |
BLE_GATT_CHR_F_WRITE_ENC;
#endif
(characteristics + idx)->access_cb = gatt_svr_chr_access;
/* Out of 128 bit UUID, 16 bits from g_nu_lookup table. Currently
* g_nu_lookup table has 16 bit UUID, XXX this can be changed to 128 bit UUID
* in future. For time being continue using 16 bit UUID on top of base 128
* bit service UUID */
(characteristics + idx)->uuid = (ble_uuid_t *)calloc(1,
sizeof(ble_uuid128_t));
if ((characteristics + idx)->uuid == NULL) {
ESP_LOGE(TAG, "Error allocating memory for characteristic UUID");
return ESP_ERR_NO_MEM;
}
memcpy((void *)(characteristics + idx)->uuid, &temp_uuid128_name,
sizeof(ble_uuid128_t));
return 0;
}
/* Function to add primary service. It also allocates memory for the
* characteristics. Returns 0 on success, returns ESP_ERR_NO_MEM on failure to
* add service. */
static int
ble_gatt_add_primary_svcs(struct ble_gatt_svc_def *gatt_db_svcs, int char_count)
{
/* Remember the count of characteristics here, as it will be used to free
* memory afterwards */
num_chr_dsc = char_count;
gatt_db_svcs->type = BLE_GATT_SVC_TYPE_PRIMARY;
/* Allocate (number of characteristics + 1) memory for characteristics, the
* addtional characteristic consist of all 0s indicating end of
* characteristics */
gatt_db_svcs->characteristics = (struct ble_gatt_chr_def *) calloc((char_count + 1),
sizeof(struct ble_gatt_chr_def));
if (gatt_db_svcs->characteristics == NULL) {
ESP_LOGE(TAG, "Memory allocation for GATT characteristics failed");
return ESP_ERR_NO_MEM;
}
return 0;
}
static int
populate_gatt_db(struct ble_gatt_svc_def **gatt_db_svcs, const protocomm_ble_config_t *config)
{
/* Allocate memory for 2 services, 2nd to be all NULL indicating end of
* services */
*gatt_db_svcs = (struct ble_gatt_svc_def *) calloc(2, sizeof(struct ble_gatt_svc_def));
if (*gatt_db_svcs == NULL) {
ESP_LOGE(TAG, "Error allocating memory for GATT services");
return ESP_ERR_NO_MEM;
}
/* Allocate space for 1st service UUID as well, assume length = 128 bit */
(*gatt_db_svcs)->uuid = (ble_uuid_t *) calloc(1, sizeof(ble_uuid128_t));
if ((*gatt_db_svcs)->uuid == NULL) {
ESP_LOGE(TAG, "Error allocating memory for GATT service UUID");
return ESP_ERR_NO_MEM;
}
/* Prepare 128 bit UUID for primary service from config service UUID. */
ble_uuid128_t uuid128 = {0};
uuid128.u.type = BLE_UUID_TYPE_128;
memcpy(uuid128.value, config->service_uuid, BLE_UUID128_VAL_LENGTH);
memcpy((void *) (*gatt_db_svcs)->uuid, &uuid128, sizeof(ble_uuid128_t));
/* GATT: Add primary service. */
int rc = ble_gatt_add_primary_svcs(*gatt_db_svcs, config->nu_lookup_count);
if (rc != 0) {
ESP_LOGE(TAG, "Error adding primary service !!!");
return rc;
}
for (int i = 0 ; i < config->nu_lookup_count; i++) {
/* GATT: Add characteristics to the service at index no. i*/
rc = ble_gatt_add_characteristics((void *) (*gatt_db_svcs)->characteristics, i);
if (rc != 0) {
ESP_LOGE(TAG, "Error adding GATT characteristic !!!");
return rc;
}
/* GATT: Add user description to characteristic no. i*/
rc = ble_gatt_add_char_dsc((void *) (*gatt_db_svcs)->characteristics,
i, BLE_GATT_UUID_CHAR_DSC);
if (rc != 0) {
ESP_LOGE(TAG, "Error adding GATT Discriptor !!");
return rc;
}
}
return 0;
}
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((void *)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 (protocomm_ble_mfg_data) {
free(protocomm_ble_mfg_data);
protocomm_ble_mfg_data = NULL;
protocomm_ble_mfg_data_len = 0;
}
}
static void free_gatt_ble_misc_memory(simple_ble_cfg_t *ble_config)
{
/* Free up gatt_db memory if exists */
if (ble_config->gatt_db->characteristics) {
for (int i = 0; i < num_chr_dsc; i++) {
if ((ble_config->gatt_db->characteristics + i)->descriptors) {
free((void *)(ble_config->gatt_db->characteristics + i)->descriptors->uuid);
free((ble_config->gatt_db->characteristics + i)->descriptors);
}
free((void *)(ble_config->gatt_db->characteristics + i)->uuid);
}
free((void *)(ble_config->gatt_db->characteristics));
}
if (ble_config->gatt_db) {
free((void *)ble_config->gatt_db->uuid);
free(ble_config->gatt_db);
}
if (ble_config) {
free(ble_config);
}
ble_config = NULL;
/* Free the uuid_name_table struct list if exists */
struct uuid128_name_buf *cur;
while (!SLIST_EMPTY(&uuid128_name_list)) {
cur = SLIST_FIRST(&uuid128_name_list);
SLIST_REMOVE_HEAD(&uuid128_name_list, link);
if (cur->uuid128_name_table) {
free(cur->uuid128_name_table);
}
free(cur);
}
/* Free the data_mbuf list if exists */
struct data_mbuf *curr;
while (!SLIST_EMPTY(&data_mbuf_list)) {
curr = SLIST_FIRST(&data_mbuf_list);
SLIST_REMOVE_HEAD(&data_mbuf_list, node);
free(curr->outbuf);
free(curr);
}
}
esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *config)
{
/* copy the 128 bit service UUID into local buffer to use as base 128 bit
* UUID. */
if (config->service_uuid != NULL) {
memcpy(ble_uuid_base, config->service_uuid, BLE_UUID128_VAL_LENGTH);
}
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 128 bit service UUID internally. */
ble_uuid128_t *svc_uuid128 = (ble_uuid128_t *)
calloc(1, sizeof(ble_uuid128_t));
if (svc_uuid128 == NULL) {
ESP_LOGE(TAG, "Error while allocating memory for 128 bit UUID");
return ESP_ERR_NO_MEM;
}
svc_uuid128->u.type = BLE_UUID_TYPE_128;
memcpy(svc_uuid128->value, config->service_uuid, BLE_UUID128_VAL_LENGTH);
adv_data.uuids128 = (void *)svc_uuid128;
/* Store service uuid128 in SLIST, to free it afterwards */
struct uuid128_name_buf *temp_uuid128_name_buf = (struct uuid128_name_buf *)
calloc(1, sizeof(struct uuid128_name_buf));
if (temp_uuid128_name_buf == NULL) {
ESP_LOGE(TAG, "Error allocating memory for UUID128 address database");
return ESP_ERR_NO_MEM;
}
SLIST_INSERT_HEAD(&uuid128_name_list, temp_uuid128_name_buf, link);
temp_uuid128_name_buf->uuid128_name_table = svc_uuid128;
if (adv_data.uuids128 == NULL) {
ESP_LOGE(TAG, "Error allocating memory for storing service UUID");
protocomm_ble_cleanup();
return ESP_ERR_NO_MEM;
}
/* 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;
}
/* Store BLE manufacturer data pointer */
if (config->manufacturer_data != NULL) {
protocomm_ble_mfg_data = config->manufacturer_data;
protocomm_ble_mfg_data_len = config->manufacturer_data_len;
}
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 = BLE_ATT_MTU_DFLT;
simple_ble_cfg_t *ble_config = (simple_ble_cfg_t *) calloc(1, sizeof(simple_ble_cfg_t));
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->connect_fn = transport_simple_ble_connect;
ble_config->disconnect_fn = transport_simple_ble_disconnect;
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->ble_bonding = config->ble_bonding;
ble_config->ble_sm_sc = config->ble_sm_sc;
if (populate_gatt_db(&ble_config->gatt_db, config) != 0) {
ESP_LOGE(TAG, "Error populating GATT Database");
free_gatt_ble_misc_memory(ble_config);
return ESP_ERR_NO_MEM;
}
esp_err_t err = simple_ble_start(ble_config);
ESP_LOGD(TAG, "Free Heap size after simple_ble_start= %d", esp_get_free_heap_size());
if (err != ESP_OK) {
ESP_LOGE(TAG, "simple_ble_start failed w/ error code 0x%x", err);
free_gatt_ble_misc_memory(ble_config);
protocomm_ble_cleanup();
return err;
}
ESP_LOGV(TAG, "Waiting for client to connect ......");
return ESP_OK;
}
esp_err_t protocomm_ble_stop(protocomm_t *pc)
{
ESP_LOGD(TAG, "protocomm_ble_stop called here...");
if ((pc != NULL) &&
(protoble_internal != NULL ) &&
(pc == protoble_internal->pc_ble)) {
esp_err_t ret = ESP_OK;
esp_err_t rc = ble_gap_adv_stop();
if (rc) {
ESP_LOGD(TAG, "Error in stopping advertisement with err code = %d",
rc);
}
#ifndef CONFIG_WIFI_PROV_KEEP_BLE_ON_AFTER_PROV
/* If flag is enabled, don't stop the stack. User application can start a new advertising to perform its BT activities */
ret = nimble_port_stop();
if (ret == 0) {
nimble_port_deinit();
}
#else
#ifdef CONFIG_WIFI_PROV_DISCONNECT_AFTER_PROV
/* Keep BT stack on, but terminate the connection after provisioning */
rc = ble_gap_terminate(s_cached_conn_handle, BLE_ERR_REM_USER_CONN_TERM);
if (rc) {
ESP_LOGI(TAG, "Error in terminating connection rc = %d",rc);
}
free_gatt_ble_misc_memory(ble_cfg_p);
#endif // CONFIG_WIFI_PROV_DISCONNECT_AFTER_PROV
#endif // CONFIG_WIFI_PROV_KEEP_BLE_ON_AFTER_PROV
protocomm_ble_cleanup();
return ret;
}
return ESP_ERR_INVALID_ARG;
}