]> granicus.if.org Git - esp-idf/blob - components/protocomm/src/transports/protocomm_ble.c
Merge branch 'bugfix/wdt_compability_app_with_old_bootloader' 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 = (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);
120         }
121         read_len -= gatt_rsp->attr_value.len;
122     } else {
123         ESP_LOGE(TAG, "%s, malloc failed", __func__);
124         return;
125     }
126     esp_err_t err = esp_ble_gatts_send_response(gatts_if, param->read.conn_id,
127                                                 param->read.trans_id, status, gatt_rsp);
128     if (err != ESP_OK) {
129         ESP_LOGE(TAG, "Send response error in read");
130     }
131     free(gatt_rsp);
132 }
133
134 static esp_err_t prepare_write_event_env(esp_gatt_if_t gatts_if,
135                                          esp_ble_gatts_cb_param_t *param)
136 {
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;
145         }
146         /* prepare_write_env.prepare_len = 0; */
147     } else {
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;
152         }
153     }
154     memcpy(prepare_write_env.prepare_buf + param->write.offset,
155            param->write.value,
156            param->write.len);
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,
169                                                                  gatt_rsp);
170             if (response_err != ESP_OK) {
171                 ESP_LOGE(TAG, "Send response error in prep write");
172             }
173             free(gatt_rsp);
174         } else {
175             ESP_LOGE(TAG, "%s, malloc failed", __func__);
176         }
177     }
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;
183         }
184         return ESP_FAIL;
185     }
186     return ESP_OK;
187 }
188
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)
190 {
191     uint8_t *outbuf = NULL;
192     ssize_t outlen = 0;
193     esp_err_t ret;
194
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);
197
198     if (param->write.is_prep) {
199         ret = prepare_write_event_env(gatts_if, param);
200         if (ret != ESP_OK) {
201             ESP_LOGE(TAG, "Error appending to prepare buffer");
202         }
203         return;
204     } else {
205         ESP_LOGD(TAG, "is_prep not set");
206     }
207
208     ret = protocomm_req_handle(protoble_internal->pc_ble,
209                                handle_to_handler(param->write.handle),
210                                param->exec_write.conn_id,
211                                param->write.value,
212                                param->write.len,
213                                &outbuf, &outlen);
214     if (ret == ESP_OK) {
215         ret = esp_ble_gatts_set_attr_value(param->write.handle, outlen, outbuf);
216         if (ret != ESP_OK) {
217             ESP_LOGE(TAG, "Failed to set the session attribute value");
218         }
219         ret = esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
220                                           param->write.trans_id, ESP_GATT_OK, NULL);
221         if (ret != ESP_OK) {
222             ESP_LOGE(TAG, "Send response error in write");
223         }
224         hexdump("Response from  write", outbuf, outlen);
225
226     } else {
227         ESP_LOGE(TAG, "Invalid content received, killing connection");
228         esp_ble_gatts_close(gatts_if, param->write.conn_id);
229     }
230     if (outbuf) {
231         free(outbuf);
232     }
233 }
234
235
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)
237 {
238     esp_err_t err;
239     uint8_t *outbuf = NULL;
240     ssize_t outlen = 0;
241     ESP_LOGD(TAG, "Inside exec_write w/ session - %d", param->exec_write.conn_id);
242
243     if ((param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC)
244             &&
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,
251                                    &outbuf, &outlen);
252
253         if (err != ESP_OK) {
254             ESP_LOGE(TAG, "Invalid content received, killing connection");
255             esp_ble_gatts_close(gatts_if, param->write.conn_id);
256         } else {
257             hexdump("Response from exec write", outbuf, outlen);
258             esp_ble_gatts_set_attr_value(prepare_write_env.handle, outlen, outbuf);
259         }
260     }
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;
265     }
266
267     err = esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, param->exec_write.trans_id, ESP_GATT_OK, NULL);
268     if (err != ESP_OK) {
269         ESP_LOGE(TAG, "Send response error in exec write");
270     }
271     if (outbuf) {
272         free(outbuf);
273     }
274 }
275
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)
277 {
278     esp_err_t ret;
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);
283         if (ret != ESP_OK) {
284             ESP_LOGE(TAG, "error closing the session after disconnect");
285         }
286     }
287     protoble_internal->gatt_mtu = ESP_GATT_DEF_BLE_MTU_SIZE;
288 }
289
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)
291 {
292     esp_err_t ret;
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);
297         if (ret != ESP_OK) {
298             ESP_LOGE(TAG, "error creating the session");
299         }
300     }
301 }
302
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)
304 {
305     protoble_internal->gatt_mtu = param->mtu.mtu;
306     return;
307 }
308
309 static esp_err_t protocomm_ble_add_endpoint(const char *ep_name,
310                                             protocomm_req_handler_t req_handler,
311                                             void *priv_data)
312 {
313     /* Endpoint UUID already added when protocomm_ble_start() was called */
314     return ESP_OK;
315 }
316
317 static esp_err_t protocomm_ble_remove_endpoint(const char *ep_name)
318 {
319     /* Endpoint UUID will be removed when protocomm_ble_stop() is called  */
320     return ESP_OK;
321 }
322
323
324 static ssize_t populate_gatt_db(esp_gatts_attr_db_t **gatt_db_generated)
325 {
326     int i;
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;
329
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");
334         return -1;
335     }
336     /* Declare service */
337     (*gatt_db_generated)[0].attr_control.auto_rsp       = ESP_GATT_RSP_BY_APP;
338
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;
345
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;
349
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 |
352                                                           ESP_GATT_PERM_WRITE;
353
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;
364         }
365     }
366     return gatt_db_generated_entries;
367 }
368
369 static void protocomm_ble_cleanup(void)
370 {
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);
376                 }
377             }
378             free(protoble_internal->g_nu_lookup);
379         }
380         free(protoble_internal);
381         protoble_internal = NULL;
382     }
383     if (protocomm_ble_device_name) {
384         free(protocomm_ble_device_name);
385         protocomm_ble_device_name = NULL;
386     }
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;
391     }
392 }
393
394 esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *config)
395 {
396     if (!pc || !config || !config->device_name || !config->nu_lookup) {
397         return ESP_ERR_INVALID_ARG;
398     }
399
400     if (protoble_internal) {
401         ESP_LOGE(TAG, "Protocomm BLE already started");
402         return ESP_FAIL;
403     }
404
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;
412     }
413     memcpy(adv_data.p_service_uuid, config->service_uuid, adv_data.service_uuid_len);
414
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;
421     }
422
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;
428     }
429
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;
436     }
437
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;
445         }
446     }
447
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;
452
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;
458     }
459
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;
467
468     /* Set parameters required for advertising */
469     ble_config->adv_data        = adv_data;
470     ble_config->adv_params      = adv_params;
471
472     ble_config->device_name     = protocomm_ble_device_name;
473     ble_config->gatt_db_count   = populate_gatt_db(&ble_config->gatt_db);
474
475     if (ble_config->gatt_db_count == -1) {
476         ESP_LOGE(TAG, "Invalid GATT database count");
477         simple_ble_deinit();
478         protocomm_ble_cleanup();
479         return ESP_ERR_INVALID_STATE;
480     }
481
482     esp_err_t err = simple_ble_start(ble_config);
483     if (err != ESP_OK) {
484         ESP_LOGE(TAG, "simple_ble_start failed w/ error code 0x%x", err);
485         simple_ble_deinit();
486         protocomm_ble_cleanup();
487         return err;
488     }
489
490     prepare_write_env.prepare_buf = NULL;
491     ESP_LOGD(TAG, "Waiting for client to connect ......");
492     return ESP_OK;
493 }
494
495 esp_err_t protocomm_ble_stop(protocomm_t *pc)
496 {
497     if ((pc != NULL) &&
498         (protoble_internal != NULL ) &&
499         (pc == protoble_internal->pc_ble)) {
500         esp_err_t ret = ESP_OK;
501         ret = simple_ble_stop();
502         if (ret) {
503             ESP_LOGE(TAG, "BLE stop failed");
504         }
505         simple_ble_deinit();
506         protocomm_ble_cleanup();
507         return ret;
508     }
509     return ESP_ERR_INVALID_ARG;
510 }