]> granicus.if.org Git - esp-idf/blob - components/protocomm/src/transports/protocomm_ble.c
Merge branch 'bugfix/mdns_add_remove_multiple_srv_master' into 'master'
[esp-idf] / components / protocomm / src / transports / protocomm_ble.c
1 // Copyright 2018 Espressif Systems (Shanghai) PTE LTD
2 //
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
6
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
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.
14
15 #include <sys/param.h>
16 #include <esp_log.h>
17 #include <esp_gatt_common_api.h>
18
19 #include <protocomm.h>
20 #include <protocomm_ble.h>
21
22 #include "protocomm_priv.h"
23 #include "simple_ble.h"
24
25 #define CHAR_VAL_LEN_MAX         (256 + 1)
26 #define PREPARE_BUF_MAX_SIZE     CHAR_VAL_LEN_MAX
27
28 static const char *TAG = "protocomm_ble";
29
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;
35
36 typedef struct {
37     uint8_t                *prepare_buf;
38     int                     prepare_len;
39     uint16_t                handle;
40 } prepare_type_env_t;
41
42 static prepare_type_env_t prepare_write_env;
43
44 typedef struct _protocomm_ble {
45     protocomm_t *pc_ble;
46     protocomm_ble_name_uuid_t *g_nu_lookup;
47     ssize_t g_nu_lookup_count;
48     uint16_t gatt_mtu;
49 } _protocomm_ble_internal_t;
50
51 static _protocomm_ble_internal_t *protoble_internal;
52
53 static esp_ble_adv_params_t adv_params = {
54     .adv_int_min         = 0x100,
55     .adv_int_max         = 0x100,
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,
60 };
61
62 static char* protocomm_ble_device_name = NULL;
63
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,
67     .include_name        = true,
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),
79 };
80
81 static void hexdump(const char *msg, uint8_t *buf, int len)
82 {
83     ESP_LOGD(TAG, "%s:", msg);
84     ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, len, ESP_LOG_DEBUG);
85 }
86
87 static const char *handle_to_handler(uint16_t handle)
88 {
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;
93         }
94     }
95     return NULL;
96 }
97
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)
99 {
100     static const uint8_t *read_buf = NULL;
101     static uint16_t read_len = 0;
102     esp_gatt_status_t status = ESP_OK;
103
104     ESP_LOGD(TAG, "Inside read w/ session - %d on param %d %d",
105              param->read.conn_id, param->read.handle, read_len);
106     if (!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);
109     } else {
110         ESP_LOGD(TAG, "Subsequent read request for attr value");
111     }
112
113     esp_gatt_rsp_t gatt_rsp = {0};
114     gatt_rsp.attr_value.len = MIN(read_len, (protoble_internal->gatt_mtu - 1));
115     gatt_rsp.attr_value.handle = param->read.handle;
116     gatt_rsp.attr_value.offset = param->read.offset;
117     gatt_rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
118     if (gatt_rsp.attr_value.len && read_buf) {
119         memcpy(gatt_rsp.attr_value.value,
120                 read_buf + param->read.offset,
121                 gatt_rsp.attr_value.len);
122     }
123     read_len -= gatt_rsp.attr_value.len;
124     esp_err_t err = esp_ble_gatts_send_response(gatts_if, param->read.conn_id,
125                                                 param->read.trans_id, status, &gatt_rsp);
126     if (err != ESP_OK) {
127         ESP_LOGE(TAG, "Send response error in read");
128     }
129 }
130
131 static esp_err_t prepare_write_event_env(esp_gatt_if_t gatts_if,
132                                          esp_ble_gatts_cb_param_t *param)
133 {
134     ESP_LOGD(TAG, "prepare write, handle = %d, value len = %d",
135              param->write.handle, param->write.len);
136     esp_gatt_status_t status = ESP_GATT_OK;
137     if (prepare_write_env.prepare_buf == NULL) {
138         prepare_write_env.prepare_buf = (uint8_t *) malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t));
139         if (prepare_write_env.prepare_buf == NULL) {
140             ESP_LOGE(TAG, "%s , failed tp allocate preparebuf", __func__);
141             status = ESP_GATT_NO_RESOURCES;
142         }
143         /* prepare_write_env.prepare_len = 0; */
144     } else {
145         if (param->write.offset > PREPARE_BUF_MAX_SIZE) {
146             status = ESP_GATT_INVALID_OFFSET;
147         } else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) {
148             status = ESP_GATT_INVALID_ATTR_LEN;
149         }
150     }
151     memcpy(prepare_write_env.prepare_buf + param->write.offset,
152            param->write.value,
153            param->write.len);
154     prepare_write_env.prepare_len += param->write.len;
155     prepare_write_env.handle = param->write.handle;
156     if (param->write.need_rsp) {
157         esp_gatt_rsp_t gatt_rsp = {0};
158         gatt_rsp.attr_value.len = param->write.len;
159         gatt_rsp.attr_value.handle = param->write.handle;
160         gatt_rsp.attr_value.offset = param->write.offset;
161         gatt_rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
162         if (gatt_rsp.attr_value.len && param->write.value) {
163             memcpy(gatt_rsp.attr_value.value, param->write.value, param->write.len);
164         }
165         esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
166                 param->write.trans_id, status,
167                 &gatt_rsp);
168         if (response_err != ESP_OK) {
169             ESP_LOGE(TAG, "Send response error in prep write");
170         }
171     }
172     if (status != ESP_GATT_OK) {
173         if (prepare_write_env.prepare_buf) {
174             free(prepare_write_env.prepare_buf);
175             prepare_write_env.prepare_buf = NULL;
176             prepare_write_env.prepare_len = 0;
177         }
178         return ESP_FAIL;
179     }
180     return ESP_OK;
181 }
182
183 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)
184 {
185     uint8_t *outbuf = NULL;
186     ssize_t outlen = 0;
187     esp_err_t ret;
188
189     ESP_LOGD(TAG, "Inside write with session - %d on attr handle - %d \nLen -%d IS Prep - %d",
190              param->write.conn_id, param->write.handle, param->write.len, param->write.is_prep);
191
192     if (param->write.is_prep) {
193         ret = prepare_write_event_env(gatts_if, param);
194         if (ret != ESP_OK) {
195             ESP_LOGE(TAG, "Error appending to prepare buffer");
196         }
197         return;
198     } else {
199         ESP_LOGD(TAG, "is_prep not set");
200     }
201
202     ret = protocomm_req_handle(protoble_internal->pc_ble,
203                                handle_to_handler(param->write.handle),
204                                param->exec_write.conn_id,
205                                param->write.value,
206                                param->write.len,
207                                &outbuf, &outlen);
208     if (ret == ESP_OK) {
209         ret = esp_ble_gatts_set_attr_value(param->write.handle, outlen, outbuf);
210         if (ret != ESP_OK) {
211             ESP_LOGE(TAG, "Failed to set the session attribute value");
212         }
213         ret = esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
214                                           param->write.trans_id, ESP_GATT_OK, NULL);
215         if (ret != ESP_OK) {
216             ESP_LOGE(TAG, "Send response error in write");
217         }
218         hexdump("Response from  write", outbuf, outlen);
219
220     } else {
221         ESP_LOGE(TAG, "Invalid content received, killing connection");
222         esp_ble_gatts_close(gatts_if, param->write.conn_id);
223     }
224     if (outbuf) {
225         free(outbuf);
226     }
227 }
228
229
230 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)
231 {
232     esp_err_t err;
233     uint8_t *outbuf = NULL;
234     ssize_t outlen = 0;
235     ESP_LOGD(TAG, "Inside exec_write w/ session - %d", param->exec_write.conn_id);
236
237     if ((param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC)
238             &&
239             prepare_write_env.prepare_buf) {
240         err = protocomm_req_handle(protoble_internal->pc_ble,
241                                    handle_to_handler(prepare_write_env.handle),
242                                    param->exec_write.conn_id,
243                                    prepare_write_env.prepare_buf,
244                                    prepare_write_env.prepare_len,
245                                    &outbuf, &outlen);
246
247         if (err != ESP_OK) {
248             ESP_LOGE(TAG, "Invalid content received, killing connection");
249             esp_ble_gatts_close(gatts_if, param->write.conn_id);
250         } else {
251             hexdump("Response from exec write", outbuf, outlen);
252             esp_ble_gatts_set_attr_value(prepare_write_env.handle, outlen, outbuf);
253         }
254     }
255     if (prepare_write_env.prepare_buf) {
256         free(prepare_write_env.prepare_buf);
257         prepare_write_env.prepare_buf = NULL;
258         prepare_write_env.prepare_len = 0;
259     }
260
261     err = esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, param->exec_write.trans_id, ESP_GATT_OK, NULL);
262     if (err != ESP_OK) {
263         ESP_LOGE(TAG, "Send response error in exec write");
264     }
265     if (outbuf) {
266         free(outbuf);
267     }
268 }
269
270 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)
271 {
272     esp_err_t ret;
273     ESP_LOGD(TAG, "Inside disconnect w/ session - %d", param->disconnect.conn_id);
274     if (protoble_internal->pc_ble->sec &&
275         protoble_internal->pc_ble->sec->close_transport_session) {
276         ret = protoble_internal->pc_ble->sec->close_transport_session(param->disconnect.conn_id);
277         if (ret != ESP_OK) {
278             ESP_LOGE(TAG, "error closing the session after disconnect");
279         }
280     }
281     protoble_internal->gatt_mtu = ESP_GATT_DEF_BLE_MTU_SIZE;
282 }
283
284 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)
285 {
286     esp_err_t ret;
287     ESP_LOGD(TAG, "Inside BLE connect w/ conn_id - %d", param->connect.conn_id);
288     if (protoble_internal->pc_ble->sec &&
289         protoble_internal->pc_ble->sec->new_transport_session) {
290         ret = protoble_internal->pc_ble->sec->new_transport_session(param->connect.conn_id);
291         if (ret != ESP_OK) {
292             ESP_LOGE(TAG, "error creating the session");
293         }
294     }
295 }
296
297 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)
298 {
299     protoble_internal->gatt_mtu = param->mtu.mtu;
300     return;
301 }
302
303 static esp_err_t protocomm_ble_add_endpoint(const char *ep_name,
304                                             protocomm_req_handler_t req_handler,
305                                             void *priv_data)
306 {
307     /* Endpoint UUID already added when protocomm_ble_start() was called */
308     return ESP_OK;
309 }
310
311 static esp_err_t protocomm_ble_remove_endpoint(const char *ep_name)
312 {
313     /* Endpoint UUID will be removed when protocomm_ble_stop() is called  */
314     return ESP_OK;
315 }
316
317
318 static ssize_t populate_gatt_db(esp_gatts_attr_db_t **gatt_db_generated)
319 {
320     int i;
321     /* We need esp_gatts_attr_db_t of size 2 * number of handlers + 1 for service */
322     ssize_t gatt_db_generated_entries = 2 * protoble_internal->g_nu_lookup_count + 1;
323
324     *gatt_db_generated = (esp_gatts_attr_db_t *) malloc(sizeof(esp_gatts_attr_db_t) *
325                                                         (gatt_db_generated_entries));
326     if ((*gatt_db_generated) == NULL) {
327         ESP_LOGE(TAG, "Failed to assign memory to gatt_db");
328         return -1;
329     }
330     /* Declare service */
331     (*gatt_db_generated)[0].attr_control.auto_rsp       = ESP_GATT_RSP_BY_APP;
332
333     (*gatt_db_generated)[0].att_desc.uuid_length        = ESP_UUID_LEN_16;
334     (*gatt_db_generated)[0].att_desc.uuid_p             = (uint8_t *) &primary_service_uuid;
335     (*gatt_db_generated)[0].att_desc.perm               = ESP_GATT_PERM_READ;
336     (*gatt_db_generated)[0].att_desc.max_length         = sizeof(uint16_t);
337     (*gatt_db_generated)[0].att_desc.length             = sizeof(GATTS_SERVICE_UUID_PROV);
338     (*gatt_db_generated)[0].att_desc.value              = (uint8_t *) &GATTS_SERVICE_UUID_PROV;
339
340     /* Declare characteristics */
341     for (i = 1 ; i < gatt_db_generated_entries ; i++) {
342         (*gatt_db_generated)[i].attr_control.auto_rsp   = ESP_GATT_RSP_BY_APP;
343
344         (*gatt_db_generated)[i].att_desc.uuid_length    = ESP_UUID_LEN_16;
345         (*gatt_db_generated)[i].att_desc.perm           = ESP_GATT_PERM_READ |
346                                                           ESP_GATT_PERM_WRITE;
347
348         if (i % 2 == 1) { /* Char Declaration */
349             (*gatt_db_generated)[i].att_desc.uuid_p     = (uint8_t *) &character_declaration_uuid;
350             (*gatt_db_generated)[i].att_desc.max_length = sizeof(uint8_t);
351             (*gatt_db_generated)[i].att_desc.length     = sizeof(uint8_t);
352             (*gatt_db_generated)[i].att_desc.value      = (uint8_t *) &char_prop_read_write;
353         } else { /* Char Value */
354             (*gatt_db_generated)[i].att_desc.uuid_p     = (uint8_t *)&protoble_internal->g_nu_lookup[i / 2 - 1].uuid;
355             (*gatt_db_generated)[i].att_desc.max_length = CHAR_VAL_LEN_MAX;
356             (*gatt_db_generated)[i].att_desc.length     = 0;
357             (*gatt_db_generated)[i].att_desc.value      = NULL;
358         }
359     }
360     return gatt_db_generated_entries;
361 }
362
363 static void protocomm_ble_cleanup(void)
364 {
365     if (protoble_internal) {
366         if (protoble_internal->g_nu_lookup) {
367             for (unsigned i = 0; i < protoble_internal->g_nu_lookup_count; i++) {
368                 if (protoble_internal->g_nu_lookup[i].name) {
369                     free((void *)protoble_internal->g_nu_lookup[i].name);
370                 }
371             }
372             free(protoble_internal->g_nu_lookup);
373         }
374         free(protoble_internal);
375         protoble_internal = NULL;
376     }
377     if (protocomm_ble_device_name) {
378         free(protocomm_ble_device_name);
379         protocomm_ble_device_name = NULL;
380     }
381     if (adv_data.p_service_uuid) {
382         free(adv_data.p_service_uuid);
383         adv_data.p_service_uuid = NULL;
384         adv_data.service_uuid_len = 0;
385     }
386 }
387
388 esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *config)
389 {
390     if (!pc || !config || !config->device_name || !config->nu_lookup) {
391         return ESP_ERR_INVALID_ARG;
392     }
393
394     if (protoble_internal) {
395         ESP_LOGE(TAG, "Protocomm BLE already started");
396         return ESP_FAIL;
397     }
398
399     /* Store service UUID internally */
400     adv_data.service_uuid_len = sizeof(config->service_uuid);
401     adv_data.p_service_uuid   = malloc(sizeof(config->service_uuid));
402     if (adv_data.p_service_uuid == NULL) {
403         ESP_LOGE(TAG, "Error allocating memory for storing service UUID");
404         protocomm_ble_cleanup();
405         return ESP_ERR_NO_MEM;
406     }
407     memcpy(adv_data.p_service_uuid, config->service_uuid, adv_data.service_uuid_len);
408
409     /* Store BLE device name internally */
410     protocomm_ble_device_name = strdup(config->device_name);
411     if (protocomm_ble_device_name == NULL) {
412         ESP_LOGE(TAG, "Error allocating memory for storing BLE device name");
413         protocomm_ble_cleanup();
414         return ESP_ERR_NO_MEM;
415     }
416
417     protoble_internal = (_protocomm_ble_internal_t *) calloc(1, sizeof(_protocomm_ble_internal_t));
418     if (protoble_internal == NULL) {
419         ESP_LOGE(TAG, "Error allocating internal protocomm structure");
420         protocomm_ble_cleanup();
421         return ESP_ERR_NO_MEM;
422     }
423
424     protoble_internal->g_nu_lookup_count =  config->nu_lookup_count;
425     protoble_internal->g_nu_lookup = malloc(config->nu_lookup_count * sizeof(protocomm_ble_name_uuid_t));
426     if (protoble_internal->g_nu_lookup == NULL) {
427         ESP_LOGE(TAG, "Error allocating internal name UUID table");
428         protocomm_ble_cleanup();
429         return ESP_ERR_NO_MEM;
430     }
431
432     for (unsigned i = 0; i < protoble_internal->g_nu_lookup_count; i++) {
433         protoble_internal->g_nu_lookup[i].uuid = config->nu_lookup[i].uuid;
434         protoble_internal->g_nu_lookup[i].name = strdup(config->nu_lookup[i].name);
435         if (protoble_internal->g_nu_lookup[i].name == NULL) {
436             ESP_LOGE(TAG, "Error allocating internal name UUID entry");
437             protocomm_ble_cleanup();
438             return ESP_ERR_NO_MEM;
439         }
440     }
441
442     pc->add_endpoint = protocomm_ble_add_endpoint;
443     pc->remove_endpoint = protocomm_ble_remove_endpoint;
444     protoble_internal->pc_ble = pc;
445     protoble_internal->gatt_mtu = ESP_GATT_DEF_BLE_MTU_SIZE;
446
447     simple_ble_cfg_t *ble_config = simple_ble_init();
448     if (ble_config == NULL) {
449         ESP_LOGE(TAG, "Ran out of memory for BLE config");
450         protocomm_ble_cleanup();
451         return ESP_ERR_NO_MEM;
452     }
453
454     /* Set function pointers required for simple BLE layer */
455     ble_config->read_fn         = transport_simple_ble_read;
456     ble_config->write_fn        = transport_simple_ble_write;
457     ble_config->exec_write_fn   = transport_simple_ble_exec_write;
458     ble_config->disconnect_fn   = transport_simple_ble_disconnect;
459     ble_config->connect_fn      = transport_simple_ble_connect;
460     ble_config->set_mtu_fn      = transport_simple_ble_set_mtu;
461
462     /* Set parameters required for advertising */
463     ble_config->adv_data        = adv_data;
464     ble_config->adv_params      = adv_params;
465
466     ble_config->device_name     = protocomm_ble_device_name;
467     ble_config->gatt_db_count   = populate_gatt_db(&ble_config->gatt_db);
468
469     if (ble_config->gatt_db_count == -1) {
470         ESP_LOGE(TAG, "Invalid GATT database count");
471         simple_ble_deinit();
472         protocomm_ble_cleanup();
473         return ESP_ERR_INVALID_STATE;
474     }
475
476     esp_err_t err = simple_ble_start(ble_config);
477     if (err != ESP_OK) {
478         ESP_LOGE(TAG, "simple_ble_start failed w/ error code 0x%x", err);
479         simple_ble_deinit();
480         protocomm_ble_cleanup();
481         return err;
482     }
483
484     prepare_write_env.prepare_buf = NULL;
485     ESP_LOGD(TAG, "Waiting for client to connect ......");
486     return ESP_OK;
487 }
488
489 esp_err_t protocomm_ble_stop(protocomm_t *pc)
490 {
491     if ((pc != NULL) &&
492         (protoble_internal != NULL ) &&
493         (pc == protoble_internal->pc_ble)) {
494         esp_err_t ret = ESP_OK;
495         ret = simple_ble_stop();
496         if (ret) {
497             ESP_LOGE(TAG, "BLE stop failed");
498         }
499         simple_ble_deinit();
500         protocomm_ble_cleanup();
501         return ret;
502     }
503     return ESP_ERR_INVALID_ARG;
504 }