1 // Copyright 2018 Espressif Systems (Shanghai) PTE LTD
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 #include <sys/param.h>
17 #include <esp_gatt_common_api.h>
19 #include <protocomm.h>
20 #include <protocomm_ble.h>
22 #include "protocomm_priv.h"
23 #include "simple_ble.h"
25 #define CHAR_VAL_LEN_MAX (256 + 1)
26 #define PREPARE_BUF_MAX_SIZE CHAR_VAL_LEN_MAX
28 static const char *TAG = "protocomm_ble";
30 /* BLE specific configuration parameters */
31 const uint16_t GATTS_SERVICE_UUID_PROV = 0xFFFF;
32 const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
33 const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
34 const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE;
42 static prepare_type_env_t prepare_write_env;
44 typedef struct _protocomm_ble {
46 protocomm_ble_name_uuid_t *g_nu_lookup;
47 ssize_t g_nu_lookup_count;
49 } _protocomm_ble_internal_t;
51 static _protocomm_ble_internal_t *protoble_internal;
53 static esp_ble_adv_params_t adv_params = {
56 .adv_type = ADV_TYPE_IND,
57 .own_addr_type = BLE_ADDR_TYPE_PUBLIC,
58 .channel_map = ADV_CHNL_ALL,
59 .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
62 static char* protocomm_ble_device_name = NULL;
64 /* The length of adv data must be less than 31 bytes */
65 static esp_ble_adv_data_t adv_data = {
66 .set_scan_rsp = false,
68 .include_txpower = true,
69 .min_interval = 0x100,
70 .max_interval = 0x100,
71 .appearance = ESP_BLE_APPEARANCE_UNKNOWN,
72 .manufacturer_len = 0,
73 .p_manufacturer_data = NULL,
74 .service_data_len = 0,
75 .p_service_data = NULL,
76 .service_uuid_len = 0,
77 .p_service_uuid = NULL,
78 .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
81 static void hexdump(const char *msg, uint8_t *buf, int len)
83 ESP_LOGD(TAG, "%s:", msg);
84 ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, len, ESP_LOG_DEBUG);
87 static const char *handle_to_handler(uint16_t handle)
89 uint16_t uuid = simple_ble_get_uuid(handle);
90 for (int i = 0; i < protoble_internal->g_nu_lookup_count; i++) {
91 if (protoble_internal->g_nu_lookup[i].uuid == uuid ) {
92 return protoble_internal->g_nu_lookup[i].name;
98 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)
100 static const uint8_t *read_buf = NULL;
101 static uint16_t read_len = 0;
102 esp_gatt_status_t status = ESP_OK;
104 ESP_LOGD(TAG, "Inside read w/ session - %d on param %d %d",
105 param->read.conn_id, param->read.handle, read_len);
107 ESP_LOGD(TAG, "Reading attr value first time");
108 status = esp_ble_gatts_get_attr_value(param->read.handle, &read_len, &read_buf);
110 ESP_LOGD(TAG, "Subsequent read request for attr value");
113 esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *) malloc(sizeof(esp_gatt_rsp_t));
114 if (gatt_rsp != NULL) {
115 gatt_rsp->attr_value.len = MIN(read_len, (protoble_internal->gatt_mtu - 1));
116 if (read_len && read_buf) {
117 memcpy(gatt_rsp->attr_value.value,
118 read_buf + param->read.offset,
119 gatt_rsp->attr_value.len);
121 read_len -= gatt_rsp->attr_value.len;
123 ESP_LOGE(TAG, "%s, malloc failed", __func__);
126 esp_err_t err = esp_ble_gatts_send_response(gatts_if, param->read.conn_id,
127 param->read.trans_id, status, gatt_rsp);
129 ESP_LOGE(TAG, "Send response error in read");
134 static esp_err_t prepare_write_event_env(esp_gatt_if_t gatts_if,
135 esp_ble_gatts_cb_param_t *param)
137 ESP_LOGD(TAG, "prepare write, handle = %d, value len = %d",
138 param->write.handle, param->write.len);
139 esp_gatt_status_t status = ESP_GATT_OK;
140 if (prepare_write_env.prepare_buf == NULL) {
141 prepare_write_env.prepare_buf = (uint8_t *) malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t));
142 if (prepare_write_env.prepare_buf == NULL) {
143 ESP_LOGE(TAG, "%s , failed tp allocate preparebuf", __func__);
144 status = ESP_GATT_NO_RESOURCES;
146 /* prepare_write_env.prepare_len = 0; */
148 if (param->write.offset > PREPARE_BUF_MAX_SIZE) {
149 status = ESP_GATT_INVALID_OFFSET;
150 } else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) {
151 status = ESP_GATT_INVALID_ATTR_LEN;
154 memcpy(prepare_write_env.prepare_buf + param->write.offset,
157 prepare_write_env.prepare_len += param->write.len;
158 prepare_write_env.handle = param->write.handle;
159 if (param->write.need_rsp) {
160 esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *) malloc(sizeof(esp_gatt_rsp_t));
161 if (gatt_rsp != NULL) {
162 gatt_rsp->attr_value.len = param->write.len;
163 gatt_rsp->attr_value.handle = param->write.handle;
164 gatt_rsp->attr_value.offset = param->write.offset;
165 gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
166 memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
167 esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
168 param->write.trans_id, status,
170 if (response_err != ESP_OK) {
171 ESP_LOGE(TAG, "Send response error in prep write");
175 ESP_LOGE(TAG, "%s, malloc failed", __func__);
178 if (status != ESP_GATT_OK) {
179 if (prepare_write_env.prepare_buf) {
180 free(prepare_write_env.prepare_buf);
181 prepare_write_env.prepare_buf = NULL;
182 prepare_write_env.prepare_len = 0;
189 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)
191 uint8_t *outbuf = NULL;
195 ESP_LOGD(TAG, "Inside write with session - %d on attr handle - %d \nLen -%d IS Prep - %d",
196 param->write.conn_id, param->write.handle, param->write.len, param->write.is_prep);
198 if (param->write.is_prep) {
199 ret = prepare_write_event_env(gatts_if, param);
201 ESP_LOGE(TAG, "Error appending to prepare buffer");
205 ESP_LOGD(TAG, "is_prep not set");
208 ret = protocomm_req_handle(protoble_internal->pc_ble,
209 handle_to_handler(param->write.handle),
210 param->exec_write.conn_id,
215 ret = esp_ble_gatts_set_attr_value(param->write.handle, outlen, outbuf);
217 ESP_LOGE(TAG, "Failed to set the session attribute value");
219 ret = esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
220 param->write.trans_id, ESP_GATT_OK, NULL);
222 ESP_LOGE(TAG, "Send response error in write");
224 hexdump("Response from write", outbuf, outlen);
227 ESP_LOGE(TAG, "Invalid content received, killing connection");
228 esp_ble_gatts_close(gatts_if, param->write.conn_id);
236 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)
239 uint8_t *outbuf = NULL;
241 ESP_LOGD(TAG, "Inside exec_write w/ session - %d", param->exec_write.conn_id);
243 if ((param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC)
245 prepare_write_env.prepare_buf) {
246 err = protocomm_req_handle(protoble_internal->pc_ble,
247 handle_to_handler(prepare_write_env.handle),
248 param->exec_write.conn_id,
249 prepare_write_env.prepare_buf,
250 prepare_write_env.prepare_len,
254 ESP_LOGE(TAG, "Invalid content received, killing connection");
255 esp_ble_gatts_close(gatts_if, param->write.conn_id);
257 hexdump("Response from exec write", outbuf, outlen);
258 esp_ble_gatts_set_attr_value(prepare_write_env.handle, outlen, outbuf);
261 if (prepare_write_env.prepare_buf) {
262 free(prepare_write_env.prepare_buf);
263 prepare_write_env.prepare_buf = NULL;
264 prepare_write_env.prepare_len = 0;
267 err = esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, param->exec_write.trans_id, ESP_GATT_OK, NULL);
269 ESP_LOGE(TAG, "Send response error in exec write");
276 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)
279 ESP_LOGD(TAG, "Inside disconnect w/ session - %d", param->disconnect.conn_id);
280 if (protoble_internal->pc_ble->sec &&
281 protoble_internal->pc_ble->sec->close_transport_session) {
282 ret = protoble_internal->pc_ble->sec->close_transport_session(param->disconnect.conn_id);
284 ESP_LOGE(TAG, "error closing the session after disconnect");
287 protoble_internal->gatt_mtu = ESP_GATT_DEF_BLE_MTU_SIZE;
290 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)
293 ESP_LOGD(TAG, "Inside BLE connect w/ conn_id - %d", param->connect.conn_id);
294 if (protoble_internal->pc_ble->sec &&
295 protoble_internal->pc_ble->sec->new_transport_session) {
296 ret = protoble_internal->pc_ble->sec->new_transport_session(param->connect.conn_id);
298 ESP_LOGE(TAG, "error creating the session");
303 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)
305 protoble_internal->gatt_mtu = param->mtu.mtu;
309 static esp_err_t protocomm_ble_add_endpoint(const char *ep_name,
310 protocomm_req_handler_t req_handler,
313 /* Endpoint UUID already added when protocomm_ble_start() was called */
317 static esp_err_t protocomm_ble_remove_endpoint(const char *ep_name)
319 /* Endpoint UUID will be removed when protocomm_ble_stop() is called */
324 static ssize_t populate_gatt_db(esp_gatts_attr_db_t **gatt_db_generated)
327 /* We need esp_gatts_attr_db_t of size 2 * number of handlers + 1 for service */
328 ssize_t gatt_db_generated_entries = 2 * protoble_internal->g_nu_lookup_count + 1;
330 *gatt_db_generated = (esp_gatts_attr_db_t *) malloc(sizeof(esp_gatts_attr_db_t) *
331 (gatt_db_generated_entries));
332 if ((*gatt_db_generated) == NULL) {
333 ESP_LOGE(TAG, "Failed to assign memory to gatt_db");
336 /* Declare service */
337 (*gatt_db_generated)[0].attr_control.auto_rsp = ESP_GATT_RSP_BY_APP;
339 (*gatt_db_generated)[0].att_desc.uuid_length = ESP_UUID_LEN_16;
340 (*gatt_db_generated)[0].att_desc.uuid_p = (uint8_t *) &primary_service_uuid;
341 (*gatt_db_generated)[0].att_desc.perm = ESP_GATT_PERM_READ;
342 (*gatt_db_generated)[0].att_desc.max_length = sizeof(uint16_t);
343 (*gatt_db_generated)[0].att_desc.length = sizeof(GATTS_SERVICE_UUID_PROV);
344 (*gatt_db_generated)[0].att_desc.value = (uint8_t *) &GATTS_SERVICE_UUID_PROV;
346 /* Declare characteristics */
347 for (i = 1 ; i < gatt_db_generated_entries ; i++) {
348 (*gatt_db_generated)[i].attr_control.auto_rsp = ESP_GATT_RSP_BY_APP;
350 (*gatt_db_generated)[i].att_desc.uuid_length = ESP_UUID_LEN_16;
351 (*gatt_db_generated)[i].att_desc.perm = ESP_GATT_PERM_READ |
354 if (i % 2 == 1) { /* Char Declaration */
355 (*gatt_db_generated)[i].att_desc.uuid_p = (uint8_t *) &character_declaration_uuid;
356 (*gatt_db_generated)[i].att_desc.max_length = sizeof(uint8_t);
357 (*gatt_db_generated)[i].att_desc.length = sizeof(uint8_t);
358 (*gatt_db_generated)[i].att_desc.value = (uint8_t *) &char_prop_read_write;
359 } else { /* Char Value */
360 (*gatt_db_generated)[i].att_desc.uuid_p = (uint8_t *)&protoble_internal->g_nu_lookup[i / 2 - 1].uuid;
361 (*gatt_db_generated)[i].att_desc.max_length = CHAR_VAL_LEN_MAX;
362 (*gatt_db_generated)[i].att_desc.length = 0;
363 (*gatt_db_generated)[i].att_desc.value = NULL;
366 return gatt_db_generated_entries;
369 static void protocomm_ble_cleanup(void)
371 if (protoble_internal) {
372 if (protoble_internal->g_nu_lookup) {
373 for (unsigned i = 0; i < protoble_internal->g_nu_lookup_count; i++) {
374 if (protoble_internal->g_nu_lookup[i].name) {
375 free((void *)protoble_internal->g_nu_lookup[i].name);
378 free(protoble_internal->g_nu_lookup);
380 free(protoble_internal);
381 protoble_internal = NULL;
383 if (protocomm_ble_device_name) {
384 free(protocomm_ble_device_name);
385 protocomm_ble_device_name = NULL;
387 if (adv_data.p_service_uuid) {
388 free(adv_data.p_service_uuid);
389 adv_data.p_service_uuid = NULL;
390 adv_data.service_uuid_len = 0;
394 esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *config)
396 if (!pc || !config || !config->device_name || !config->nu_lookup) {
397 return ESP_ERR_INVALID_ARG;
400 if (protoble_internal) {
401 ESP_LOGE(TAG, "Protocomm BLE already started");
405 /* Store service UUID internally */
406 adv_data.service_uuid_len = sizeof(config->service_uuid);
407 adv_data.p_service_uuid = malloc(sizeof(config->service_uuid));
408 if (adv_data.p_service_uuid == NULL) {
409 ESP_LOGE(TAG, "Error allocating memory for storing service UUID");
410 protocomm_ble_cleanup();
411 return ESP_ERR_NO_MEM;
413 memcpy(adv_data.p_service_uuid, config->service_uuid, adv_data.service_uuid_len);
415 /* Store BLE device name internally */
416 protocomm_ble_device_name = strdup(config->device_name);
417 if (protocomm_ble_device_name == NULL) {
418 ESP_LOGE(TAG, "Error allocating memory for storing BLE device name");
419 protocomm_ble_cleanup();
420 return ESP_ERR_NO_MEM;
423 protoble_internal = (_protocomm_ble_internal_t *) calloc(1, sizeof(_protocomm_ble_internal_t));
424 if (protoble_internal == NULL) {
425 ESP_LOGE(TAG, "Error allocating internal protocomm structure");
426 protocomm_ble_cleanup();
427 return ESP_ERR_NO_MEM;
430 protoble_internal->g_nu_lookup_count = config->nu_lookup_count;
431 protoble_internal->g_nu_lookup = malloc(config->nu_lookup_count * sizeof(protocomm_ble_name_uuid_t));
432 if (protoble_internal->g_nu_lookup == NULL) {
433 ESP_LOGE(TAG, "Error allocating internal name UUID table");
434 protocomm_ble_cleanup();
435 return ESP_ERR_NO_MEM;
438 for (unsigned i = 0; i < protoble_internal->g_nu_lookup_count; i++) {
439 protoble_internal->g_nu_lookup[i].uuid = config->nu_lookup[i].uuid;
440 protoble_internal->g_nu_lookup[i].name = strdup(config->nu_lookup[i].name);
441 if (protoble_internal->g_nu_lookup[i].name == NULL) {
442 ESP_LOGE(TAG, "Error allocating internal name UUID entry");
443 protocomm_ble_cleanup();
444 return ESP_ERR_NO_MEM;
448 pc->add_endpoint = protocomm_ble_add_endpoint;
449 pc->remove_endpoint = protocomm_ble_remove_endpoint;
450 protoble_internal->pc_ble = pc;
451 protoble_internal->gatt_mtu = ESP_GATT_DEF_BLE_MTU_SIZE;
453 simple_ble_cfg_t *ble_config = simple_ble_init();
454 if (ble_config == NULL) {
455 ESP_LOGE(TAG, "Ran out of memory for BLE config");
456 protocomm_ble_cleanup();
457 return ESP_ERR_NO_MEM;
460 /* Set function pointers required for simple BLE layer */
461 ble_config->read_fn = transport_simple_ble_read;
462 ble_config->write_fn = transport_simple_ble_write;
463 ble_config->exec_write_fn = transport_simple_ble_exec_write;
464 ble_config->disconnect_fn = transport_simple_ble_disconnect;
465 ble_config->connect_fn = transport_simple_ble_connect;
466 ble_config->set_mtu_fn = transport_simple_ble_set_mtu;
468 /* Set parameters required for advertising */
469 ble_config->adv_data = adv_data;
470 ble_config->adv_params = adv_params;
472 ble_config->device_name = protocomm_ble_device_name;
473 ble_config->gatt_db_count = populate_gatt_db(&ble_config->gatt_db);
475 if (ble_config->gatt_db_count == -1) {
476 ESP_LOGE(TAG, "Invalid GATT database count");
478 protocomm_ble_cleanup();
479 return ESP_ERR_INVALID_STATE;
482 esp_err_t err = simple_ble_start(ble_config);
484 ESP_LOGE(TAG, "simple_ble_start failed w/ error code 0x%x", err);
486 protocomm_ble_cleanup();
490 prepare_write_env.prepare_buf = NULL;
491 ESP_LOGD(TAG, "Waiting for client to connect ......");
495 esp_err_t protocomm_ble_stop(protocomm_t *pc)
498 (protoble_internal != NULL ) &&
499 (pc == protoble_internal->pc_ble)) {
500 esp_err_t ret = ESP_OK;
501 ret = simple_ble_stop();
503 ESP_LOGE(TAG, "BLE stop failed");
506 protocomm_ble_cleanup();
509 return ESP_ERR_INVALID_ARG;