Merge branch 'bugfix/ai_fixes_common_components_v5.4' into 'release/v5.4'

Bugfix/ai fixes common components (v5.4)

See merge request espressif/esp-idf!47568
This commit is contained in:
Rahul Tank
2026-04-24 10:54:19 +05:30
19 changed files with 1171 additions and 406 deletions
+8 -4
View File
@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -558,15 +558,19 @@ bt_status_t btc_init(void)
void btc_deinit(void) void btc_deinit(void)
{ {
osi_thread_t *thread = btc_thread;
if (!thread) {
return;
}
osi_thread_free(thread);
btc_thread = NULL;
#if BTC_GAP_BT_INCLUDED #if BTC_GAP_BT_INCLUDED
btc_gap_bt_deinit(); btc_gap_bt_deinit();
#endif #endif
#if BTC_DYNAMIC_MEMORY #if BTC_DYNAMIC_MEMORY
btc_deinit_mem(); btc_deinit_mem();
#endif #endif
osi_thread_free(btc_thread);
btc_thread = NULL;
#if (BLE_INCLUDED == TRUE) #if (BLE_INCLUDED == TRUE)
btc_gap_ble_deinit(); btc_gap_ble_deinit();
#endif ///BLE_INCLUDED == TRUE #endif ///BLE_INCLUDED == TRUE
@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -93,6 +93,9 @@ void btc_blufi_report_error(esp_blufi_error_state_t state)
btc_transfer_context(&msg, &param, sizeof(esp_blufi_cb_param_t), NULL, NULL); btc_transfer_context(&msg, &param, sizeof(esp_blufi_cb_param_t), NULL, NULL);
} }
/* FALSE POSITIVE: blufi_env is a single-connection global by design.
* The GAP layer rejects new connections while is_connected==true,
* so concurrent multi-connection access to this state never occurs. */
void btc_blufi_recv_handler(uint8_t *data, int len) void btc_blufi_recv_handler(uint8_t *data, int len)
{ {
if (len < sizeof(struct blufi_hdr)) { if (len < sizeof(struct blufi_hdr)) {
@@ -129,12 +132,21 @@ void btc_blufi_recv_handler(uint8_t *data, int len)
blufi_env.recv_seq++; blufi_env.recv_seq++;
#define BLUFI_RESET_AGGR_BUF() do { \
if (blufi_env.aggr_buf) { \
osi_free(blufi_env.aggr_buf); \
blufi_env.aggr_buf = NULL; \
} \
blufi_env.offset = 0; \
} while (0)
// first step, decrypt // first step, decrypt
if (BLUFI_FC_IS_ENC(hdr->fc) if (BLUFI_FC_IS_ENC(hdr->fc)
&& (blufi_env.cbs && blufi_env.cbs->decrypt_func)) { && (blufi_env.cbs && blufi_env.cbs->decrypt_func)) {
ret = blufi_env.cbs->decrypt_func(hdr->seq, hdr->data, hdr->data_len); ret = blufi_env.cbs->decrypt_func(hdr->seq, hdr->data, hdr->data_len);
if (ret != hdr->data_len) { /* enc must be success and enc len must equal to plain len */ if (ret != hdr->data_len) { /* enc must be success and enc len must equal to plain len */
BTC_TRACE_ERROR("%s decrypt error %d\n", __func__, ret); BTC_TRACE_ERROR("%s decrypt error %d\n", __func__, ret);
BLUFI_RESET_AGGR_BUF();
btc_blufi_report_error(ESP_BLUFI_DECRYPT_ERROR); btc_blufi_report_error(ESP_BLUFI_DECRYPT_ERROR);
return; return;
} }
@@ -147,6 +159,7 @@ void btc_blufi_recv_handler(uint8_t *data, int len)
checksum_pkt = hdr->data[hdr->data_len] | (((uint16_t) hdr->data[hdr->data_len + 1]) << 8); checksum_pkt = hdr->data[hdr->data_len] | (((uint16_t) hdr->data[hdr->data_len + 1]) << 8);
if (checksum != checksum_pkt) { if (checksum != checksum_pkt) {
BTC_TRACE_ERROR("%s checksum error %04x, pkt %04x\n", __func__, checksum, checksum_pkt); BTC_TRACE_ERROR("%s checksum error %04x, pkt %04x\n", __func__, checksum, checksum_pkt);
BLUFI_RESET_AGGR_BUF();
btc_blufi_report_error(ESP_BLUFI_CHECKSUM_ERROR); btc_blufi_report_error(ESP_BLUFI_CHECKSUM_ERROR);
return; return;
} }
@@ -157,6 +170,11 @@ void btc_blufi_recv_handler(uint8_t *data, int len)
} }
if (BLUFI_FC_IS_FRAG(hdr->fc)) { if (BLUFI_FC_IS_FRAG(hdr->fc)) {
if(hdr->data_len<2) {
BTC_TRACE_ERROR("%s: Invalid fragment data length: %d", __func__, hdr->data_len);
btc_blufi_report_error(ESP_BLUFI_DATA_FORMAT_ERROR);
return;
}
if (blufi_env.offset == 0) { if (blufi_env.offset == 0) {
/* /*
blufi_env.aggr_buf should be NULL if blufi_env.offset is 0. blufi_env.aggr_buf should be NULL if blufi_env.offset is 0.
@@ -165,9 +183,16 @@ void btc_blufi_recv_handler(uint8_t *data, int len)
*/ */
if (blufi_env.aggr_buf) { if (blufi_env.aggr_buf) {
BTC_TRACE_ERROR("%s msg error, blufi_env.aggr_buf is not freed\n", __func__); BTC_TRACE_ERROR("%s msg error, blufi_env.aggr_buf is not freed\n", __func__);
osi_free(blufi_env.aggr_buf);
blufi_env.aggr_buf = NULL;
btc_blufi_report_error(ESP_BLUFI_MSG_STATE_ERROR); btc_blufi_report_error(ESP_BLUFI_MSG_STATE_ERROR);
return; return;
} }
if (hdr->data_len < 2) {
BTC_TRACE_ERROR("%s frag header too short: data_len=%d\n", __func__, hdr->data_len);
btc_blufi_report_error(ESP_BLUFI_DATA_FORMAT_ERROR);
return;
}
blufi_env.total_len = hdr->data[0] | (((uint16_t) hdr->data[1]) << 8); blufi_env.total_len = hdr->data[0] | (((uint16_t) hdr->data[1]) << 8);
blufi_env.aggr_buf = osi_malloc(blufi_env.total_len); blufi_env.aggr_buf = osi_malloc(blufi_env.total_len);
if (blufi_env.aggr_buf == NULL) { if (blufi_env.aggr_buf == NULL) {
@@ -181,6 +206,7 @@ void btc_blufi_recv_handler(uint8_t *data, int len)
blufi_env.offset += (hdr->data_len - 2); blufi_env.offset += (hdr->data_len - 2);
} else { } else {
BTC_TRACE_ERROR("%s payload is longer than packet length, len %d \n", __func__, blufi_env.total_len); BTC_TRACE_ERROR("%s payload is longer than packet length, len %d \n", __func__, blufi_env.total_len);
BLUFI_RESET_AGGR_BUF();
btc_blufi_report_error(ESP_BLUFI_DATA_FORMAT_ERROR); btc_blufi_report_error(ESP_BLUFI_DATA_FORMAT_ERROR);
return; return;
} }
@@ -190,12 +216,14 @@ void btc_blufi_recv_handler(uint8_t *data, int len)
/* blufi_env.aggr_buf should not be NULL */ /* blufi_env.aggr_buf should not be NULL */
if (blufi_env.aggr_buf == NULL) { if (blufi_env.aggr_buf == NULL) {
BTC_TRACE_ERROR("%s buffer is NULL\n", __func__); BTC_TRACE_ERROR("%s buffer is NULL\n", __func__);
blufi_env.offset = 0;
btc_blufi_report_error(ESP_BLUFI_DH_MALLOC_ERROR); btc_blufi_report_error(ESP_BLUFI_DH_MALLOC_ERROR);
return; return;
} }
/* payload length should be equal to total_len */ /* payload length should be equal to total_len */
if ((blufi_env.offset + hdr->data_len) != blufi_env.total_len) { if ((blufi_env.offset + hdr->data_len) != blufi_env.total_len) {
BTC_TRACE_ERROR("%s payload is longer than packet length, len %d \n", __func__, blufi_env.total_len); BTC_TRACE_ERROR("%s payload is longer than packet length, len %d \n", __func__, blufi_env.total_len);
BLUFI_RESET_AGGR_BUF();
btc_blufi_report_error(ESP_BLUFI_DATA_FORMAT_ERROR); btc_blufi_report_error(ESP_BLUFI_DATA_FORMAT_ERROR);
return; return;
} }
@@ -211,6 +239,9 @@ void btc_blufi_recv_handler(uint8_t *data, int len)
} }
} }
} }
/* Known limitation: this function runs in the BTC task (app-initiated sends)
* AND in the NimBLE task (via recv_handler), racing on send_seq/frag_size.
* portENTER_CRITICAL cannot protect here; */
void btc_blufi_send_encap(uint8_t type, uint8_t *data, int total_data_len) void btc_blufi_send_encap(uint8_t type, uint8_t *data, int total_data_len)
{ {
struct blufi_hdr *hdr = NULL; struct blufi_hdr *hdr = NULL;
@@ -94,7 +94,7 @@ void btc_blufi_protocol_handler(uint8_t type, uint8_t *data, int len)
btc_transfer_context(&msg, NULL, 0, NULL, NULL); btc_transfer_context(&msg, NULL, 0, NULL, NULL);
break; break;
default: default:
BTC_TRACE_ERROR("%s Unkown Ctrl pkt %02x\n", __func__, type); BTC_TRACE_ERROR("%s Unknown Ctrl pkt %02x\n", __func__, type);
break; break;
} }
break; break;
@@ -111,6 +111,10 @@ void btc_blufi_protocol_handler(uint8_t type, uint8_t *data, int len)
} }
break; break;
case BLUFI_TYPE_DATA_SUBTYPE_STA_BSSID: case BLUFI_TYPE_DATA_SUBTYPE_STA_BSSID:
if (len < 6) {
BTC_TRACE_ERROR("%s STA_BSSID data too short: %d\n", __func__, len);
return;
}
msg.sig = BTC_SIG_API_CB; msg.sig = BTC_SIG_API_CB;
msg.pid = BTC_PID_BLUFI; msg.pid = BTC_PID_BLUFI;
msg.act = ESP_BLUFI_EVENT_RECV_STA_BSSID; msg.act = ESP_BLUFI_EVENT_RECV_STA_BSSID;
@@ -241,7 +245,7 @@ void btc_blufi_protocol_handler(uint8_t type, uint8_t *data, int len)
btc_transfer_context(&msg, &param, sizeof(esp_blufi_cb_param_t), btc_blufi_cb_deep_copy, btc_blufi_cb_deep_free); btc_transfer_context(&msg, &param, sizeof(esp_blufi_cb_param_t), btc_blufi_cb_deep_copy, btc_blufi_cb_deep_free);
break; break;
default: default:
BTC_TRACE_ERROR("%s Unkown Ctrl pkt %02x\n", __func__, type); BTC_TRACE_ERROR("%s Unknown Ctrl pkt %02x\n", __func__, type);
break; break;
} }
break; break;
@@ -44,6 +44,7 @@ typedef struct {
/* Control reference */ /* Control reference */
esp_blufi_callbacks_t *cbs; esp_blufi_callbacks_t *cbs;
BOOLEAN enabled; BOOLEAN enabled;
BOOLEAN notify_enabled;
uint8_t send_seq; uint8_t send_seq;
uint8_t recv_seq; uint8_t recv_seq;
uint8_t sec_mode; uint8_t sec_mode;
@@ -53,6 +53,9 @@ static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
.uuid = BLE_UUID16_DECLARE(BLUFI_SERVICE_UUID), .uuid = BLE_UUID16_DECLARE(BLUFI_SERVICE_UUID),
.characteristics = (struct ble_gatt_chr_def[]) .characteristics = (struct ble_gatt_chr_def[])
{ { { {
/* FALSE POSITIVE: No BLE_GATT_CHR_F_WRITE_ENC/READ_ENC by design.
* BLUFI uses its own app-layer security (DH key exchange + AES + CRC).
* BLE link-layer pairing is impossible before provisioning credentials exist. */
/*** Characteristic: P2E */ /*** Characteristic: P2E */
.uuid = BLE_UUID16_DECLARE(BLUFI_CHAR_P2E_UUID), .uuid = BLE_UUID16_DECLARE(BLUFI_CHAR_P2E_UUID),
.access_cb = gatt_svr_access_cb, .access_cb = gatt_svr_access_cb,
@@ -89,7 +92,7 @@ void esp_blufi_gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *a
case BLE_GATT_REGISTER_OP_CHR: case BLE_GATT_REGISTER_OP_CHR:
ESP_LOGI(TAG, "registering characteristic %s with " ESP_LOGI(TAG, "registering characteristic %s with "
"def_handle=%d val_handle=%d\n", "def_handle=%d val_handle=%d",
ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
ctxt->chr.def_handle, ctxt->chr.def_handle,
ctxt->chr.val_handle); ctxt->chr.val_handle);
@@ -174,26 +177,23 @@ static size_t read_value(uint16_t conn_handle, uint16_t attr_handle,
void *arg) void *arg)
{ {
const struct gatt_value *value = (const struct gatt_value *) arg; const struct gatt_value *value = (const struct gatt_value *) arg;
char str[BLE_UUID_STR_LEN];
int rc; int rc;
memset(str, '\0', sizeof(str));
if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
if (ctxt->chr->flags & BLE_GATT_CHR_F_READ_AUTHOR) { if (ctxt->chr->flags & BLE_GATT_CHR_F_READ_AUTHOR) {
return BLE_ATT_ERR_INSUFFICIENT_AUTHOR; return BLE_ATT_ERR_INSUFFICIENT_AUTHOR;
} }
ble_uuid_to_str(ctxt->chr->uuid, str);
} else { } else {
if (ctxt->dsc->att_flags & BLE_ATT_F_READ_AUTHOR) { if (ctxt->dsc->att_flags & BLE_ATT_F_READ_AUTHOR) {
return BLE_ATT_ERR_INSUFFICIENT_AUTHOR; return BLE_ATT_ERR_INSUFFICIENT_AUTHOR;
} }
ble_uuid_to_str(ctxt->dsc->uuid, str);
} }
rc = os_mbuf_append(ctxt->om, value->buf->om_data, value->buf->om_len); if (value->buf == NULL) {
return BLE_ATT_ERR_UNLIKELY;
}
rc = os_mbuf_appendfrom(ctxt->om, value->buf, 0, OS_MBUF_PKTLEN(value->buf));
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
} }
@@ -259,14 +259,14 @@ static void deinit_gatt_values(void)
for (svc = gatt_svr_svcs; svc && svc->uuid; svc++) { for (svc = gatt_svr_svcs; svc && svc->uuid; svc++) {
for (chr = svc->characteristics; chr && chr->uuid; chr++) { for (chr = svc->characteristics; chr && chr->uuid; chr++) {
if (i < SERVER_MAX_VALUES && gatt_values[i].buf != NULL) { if (i < SERVER_MAX_VALUES && gatt_values[i].buf != NULL) {
os_mbuf_free(gatt_values[i].buf); /* Free the buffer */ os_mbuf_free_chain(gatt_values[i].buf); /* Free the buffer */
gatt_values[i].buf = NULL; /* Nullify the pointer to avoid dangling references */ gatt_values[i].buf = NULL; /* Nullify the pointer to avoid dangling references */
} }
++i; ++i;
for (dsc = chr->descriptors; dsc && dsc->uuid; dsc++) { for (dsc = chr->descriptors; dsc && dsc->uuid; dsc++) {
if (i < SERVER_MAX_VALUES && gatt_values[i].buf != NULL) { if (i < SERVER_MAX_VALUES && gatt_values[i].buf != NULL) {
os_mbuf_free(gatt_values[i].buf); /* Free the buffer */ os_mbuf_free_chain(gatt_values[i].buf); /* Free the buffer */
gatt_values[i].buf = NULL; /* Nullify the pointer to avoid dangling references */ gatt_values[i].buf = NULL; /* Nullify the pointer to avoid dangling references */
} }
++i; ++i;
@@ -349,6 +349,7 @@ esp_blufi_gap_event(struct ble_gap_event *event, void *arg)
ESP_LOGI(TAG, "disconnect; reason=%d", event->disconnect.reason); ESP_LOGI(TAG, "disconnect; reason=%d", event->disconnect.reason);
memcpy(blufi_env.remote_bda, event->disconnect.conn.peer_id_addr.val, ESP_BLUFI_BD_ADDR_LEN); memcpy(blufi_env.remote_bda, event->disconnect.conn.peer_id_addr.val, ESP_BLUFI_BD_ADDR_LEN);
blufi_env.is_connected = false; blufi_env.is_connected = false;
blufi_env.notify_enabled = false;
blufi_env.recv_seq = blufi_env.send_seq = 0; blufi_env.recv_seq = blufi_env.send_seq = 0;
blufi_env.sec_mode = 0x0; blufi_env.sec_mode = 0x0;
blufi_env.offset = 0; blufi_env.offset = 0;
@@ -377,14 +378,14 @@ esp_blufi_gap_event(struct ble_gap_event *event, void *arg)
case BLE_GAP_EVENT_ADV_COMPLETE: case BLE_GAP_EVENT_ADV_COMPLETE:
ESP_LOGI(TAG, "advertise complete; reason=%d", ESP_LOGI(TAG, "advertise complete; reason=%d",
event->adv_complete.reason); event->adv_complete.reason);
if (arg != NULL) { if (event->adv_complete.reason != 0 && arg != NULL) {
((void(*)(void))arg)(); ((void(*)(void))arg)();
} }
return 0; return 0;
case BLE_GAP_EVENT_SUBSCRIBE: case BLE_GAP_EVENT_SUBSCRIBE:
ESP_LOGI(TAG, "subscribe event; conn_handle=%d attr_handle=%d " ESP_LOGI(TAG, "subscribe event; conn_handle=%d attr_handle=%d "
"reason=%d prevn=%d curn=%d previ=%d curi=%d\n", "reason=%d prevn=%d curn=%d previ=%d curi=%d",
event->subscribe.conn_handle, event->subscribe.conn_handle,
event->subscribe.attr_handle, event->subscribe.attr_handle,
event->subscribe.reason, event->subscribe.reason,
@@ -392,6 +393,9 @@ esp_blufi_gap_event(struct ble_gap_event *event, void *arg)
event->subscribe.cur_notify, event->subscribe.cur_notify,
event->subscribe.prev_indicate, event->subscribe.prev_indicate,
event->subscribe.cur_indicate); event->subscribe.cur_indicate);
if (event->subscribe.attr_handle == gatt_values[1].val_handle) {
blufi_env.notify_enabled = (event->subscribe.cur_notify == 1);
}
return 0; return 0;
case BLE_GAP_EVENT_MTU: case BLE_GAP_EVENT_MTU:
@@ -454,9 +458,11 @@ void esp_blufi_adv_start(void)
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
name = ble_svc_gap_device_name(); name = ble_svc_gap_device_name();
if (name != NULL) {
fields.name = (uint8_t *)name; fields.name = (uint8_t *)name;
fields.name_len = strlen(name); fields.name_len = strlen(name);
fields.name_is_complete = 1; fields.name_is_complete = 1;
}
fields.uuids16 = (ble_uuid16_t[]) { fields.uuids16 = (ble_uuid16_t[]) {
BLE_UUID16_INIT(BLUFI_APP_UUID) BLE_UUID16_INIT(BLUFI_APP_UUID)
@@ -496,12 +502,18 @@ uint8_t esp_blufi_init(void)
esp_blufi_cb_param_t param; esp_blufi_cb_param_t param;
param.init_finish.state = ESP_BLUFI_INIT_OK; param.init_finish.state = ESP_BLUFI_INIT_OK;
btc_blufi_cb_to_app(ESP_BLUFI_EVENT_INIT_FINISH, &param); btc_blufi_cb_to_app(ESP_BLUFI_EVENT_INIT_FINISH, &param);
return ESP_BLUFI_ERROR; return ESP_BLUFI_SUCCESS;
} }
void esp_blufi_deinit(void) void esp_blufi_deinit(void)
{ {
blufi_env.enabled = false; blufi_env.enabled = false;
if (blufi_env.aggr_buf != NULL) {
osi_free(blufi_env.aggr_buf);
blufi_env.aggr_buf = NULL;
}
esp_blufi_cb_param_t param; esp_blufi_cb_param_t param;
btc_msg_t msg; btc_msg_t msg;
memset (&msg, 0x0, sizeof (msg)); memset (&msg, 0x0, sizeof (msg));
@@ -516,6 +528,12 @@ void esp_blufi_send_notify(void *arg)
{ {
struct pkt_info *pkts = (struct pkt_info *) arg; struct pkt_info *pkts = (struct pkt_info *) arg;
struct os_mbuf *om; struct os_mbuf *om;
if (!blufi_env.notify_enabled) {
ESP_LOGW(TAG, "esp_blufi_send_notify: client has not subscribed, dropping notification");
return;
}
om = ble_hs_mbuf_from_flat(pkts->pkt, pkts->pkt_len); om = ble_hs_mbuf_from_flat(pkts->pkt, pkts->pkt_len);
if (om == NULL) { if (om == NULL) {
ESP_LOGE(TAG, "Error in allocating memory"); ESP_LOGE(TAG, "Error in allocating memory");
@@ -530,7 +548,9 @@ void esp_blufi_send_notify(void *arg)
void esp_blufi_disconnect(void) void esp_blufi_disconnect(void)
{ {
if(blufi_env.is_connected) {
ble_gap_terminate(blufi_env.conn_id, BLE_ERR_REM_USER_CONN_TERM); ble_gap_terminate(blufi_env.conn_id, BLE_ERR_REM_USER_CONN_TERM);
}
} }
void esp_blufi_adv_stop(void) void esp_blufi_adv_stop(void)
@@ -560,6 +580,9 @@ void esp_blufi_btc_init(void)
void esp_blufi_btc_deinit(void) void esp_blufi_btc_deinit(void)
{ {
/* btc_deinit() destroys the shared global BTC thread. In the NimBLE path
* BTC is only used by BLUFI, so this is safe. If another profile ever uses
* BTC alongside BLUFI, add reference counting to btc_init/btc_deinit. */
btc_deinit(); btc_deinit();
} }
@@ -596,6 +619,7 @@ int esp_blufi_handle_gap_events(struct ble_gap_event *event, void *arg)
esp_blufi_cb_param_t param; esp_blufi_cb_param_t param;
blufi_env.is_connected = false; blufi_env.is_connected = false;
blufi_env.notify_enabled = false;
blufi_env.recv_seq = blufi_env.send_seq = 0; blufi_env.recv_seq = blufi_env.send_seq = 0;
blufi_env.sec_mode = 0x0; blufi_env.sec_mode = 0x0;
blufi_env.offset = 0; blufi_env.offset = 0;
@@ -623,6 +647,12 @@ int esp_blufi_handle_gap_events(struct ble_gap_event *event, void *arg)
blufi_env.frag_size = (event->mtu.value < BLUFI_MAX_DATA_LEN ? event->mtu.value : blufi_env.frag_size = (event->mtu.value < BLUFI_MAX_DATA_LEN ? event->mtu.value :
BLUFI_MAX_DATA_LEN) - BLUFI_MTU_RESERVED_SIZE; BLUFI_MAX_DATA_LEN) - BLUFI_MTU_RESERVED_SIZE;
return 0; return 0;
case BLE_GAP_EVENT_SUBSCRIBE:
if (event->subscribe.attr_handle == gatt_values[1].val_handle) {
blufi_env.notify_enabled = (event->subscribe.cur_notify == 1);
}
return 0;
} }
} }
@@ -49,6 +49,7 @@ int tc_cbc_mode_encrypt(uint8_t *out, unsigned int outlen, const uint8_t *in,
/* input sanity check: */ /* input sanity check: */
if (out == (uint8_t *) 0 || if (out == (uint8_t *) 0 ||
in == (const uint8_t *) 0 || in == (const uint8_t *) 0 ||
iv == (const uint8_t *) 0 ||
sched == (TCAesKeySched_t) 0 || sched == (TCAesKeySched_t) 0 ||
inlen == 0 || inlen == 0 ||
outlen == 0 || outlen == 0 ||
@@ -90,6 +91,7 @@ int tc_cbc_mode_decrypt(uint8_t *out, unsigned int outlen, const uint8_t *in,
/* sanity check the inputs */ /* sanity check the inputs */
if (out == (uint8_t *) 0 || if (out == (uint8_t *) 0 ||
in == (const uint8_t *) 0 || in == (const uint8_t *) 0 ||
iv == (const uint8_t *) 0 ||
sched == (TCAesKeySched_t) 0 || sched == (TCAesKeySched_t) 0 ||
inlen == 0 || inlen == 0 ||
outlen == 0 || outlen == 0 ||
@@ -105,7 +107,7 @@ int tc_cbc_mode_decrypt(uint8_t *out, unsigned int outlen, const uint8_t *in,
* that would not otherwise be possible. * that would not otherwise be possible.
*/ */
p = iv; p = iv;
for (n = m = 0; n < inlen; ++n) { for (n = m = 0; n < outlen; ++n) {
if ((n % TC_AES_BLOCK_SIZE) == 0) { if ((n % TC_AES_BLOCK_SIZE) == 0) {
(void)tc_aes_decrypt(buffer, in, sched); (void)tc_aes_decrypt(buffer, in, sched);
in += TC_AES_BLOCK_SIZE; in += TC_AES_BLOCK_SIZE;
+26 -2
View File
@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -115,6 +115,9 @@ static void tc_ctr_prng_update(TCCtrPrng_t * const ctx, uint8_t const * const pr
/* 10.2.1.2 step 6 */ /* 10.2.1.2 step 6 */
memcpy(ctx->V, &(temp[TC_AES_KEY_SIZE]), TC_AES_BLOCK_SIZE); memcpy(ctx->V, &(temp[TC_AES_KEY_SIZE]), TC_AES_BLOCK_SIZE);
/* Clear transient key material from stack */
_set(temp, 0, sizeof(temp));
} }
} }
@@ -162,6 +165,11 @@ int tc_ctr_prng_init(TCCtrPrng_t * const ctx,
result = TC_CRYPTO_SUCCESS; result = TC_CRYPTO_SUCCESS;
} }
_set(personalization_buf, 0, sizeof(personalization_buf));
_set(seed_material, 0, sizeof(seed_material));
_set(zeroArr, 0, sizeof(zeroArr));
return result; return result;
} }
@@ -203,6 +211,10 @@ int tc_ctr_prng_reseed(TCCtrPrng_t * const ctx,
result = TC_CRYPTO_SUCCESS; result = TC_CRYPTO_SUCCESS;
} }
_set(additional_input_buf, 0, sizeof(additional_input_buf));
_set(seed_material, 0, sizeof(seed_material));
return result; return result;
} }
@@ -220,7 +232,19 @@ int tc_ctr_prng_generate(TCCtrPrng_t * const ctx,
unsigned int result = TC_CRYPTO_FAIL; unsigned int result = TC_CRYPTO_FAIL;
if ((0 != ctx) && (0 != out) && (outlen < MAX_BYTES_PER_REQ)) { if (0 == ctx) {
return TC_CRYPTO_FAIL;
}
if (0U == outlen) {
return TC_CRYPTO_SUCCESS;
}
if ((0 == out) || (outlen >= MAX_BYTES_PER_REQ)) {
return TC_CRYPTO_FAIL;
}
{
/* 10.2.1.5.1 step 1 */ /* 10.2.1.5.1 step 1 */
if (ctx->reseedCount > MAX_REQS_BEFORE_RESEED) { if (ctx->reseedCount > MAX_REQS_BEFORE_RESEED) {
result = TC_CTR_PRNG_RESEED_REQ; result = TC_CTR_PRNG_RESEED_REQ;
+62 -22
View File
@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -58,6 +58,7 @@
#include <tinycrypt/ecc.h> #include <tinycrypt/ecc.h>
#include <tinycrypt/ecc_platform_specific.h> #include <tinycrypt/ecc_platform_specific.h>
#include <assert.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
@@ -302,11 +303,19 @@ void uECC_vli_modAdd(uECC_word_t *result, const uECC_word_t *left,
const uECC_word_t *right, const uECC_word_t *mod, const uECC_word_t *right, const uECC_word_t *mod,
wordcount_t num_words) wordcount_t num_words)
{ {
wordcount_t i;
uECC_word_t tmp[NUM_ECC_WORDS];
if (num_words <= 0 || num_words > NUM_ECC_WORDS) {
return;
}
uECC_word_t carry = uECC_vli_add(result, left, right, num_words); uECC_word_t carry = uECC_vli_add(result, left, right, num_words);
if (carry || uECC_vli_cmp_unsafe(mod, result, num_words) != 1) { uECC_word_t borrow = uECC_vli_sub(tmp, result, mod, num_words);
/* result > mod (result = mod + remainder), so subtract mod to get uECC_word_t do_reduce = (uECC_word_t)(carry || !borrow);
* remainder. */
uECC_vli_sub(result, result, mod, num_words); for (i = 0; i < num_words; ++i) {
result[i] = cond_set(tmp[i], result[i], do_reduce);
} }
} }
@@ -314,11 +323,17 @@ void uECC_vli_modSub(uECC_word_t *result, const uECC_word_t *left,
const uECC_word_t *right, const uECC_word_t *mod, const uECC_word_t *right, const uECC_word_t *mod,
wordcount_t num_words) wordcount_t num_words)
{ {
wordcount_t i;
uECC_word_t tmp[NUM_ECC_WORDS];
if (num_words <= 0 || num_words > NUM_ECC_WORDS) {
return;
}
uECC_word_t l_borrow = uECC_vli_sub(result, left, right, num_words); uECC_word_t l_borrow = uECC_vli_sub(result, left, right, num_words);
if (l_borrow) { uECC_vli_add(tmp, result, mod, num_words);
/* In this case, result == -diff == (max int) - diff. Since -x % d == d - x, for (i = 0; i < num_words; ++i) {
* we can get the correct result from result + mod (with overflow). */ result[i] = cond_set(tmp[i], result[i], l_borrow);
uECC_vli_add(result, result, mod, num_words);
} }
} }
@@ -332,6 +347,10 @@ void uECC_vli_mmod(uECC_word_t *result, uECC_word_t *product,
uECC_word_t *v[2] = {tmp, product}; uECC_word_t *v[2] = {tmp, product};
uECC_word_t index; uECC_word_t index;
if (num_words <= 0 || num_words > NUM_ECC_WORDS) {
return;
}
/* Shift mod so its highest set bit is at the maximum position. */ /* Shift mod so its highest set bit is at the maximum position. */
bitcount_t shift = (num_words * 2 * uECC_WORD_BITS) - bitcount_t shift = (num_words * 2 * uECC_WORD_BITS) -
uECC_vli_numBits(mod, num_words); uECC_vli_numBits(mod, num_words);
@@ -353,9 +372,8 @@ void uECC_vli_mmod(uECC_word_t *result, uECC_word_t *product,
wordcount_t i; wordcount_t i;
for (i = 0; i < num_words * 2; ++i) { for (i = 0; i < num_words * 2; ++i) {
uECC_word_t diff = v[index][i] - mod_multiple[i] - borrow; uECC_word_t diff = v[index][i] - mod_multiple[i] - borrow;
if (diff != v[index][i]) { uECC_word_t val = (diff > v[index][i]);
borrow = (diff > v[index][i]); borrow = cond_set(val, borrow, (diff != v[index][i]));
}
v[1 - index][i] = diff; v[1 - index][i] = diff;
} }
/* Swap the index if there was no borrow */ /* Swap the index if there was no borrow */
@@ -372,6 +390,10 @@ void uECC_vli_modMult(uECC_word_t *result, const uECC_word_t *left,
const uECC_word_t *right, const uECC_word_t *mod, const uECC_word_t *right, const uECC_word_t *mod,
wordcount_t num_words) wordcount_t num_words)
{ {
if (num_words <= 0 || num_words > NUM_ECC_WORDS) {
return;
}
uECC_word_t product[2 * NUM_ECC_WORDS]; uECC_word_t product[2 * NUM_ECC_WORDS];
uECC_vli_mult(product, left, right, num_words); uECC_vli_mult(product, left, right, num_words);
uECC_vli_mmod(result, product, mod, num_words); uECC_vli_mmod(result, product, mod, num_words);
@@ -642,7 +664,8 @@ void apply_z(uECC_word_t * X1, uECC_word_t * Y1, const uECC_word_t * const Z,
uECC_vli_modMult_fast(Y1, Y1, t1, curve); /* y1 * z^3 */ uECC_vli_modMult_fast(Y1, Y1, t1, curve); /* y1 * z^3 */
} }
#if !SOC_ECC_SUPPORTED #if !SOC_ECC_SUPPORTED || SOC_ESP_NIMBLE_CONTROLLER
/* Keep ESP32-C6 on the software micro-ecc path for BLE SC compatibility. */
/* P = (x1, y1) => 2P, (x2, y2) => P' */ /* P = (x1, y1) => 2P, (x2, y2) => P' */
static void XYcZ_initial_double(uECC_word_t * X1, uECC_word_t * Y1, static void XYcZ_initial_double(uECC_word_t * X1, uECC_word_t * Y1,
uECC_word_t * X2, uECC_word_t * Y2, uECC_word_t * X2, uECC_word_t * Y2,
@@ -676,7 +699,7 @@ static void XYcZ_addC(uECC_word_t * X1, uECC_word_t * Y1,
uECC_Curve curve) uECC_Curve curve)
{ {
/* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */
uECC_word_t t5[NUM_ECC_WORDS]; uECC_word_t t5[NUM_ECC_WORDS] = {0};
uECC_word_t t6[NUM_ECC_WORDS]; uECC_word_t t6[NUM_ECC_WORDS];
uECC_word_t t7[NUM_ECC_WORDS]; uECC_word_t t7[NUM_ECC_WORDS];
wordcount_t num_words = curve->num_words; wordcount_t num_words = curve->num_words;
@@ -715,7 +738,7 @@ void XYcZ_add(uECC_word_t * X1, uECC_word_t * Y1,
uECC_Curve curve) uECC_Curve curve)
{ {
/* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */
uECC_word_t t5[NUM_ECC_WORDS]; uECC_word_t t5[NUM_ECC_WORDS] = {0};
wordcount_t num_words = curve->num_words; wordcount_t num_words = curve->num_words;
uECC_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */ uECC_vli_modSub(t5, X2, X1, curve->p, num_words); /* t5 = x2 - x1 */
@@ -741,7 +764,7 @@ void EccPoint_mult(uECC_word_t * result, const uECC_word_t * point,
const uECC_word_t * initial_Z, const uECC_word_t * initial_Z,
bitcount_t num_bits, uECC_Curve curve) bitcount_t num_bits, uECC_Curve curve)
{ {
#if SOC_ECC_SUPPORTED #if SOC_ECC_SUPPORTED && !SOC_ESP_NIMBLE_CONTROLLER
wordcount_t num_words = curve->num_words; wordcount_t num_words = curve->num_words;
/* Only p256r1 is supported currently. */ /* Only p256r1 is supported currently. */
@@ -799,10 +822,22 @@ uECC_word_t regularize_k(const uECC_word_t * const k, uECC_word_t *k0,
wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
bitcount_t num_n_bits = curve->num_n_bits; bitcount_t num_n_bits = curve->num_n_bits;
uECC_word_t carry;
uECC_word_t bit = 0;
bitcount_t max_n_bits;
uECC_word_t carry = uECC_vli_add(k0, k, curve->n, num_n_words) || if (num_n_words <= 0 || num_n_words > NUM_ECC_WORDS) {
(num_n_bits < ((bitcount_t)num_n_words * uECC_WORD_SIZE * 8) && return 0;
uECC_vli_testBit(k0, num_n_bits)); }
max_n_bits = (bitcount_t)num_n_words * uECC_WORD_SIZE * 8;
carry = uECC_vli_add(k0, k, curve->n, num_n_words);
if (num_n_bits < max_n_bits) {
bit = (uECC_vli_testBit(k0, num_n_bits) != 0);
}
carry |= bit;
uECC_vli_add(k1, k0, curve->n, num_n_words); uECC_vli_add(k1, k0, curve->n, num_n_words);
@@ -813,17 +848,22 @@ uECC_word_t EccPoint_compute_public_key(uECC_word_t *result,
uECC_word_t *private_key, uECC_word_t *private_key,
uECC_Curve curve) uECC_Curve curve)
{ {
#if !SOC_ECC_SUPPORTED || SOC_ESP_NIMBLE_CONTROLLER
uECC_word_t tmp1[NUM_ECC_WORDS]; uECC_word_t tmp1[NUM_ECC_WORDS];
uECC_word_t tmp2[NUM_ECC_WORDS]; uECC_word_t tmp2[NUM_ECC_WORDS];
uECC_word_t *p2[2] = {tmp1, tmp2}; uECC_word_t *p2[2] = {tmp1, tmp2};
uECC_word_t carry; uECC_word_t carry;
#endif
#if SOC_ECC_SUPPORTED && !SOC_ESP_NIMBLE_CONTROLLER
EccPoint_mult(result, curve->G, private_key, 0, curve->num_n_bits, curve);
#else
/* Regularize the bitcount for the private key so that attackers cannot /* Regularize the bitcount for the private key so that attackers cannot
* use a side channel attack to learn the number of leading zeros. */ * use a side channel attack to learn the number of leading zeros. */
carry = regularize_k(private_key, tmp1, tmp2, curve); carry = regularize_k(private_key, tmp1, tmp2, curve);
EccPoint_mult(result, curve->G, p2[!carry], 0, curve->num_n_bits + 1, curve); EccPoint_mult(result, curve->G, p2[!carry], 0, curve->num_n_bits + 1, curve);
#endif
if (EccPoint_isZero(result, curve)) { if (EccPoint_isZero(result, curve)) {
return 0; return 0;
@@ -862,7 +902,7 @@ int uECC_generate_random_int(uECC_word_t *random, const uECC_word_t *top,
uECC_word_t tries; uECC_word_t tries;
bitcount_t num_bits = uECC_vli_numBits(top, num_words); bitcount_t num_bits = uECC_vli_numBits(top, num_words);
if (!g_rng_function) { if (!g_rng_function || num_words <= 0 || num_words > NUM_ECC_WORDS) {
return 0; return 0;
} }
@@ -896,7 +936,7 @@ int uECC_valid_point(const uECC_word_t *point, uECC_Curve curve)
return -2; return -2;
} }
#if SOC_ECC_SUPPORTED #if SOC_ECC_SUPPORTED && !SOC_ESP_NIMBLE_CONTROLLER
/* Only p256r1 is supported currently. */ /* Only p256r1 is supported currently. */
if (curve != uECC_secp256r1()) { if (curve != uECC_secp256r1()) {
return -5; return -5;
+13 -8
View File
@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -63,12 +63,6 @@
#include <tinycrypt/ecc_dh.h> #include <tinycrypt/ecc_dh.h>
#include <string.h> #include <string.h>
#if default_RNG_defined
static uECC_RNG_Function g_rng_function = &default_CSPRNG;
#else
static uECC_RNG_Function g_rng_function = 0;
#endif
int uECC_make_key_with_d(uint8_t *public_key, uint8_t *private_key, int uECC_make_key_with_d(uint8_t *public_key, uint8_t *private_key,
unsigned int *d, uECC_Curve curve) unsigned int *d, uECC_Curve curve)
{ {
@@ -153,9 +147,11 @@ int uECC_shared_secret(const uint8_t *public_key, const uint8_t *private_key,
uECC_word_t _private[NUM_ECC_WORDS]; uECC_word_t _private[NUM_ECC_WORDS];
uECC_word_t tmp[NUM_ECC_WORDS]; uECC_word_t tmp[NUM_ECC_WORDS];
#if !SOC_ECC_SUPPORTED || SOC_ESP_NIMBLE_CONTROLLER
uECC_word_t *p2[2] = {_private, tmp}; uECC_word_t *p2[2] = {_private, tmp};
uECC_word_t *initial_Z = 0; uECC_word_t *initial_Z = 0;
uECC_word_t carry; uECC_word_t carry;
#endif
wordcount_t num_words = curve->num_words; wordcount_t num_words = curve->num_words;
wordcount_t num_bytes = curve->num_bytes; wordcount_t num_bytes = curve->num_bytes;
int r; int r;
@@ -171,13 +167,17 @@ int uECC_shared_secret(const uint8_t *public_key, const uint8_t *private_key,
public_key + num_bytes, public_key + num_bytes,
num_bytes); num_bytes);
#if SOC_ECC_SUPPORTED && !SOC_ESP_NIMBLE_CONTROLLER
EccPoint_mult(_public, _public, _private, 0, curve->num_n_bits, curve);
#else
/* Regularize the bitcount for the private key so that attackers cannot use a /* Regularize the bitcount for the private key so that attackers cannot use a
* side channel attack to learn the number of leading zeros. */ * side channel attack to learn the number of leading zeros. */
carry = regularize_k(_private, _private, tmp, curve); carry = regularize_k(_private, _private, tmp, curve);
/* If an RNG function was specified, try to get a random initial Z value to /* If an RNG function was specified, try to get a random initial Z value to
* improve protection against side-channel attacks. */ * improve protection against side-channel attacks. */
if (g_rng_function) { uECC_RNG_Function rng_function = uECC_get_rng();
if (rng_function) {
if (!uECC_generate_random_int(p2[carry], curve->p, num_words)) { if (!uECC_generate_random_int(p2[carry], curve->p, num_words)) {
r = 0; r = 0;
goto clear_and_out; goto clear_and_out;
@@ -187,14 +187,19 @@ int uECC_shared_secret(const uint8_t *public_key, const uint8_t *private_key,
EccPoint_mult(_public, _public, p2[!carry], initial_Z, curve->num_n_bits + 1, EccPoint_mult(_public, _public, p2[!carry], initial_Z, curve->num_n_bits + 1,
curve); curve);
#endif
uECC_vli_nativeToBytes(secret, num_bytes, _public); uECC_vli_nativeToBytes(secret, num_bytes, _public);
r = !EccPoint_isZero(_public, curve); r = !EccPoint_isZero(_public, curve);
clear_and_out: clear_and_out:
/* erasing temporary buffer used to store secret: */ /* erasing temporary buffer used to store secret: */
#if !SOC_ECC_SUPPORTED || SOC_ESP_NIMBLE_CONTROLLER
memset(p2, 0, sizeof(p2)); memset(p2, 0, sizeof(p2));
__asm__ __volatile__("" :: "g"(p2) : "memory"); __asm__ __volatile__("" :: "g"(p2) : "memory");
#endif
memset(_public, 0, sizeof(_public));
__asm__ __volatile__("" :: "g"(_public) : "memory");
memset(tmp, 0, sizeof(tmp)); memset(tmp, 0, sizeof(tmp));
__asm__ __volatile__("" :: "g"(tmp) : "memory"); __asm__ __volatile__("" :: "g"(tmp) : "memory");
memset(_private, 0, sizeof(_private)); memset(_private, 0, sizeof(_private));
+12 -10
View File
@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -61,12 +61,6 @@
#include <tinycrypt/ecc.h> #include <tinycrypt/ecc.h>
#include <tinycrypt/ecc_dsa.h> #include <tinycrypt/ecc_dsa.h>
#if default_RNG_defined
static uECC_RNG_Function g_rng_function = &default_CSPRNG;
#else
static uECC_RNG_Function g_rng_function = 0;
#endif
static void bits2int(uECC_word_t *native, const uint8_t *bits, static void bits2int(uECC_word_t *native, const uint8_t *bits,
unsigned bits_size, uECC_Curve curve) unsigned bits_size, uECC_Curve curve)
{ {
@@ -107,9 +101,11 @@ int uECC_sign_with_k(const uint8_t *private_key, const uint8_t *message_hash,
uECC_word_t tmp[NUM_ECC_WORDS]; uECC_word_t tmp[NUM_ECC_WORDS];
uECC_word_t s[NUM_ECC_WORDS]; uECC_word_t s[NUM_ECC_WORDS];
#if !SOC_ECC_SUPPORTED || SOC_ESP_NIMBLE_CONTROLLER
uECC_word_t *k2[2] = {tmp, s}; uECC_word_t *k2[2] = {tmp, s};
uECC_word_t p[NUM_ECC_WORDS * 2];
uECC_word_t carry; uECC_word_t carry;
#endif
uECC_word_t p[NUM_ECC_WORDS * 2];
wordcount_t num_words = curve->num_words; wordcount_t num_words = curve->num_words;
wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits); wordcount_t num_n_words = BITS_TO_WORDS(curve->num_n_bits);
bitcount_t num_n_bits = curve->num_n_bits; bitcount_t num_n_bits = curve->num_n_bits;
@@ -120,15 +116,20 @@ int uECC_sign_with_k(const uint8_t *private_key, const uint8_t *message_hash,
return 0; return 0;
} }
#if SOC_ECC_SUPPORTED && !SOC_ESP_NIMBLE_CONTROLLER
EccPoint_mult(p, curve->G, k, 0, num_n_bits, curve);
#else
carry = regularize_k(k, tmp, s, curve); carry = regularize_k(k, tmp, s, curve);
EccPoint_mult(p, curve->G, k2[!carry], 0, num_n_bits + 1, curve); EccPoint_mult(p, curve->G, k2[!carry], 0, num_n_bits + 1, curve);
#endif
if (uECC_vli_isZero(p, num_words)) { if (uECC_vli_isZero(p, num_words)) {
return 0; return 0;
} }
/* If an RNG function was specified, get a random number /* If an RNG function was specified, get a random number
to prevent side channel analysis of k. */ to prevent side channel analysis of k. */
if (!g_rng_function) { uECC_RNG_Function rng_function = uECC_get_rng();
if (!rng_function) {
uECC_vli_clear(tmp, num_n_words); uECC_vli_clear(tmp, num_n_words);
tmp[0] = 1; tmp[0] = 1;
} }
@@ -154,7 +155,8 @@ int uECC_sign_with_k(const uint8_t *private_key, const uint8_t *message_hash,
bits2int(tmp, message_hash, hash_size, curve); bits2int(tmp, message_hash, hash_size, curve);
uECC_vli_modAdd(s, tmp, s, curve->n, num_n_words); /* s = e + r*d */ uECC_vli_modAdd(s, tmp, s, curve->n, num_n_words); /* s = e + r*d */
uECC_vli_modMult(s, s, k, curve->n, num_n_words); /* s = (e + r*d) / k */ uECC_vli_modMult(s, s, k, curve->n, num_n_words); /* s = (e + r*d) / k */
if (uECC_vli_numBits(s, num_n_words) > (bitcount_t)curve->num_bytes * 8) { if (uECC_vli_isZero(s, num_n_words) ||
uECC_vli_numBits(s, num_n_words) > (bitcount_t)curve->num_bytes * 8) {
return 0; return 0;
} }
@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -86,6 +86,9 @@ static void update(TCHmacPrng_t prng, const uint8_t *e, unsigned int len)
const uint8_t separator0 = 0x00; const uint8_t separator0 = 0x00;
const uint8_t separator1 = 0x01; const uint8_t separator1 = 0x01;
/* tc_hmac_final wipes the HMAC context, so reload key before init */
(void)tc_hmac_set_key(&prng->h, prng->key, sizeof(prng->key));
/* use current state, e and separator 0 to compute a new prng key: */ /* use current state, e and separator 0 to compute a new prng key: */
(void)tc_hmac_init(&prng->h); (void)tc_hmac_init(&prng->h);
(void)tc_hmac_update(&prng->h, prng->v, sizeof(prng->v)); (void)tc_hmac_update(&prng->h, prng->v, sizeof(prng->v));
@@ -101,6 +104,7 @@ static void update(TCHmacPrng_t prng, const uint8_t *e, unsigned int len)
(void)tc_hmac_final(prng->v, sizeof(prng->v), &prng->h); (void)tc_hmac_final(prng->v, sizeof(prng->v), &prng->h);
/* use current state, e and separator 1 to compute a new prng key: */ /* use current state, e and separator 1 to compute a new prng key: */
(void)tc_hmac_set_key(&prng->h, prng->key, sizeof(prng->key));
(void)tc_hmac_init(&prng->h); (void)tc_hmac_init(&prng->h);
(void)tc_hmac_update(&prng->h, prng->v, sizeof(prng->v)); (void)tc_hmac_update(&prng->h, prng->v, sizeof(prng->v));
(void)tc_hmac_update(&prng->h, &separator1, sizeof(separator1)); (void)tc_hmac_update(&prng->h, &separator1, sizeof(separator1));
@@ -113,6 +117,9 @@ static void update(TCHmacPrng_t prng, const uint8_t *e, unsigned int len)
(void)tc_hmac_init(&prng->h); (void)tc_hmac_init(&prng->h);
(void)tc_hmac_update(&prng->h, prng->v, sizeof(prng->v)); (void)tc_hmac_update(&prng->h, prng->v, sizeof(prng->v));
(void)tc_hmac_final(prng->v, sizeof(prng->v), &prng->h); (void)tc_hmac_final(prng->v, sizeof(prng->v), &prng->h);
/* keep HMAC context prepared for callers that reuse prng->h */
(void)tc_hmac_set_key(&prng->h, prng->key, sizeof(prng->key));
} }
int tc_hmac_prng_init(TCHmacPrng_t prng, int tc_hmac_prng_init(TCHmacPrng_t prng,
@@ -198,6 +205,7 @@ int tc_hmac_prng_generate(uint8_t *out, unsigned int outlen, TCHmacPrng_t prng)
while (outlen != 0) { while (outlen != 0) {
/* operate HMAC in OFB mode to create "random" outputs */ /* operate HMAC in OFB mode to create "random" outputs */
(void)tc_hmac_set_key(&prng->h, prng->key, sizeof(prng->key));
(void)tc_hmac_init(&prng->h); (void)tc_hmac_init(&prng->h);
(void)tc_hmac_update(&prng->h, prng->v, sizeof(prng->v)); (void)tc_hmac_update(&prng->h, prng->v, sizeof(prng->v));
(void)tc_hmac_final(prng->v, sizeof(prng->v), &prng->h); (void)tc_hmac_final(prng->v, sizeof(prng->v), &prng->h);
+55 -48
View File
@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -40,6 +40,34 @@ static inline void unlock_devices(void)
} }
} }
/*
* Atomically checks whether a device is still in the global list and acquires
* its mutex before releasing the list lock. This avoids TOCTOU between
* `esp_hidh_dev_exists()` and `esp_hidh_dev_lock()` for call sites that need
* a stable pointer.
*/
static bool lock_known_device(esp_hidh_dev_t *dev)
{
if (dev == NULL) {
return false;
}
bool found = false;
esp_hidh_dev_t *d = NULL;
lock_devices();
TAILQ_FOREACH(d, &s_esp_hidh_devices, devices) {
if (d == dev) {
found = true;
if (dev->mutex != NULL) {
xSemaphoreTake(dev->mutex, portMAX_DELAY);
}
break;
}
}
unlock_devices();
return found;
}
/* /*
* Public Functions * Public Functions
* */ * */
@@ -207,8 +235,7 @@ esp_hidh_dev_t *esp_hidh_dev_open(uint8_t *bda, esp_hid_transport_t transport, u
esp_err_t esp_hidh_dev_close(esp_hidh_dev_t *dev) esp_err_t esp_hidh_dev_close(esp_hidh_dev_t *dev)
{ {
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
ret = dev->close(dev); ret = dev->close(dev);
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} else { } else {
@@ -219,8 +246,7 @@ esp_err_t esp_hidh_dev_close(esp_hidh_dev_t *dev)
void esp_hidh_dev_dump(esp_hidh_dev_t *dev, FILE *fp) void esp_hidh_dev_dump(esp_hidh_dev_t *dev, FILE *fp)
{ {
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
dev->dump(dev, fp); dev->dump(dev, fp);
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} }
@@ -229,8 +255,7 @@ void esp_hidh_dev_dump(esp_hidh_dev_t *dev, FILE *fp)
esp_err_t esp_hidh_dev_output_set(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, uint8_t *value, size_t value_len) esp_err_t esp_hidh_dev_output_set(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, uint8_t *value, size_t value_len)
{ {
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
ret = dev->report_write(dev, map_index, report_id, ESP_HID_REPORT_TYPE_OUTPUT, value, value_len); ret = dev->report_write(dev, map_index, report_id, ESP_HID_REPORT_TYPE_OUTPUT, value, value_len);
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} else { } else {
@@ -242,8 +267,7 @@ esp_err_t esp_hidh_dev_output_set(esp_hidh_dev_t *dev, size_t map_index, size_t
esp_err_t esp_hidh_dev_feature_set(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, uint8_t *value, size_t value_len) esp_err_t esp_hidh_dev_feature_set(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, uint8_t *value, size_t value_len)
{ {
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
ret = dev->report_write(dev, map_index, report_id, ESP_HID_REPORT_TYPE_FEATURE, value, value_len); ret = dev->report_write(dev, map_index, report_id, ESP_HID_REPORT_TYPE_FEATURE, value, value_len);
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} else { } else {
@@ -255,8 +279,7 @@ esp_err_t esp_hidh_dev_feature_set(esp_hidh_dev_t *dev, size_t map_index, size_t
esp_err_t esp_hidh_dev_feature_get(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, size_t max_length, uint8_t *value, size_t *value_len) esp_err_t esp_hidh_dev_feature_get(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, size_t max_length, uint8_t *value, size_t *value_len)
{ {
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
ret = dev->report_read(dev, map_index, report_id, ESP_HID_REPORT_TYPE_FEATURE, max_length, value, value_len); ret = dev->report_read(dev, map_index, report_id, ESP_HID_REPORT_TYPE_FEATURE, max_length, value, value_len);
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} else { } else {
@@ -268,8 +291,7 @@ esp_err_t esp_hidh_dev_feature_get(esp_hidh_dev_t *dev, size_t map_index, size_t
esp_err_t esp_hidh_dev_set_report(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type, uint8_t *data, size_t length) esp_err_t esp_hidh_dev_set_report(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type, uint8_t *data, size_t length)
{ {
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
if (dev->set_report) { if (dev->set_report) {
ret = dev->set_report(dev, map_index, report_id, report_type, data, length); ret = dev->set_report(dev, map_index, report_id, report_type, data, length);
} else { } else {
@@ -286,8 +308,7 @@ esp_err_t esp_hidh_dev_get_report(esp_hidh_dev_t *dev, size_t map_index, size_t
size_t max_len) size_t max_len)
{ {
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
ret = dev->report_read(dev, map_index, report_id, report_type, max_len, NULL, NULL); ret = dev->report_read(dev, map_index, report_id, report_type, max_len, NULL, NULL);
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} else { } else {
@@ -299,8 +320,7 @@ esp_err_t esp_hidh_dev_get_report(esp_hidh_dev_t *dev, size_t map_index, size_t
esp_err_t esp_hidh_dev_get_idle(esp_hidh_dev_t *dev) esp_err_t esp_hidh_dev_get_idle(esp_hidh_dev_t *dev)
{ {
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
if (dev->get_idle) { if (dev->get_idle) {
ret = dev->get_idle(dev); ret = dev->get_idle(dev);
} else { } else {
@@ -316,8 +336,7 @@ esp_err_t esp_hidh_dev_get_idle(esp_hidh_dev_t *dev)
esp_err_t esp_hidh_dev_set_idle(esp_hidh_dev_t *dev, uint8_t idle_time) esp_err_t esp_hidh_dev_set_idle(esp_hidh_dev_t *dev, uint8_t idle_time)
{ {
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
if (dev->set_idle) { if (dev->set_idle) {
ret = dev->set_idle(dev, idle_time); ret = dev->set_idle(dev, idle_time);
} else { } else {
@@ -333,8 +352,7 @@ esp_err_t esp_hidh_dev_set_idle(esp_hidh_dev_t *dev, uint8_t idle_time)
esp_err_t esp_hidh_dev_get_protocol(esp_hidh_dev_t *dev) esp_err_t esp_hidh_dev_get_protocol(esp_hidh_dev_t *dev)
{ {
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
if (dev->get_protocol) { if (dev->get_protocol) {
ret = dev->get_protocol(dev); ret = dev->get_protocol(dev);
} else { } else {
@@ -350,8 +368,7 @@ esp_err_t esp_hidh_dev_get_protocol(esp_hidh_dev_t *dev)
esp_err_t esp_hidh_dev_set_protocol(esp_hidh_dev_t *dev, uint8_t protocol_mode) esp_err_t esp_hidh_dev_set_protocol(esp_hidh_dev_t *dev, uint8_t protocol_mode)
{ {
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
if (dev->set_protocol) { if (dev->set_protocol) {
ret = dev->set_protocol(dev, protocol_mode); ret = dev->set_protocol(dev, protocol_mode);
} else { } else {
@@ -368,8 +385,7 @@ const uint8_t *esp_hidh_dev_bda_get(esp_hidh_dev_t *dev)
{ {
uint8_t *ret = NULL; uint8_t *ret = NULL;
#if CONFIG_BLUEDROID_ENABLED || CONFIG_BT_NIMBLE_ENABLED #if CONFIG_BLUEDROID_ENABLED || CONFIG_BT_NIMBLE_ENABLED
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
ret = dev->addr.bda; ret = dev->addr.bda;
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} }
@@ -380,8 +396,7 @@ const uint8_t *esp_hidh_dev_bda_get(esp_hidh_dev_t *dev)
esp_hid_transport_t esp_hidh_dev_transport_get(esp_hidh_dev_t *dev) esp_hid_transport_t esp_hidh_dev_transport_get(esp_hidh_dev_t *dev)
{ {
esp_hid_transport_t ret = ESP_HID_TRANSPORT_MAX; esp_hid_transport_t ret = ESP_HID_TRANSPORT_MAX;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
ret = dev->transport; ret = dev->transport;
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} }
@@ -391,8 +406,7 @@ esp_hid_transport_t esp_hidh_dev_transport_get(esp_hidh_dev_t *dev)
const esp_hid_device_config_t *esp_hidh_dev_config_get(esp_hidh_dev_t *dev) const esp_hid_device_config_t *esp_hidh_dev_config_get(esp_hidh_dev_t *dev)
{ {
esp_hid_device_config_t *ret = NULL; esp_hid_device_config_t *ret = NULL;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
ret = &dev->config; ret = &dev->config;
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} }
@@ -402,8 +416,7 @@ const esp_hid_device_config_t *esp_hidh_dev_config_get(esp_hidh_dev_t *dev)
const char *esp_hidh_dev_name_get(esp_hidh_dev_t *dev) const char *esp_hidh_dev_name_get(esp_hidh_dev_t *dev)
{ {
const char * ret = NULL; const char * ret = NULL;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
ret = dev->config.device_name ? dev->config.device_name : ""; ret = dev->config.device_name ? dev->config.device_name : "";
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} }
@@ -413,8 +426,7 @@ const char *esp_hidh_dev_name_get(esp_hidh_dev_t *dev)
const char *esp_hidh_dev_manufacturer_get(esp_hidh_dev_t *dev) const char *esp_hidh_dev_manufacturer_get(esp_hidh_dev_t *dev)
{ {
const char *ret = NULL; const char *ret = NULL;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
ret = dev->config.manufacturer_name ? dev->config.manufacturer_name : ""; ret = dev->config.manufacturer_name ? dev->config.manufacturer_name : "";
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} }
@@ -424,8 +436,7 @@ const char *esp_hidh_dev_manufacturer_get(esp_hidh_dev_t *dev)
const char *esp_hidh_dev_serial_get(esp_hidh_dev_t *dev) const char *esp_hidh_dev_serial_get(esp_hidh_dev_t *dev)
{ {
const char *ret = NULL; const char *ret = NULL;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
ret = dev->config.serial_number ? dev->config.serial_number : ""; ret = dev->config.serial_number ? dev->config.serial_number : "";
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} }
@@ -435,8 +446,7 @@ const char *esp_hidh_dev_serial_get(esp_hidh_dev_t *dev)
uint16_t esp_hidh_dev_vendor_id_get(esp_hidh_dev_t *dev) uint16_t esp_hidh_dev_vendor_id_get(esp_hidh_dev_t *dev)
{ {
uint16_t ret = 0; uint16_t ret = 0;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
ret = dev->config.vendor_id; ret = dev->config.vendor_id;
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} }
@@ -446,8 +456,7 @@ uint16_t esp_hidh_dev_vendor_id_get(esp_hidh_dev_t *dev)
uint16_t esp_hidh_dev_product_id_get(esp_hidh_dev_t *dev) uint16_t esp_hidh_dev_product_id_get(esp_hidh_dev_t *dev)
{ {
uint16_t ret = 0; uint16_t ret = 0;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
ret = dev->config.product_id; ret = dev->config.product_id;
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} }
@@ -457,8 +466,7 @@ uint16_t esp_hidh_dev_product_id_get(esp_hidh_dev_t *dev)
uint16_t esp_hidh_dev_version_get(esp_hidh_dev_t *dev) uint16_t esp_hidh_dev_version_get(esp_hidh_dev_t *dev)
{ {
uint16_t ret = 0; uint16_t ret = 0;
if (!esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
ret = dev->config.version; ret = dev->config.version;
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} }
@@ -468,8 +476,7 @@ uint16_t esp_hidh_dev_version_get(esp_hidh_dev_t *dev)
esp_hid_usage_t esp_hidh_dev_usage_get(esp_hidh_dev_t *dev) esp_hid_usage_t esp_hidh_dev_usage_get(esp_hidh_dev_t *dev)
{ {
esp_hid_usage_t ret = ESP_HID_USAGE_GENERIC; esp_hid_usage_t ret = ESP_HID_USAGE_GENERIC;
if (esp_hidh_dev_exists(dev)) { if (lock_known_device(dev)) {
esp_hidh_dev_lock(dev);
ret = dev->usage; ret = dev->usage;
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
} }
@@ -481,11 +488,9 @@ esp_err_t esp_hidh_dev_reports_get(esp_hidh_dev_t *dev, size_t *num_reports, esp
esp_err_t ret = 0; esp_err_t ret = 0;
esp_hid_report_item_t *r = NULL; esp_hid_report_item_t *r = NULL;
if (!esp_hidh_dev_exists(dev)) { if (!lock_known_device(dev)) {
return ESP_FAIL; return ESP_FAIL;
} }
esp_hidh_dev_lock(dev);
do { do {
r = (esp_hid_report_item_t *)malloc(sizeof(esp_hid_report_item_t) * dev->reports_len); r = (esp_hid_report_item_t *)malloc(sizeof(esp_hid_report_item_t) * dev->reports_len);
if (r == NULL) { if (r == NULL) {
@@ -521,10 +526,9 @@ error_:;
esp_err_t esp_hidh_dev_report_maps_get(esp_hidh_dev_t *dev, size_t *num_maps, esp_hid_raw_report_map_t **maps) esp_err_t esp_hidh_dev_report_maps_get(esp_hidh_dev_t *dev, size_t *num_maps, esp_hid_raw_report_map_t **maps)
{ {
if (!esp_hidh_dev_exists(dev)) { if (!lock_known_device(dev)) {
return ESP_FAIL; return ESP_FAIL;
} }
esp_hidh_dev_lock(dev);
*num_maps = dev->config.report_maps_len; *num_maps = dev->config.report_maps_len;
*maps = dev->config.report_maps; *maps = dev->config.report_maps;
esp_hidh_dev_unlock(dev); esp_hidh_dev_unlock(dev);
@@ -692,6 +696,9 @@ static void esp_hidh_dev_resources_free(esp_hidh_dev_t *dev)
} }
} }
free((void *)dev->config.report_maps); free((void *)dev->config.report_maps);
#if CONFIG_BT_NIMBLE_ENABLED
free((void *)dev->protocol_mode);
#endif
esp_hidh_dev_report_t *r; esp_hidh_dev_report_t *r;
while (dev->reports) { while (dev->reports) {
r = dev->reports; r = dev->reports;
+175 -34
View File
@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -10,6 +10,7 @@
#include "ble_hidd.h" #include "ble_hidd.h"
#include "esp_private/esp_hidd_private.h" #include "esp_private/esp_hidd_private.h"
#include "esp_log.h" #include "esp_log.h"
#include "freertos/semphr.h"
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
@@ -35,6 +36,27 @@ static const char *TAG = "NIMBLE_HIDD";
typedef struct esp_ble_hidd_dev_s esp_ble_hidd_dev_t; typedef struct esp_ble_hidd_dev_s esp_ble_hidd_dev_t;
// there can be only one BLE HID device // there can be only one BLE HID device
static esp_ble_hidd_dev_t *s_dev = NULL; static esp_ble_hidd_dev_t *s_dev = NULL;
static SemaphoreHandle_t s_hidd_mutex = NULL;
static bool s_gap_listener_registered = false;
static void (*s_prev_reset_cb)(int reason) = NULL;
static void (*s_prev_sync_cb)(void) = NULL;
static struct ble_gap_event_listener nimble_gap_event_listener;
static void nimble_host_synced(void);
void nimble_host_reset(int reason);
static inline void lock_hidd(void)
{
if (s_hidd_mutex) {
xSemaphoreTake(s_hidd_mutex, portMAX_DELAY);
}
}
static inline void unlock_hidd(void)
{
if (s_hidd_mutex) {
xSemaphoreGive(s_hidd_mutex);
}
}
/** service index is used to identify the hid service instance /** service index is used to identify the hid service instance
of the registered characteristic. of the registered characteristic.
Assuming the first instance of the hid service is registered first. Assuming the first instance of the hid service is registered first.
@@ -90,7 +112,10 @@ static int create_hid_db(int device_index)
/* fill hid info */ /* fill hid info */
memcpy(&hparams.hid_info, hidInfo, sizeof hparams.hid_info); memcpy(&hparams.hid_info, hidInfo, sizeof hparams.hid_info);
/* fill report map */ if (s_dev->devices[device_index].reports_map.len > REPORT_MAP_SIZE) {
ESP_LOGE(TAG, "Report map too large (%d > %d); aborting", s_dev->devices[device_index].reports_map.len, REPORT_MAP_SIZE);
return ESP_FAIL;
}
memcpy(&hparams.report_map, (uint8_t *)s_dev->devices[device_index].reports_map.data, s_dev->devices[device_index].reports_map.len); memcpy(&hparams.report_map, (uint8_t *)s_dev->devices[device_index].reports_map.data, s_dev->devices[device_index].reports_map.len);
hparams.report_map_len = s_dev->devices[device_index].reports_map.len; hparams.report_map_len = s_dev->devices[device_index].reports_map.len;
hparams.external_rpt_ref = BLE_SVC_BAS_UUID16; hparams.external_rpt_ref = BLE_SVC_BAS_UUID16;
@@ -102,6 +127,10 @@ static int create_hid_db(int device_index)
for (uint8_t i = 0; i < s_dev->devices[device_index].reports_len; i++) { for (uint8_t i = 0; i < s_dev->devices[device_index].reports_len; i++) {
hidd_le_report_item_t *report = &s_dev->devices[device_index].reports[i]; hidd_le_report_item_t *report = &s_dev->devices[device_index].reports[i];
if (report->protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT) { if (report->protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT) {
if (report_mode_rpts >= MAX_REPORTS) {
ESP_LOGE(TAG, "Too many report-mode reports (%d >= MAX_REPORTS); truncating", report_mode_rpts);
break;
}
/* only consider report mode reports, all boot mode reports will be registered by default */ /* only consider report mode reports, all boot mode reports will be registered by default */
if (report->report_type == ESP_HID_REPORT_TYPE_INPUT) { if (report->report_type == ESP_HID_REPORT_TYPE_INPUT) {
/* Input Report */ /* Input Report */
@@ -186,6 +215,15 @@ static int nimble_hid_stop_gatts(esp_ble_hidd_dev_t *dev)
{ {
int rc = ESP_OK; int rc = ESP_OK;
if (s_gap_listener_registered) {
ble_gap_event_listener_unregister(&nimble_gap_event_listener);
s_gap_listener_registered = false;
}
if (dev && dev->connected) {
ble_gap_terminate(dev->conn_id, BLE_ERR_REM_USER_CONN_TERM);
}
/* stop gatt database */ /* stop gatt database */
ble_gatts_stop(); ble_gatts_stop();
@@ -249,6 +287,7 @@ static int ble_hid_init_config(esp_ble_hidd_dev_t *dev, const esp_hid_device_con
dev->devices[d].reports = (hidd_le_report_item_t *)malloc(rmap->reports_len * sizeof(hidd_le_report_item_t)); dev->devices[d].reports = (hidd_le_report_item_t *)malloc(rmap->reports_len * sizeof(hidd_le_report_item_t));
if (dev->devices[d].reports == NULL) { if (dev->devices[d].reports == NULL) {
ESP_LOGE(TAG, "reports malloc(%d) failed", rmap->reports_len * sizeof(hidd_le_report_item_t)); ESP_LOGE(TAG, "reports malloc(%d) failed", rmap->reports_len * sizeof(hidd_le_report_item_t));
free(rmap->reports);
free(rmap); free(rmap);
return ESP_FAIL; return ESP_FAIL;
} }
@@ -287,22 +326,42 @@ static int ble_hid_free_config(esp_ble_hidd_dev_t *dev)
static int nimble_hidd_dev_deinit(void *devp) static int nimble_hidd_dev_deinit(void *devp)
{ {
esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp;
lock_hidd();
if (!s_dev) { if (!s_dev) {
ESP_LOGE(TAG, "HID device profile already uninitialized"); ESP_LOGE(TAG, "HID device profile already uninitialized");
unlock_hidd();
return ESP_OK; return ESP_OK;
} }
if (s_dev != dev) { if (s_dev != dev) {
ESP_LOGE(TAG, "Wrong HID device provided"); ESP_LOGE(TAG, "Wrong HID device provided");
unlock_hidd();
return ESP_FAIL; return ESP_FAIL;
} }
s_dev = NULL;
service_index = -1; // resetting the value
nimble_hid_stop_gatts(dev); nimble_hid_stop_gatts(dev);
s_dev = NULL;
service_index = -1;
if (ble_hs_cfg.reset_cb == nimble_host_reset) {
ble_hs_cfg.reset_cb = s_prev_reset_cb;
}
if (ble_hs_cfg.sync_cb == nimble_host_synced) {
ble_hs_cfg.sync_cb = s_prev_sync_cb;
}
ble_hs_cfg.gatts_register_cb = NULL;
unlock_hidd();
/* Known timing issue: STOP_EVENT is posted here but ble_hid_free_config (called
* next) immediately deletes the event loop task, which may discard the queued
* event before it is dispatched. A proper fix requires deleting the event loop
* from within the STOP_EVENT handler itself (architectural refactor needed). */
esp_event_post_to(dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_STOP_EVENT, NULL, 0, portMAX_DELAY); esp_event_post_to(dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_STOP_EVENT, NULL, 0, portMAX_DELAY);
ble_hid_free_config(dev); ble_hid_free_config(dev);
free(dev); free(dev);
if (s_hidd_mutex) {
vSemaphoreDelete(s_hidd_mutex);
s_hidd_mutex = NULL;
}
return ESP_OK; return ESP_OK;
} }
@@ -316,20 +375,19 @@ static int nimble_hidd_dev_battery_set(void *devp, uint8_t level)
{ {
int rc; int rc;
esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp; esp_ble_hidd_dev_t *dev = (esp_ble_hidd_dev_t *)devp;
lock_hidd();
if (!dev || s_dev != dev) { if (!dev || s_dev != dev) {
unlock_hidd();
return ESP_FAIL; return ESP_FAIL;
} }
if (!dev->connected) {
/* Return success if not yet connected */
return ESP_OK;
}
rc = ble_svc_bas_battery_level_set(level); rc = ble_svc_bas_battery_level_set(level);
if (rc) { if (rc != 0 && dev->connected) {
ESP_LOGE(TAG, "esp_ble_gatts_send_notify failed: %d", rc); ESP_LOGE(TAG, "ble_svc_bas_battery_level_set failed: %d", rc);
unlock_hidd();
return ESP_FAIL; return ESP_FAIL;
} }
unlock_hidd();
return ESP_OK; return ESP_OK;
} }
@@ -338,6 +396,9 @@ static int nimble_hidd_dev_battery_set(void *devp, uint8_t level)
static hidd_le_report_item_t* find_report(uint8_t id, uint8_t type, uint8_t *mode) static hidd_le_report_item_t* find_report(uint8_t id, uint8_t type, uint8_t *mode)
{ {
hidd_le_report_item_t *rpt; hidd_le_report_item_t *rpt;
if (!s_dev) {
return NULL;
}
for (uint8_t d = 0; d < s_dev->devices_len; d++) { for (uint8_t d = 0; d < s_dev->devices_len; d++) {
for (uint8_t i = 0; i < s_dev->devices[d].reports_len; i++) { for (uint8_t i = 0; i < s_dev->devices[d].reports_len; i++) {
rpt = &s_dev->devices[d].reports[i]; rpt = &s_dev->devices[d].reports[i];
@@ -348,17 +409,19 @@ static hidd_le_report_item_t* find_report(uint8_t id, uint8_t type, uint8_t *mod
} }
return NULL; return NULL;
} }
static hidd_le_report_item_t* find_report_by_usage_and_type(uint8_t usage, uint8_t type, uint8_t *mode)
static hidd_le_report_item_t* find_report_by_usage_and_type(uint8_t dev_index, uint8_t usage, uint8_t type, uint8_t *mode)
{ {
hidd_le_report_item_t *rpt; hidd_le_report_item_t *rpt;
for (uint8_t d = 0; d < s_dev->devices_len; d++) { if (!s_dev || dev_index >= s_dev->devices_len) {
for (uint8_t i = 0; i < s_dev->devices[d].reports_len; i++) { return NULL;
rpt = &s_dev->devices[d].reports[i]; }
for (uint8_t i = 0; i < s_dev->devices[dev_index].reports_len; i++) {
rpt = &s_dev->devices[dev_index].reports[i];
if (rpt->usage == usage && rpt->report_type == type && (!mode || (mode && *mode == rpt->protocol_mode))) { if (rpt->usage == usage && rpt->report_type == type && (!mode || (mode && *mode == rpt->protocol_mode))) {
return rpt; return rpt;
} }
} }
}
return NULL; return NULL;
} }
@@ -372,6 +435,11 @@ static int nimble_hidd_dev_input_set(void *devp, size_t index, size_t id, uint8_
return ESP_FAIL; return ESP_FAIL;
} }
if (index >= s_dev->devices_len) {
ESP_LOGE(TAG, "%s invalid device index %zu (devices_len=%u)", __func__, index, s_dev->devices_len);
return ESP_FAIL;
}
if (!dev->connected) { if (!dev->connected) {
ESP_LOGE(TAG, "%s Device Not Connected", __func__); ESP_LOGE(TAG, "%s Device Not Connected", __func__);
return ESP_FAIL; return ESP_FAIL;
@@ -418,6 +486,11 @@ static int nimble_hidd_dev_feature_set(void *devp, size_t index, size_t id, uint
return ESP_FAIL; return ESP_FAIL;
} }
if (index >= s_dev->devices_len) {
ESP_LOGE(TAG, "%s invalid device index %zu (devices_len=%u)", __func__, index, s_dev->devices_len);
return ESP_FAIL;
}
if (!dev->connected) { if (!dev->connected) {
ESP_LOGE(TAG, "%s Device Not Connected", __func__); ESP_LOGE(TAG, "%s Device Not Connected", __func__);
return ESP_FAIL; return ESP_FAIL;
@@ -476,63 +549,88 @@ static void ble_hidd_dev_free(void)
free(s_dev); free(s_dev);
s_dev = NULL; s_dev = NULL;
} }
if (s_hidd_mutex) {
vSemaphoreDelete(s_hidd_mutex);
s_hidd_mutex = NULL;
}
} }
static int nimble_hid_gap_event(struct ble_gap_event *event, void *arg) static int nimble_hid_gap_event(struct ble_gap_event *event, void *arg)
{ {
struct ble_gap_conn_desc desc; struct ble_gap_conn_desc desc;
struct os_mbuf *om;
uint8_t data; uint8_t data;
int rc; int rc;
esp_ble_hidd_dev_t *dev;
lock_hidd();
dev = s_dev;
if (dev == NULL) {
unlock_hidd();
return 0;
}
/* Known limitation: HOGP spec requires the BLE link to be encrypted before
* the host may access HID report characteristics. Encryption is not enforced
* here; it must be enforced either by adding BLE_GATT_CHR_F_READ_ENC /
* BLE_GATT_CHR_F_WRITE_ENC flags to characteristics in ble_svc_hid.c, or by
* configuring ble_hs_cfg.sm_* security parameters at the application level. */
switch (event->type) { switch (event->type) {
case BLE_GAP_EVENT_CONNECT: case BLE_GAP_EVENT_CONNECT:
/* A new connection was established or a connection attempt failed. */ /* A new connection was established or a connection attempt failed. */
ESP_LOGD(TAG, "connection %s; status=%d", ESP_LOGD(TAG, "connection %s; status=%d",
event->connect.status == 0 ? "established" : "failed", event->connect.status == 0 ? "established" : "failed",
event->connect.status); event->connect.status);
if (event->connect.status != 0) {
unlock_hidd();
return 0;
}
rc = ble_gap_conn_find(event->connect.conn_handle, &desc); rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
assert(rc == 0); assert(rc == 0);
/* save connection handle */ /* save connection handle */
s_dev->connected = true; dev->connected = true;
s_dev->conn_id = event->connect.conn_handle; dev->conn_id = event->connect.conn_handle;
esp_hidd_event_data_t cb_param = { esp_hidd_event_data_t cb_param = {
.connect.dev = s_dev->dev, .connect.dev = dev->dev,
.connect.status = event->connect.status .connect.status = event->connect.status
}; };
/* reset the protocol mode value */ /* reset the protocol mode value */
data = ESP_HID_PROTOCOL_MODE_REPORT; data = ESP_HID_PROTOCOL_MODE_REPORT;
om = ble_hs_mbuf_from_flat(&data, 1); for (int i = 0; i < dev->devices_len; i++) {
struct os_mbuf *om = ble_hs_mbuf_from_flat(&data, 1);
if (om == NULL) { if (om == NULL) {
ESP_LOGD(TAG, "No memory to allocate mbuf"); ESP_LOGD(TAG, "No memory to allocate mbuf");
break;
} }
/* NOTE : om is freed by stack */ rc = ble_att_svr_write_local(dev->devices[i].hid_protocol_handle, om);
for (int i = 0; i < s_dev->devices_len; i++) {
rc = ble_att_svr_write_local(s_dev->devices[i].hid_protocol_handle, om);
if (rc != 0) { if (rc != 0) {
ESP_LOGE(TAG, "Write on Protocol Mode Failed: %d", rc); ESP_LOGE(TAG, "Write on Protocol Mode Failed: %d", rc);
} }
} }
unlock_hidd();
esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_CONNECT_EVENT, esp_event_post_to(dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_CONNECT_EVENT,
&cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY); &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY);
return 0; return 0;
break; break;
case BLE_GAP_EVENT_DISCONNECT: case BLE_GAP_EVENT_DISCONNECT:
ESP_LOGD(TAG, "disconnect; reason=%d", event->disconnect.reason); ESP_LOGD(TAG, "disconnect; reason=%d", event->disconnect.reason);
if (s_dev->connected) { if (dev->connected) {
s_dev->connected = false; dev->connected = false;
esp_hidd_event_data_t cb_param = {0}; esp_hidd_event_data_t cb_param = {0};
cb_param.disconnect.dev = s_dev->dev; cb_param.disconnect.dev = dev->dev;
cb_param.disconnect.reason = event->disconnect.reason; cb_param.disconnect.reason = event->disconnect.reason;
esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_DISCONNECT_EVENT, unlock_hidd();
esp_event_post_to(dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_DISCONNECT_EVENT,
&cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY); &cb_param, sizeof(esp_hidd_event_data_t), portMAX_DELAY);
} else {
unlock_hidd();
} }
return 0; return 0;
} }
unlock_hidd();
return 0; return 0;
} }
@@ -554,9 +652,14 @@ static void nimble_gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, voi
ctxt->svc.handle); ctxt->svc.handle);
uuid16 = ble_uuid_u16(ctxt->svc.svc_def->uuid); uuid16 = ble_uuid_u16(ctxt->svc.svc_def->uuid);
if (uuid16 == BLE_SVC_HID_UUID16) { if (uuid16 == BLE_SVC_HID_UUID16) {
if (service_index + 1 >= (int)s_dev->devices_len) {
ESP_LOGE(TAG, "Too many HID services registered (%d >= devices_len %d); ignoring",
service_index + 1, s_dev->devices_len);
} else {
++service_index; ++service_index;
s_dev->devices[service_index].hid_svc = ctxt->svc.handle; s_dev->devices[service_index].hid_svc = ctxt->svc.handle;
} }
}
break; break;
@@ -566,6 +669,10 @@ static void nimble_gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, voi
ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
ctxt->chr.def_handle, ctxt->chr.def_handle,
ctxt->chr.val_handle); ctxt->chr.val_handle);
if (service_index < 0 || (uint8_t)service_index >= s_dev->devices_len) {
break;
}
uuid16 = ble_uuid_u16(ctxt->chr.chr_def->uuid); uuid16 = ble_uuid_u16(ctxt->chr.chr_def->uuid);
if (uuid16 == BLE_SVC_HID_CHR_UUID16_HID_CTRL_PT) { if (uuid16 == BLE_SVC_HID_CHR_UUID16_HID_CTRL_PT) {
/* assuming this characteristic is from the last registered hid service */ /* assuming this characteristic is from the last registered hid service */
@@ -577,7 +684,7 @@ static void nimble_gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, voi
} }
if (uuid16 == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_INP) { if (uuid16 == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_INP) {
protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT; protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT;
rpt = find_report_by_usage_and_type(ESP_HID_USAGE_KEYBOARD, ESP_HID_REPORT_TYPE_INPUT, &protocol_mode); rpt = find_report_by_usage_and_type(service_index, ESP_HID_USAGE_KEYBOARD, ESP_HID_REPORT_TYPE_INPUT, &protocol_mode);
if (rpt == NULL) { if (rpt == NULL) {
ESP_LOGE(TAG, "Unknown boot kbd input report registration"); ESP_LOGE(TAG, "Unknown boot kbd input report registration");
return; return;
@@ -586,7 +693,7 @@ static void nimble_gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, voi
} }
if (uuid16 == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_OUT) { if (uuid16 == BLE_SVC_HID_CHR_UUID16_BOOT_KBD_OUT) {
protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT; protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT;
rpt = find_report_by_usage_and_type(ESP_HID_USAGE_KEYBOARD, ESP_HID_REPORT_TYPE_OUTPUT, &protocol_mode); rpt = find_report_by_usage_and_type(service_index, ESP_HID_USAGE_KEYBOARD, ESP_HID_REPORT_TYPE_OUTPUT, &protocol_mode);
if (rpt == NULL) { if (rpt == NULL) {
ESP_LOGE(TAG, "Unknown boot kbd output report registration"); ESP_LOGE(TAG, "Unknown boot kbd output report registration");
return; return;
@@ -595,7 +702,7 @@ static void nimble_gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, voi
} }
if (uuid16 == BLE_SVC_HID_CHR_UUID16_BOOT_MOUSE_INP) { if (uuid16 == BLE_SVC_HID_CHR_UUID16_BOOT_MOUSE_INP) {
protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT; protocol_mode = ESP_HID_PROTOCOL_MODE_BOOT;
rpt = find_report_by_usage_and_type(ESP_HID_USAGE_MOUSE, ESP_HID_REPORT_TYPE_INPUT, &protocol_mode); rpt = find_report_by_usage_and_type(service_index, ESP_HID_USAGE_MOUSE, ESP_HID_REPORT_TYPE_INPUT, &protocol_mode);
if (rpt == NULL) { if (rpt == NULL) {
ESP_LOGE(TAG, "Unknown boot mouse input report registration"); ESP_LOGE(TAG, "Unknown boot mouse input report registration");
return; return;
@@ -616,6 +723,11 @@ static void nimble_gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, voi
ble_hs_mbuf_to_flat(om, &report_info, sizeof report_info, NULL); ble_hs_mbuf_to_flat(om, &report_info, sizeof report_info, NULL);
report_type = (uint8_t)((report_info & 0xFF00) >> 8); report_type = (uint8_t)((report_info & 0xFF00) >> 8);
report_id = report_info & 0x00FF; report_id = report_info & 0x00FF;
if (ctxt->dsc.dsc_def->arg == NULL) {
ESP_LOGE(TAG, "Report Reference descriptor has NULL arg; skipping handle assignment");
os_mbuf_free_chain(om);
break;
}
report_handle = (*(uint16_t*)(ctxt->dsc.dsc_def->arg)); report_handle = (*(uint16_t*)(ctxt->dsc.dsc_def->arg));
protocol_mode = ESP_HID_PROTOCOL_MODE_REPORT; protocol_mode = ESP_HID_PROTOCOL_MODE_REPORT;
rpt = find_report(report_id, report_type, &protocol_mode); rpt = find_report(report_id, report_type, &protocol_mode);
@@ -635,15 +747,24 @@ static void nimble_gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, voi
static void nimble_host_synced(void) static void nimble_host_synced(void)
{ {
lock_hidd();
if (s_prev_sync_cb && s_prev_sync_cb != nimble_host_synced) {
s_prev_sync_cb();
}
if (s_dev && s_dev->event_loop_handle != NULL) {
esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_START_EVENT, NULL, 0, portMAX_DELAY); esp_event_post_to(s_dev->event_loop_handle, ESP_HIDD_EVENTS, ESP_HIDD_START_EVENT, NULL, 0, portMAX_DELAY);
}
unlock_hidd();
} }
void nimble_host_reset(int reason) void nimble_host_reset(int reason)
{ {
if (s_prev_reset_cb && s_prev_reset_cb != nimble_host_reset) {
s_prev_reset_cb(reason);
}
MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason); MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason);
} }
static struct ble_gap_event_listener nimble_gap_event_listener;
esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_config_t *config, esp_event_handler_t callback) esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_config_t *config, esp_event_handler_t callback)
{ {
int rc; int rc;
@@ -658,6 +779,15 @@ esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_conf
ESP_LOGE(TAG, "HID device could not be allocated"); ESP_LOGE(TAG, "HID device could not be allocated");
return ESP_FAIL; return ESP_FAIL;
} }
if (s_hidd_mutex == NULL) {
s_hidd_mutex = xSemaphoreCreateMutex();
if (s_hidd_mutex == NULL) {
free(s_dev);
s_dev = NULL;
ESP_LOGE(TAG, "HID device mutex allocation failed");
return ESP_ERR_NO_MEM;
}
}
// Reset the hid device target environment // Reset the hid device target environment
s_dev->control = ESP_HID_CONTROL_EXIT_SUSPEND; s_dev->control = ESP_HID_CONTROL_EXIT_SUSPEND;
@@ -708,15 +838,26 @@ esp_err_t esp_ble_hidd_dev_init(esp_hidd_dev_t *dev_p, const esp_hid_device_conf
} }
} }
s_prev_reset_cb = ble_hs_cfg.reset_cb;
s_prev_sync_cb = ble_hs_cfg.sync_cb;
ble_hs_cfg.reset_cb = nimble_host_reset; ble_hs_cfg.reset_cb = nimble_host_reset;
ble_hs_cfg.sync_cb = nimble_host_synced; ble_hs_cfg.sync_cb = nimble_host_synced;
ble_hs_cfg.gatts_register_cb = nimble_gatt_svr_register_cb; ble_hs_cfg.gatts_register_cb = nimble_gatt_svr_register_cb;
rc = nimble_hid_start_gatts(); rc = nimble_hid_start_gatts();
if (rc != ESP_OK) { if (rc != ESP_OK) {
if (ble_hs_cfg.reset_cb == nimble_host_reset) {
ble_hs_cfg.reset_cb = s_prev_reset_cb;
}
if (ble_hs_cfg.sync_cb == nimble_host_synced) {
ble_hs_cfg.sync_cb = s_prev_sync_cb;
}
ble_hs_cfg.gatts_register_cb = NULL;
ble_hidd_dev_free();
return rc; return rc;
} }
ble_gap_event_listener_register(&nimble_gap_event_listener, ble_gap_event_listener_register(&nimble_gap_event_listener,
nimble_hid_gap_event, NULL); nimble_hid_gap_event, NULL);
s_gap_listener_registered = true;
return rc; return rc;
} }
+442 -128
View File
@@ -1,9 +1,10 @@
/* /*
* SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
#include <stdlib.h>
#include <string.h> #include <string.h>
#include "ble_hidh.h" #include "ble_hidh.h"
#include "esp_private/esp_hidh_private.h" #include "esp_private/esp_hidh_private.h"
@@ -18,7 +19,6 @@
#include "freertos/semphr.h" #include "freertos/semphr.h"
#include <assert.h> #include <assert.h>
#include <string.h>
#include <errno.h> #include <errno.h>
#include "nimble/nimble_opt.h" #include "nimble/nimble_opt.h"
#include "host/ble_hs.h" #include "host/ble_hs.h"
@@ -26,6 +26,7 @@
#include "host/ble_hs_adv.h" #include "host/ble_hs_adv.h"
#include "host/ble_hs_hci.h" #include "host/ble_hs_hci.h"
#include "host/ble_att.h" #include "host/ble_att.h"
#include "host/ble_sm.h"
#include "services/gap/ble_svc_gap.h" #include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h" #include "services/gatt/ble_svc_gatt.h"
#include "services/bas/ble_svc_bas.h" #include "services/bas/ble_svc_bas.h"
@@ -40,12 +41,22 @@
static const char *TAG = "NIMBLE_HIDH"; static const char *TAG = "NIMBLE_HIDH";
static SemaphoreHandle_t s_ble_hidh_cb_semaphore = NULL; static SemaphoreHandle_t s_ble_hidh_cb_semaphore = NULL;
static SemaphoreHandle_t s_ble_hidh_op_mutex = NULL;
static void (*s_prev_reset_cb)(int reason) = NULL;
static void (*s_prev_sync_cb)(void) = NULL;
#define HIDH_MAX_SVCS 10
#define HIDH_MAX_CHRS 20
#define HIDH_MAX_DSCS 20
/* variables used for attribute discovery */ /* variables used for attribute discovery */
static int services_discovered; static int services_discovered;
static int chrs_discovered; static int chrs_discovered;
static int dscs_discovered; static int dscs_discovered;
static int status = 0; static int status = 0;
static int s_gatt_op_status;
static inline void WAIT_CB(void) static inline void WAIT_CB(void)
{ {
xSemaphoreTake(s_ble_hidh_cb_semaphore, portMAX_DELAY); xSemaphoreTake(s_ble_hidh_cb_semaphore, portMAX_DELAY);
@@ -56,41 +67,65 @@ static inline void SEND_CB(void)
xSemaphoreGive(s_ble_hidh_cb_semaphore); xSemaphoreGive(s_ble_hidh_cb_semaphore);
} }
static inline void LOCK_OPS(void)
{
if (s_ble_hidh_op_mutex) {
xSemaphoreTake(s_ble_hidh_op_mutex, portMAX_DELAY);
}
}
static inline void UNLOCK_OPS(void)
{
if (s_ble_hidh_op_mutex) {
xSemaphoreGive(s_ble_hidh_op_mutex);
}
}
static esp_event_loop_handle_t event_loop_handle; static esp_event_loop_handle_t event_loop_handle;
static uint8_t *s_read_data_val = NULL; static uint8_t *s_read_data_val = NULL;
static uint16_t s_read_data_len = 0; static uint16_t s_read_data_len = 0;
static int s_read_status = 0; static int s_read_status = 0;
/**
* Utility function to log an array of bytes.
*/
void
print_bytes(const uint8_t *bytes, int len)
{
int i;
for (i = 0; i < len; i++) {
MODLOG_DFLT(DEBUG, "%s0x%02x", i != 0 ? ":" : "", bytes[i]);
}
}
static void static void
print_mbuf(const struct os_mbuf *om) print_mbuf(const struct os_mbuf *om)
{ {
int colon;
colon = 0;
while (om != NULL) { while (om != NULL) {
if (colon) { ESP_LOG_BUFFER_HEXDUMP(TAG, om->om_data, om->om_len, ESP_LOG_DEBUG);
MODLOG_DFLT(DEBUG, ":");
} else {
colon = 1;
}
print_bytes(om->om_data, om->om_len);
om = SLIST_NEXT(om, om_next); om = SLIST_NEXT(om, om_next);
} }
} }
static char *nimble_hidh_dup_cstr(const uint8_t *data, size_t len)
{
char *p;
if (data == NULL || len == 0) {
return NULL;
}
p = (char *)malloc(len + 1);
if (p == NULL) {
return NULL;
}
memcpy(p, data, len);
p[len] = '\0';
return p;
}
static uint8_t *nimble_hidh_dup_bytes(const uint8_t *data, size_t len)
{
uint8_t *p;
if (data == NULL || len == 0) {
return NULL;
}
p = (uint8_t *)malloc(len);
if (p == NULL) {
return NULL;
}
memcpy(p, data, len);
return p;
}
static int static int
nimble_on_read(uint16_t conn_handle, nimble_on_read(uint16_t conn_handle,
const struct ble_gatt_error *error, const struct ble_gatt_error *error,
@@ -98,65 +133,107 @@ nimble_on_read(uint16_t conn_handle,
void *arg) void *arg)
{ {
int old_offset; int old_offset;
(void)arg;
MODLOG_DFLT(INFO, "Read complete; status=%d conn_handle=%d", error->status, MODLOG_DFLT(INFO, "Read complete; status=%d conn_handle=%d", error->status,
conn_handle); conn_handle);
s_read_status = error->status; s_read_status = error->status;
switch (s_read_status) { switch (s_read_status) {
case 0: case 0:
MODLOG_DFLT(DEBUG, " attr_handle=%d value=", attr->handle); MODLOG_DFLT(DEBUG, " attr_handle=%d value=", attr->handle);
if ((uint32_t)s_read_data_len + OS_MBUF_PKTLEN(attr->om) > 4096) { // max allowed hid report size
ESP_LOGE(TAG, "Read data too large, aborting");
free(s_read_data_val);
s_read_data_val = NULL;
s_read_data_len = 0;
s_read_status = BLE_HS_ENOMEM;
SEND_CB();
return -1;
}
old_offset = s_read_data_len; old_offset = s_read_data_len;
s_read_data_len += OS_MBUF_PKTLEN(attr->om); s_read_data_len += OS_MBUF_PKTLEN(attr->om);
s_read_data_val = realloc(s_read_data_val, s_read_data_len + 1); // 1 extra byte to store null char uint8_t *tmp = realloc(s_read_data_val, s_read_data_len + 1);
if (tmp == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for read data");
free(s_read_data_val);
s_read_data_val = NULL;
s_read_data_len = 0;
s_read_status = BLE_HS_ENOMEM;
SEND_CB();
return -1;
}
s_read_data_val = tmp;
ble_hs_mbuf_to_flat(attr->om, s_read_data_val + old_offset, OS_MBUF_PKTLEN(attr->om), NULL); ble_hs_mbuf_to_flat(attr->om, s_read_data_val + old_offset, OS_MBUF_PKTLEN(attr->om), NULL);
print_mbuf(attr->om); print_mbuf(attr->om);
return 0; return 0;
case BLE_HS_EDONE: case BLE_HS_EDONE:
s_read_data_val[s_read_data_len] = 0; // to insure strings are ended with \0 */ if (s_read_data_val) {
s_read_data_val[s_read_data_len] = 0; // to ensure strings are ended with \0
}
s_read_status = 0; s_read_status = 0;
SEND_CB(); SEND_CB();
return 0; return 0;
} default:
SEND_CB();
return 0; return 0;
}
} }
static int read_char(uint16_t conn_handle, uint16_t handle, uint8_t **out, uint16_t *out_len) static int read_char(uint16_t conn_handle, uint16_t handle, uint8_t **out, uint16_t *out_len)
{ {
int rc;
LOCK_OPS();
s_read_data_val = NULL; s_read_data_val = NULL;
s_read_data_len = 0; s_read_data_len = 0;
int rc; s_read_status = 0;
/* read long because the server may not support the large enough mtu */ /* read long because the server may not support the large enough mtu */
rc = ble_gattc_read_long(conn_handle, handle, 0, nimble_on_read, NULL); rc = ble_gattc_read_long(conn_handle, handle, 0, nimble_on_read, NULL);
if (rc != 0) { if (rc != 0) {
ESP_LOGE(TAG, "read_char failed"); ESP_LOGE(TAG, "read_char failed");
UNLOCK_OPS();
return rc; return rc;
} }
WAIT_CB(); WAIT_CB();
if (s_read_status == 0) { if (s_read_status == 0) {
*out = s_read_data_val; *out = s_read_data_val;
*out_len = s_read_data_len; *out_len = s_read_data_len;
} else {
free(s_read_data_val);
s_read_data_val = NULL;
} }
return s_read_status; rc = s_read_status;
UNLOCK_OPS();
return rc;
} }
static int read_descr(uint16_t conn_handle, uint16_t handle, uint8_t **out, uint16_t *out_len) static int read_descr(uint16_t conn_handle, uint16_t handle, uint8_t **out, uint16_t *out_len)
{ {
int rc; int rc;
LOCK_OPS();
s_read_data_val = NULL; s_read_data_val = NULL;
s_read_data_len = 0; s_read_data_len = 0;
s_read_status = 0;
rc = ble_gattc_read_long(conn_handle, handle, 0, nimble_on_read, NULL); rc = ble_gattc_read_long(conn_handle, handle, 0, nimble_on_read, NULL);
if (rc != 0) { if (rc != 0) {
ESP_LOGE(TAG, "read_descr failed"); ESP_LOGE(TAG, "read_descr failed");
UNLOCK_OPS();
return rc; return rc;
} }
WAIT_CB(); WAIT_CB();
if (s_read_status == 0) { if (s_read_status == 0) {
*out = s_read_data_val; *out = s_read_data_val;
*out_len = s_read_data_len; *out_len = s_read_data_len;
} else {
free(s_read_data_val);
s_read_data_val = NULL;
} }
return s_read_status; rc = s_read_status;
UNLOCK_OPS();
return rc;
} }
static int static int
@@ -173,6 +250,10 @@ svc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
status = error->status; status = error->status;
switch (error->status) { switch (error->status) {
case 0: case 0:
if (services_discovered >= 10) {
ESP_LOGE(TAG, "Too many services, ignoring");
break;
}
memcpy(service_result + services_discovered, service, sizeof(struct ble_gatt_svc)); memcpy(service_result + services_discovered, service, sizeof(struct ble_gatt_svc));
services_discovered++; services_discovered++;
uuid16 = ble_uuid_u16(&service->uuid.u); uuid16 = ble_uuid_u16(&service->uuid.u);
@@ -223,6 +304,10 @@ chr_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
case 0: case 0:
ESP_LOGD(TAG, "Char discovered : def handle : %04x, val_handle : %04x, properties : %02x\n, uuid : %04x", ESP_LOGD(TAG, "Char discovered : def handle : %04x, val_handle : %04x, properties : %02x\n, uuid : %04x",
chr->def_handle, chr->val_handle, chr->properties, ble_uuid_u16(&chr->uuid.u)); chr->def_handle, chr->val_handle, chr->properties, ble_uuid_u16(&chr->uuid.u));
if (chrs_discovered >= 20) {
ESP_LOGE(TAG, "Too many characteristics, ignoring");
break;
}
memcpy(chrs + chrs_discovered, chr, sizeof(struct ble_gatt_chr)); memcpy(chrs + chrs_discovered, chr, sizeof(struct ble_gatt_chr));
chrs_discovered++; chrs_discovered++;
break; break;
@@ -262,6 +347,10 @@ desc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
case 0: case 0:
ESP_LOGD(TAG, "DISC discovered : handle : %04x, uuid : %04x", ESP_LOGD(TAG, "DISC discovered : handle : %04x, uuid : %04x",
dsc->handle, ble_uuid_u16(&dsc->uuid.u)); dsc->handle, ble_uuid_u16(&dsc->uuid.u));
if (dscs_discovered >= 20) {
ESP_LOGE(TAG, "Too many descriptors, ignoring");
break;
}
memcpy(dscr + dscs_discovered, dsc, sizeof(struct ble_gatt_dsc)); memcpy(dscr + dscs_discovered, dsc, sizeof(struct ble_gatt_dsc));
dscs_discovered++; dscs_discovered++;
break; break;
@@ -296,45 +385,52 @@ static void read_device_services(esp_hidh_dev_t *dev)
uint16_t suuid, cuuid, duuid; uint16_t suuid, cuuid, duuid;
uint16_t chandle, dhandle; uint16_t chandle, dhandle;
esp_hidh_dev_report_t *report = NULL; esp_hidh_dev_report_t *report = NULL;
uint8_t *rdata = 0; uint8_t *rdata = NULL;
uint16_t rlen = 0; uint16_t rlen = 0;
esp_hid_report_item_t *r; esp_hid_report_item_t *r;
esp_hid_report_map_t *map; esp_hid_report_map_t *map;
struct ble_gatt_svc service_result[HIDH_MAX_SVCS];
struct ble_gatt_svc service_result[10]; uint16_t svc_count;
uint16_t dcount = 10;
uint8_t hidindex = 0; uint8_t hidindex = 0;
int rc; int rc;
services_discovered = 0;
esp_hidh_dev_lock(dev);
rc = ble_gattc_disc_all_svcs(dev->ble.conn_id, svc_disced, service_result); rc = ble_gattc_disc_all_svcs(dev->ble.conn_id, svc_disced, service_result);
if (rc != 0) { if (rc != 0) {
ESP_LOGD(TAG, "Error discovering services : %d", rc); ESP_LOGD(TAG, "Error discovering services : %d", rc);
assert(rc != 0); goto done;
} }
WAIT_CB(); WAIT_CB();
if (status != 0) { if (status != 0) {
ESP_LOGE(TAG, "failed to find services"); ESP_LOGE(TAG, "failed to find services");
assert(status == 0); goto done;
}
svc_count = services_discovered;
if (svc_count > HIDH_MAX_SVCS) {
ESP_LOGE(TAG, "Too many services %u (max %d)", svc_count, HIDH_MAX_SVCS);
goto done;
} }
dcount = services_discovered; /* fatal if services are more than 10 */
if (rc == ESP_OK) {
ESP_LOGD(TAG, "Found %u HID Services", dev->config.report_maps_len); ESP_LOGD(TAG, "Found %u HID Services", dev->config.report_maps_len);
dev->config.report_maps = (esp_hid_raw_report_map_t *)malloc(dev->config.report_maps_len * sizeof(esp_hid_raw_report_map_t)); if (dev->config.report_maps_len > 0) {
dev->config.report_maps = (esp_hid_raw_report_map_t *)calloc(dev->config.report_maps_len,
sizeof(esp_hid_raw_report_map_t));
if (dev->config.report_maps == NULL) { if (dev->config.report_maps == NULL) {
ESP_LOGE(TAG, "malloc report maps failed"); ESP_LOGE(TAG, "malloc report maps failed");
return; goto done;
} }
dev->protocol_mode = (uint8_t *)malloc(dev->config.report_maps_len * sizeof(uint8_t)); dev->protocol_mode = (uint8_t *)calloc(dev->config.report_maps_len, sizeof(uint8_t));
if (dev->protocol_mode == NULL) { if (dev->protocol_mode == NULL) {
ESP_LOGE(TAG, "malloc protocol_mode failed"); ESP_LOGE(TAG, "malloc protocol_mode failed");
return; free(dev->config.report_maps);
dev->config.report_maps = NULL;
goto done;
}
} }
/* read characteristic value may fail, so we should init report maps */ for (uint16_t s = 0; s < svc_count; s++) {
memset(dev->config.report_maps, 0, dev->config.report_maps_len * sizeof(esp_hid_raw_report_map_t));
for (uint16_t s = 0; s < dcount; s++) {
suuid = ble_uuid_u16(&service_result[s].uuid.u); suuid = ble_uuid_u16(&service_result[s].uuid.u);
ESP_LOGD(TAG, "Service discovered : start_handle : %d, end handle : %d, uuid: 0x%04x", ESP_LOGD(TAG, "Service discovered : start_handle : %d, end handle : %d, uuid: 0x%04x",
service_result[s].start_handle, service_result[s].end_handle, suuid); service_result[s].start_handle, service_result[s].end_handle, suuid);
@@ -342,32 +438,46 @@ static void read_device_services(esp_hidh_dev_t *dev)
if (suuid != BLE_SVC_BAS_UUID16 if (suuid != BLE_SVC_BAS_UUID16
&& suuid != BLE_SVC_DIS_UUID16 && suuid != BLE_SVC_DIS_UUID16
&& suuid != BLE_SVC_HID_UUID16 && suuid != BLE_SVC_HID_UUID16
&& suuid != 0x1800) {//device name? && suuid != 0x1800) { /* device name? */
continue; continue;
} }
struct ble_gatt_chr char_result[20]; struct ble_gatt_chr char_result[HIDH_MAX_CHRS];
uint16_t ccount = 20; uint16_t ccount = HIDH_MAX_CHRS;
rc = ble_gattc_disc_all_chrs(dev->ble.conn_id, service_result[s].start_handle, rc = ble_gattc_disc_all_chrs(dev->ble.conn_id, service_result[s].start_handle,
service_result[s].end_handle, chr_disced, char_result); service_result[s].end_handle, chr_disced, char_result);
if (rc != 0) {
ESP_LOGE(TAG, "failed to start char discovery for service : %d, rc=%d", s, rc);
chrs_discovered = 0;
continue;
}
WAIT_CB(); WAIT_CB();
if (status != 0) { if (status != 0) {
ESP_LOGE(TAG, "failed to find chars for service : %d", s); ESP_LOGE(TAG, "failed to find chars for service : %u", s);
assert(status == 0); chrs_discovered = 0;
continue;
} }
ccount = chrs_discovered; ccount = chrs_discovered;
if (rc == ESP_OK) { if (ccount > HIDH_MAX_CHRS) {
ESP_LOGE(TAG, "Too many characteristics %u (max %d)", ccount, HIDH_MAX_CHRS);
chrs_discovered = 0;
continue;
}
if (rc == 0) {
for (uint16_t c = 0; c < ccount; c++) { for (uint16_t c = 0; c < ccount; c++) {
cuuid = ble_uuid_u16(&char_result[c].uuid.u); cuuid = ble_uuid_u16(&char_result[c].uuid.u);
chandle = char_result[c].val_handle; chandle = char_result[c].val_handle;
ESP_LOGD(TAG, " CHAR:(%d), handle: %d, perm: 0x%02x, uuid: 0x%04x", ESP_LOGD(TAG, " CHAR:(%d), handle: %d, perm: 0x%02x, uuid: 0x%04x",
c + 1, chandle, char_result[c].properties, cuuid); c + 1, chandle, char_result[c].properties, cuuid);
if (suuid == BLE_SVC_GAP_UUID16) { if (suuid == BLE_SVC_GAP_UUID16) {
if (dev->config.device_name == NULL && cuuid == BLE_SVC_GAP_CHR_UUID16_DEVICE_NAME if (dev->config.device_name == NULL && cuuid == BLE_SVC_GAP_CHR_UUID16_DEVICE_NAME
&& (char_result[c].properties & BLE_GATT_CHR_PROP_READ) != 0) { && (char_result[c].properties & BLE_GATT_CHR_PROP_READ) != 0) {
if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) { if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) {
dev->config.device_name = (const char *)rdata; char *dn = nimble_hidh_dup_cstr(rdata, rlen);
if (dn) {
free((void *)dev->config.device_name);
dev->config.device_name = dn;
}
} }
} else { } else {
continue; continue;
@@ -387,14 +497,20 @@ static void read_device_services(esp_hidh_dev_t *dev)
dev->config.product_id = *((uint16_t *)&rdata[3]); dev->config.product_id = *((uint16_t *)&rdata[3]);
dev->config.version = *((uint16_t *)&rdata[5]); dev->config.version = *((uint16_t *)&rdata[5]);
} }
free(rdata);
} else if (cuuid == BLE_SVC_DIS_CHR_UUID16_MANUFACTURER_NAME) { } else if (cuuid == BLE_SVC_DIS_CHR_UUID16_MANUFACTURER_NAME) {
if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) { if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) {
dev->config.manufacturer_name = (const char *)rdata; char *mn = nimble_hidh_dup_cstr(rdata, rlen);
if (mn) {
free((void *)dev->config.manufacturer_name);
dev->config.manufacturer_name = mn;
}
} }
} else if (cuuid == BLE_SVC_DIS_CHR_UUID16_SERIAL_NUMBER) { } else if (cuuid == BLE_SVC_DIS_CHR_UUID16_SERIAL_NUMBER) {
if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) { if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) {
dev->config.serial_number = (const char *)rdata; char *sn = nimble_hidh_dup_cstr(rdata, rlen);
if (sn) {
free((void *)dev->config.serial_number);
dev->config.serial_number = sn;
} }
} }
} }
@@ -404,6 +520,9 @@ static void read_device_services(esp_hidh_dev_t *dev)
if (char_result[c].properties & BLE_GATT_CHR_PROP_READ) { if (char_result[c].properties & BLE_GATT_CHR_PROP_READ) {
if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) { if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) {
dev->protocol_mode[hidindex] = *((uint8_t *)rdata); dev->protocol_mode[hidindex] = *((uint8_t *)rdata);
free(rdata);
rdata = NULL;
}
} }
} }
continue; continue;
@@ -411,7 +530,10 @@ static void read_device_services(esp_hidh_dev_t *dev)
if (cuuid == BLE_SVC_HID_CHR_UUID16_REPORT_MAP) { if (cuuid == BLE_SVC_HID_CHR_UUID16_REPORT_MAP) {
if (char_result[c].properties & BLE_GATT_CHR_PROP_READ) { if (char_result[c].properties & BLE_GATT_CHR_PROP_READ) {
if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) { if (read_char(dev->ble.conn_id, chandle, &rdata, &rlen) == 0 && rlen) {
dev->config.report_maps[hidindex].data = (const uint8_t *)rdata; uint8_t *copy = nimble_hidh_dup_bytes(rdata, rlen);
if (copy) {
free((void *)dev->config.report_maps[hidindex].data);
dev->config.report_maps[hidindex].data = copy;
dev->config.report_maps[hidindex].len = rlen; dev->config.report_maps[hidindex].len = rlen;
} }
} }
@@ -421,7 +543,7 @@ static void read_device_services(esp_hidh_dev_t *dev)
report = (esp_hidh_dev_report_t *)malloc(sizeof(esp_hidh_dev_report_t)); report = (esp_hidh_dev_report_t *)malloc(sizeof(esp_hidh_dev_report_t));
if (report == NULL) { if (report == NULL) {
ESP_LOGE(TAG, "malloc esp_hidh_dev_report_t failed"); ESP_LOGE(TAG, "malloc esp_hidh_dev_report_t failed");
return; goto done;
} }
report->next = NULL; report->next = NULL;
report->permissions = char_result[c].properties; report->permissions = char_result[c].properties;
@@ -450,28 +572,76 @@ static void read_device_services(esp_hidh_dev_t *dev)
report->usage = ESP_HID_USAGE_GENERIC; report->usage = ESP_HID_USAGE_GENERIC;
report->value_len = 0; report->value_len = 0;
} }
} else {
report->protocol_mode = ESP_HID_PROTOCOL_MODE_REPORT;
report->report_type = 0;
report->usage = ESP_HID_USAGE_GENERIC;
report->value_len = 0;
}
} else { } else {
continue; continue;
} }
} }
struct ble_gatt_dsc descr_result[20]; struct ble_gatt_dsc descr_result[HIDH_MAX_DSCS];
uint16_t dcount = 20; uint16_t num_dsc = HIDH_MAX_DSCS;
uint16_t chr_end_handle; uint16_t chr_end_handle;
if (c + 1 < ccount) { if (c + 1 < ccount) {
chr_end_handle = char_result[c + 1].def_handle; chr_end_handle = char_result[c + 1].def_handle - 1;
} else { } else {
chr_end_handle = service_result[s].end_handle; chr_end_handle = service_result[s].end_handle;
} }
if (chr_end_handle <= char_result[c].val_handle) {
ESP_LOGD(TAG, "skip descriptor discovery for val_handle=%u end_handle=%u",
char_result[c].val_handle, chr_end_handle);
if (suuid == BLE_SVC_HID_UUID16 && report != NULL) {
report->next = dev->reports;
dev->reports = report;
dev->reports_len++;
}
dscs_discovered = 0;
continue;
}
/*
* NimBLE expects the characteristic value handle here; it
* discovers descriptors in the range (chr_val_handle, end].
*/
rc = ble_gattc_disc_all_dscs(dev->ble.conn_id, char_result[c].val_handle, rc = ble_gattc_disc_all_dscs(dev->ble.conn_id, char_result[c].val_handle,
chr_end_handle, desc_disced, descr_result); chr_end_handle, desc_disced, descr_result);
if (rc != 0) {
ESP_LOGD(TAG, "descriptor discovery not started for characteristic : %d, rc=%d", c, rc);
if (suuid == BLE_SVC_HID_UUID16 && report != NULL) {
report->next = dev->reports;
dev->reports = report;
dev->reports_len++;
}
dscs_discovered = 0;
continue;
}
WAIT_CB(); WAIT_CB();
if (status != 0) { if (status != 0) {
ESP_LOGE(TAG, "failed to find descriptors for characteristic : %d", c); ESP_LOGE(TAG, "failed to find descriptors for characteristic : %u", c);
assert(status == 0); if (suuid == BLE_SVC_HID_UUID16 && report != NULL) {
report->next = dev->reports;
dev->reports = report;
dev->reports_len++;
}
dscs_discovered = 0;
continue;
}
num_dsc = dscs_discovered;
if (num_dsc > HIDH_MAX_DSCS) {
ESP_LOGE(TAG, "Too many descriptors %u (max %d)", num_dsc, HIDH_MAX_DSCS);
if (suuid == BLE_SVC_HID_UUID16 && report != NULL) {
report->next = dev->reports;
dev->reports = report;
dev->reports_len++;
}
dscs_discovered = 0;
continue;
} }
dcount = dscs_discovered;
if (rc == ESP_OK) { if (rc == ESP_OK) {
for (uint16_t d = 0; d < dcount; d++) { for (uint16_t d = 0; d < num_dsc; d++) {
duuid = ble_uuid_u16(&descr_result[d].uuid.u); duuid = ble_uuid_u16(&descr_result[d].uuid.u);
dhandle = descr_result[d].handle; dhandle = descr_result[d].handle;
ESP_LOGD(TAG, " DESCR:(%d), handle: %d, uuid: 0x%04x", d + 1, dhandle, duuid); ESP_LOGD(TAG, " DESCR:(%d), handle: %d, uuid: 0x%04x", d + 1, dhandle, duuid);
@@ -485,7 +655,7 @@ static void read_device_services(esp_hidh_dev_t *dev)
if (duuid == BLE_GATT_DSC_CLT_CFG_UUID16 && (report->permissions & BLE_GATT_CHR_PROP_NOTIFY) != 0) { if (duuid == BLE_GATT_DSC_CLT_CFG_UUID16 && (report->permissions & BLE_GATT_CHR_PROP_NOTIFY) != 0) {
report->ccc_handle = dhandle; report->ccc_handle = dhandle;
} else if (duuid == BLE_SVC_HID_DSC_UUID16_RPT_REF) { } else if (duuid == BLE_SVC_HID_DSC_UUID16_RPT_REF) {
if (read_descr(dev->ble.conn_id, dhandle, &rdata, &rlen) == 0 && rlen) { if (read_descr(dev->ble.conn_id, dhandle, &rdata, &rlen) == 0 && rlen >= 2) {
report->report_id = rdata[0]; report->report_id = rdata[0];
report->report_type = rdata[1]; report->report_type = rdata[1];
free(rdata); free(rdata);
@@ -505,11 +675,11 @@ static void read_device_services(esp_hidh_dev_t *dev)
hidindex++; hidindex++;
} }
} }
chrs_discovered = 0; // reset the chars array for the next service chrs_discovered = 0;
} }
for (uint8_t d = 0; d < dev->config.report_maps_len; d++) { for (uint8_t d = 0; d < dev->config.report_maps_len; d++) {
if (dev->reports_len && dev->config.report_maps[d].len) { if (dev->reports_len && dev->config.report_maps[d].len && dev->config.report_maps[d].data) {
map = esp_hid_parse_report_map(dev->config.report_maps[d].data, dev->config.report_maps[d].len); map = esp_hid_parse_report_map(dev->config.report_maps[d].data, dev->config.report_maps[d].len);
if (map) { if (map) {
if (dev->ble.appearance == 0) { if (dev->ble.appearance == 0) {
@@ -543,40 +713,15 @@ static void read_device_services(esp_hidh_dev_t *dev)
} }
} }
} }
} done:
} esp_hidh_dev_unlock(dev);
static int
on_subscribe(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg)
{
uint16_t conn_id;
conn_id = *((uint16_t*) arg);
assert(conn_id == conn_handle);
MODLOG_DFLT(INFO, "Subscribe complete; status=%d conn_handle=%d "
"attr_handle=%d\n",
error->status, conn_handle, attr->handle);
SEND_CB();
return 0;
} }
static void register_for_notify(uint16_t conn_handle, uint16_t handle) static void register_for_notify(uint16_t conn_handle, uint16_t handle)
{ {
uint8_t value[2]; /* NimBLE notifications are enabled via CCC descriptor writes. */
int rc; (void)conn_handle;
value[0] = 1; (void)handle;
value[1] = 0;
rc = ble_gattc_write_flat(conn_handle, handle, value, sizeof value, on_subscribe, (void *)&conn_handle);
if (rc != 0) {
ESP_LOGE(TAG, "Error: Failed to subscribe to characteristic; "
"rc=%d\n", rc);
}
WAIT_CB();
} }
static int static int
@@ -585,52 +730,84 @@ on_write(uint16_t conn_handle,
struct ble_gatt_attr *attr, struct ble_gatt_attr *attr,
void *arg) void *arg)
{ {
uint16_t conn_id; esp_hidh_dev_t *dev = (esp_hidh_dev_t *)arg;
conn_id = *((uint16_t*) arg);
assert(conn_id == conn_handle); if (dev == NULL) {
s_gatt_op_status = BLE_HS_EAPP;
SEND_CB();
return 0;
}
if (esp_hidh_dev_get_by_conn_id(conn_handle) != dev) {
s_gatt_op_status = BLE_HS_ENOTCONN;
SEND_CB();
return 0;
}
MODLOG_DFLT(DEBUG, "write complete; status=%d conn_handle=%d " MODLOG_DFLT(DEBUG, "write complete; status=%d conn_handle=%d "
"attr_handle=%d\n", "attr_handle=%d\n",
error->status, conn_handle, attr->handle); error->status, conn_handle, attr->handle);
s_gatt_op_status = error->status;
SEND_CB(); SEND_CB();
return 0; return 0;
} }
static void write_char_descr(uint16_t conn_id, uint16_t handle, uint16_t value_len, uint8_t *value)
static void write_char_descr(esp_hidh_dev_t *dev, uint16_t handle, uint16_t value_len, uint8_t *value)
{ {
ble_gattc_write_flat(conn_id, handle, value, value_len, on_write, &conn_id); int rc;
if (dev == NULL || dev->ble.conn_id < 0) {
return;
}
s_gatt_op_status = 0;
rc = ble_gattc_write_flat(dev->ble.conn_id, handle, value, value_len, on_write, dev);
if (rc != 0) {
ESP_LOGE(TAG, "write_char_descr failed rc=%d", rc);
return;
}
WAIT_CB(); WAIT_CB();
if (s_gatt_op_status != 0) {
ESP_LOGE(TAG, "GATT write failed status=%d", s_gatt_op_status);
}
} }
static void attach_report_listeners(esp_hidh_dev_t *dev) static void attach_report_listeners(esp_hidh_dev_t *dev)
{ {
uint16_t ccc_data = 1;
esp_hidh_dev_report_t *report;
if (dev == NULL) { if (dev == NULL) {
return; return;
} }
uint16_t ccc_data = 1;
esp_hidh_dev_report_t *report = dev->reports;
//subscribe to battery notifications LOCK_OPS();
if (dev->ble.conn_id < 0 || !dev->connected) {
UNLOCK_OPS();
return;
}
report = dev->reports;
if (dev->ble.battery_handle) { if (dev->ble.battery_handle) {
register_for_notify(dev->ble.conn_id, dev->ble.battery_handle); register_for_notify(dev->ble.conn_id, dev->ble.battery_handle);
if (dev->ble.battery_ccc_handle) { if (dev->ble.battery_ccc_handle && dev->ble.conn_id >= 0 && dev->connected) {
//Write CCC descr to enable notifications write_char_descr(dev, dev->ble.battery_ccc_handle, 2, (uint8_t *)&ccc_data);
write_char_descr(dev->ble.conn_id, dev->ble.battery_ccc_handle, 2, (uint8_t *)&ccc_data);
} }
} }
while (report) { while (report) {
/* subscribe to notifications */ if (dev->ble.conn_id < 0 || !dev->connected) {
break;
}
if ((report->permissions & BLE_GATT_CHR_PROP_NOTIFY) != 0 && report->protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT) { if ((report->permissions & BLE_GATT_CHR_PROP_NOTIFY) != 0 && report->protocol_mode == ESP_HID_PROTOCOL_MODE_REPORT) {
register_for_notify(dev->ble.conn_id, report->handle); register_for_notify(dev->ble.conn_id, report->handle);
if (report->ccc_handle) { if (report->ccc_handle && dev->ble.conn_id >= 0 && dev->connected) {
/* Write CCC descr to enable notifications */ write_char_descr(dev, report->ccc_handle, 2, (uint8_t *)&ccc_data);
write_char_descr(dev->ble.conn_id, report->ccc_handle, 2, (uint8_t *)&ccc_data);
} }
} }
report = report->next; report = report->next;
} }
UNLOCK_OPS();
} }
static int static int
@@ -644,17 +821,32 @@ esp_hidh_gattc_event_handler(struct ble_gap_event *event, void *arg)
switch (event->type) { switch (event->type) {
case BLE_GAP_EVENT_CONNECT: case BLE_GAP_EVENT_CONNECT:
/* A new connection was established or a connection attempt failed. */ /* A new connection was established or a connection attempt failed. */
dev = (esp_hidh_dev_t *)arg;
if (event->connect.status == 0) { if (event->connect.status == 0) {
/* Connection successfully established. */ /* Connection successfully established. */
MODLOG_DFLT(INFO, "Connection established "); MODLOG_DFLT(INFO, "Connection established ");
rc = ble_gap_conn_find(event->connect.conn_handle, &desc); if (dev == NULL) {
assert(rc == 0); ESP_LOGE(TAG, "Connect success with NULL device context");
dev = esp_hidh_dev_get_by_bda(desc.peer_ota_addr.val); SEND_CB();
if (!dev) { return 0;
ESP_LOGE(TAG, "Connect received for unknown device");
} }
dev->status = -1; // set to not found and clear if HID service is found
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
if (rc != 0) {
ESP_LOGE(TAG, "ble_gap_conn_find failed; rc=%d", rc);
dev->status = rc;
dev->ble.conn_id = -1;
SEND_CB();
return 0;
}
if (memcmp(desc.peer_ota_addr.val, dev->addr.bda, sizeof(dev->addr.bda)) != 0 &&
memcmp(desc.peer_id_addr.val, dev->addr.bda, sizeof(dev->addr.bda)) != 0) {
ESP_LOGW(TAG, "Connect address mismatch vs open request");
}
dev->status = -1; /* not found until HID service is discovered */
dev->ble.conn_id = event->connect.conn_handle; dev->ble.conn_id = event->connect.conn_handle;
/* Try to set the mtu to the max value */ /* Try to set the mtu to the max value */
@@ -666,13 +858,22 @@ esp_hidh_gattc_event_handler(struct ble_gap_event *event, void *arg)
if (rc != 0) { if (rc != 0) {
ESP_LOGE(TAG, "Failed to negotiate MTU; rc = %d", rc); ESP_LOGE(TAG, "Failed to negotiate MTU; rc = %d", rc);
} }
rc = ble_gap_security_initiate(event->connect.conn_handle);
if (rc != 0) {
ESP_LOGW(TAG, "Failed to initiate security; rc = %d", rc);
}
SEND_CB(); SEND_CB();
} else { } else {
MODLOG_DFLT(ERROR, "Error: Connection failed; status=%d\n", MODLOG_DFLT(ERROR, "Error: Connection failed; status=%d\n",
event->connect.status); event->connect.status);
dev->status = event->connect.status; // ESP_GATT_CONN_FAIL_ESTABLISH; if (dev == NULL) {
ESP_LOGE(TAG, "Connect failed with NULL device context");
SEND_CB();
return 0;
}
dev->status = event->connect.status;
dev->ble.conn_id = -1; dev->ble.conn_id = -1;
SEND_CB(); // return from connection SEND_CB();
} }
return 0; return 0;
@@ -724,6 +925,16 @@ esp_hidh_gattc_event_handler(struct ble_gap_event *event, void *arg)
ESP_LOGE(TAG, "NOTIFY received for unknown device"); ESP_LOGE(TAG, "NOTIFY received for unknown device");
break; break;
} }
rc = ble_gap_conn_find(event->notify_rx.conn_handle, &desc);
if (rc != 0) {
ESP_LOGW(TAG, "Dropping notification: unknown link");
return 0;
}
/* Enforce encrypted notifications only for strict security policy. */
if ((ble_hs_cfg.sm_mitm || ble_hs_cfg.sm_sc_only) && !desc.sec_state.encrypted) {
ESP_LOGW(TAG, "Dropping notification on unencrypted link");
return 0;
}
if (event_loop_handle) { if (event_loop_handle) {
esp_hidh_event_data_t p = {0}; esp_hidh_event_data_t p = {0};
if (event->notify_rx.attr_handle == dev->ble.battery_handle) { if (event->notify_rx.attr_handle == dev->ble.battery_handle) {
@@ -737,7 +948,8 @@ esp_hidh_gattc_event_handler(struct ble_gap_event *event, void *arg)
esp_hidh_event_data_t *p_param = NULL; esp_hidh_event_data_t *p_param = NULL;
size_t event_data_size = sizeof(esp_hidh_event_data_t); size_t event_data_size = sizeof(esp_hidh_event_data_t);
if (report->protocol_mode != dev->protocol_mode[report->map_index]) { if (dev->protocol_mode == NULL ||
report->protocol_mode != dev->protocol_mode[report->map_index]) {
/* only pass the notifications in the current protocol mode */ /* only pass the notifications in the current protocol mode */
ESP_LOGD(TAG, "Wrong protocol mode, dropping notification"); ESP_LOGD(TAG, "Wrong protocol mode, dropping notification");
return 0; return 0;
@@ -784,6 +996,45 @@ esp_hidh_gattc_event_handler(struct ble_gap_event *event, void *arg)
break; break;
return 0; return 0;
case BLE_GAP_EVENT_ENC_CHANGE:
rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
if (rc != 0) {
ESP_LOGW(TAG, "ENC_CHANGE for unknown connection");
return 0;
}
ESP_LOGI(TAG, "ENC_CHANGE status=%d encrypted=%d authenticated=%d bonded=%d",
event->enc_change.status, desc.sec_state.encrypted,
desc.sec_state.authenticated, desc.sec_state.bonded);
return 0;
case BLE_GAP_EVENT_PASSKEY_ACTION:
switch (event->passkey.params.action) {
case BLE_SM_IOACT_NUMCMP: {
struct ble_sm_io pkey = {
.action = BLE_SM_IOACT_NUMCMP,
.numcmp_accept = 1,
};
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
ESP_LOGI(TAG, "PASSKEY NUMCMP auto-accept rc=%d", rc);
return 0;
}
case BLE_SM_IOACT_DISP:
ESP_LOGI(TAG, "PASSKEY display requested");
return 0;
case BLE_SM_IOACT_INPUT: {
struct ble_sm_io pkey = {
.action = BLE_SM_IOACT_INPUT,
.passkey = 123456,
};
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
ESP_LOGI(TAG, "PASSKEY INPUT injected rc=%d", rc);
return 0;
}
default:
ESP_LOGW(TAG, "PASSKEY action=%u", event->passkey.params.action);
return 0;
}
case BLE_GAP_EVENT_MTU: case BLE_GAP_EVENT_MTU:
MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n", MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n",
event->mtu.conn_handle, event->mtu.conn_handle,
@@ -802,6 +1053,9 @@ esp_hidh_gattc_event_handler(struct ble_gap_event *event, void *arg)
static esp_err_t esp_ble_hidh_dev_close(esp_hidh_dev_t *dev) static esp_err_t esp_ble_hidh_dev_close(esp_hidh_dev_t *dev)
{ {
if (dev == NULL || dev->ble.conn_id < 0) {
return ESP_FAIL;
}
return ble_gap_terminate(dev->ble.conn_id, BLE_ERR_REM_USER_CONN_TERM); return ble_gap_terminate(dev->ble.conn_id, BLE_ERR_REM_USER_CONN_TERM);
} }
@@ -817,9 +1071,20 @@ static esp_err_t esp_ble_hidh_dev_report_write(esp_hidh_dev_t *dev, size_t map_i
ESP_LOGE(TAG, "%s report %d takes maximum %d bytes. you have provided %d", esp_hid_report_type_str(report_type), report_id, report->value_len, value_len); ESP_LOGE(TAG, "%s report %d takes maximum %d bytes. you have provided %d", esp_hid_report_type_str(report_type), report_id, report->value_len, value_len);
return ESP_FAIL; return ESP_FAIL;
} }
rc = ble_gattc_write_flat(dev->ble.conn_id, report->handle, value, value_len, on_write, &dev->ble.conn_id); LOCK_OPS();
WAIT_CB();// this is not blocking in bluedroid code s_gatt_op_status = 0;
return rc; rc = ble_gattc_write_flat(dev->ble.conn_id, report->handle, value, value_len, on_write, dev);
if (rc != 0) {
WAIT_CB();
rc = s_gatt_op_status;
}
WAIT_CB();
rc = s_gatt_op_status;
UNLOCK_OPS();
if (rc != 0) {
return ESP_FAIL;
}
return ESP_OK;
} }
static esp_err_t esp_ble_hidh_dev_report_read(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type, size_t max_length, uint8_t *value, size_t *value_len) static esp_err_t esp_ble_hidh_dev_report_read(esp_hidh_dev_t *dev, size_t map_index, size_t report_id, int report_type, size_t max_length, uint8_t *value, size_t *value_len)
@@ -831,13 +1096,16 @@ static esp_err_t esp_ble_hidh_dev_report_read(esp_hidh_dev_t *dev, size_t map_in
} }
uint16_t len = max_length; uint16_t len = max_length;
uint8_t *v = NULL; uint8_t *v = NULL;
LOCK_OPS();
int s = read_char(dev->ble.conn_id, report->handle, &v, &len); int s = read_char(dev->ble.conn_id, report->handle, &v, &len);
UNLOCK_OPS();
if (s == 0) { if (s == 0) {
if (len > max_length) { if (len > max_length) {
len = max_length; len = max_length;
} }
*value_len = len; *value_len = len;
memcpy(value, v, len); memcpy(value, v, len);
free(v);
return ESP_OK; return ESP_OK;
} }
ESP_LOGE(TAG, "%s report %d read failed: 0x%x", esp_hid_report_type_str(report_type), report_id, s); ESP_LOGE(TAG, "%s report %d read failed: 0x%x", esp_hid_report_type_str(report_type), report_id, s);
@@ -854,6 +1122,10 @@ static void esp_ble_hidh_dev_dump(esp_hidh_dev_t *dev, FILE *fp)
fprintf(fp, "PID: 0x%04x, VID: 0x%04x, VERSION: 0x%04x\n", dev->config.product_id, dev->config.vendor_id, dev->config.version); fprintf(fp, "PID: 0x%04x, VID: 0x%04x, VERSION: 0x%04x\n", dev->config.product_id, dev->config.vendor_id, dev->config.version);
fprintf(fp, "Battery: Handle: %u, CCC Handle: %u\n", dev->ble.battery_handle, dev->ble.battery_ccc_handle); fprintf(fp, "Battery: Handle: %u, CCC Handle: %u\n", dev->ble.battery_handle, dev->ble.battery_ccc_handle);
fprintf(fp, "Report Maps: %d\n", dev->config.report_maps_len); fprintf(fp, "Report Maps: %d\n", dev->config.report_maps_len);
if (dev->config.report_maps_len > 0 && dev->config.report_maps == NULL) {
fprintf(fp, " (report_maps allocation missing)\n");
return;
}
for (uint8_t d = 0; d < dev->config.report_maps_len; d++) { for (uint8_t d = 0; d < dev->config.report_maps_len; d++) {
fprintf(fp, " Report Map Length: %d\n", dev->config.report_maps[d].len); fprintf(fp, " Report Map Length: %d\n", dev->config.report_maps[d].len);
esp_hidh_dev_report_t *report = dev->reports; esp_hidh_dev_report_t *report = dev->reports;
@@ -872,6 +1144,9 @@ static void esp_ble_hidh_dev_dump(esp_hidh_dev_t *dev, FILE *fp)
static void nimble_host_synced(void) static void nimble_host_synced(void)
{ {
if (s_prev_sync_cb && s_prev_sync_cb != nimble_host_synced) {
s_prev_sync_cb();
}
/* /*
no need to perform anything here no need to perform anything here
*/ */
@@ -879,7 +1154,14 @@ static void nimble_host_synced(void)
static void nimble_host_reset(int reason) static void nimble_host_reset(int reason)
{ {
if (s_prev_reset_cb && s_prev_reset_cb != nimble_host_reset) {
s_prev_reset_cb(reason);
}
MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason); MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason);
if (s_ble_hidh_cb_semaphore) {
xSemaphoreGive(s_ble_hidh_cb_semaphore);
}
} }
esp_err_t esp_ble_hidh_init(const esp_hidh_config_t *config) esp_err_t esp_ble_hidh_init(const esp_hidh_config_t *config)
@@ -892,7 +1174,19 @@ esp_err_t esp_ble_hidh_init(const esp_hidh_config_t *config)
s_ble_hidh_cb_semaphore = xSemaphoreCreateBinary(); s_ble_hidh_cb_semaphore = xSemaphoreCreateBinary();
ESP_RETURN_ON_FALSE(s_ble_hidh_cb_semaphore, ESP_RETURN_ON_FALSE(s_ble_hidh_cb_semaphore,
ESP_ERR_NO_MEM, TAG, "Allocation failed"); ESP_ERR_NO_MEM, TAG, "Allocation failed");
s_ble_hidh_op_mutex = xSemaphoreCreateMutex();
if (s_ble_hidh_op_mutex == NULL) {
vSemaphoreDelete(s_ble_hidh_cb_semaphore);
s_ble_hidh_cb_semaphore = NULL;
return ESP_ERR_NO_MEM;
}
s_prev_reset_cb = ble_hs_cfg.reset_cb;
s_prev_sync_cb = ble_hs_cfg.sync_cb;
/* Enforce secure pairing defaults for HID traffic. */
ble_hs_cfg.sm_io_cap = BLE_HS_IO_KEYBOARD_ONLY;
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_sc = 1;
ble_hs_cfg.reset_cb = nimble_host_reset; ble_hs_cfg.reset_cb = nimble_host_reset;
ble_hs_cfg.sync_cb = nimble_host_synced; ble_hs_cfg.sync_cb = nimble_host_synced;
return ret; return ret;
@@ -901,10 +1195,26 @@ esp_err_t esp_ble_hidh_init(const esp_hidh_config_t *config)
esp_err_t esp_ble_hidh_deinit(void) esp_err_t esp_ble_hidh_deinit(void)
{ {
ESP_RETURN_ON_FALSE(s_ble_hidh_cb_semaphore, ESP_ERR_INVALID_STATE, TAG, "Already deinitialized"); ESP_RETURN_ON_FALSE(s_ble_hidh_cb_semaphore, ESP_ERR_INVALID_STATE, TAG, "Already deinitialized");
free(s_read_data_val);
s_read_data_val = NULL;
s_read_data_len = 0;
s_read_status = 0;
if (s_ble_hidh_cb_semaphore) { if (s_ble_hidh_cb_semaphore) {
vSemaphoreDelete(s_ble_hidh_cb_semaphore); vSemaphoreDelete(s_ble_hidh_cb_semaphore);
s_ble_hidh_cb_semaphore = NULL; s_ble_hidh_cb_semaphore = NULL;
} }
if (s_ble_hidh_op_mutex) {
vSemaphoreDelete(s_ble_hidh_op_mutex);
s_ble_hidh_op_mutex = NULL;
}
if (ble_hs_cfg.reset_cb == nimble_host_reset) {
ble_hs_cfg.reset_cb = s_prev_reset_cb;
}
if (ble_hs_cfg.sync_cb == nimble_host_synced) {
ble_hs_cfg.sync_cb = s_prev_sync_cb;
}
return ESP_OK; return ESP_OK;
} }
@@ -931,10 +1241,12 @@ esp_hidh_dev_t *esp_ble_hidh_dev_open(uint8_t *bda, uint8_t address_type)
memcpy(addr.val, bda, sizeof(addr.val)); memcpy(addr.val, bda, sizeof(addr.val));
addr.type = address_type; addr.type = address_type;
ret = ble_gap_connect(own_addr_type, &addr, 30000, NULL, esp_hidh_gattc_event_handler, NULL); LOCK_OPS();
ret = ble_gap_connect(own_addr_type, &addr, 30000, NULL, esp_hidh_gattc_event_handler, dev);
if (ret) { if (ret) {
esp_hidh_dev_free_inner(dev); esp_hidh_dev_free_inner(dev);
ESP_LOGE(TAG, "esp_ble_gattc_open failed: %d", ret); ESP_LOGE(TAG, "esp_ble_gattc_open failed: %d", ret);
UNLOCK_OPS();
return NULL; return NULL;
} }
WAIT_CB(); WAIT_CB();
@@ -942,6 +1254,7 @@ esp_hidh_dev_t *esp_ble_hidh_dev_open(uint8_t *bda, uint8_t address_type)
ret = dev->status; ret = dev->status;
ESP_LOGE(TAG, "dev open failed! status: 0x%x", dev->status); ESP_LOGE(TAG, "dev open failed! status: 0x%x", dev->status);
esp_hidh_dev_free_inner(dev); esp_hidh_dev_free_inner(dev);
UNLOCK_OPS();
return NULL; return NULL;
} }
@@ -949,10 +1262,10 @@ esp_hidh_dev_t *esp_ble_hidh_dev_open(uint8_t *bda, uint8_t address_type)
dev->report_write = esp_ble_hidh_dev_report_write; dev->report_write = esp_ble_hidh_dev_report_write;
dev->report_read = esp_ble_hidh_dev_report_read; dev->report_read = esp_ble_hidh_dev_report_read;
dev->dump = esp_ble_hidh_dev_dump; dev->dump = esp_ble_hidh_dev_dump;
dev->connected = true;
/* perform service discovery and fill the report maps */ /* perform service discovery and fill the report maps */
read_device_services(dev); read_device_services(dev);
dev->connected = true;
if (event_loop_handle) { if (event_loop_handle) {
esp_hidh_event_data_t p = {0}; esp_hidh_event_data_t p = {0};
@@ -962,6 +1275,7 @@ esp_hidh_dev_t *esp_ble_hidh_dev_open(uint8_t *bda, uint8_t address_type)
} }
attach_report_listeners(dev); attach_report_listeners(dev);
UNLOCK_OPS();
return dev; return dev;
} }
#endif // CONFIG_BT_NIMBLE_HID_SERVICE #endif // CONFIG_BT_NIMBLE_HID_SERVICE
+6 -1
View File
@@ -197,6 +197,9 @@ esp_err_t esp_blufi_host_init(void)
ble_hs_cfg.gatts_register_cb = esp_blufi_gatt_svr_register_cb; ble_hs_cfg.gatts_register_cb = esp_blufi_gatt_svr_register_cb;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr; ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
/* FALSE POSITIVE: BLUFI uses its own app-layer security (DH + AES), not BLE SM.
* sm_mitm/sm_sc/sm_bonding are opt-in via Kconfig to let the example show
* multiple configurations; Just Works is acceptable for BLUFI provisioning. */
ble_hs_cfg.sm_io_cap = 4; ble_hs_cfg.sm_io_cap = 4;
#ifdef CONFIG_EXAMPLE_BONDING #ifdef CONFIG_EXAMPLE_BONDING
ble_hs_cfg.sm_bonding = 1; ble_hs_cfg.sm_bonding = 1;
@@ -242,11 +245,13 @@ esp_err_t esp_blufi_host_deinit(void)
{ {
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
esp_blufi_gatt_svr_deinit();
ret = nimble_port_stop(); ret = nimble_port_stop();
if (ret != ESP_OK) { if (ret != ESP_OK) {
return ret; return ret;
} }
esp_blufi_gatt_svr_deinit();
if (ret == 0) { if (ret == 0) {
esp_nimble_deinit(); esp_nimble_deinit();
} }
@@ -90,6 +90,82 @@ I (50557) HID_DEV_DEMO: Send the volume
... ...
``` ```
## NimBLE Logging Scheme
The following are verbatim NimBLE log excerpts from `logs.txt` for each device role.
### Media Mode
```text
I (496) HID_DEV_DEMO: setting ble device
I (496) HID_DEV_DEMO: BLE Host Task Started
I (506) HID_DEV_BLE: START
I (506) NimBLE: GAP procedure initiated: advertise;
I (35886) ESP_HID_GAP: connection established; status=0
I (35896) HID_DEV_BLE: CONNECT
I (35946) ESP_HID_GAP: mtu update event; conn_handle=0 cid=4 mtu=256
I (36476) ESP_HID_GAP: PASSKEY_ACTION_EVENT started
I (36476) ESP_HID_GAP: Enter passkey 123456on the peer side
I (37896) ESP_HID_GAP: subscribe event; conn_handle=0 attr_handle=37 reason=1 prevn=0 curn=1 previ=0 curi=0
I (40936) NimBLE: encryption change event; status=0
I (40946) HID_DEV_DEMO: Send the volume
I (40946) NimBLE: GATT procedure initiated: notify;
I (40946) NimBLE: att_handle=37
I (40946) NimBLE: notify_tx event; conn_handle=0 attr_handle=37 status=0 is_indication=0
```
### Keyboard Mode
```text
I (436) HID_DEV_DEMO: setting ble device
I (436) HID_DEV_DEMO: BLE Host Task Started
I (436) HID_DEV_BLE: START
I (446) NimBLE: GAP procedure initiated: advertise;
I (11526) ESP_HID_GAP: connection established; status=0
I (11526) HID_DEV_BLE: CONNECT
I (11576) ESP_HID_GAP: mtu update event; conn_handle=0 cid=4 mtu=256
I (12116) ESP_HID_GAP: PASSKEY_ACTION_EVENT started
I (12116) ESP_HID_GAP: Enter passkey 123456on the peer side
I (13826) ESP_HID_GAP: subscribe event; conn_handle=0 attr_handle=37 reason=1 prevn=0 curn=1 previ=0 curi=0
I (16576) NimBLE: encryption change event; status=0
########################################################################
BT hid keyboard demo usage:
########################################################################
I (22306) NimBLE: GATT procedure initiated: notify;
I (22306) NimBLE: att_handle=37
I (22306) NimBLE: notify_tx event; conn_handle=0 attr_handle=37 status=0 is_indication=0
```
### Mouse Mode
```text
I (446) HID_DEV_DEMO: setting ble device
I (446) HID_DEV_DEMO: BLE Host Task Started
I (456) HID_DEV_BLE: START
I (456) NimBLE: GAP procedure initiated: advertise;
I (10556) ESP_HID_GAP: connection established; status=0
I (10556) HID_DEV_BLE: CONNECT
I (10606) ESP_HID_GAP: mtu update event; conn_handle=0 cid=4 mtu=256
I (11136) ESP_HID_GAP: PASSKEY_ACTION_EVENT started
I (11136) ESP_HID_GAP: Enter passkey 123456on the peer side
I (12656) ESP_HID_GAP: subscribe event; conn_handle=0 attr_handle=37 reason=1 prevn=0 curn=1 previ=0 curi=0
I (15606) NimBLE: encryption change event; status=0
########################################################################
BT hid mouse demo usage:
You can input these value to simulate mouse: 'q', 'w', 'e', 'a', 's', 'd', 'h'
q -- click the left key
w -- move up
e -- click the right key
a -- move left
s -- move down
d -- move right
h -- show the help
########################################################################
I (18166) NimBLE: GATT procedure initiated: notify;
I (18166) NimBLE: att_handle=37
I (18166) NimBLE: notify_tx event; conn_handle=0 attr_handle=37 status=0 is_indication=0
```
## Troubleshooting ## Troubleshooting
1. When using NimBLE stack, some iOS devices do not show the volume pop up. To fix this, please set CONFIG_BT_NIMBLE_SM_LVL to value 2. iOS needs Authenticated Pairing with Encryption to show up the pop ups. 1. When using NimBLE stack, some iOS devices do not show the volume pop up. To fix this, please set CONFIG_BT_NIMBLE_SM_LVL to value 2. iOS needs Authenticated Pairing with Encryption to show up the pop ups.
@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Unlicense OR CC0-1.0 * SPDX-License-Identifier: Unlicense OR CC0-1.0
*/ */
@@ -856,9 +856,13 @@ nimble_hid_gap_event(struct ble_gap_event *event, void *arg)
/* Encryption has been enabled or disabled for this connection. */ /* Encryption has been enabled or disabled for this connection. */
MODLOG_DFLT(INFO, "encryption change event; status=%d ", MODLOG_DFLT(INFO, "encryption change event; status=%d ",
event->enc_change.status); event->enc_change.status);
if (event->enc_change.status == 0) {
rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
assert(rc == 0); assert(rc == 0);
ble_hid_task_start_up(); ble_hid_task_start_up();
} else {
ESP_LOGW(TAG, "encryption failed; waiting for disconnect/retry");
}
return 0; return 0;
case BLE_GAP_EVENT_NOTIFY_TX: case BLE_GAP_EVENT_NOTIFY_TX:
@@ -889,7 +893,7 @@ nimble_hid_gap_event(struct ble_gap_event *event, void *arg)
case BLE_GAP_EVENT_PASSKEY_ACTION: case BLE_GAP_EVENT_PASSKEY_ACTION:
ESP_LOGI(TAG, "PASSKEY_ACTION_EVENT started"); ESP_LOGI(TAG, "PASSKEY_ACTION_EVENT started");
struct ble_sm_io pkey = {0}; struct ble_sm_io pkey = {0};
int key = 0; int key = 1;
if (event->passkey.params.action == BLE_SM_IOACT_DISP) { if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
pkey.action = event->passkey.params.action; pkey.action = event->passkey.params.action;
+67
View File
@@ -94,6 +94,73 @@ I (18212) ESP_HIDH_DEMO: 00 00
... ...
``` ```
## NimBLE Logging Scheme
The following are verbatim NimBLE log excerpts from `logs.txt` for each device role.
### Media Mode (CCONTROL)
```text
I (2446) ESP_HIDH_DEMO: SCAN...
I (2446) NimBLE: GAP procedure initiated: discovery;
I (7456) NimBLE: discovery complete; reason=0
I (7466) NimBLE: GAP procedure initiated: connect;
I (7666) NimBLE: Connection established
I (7676) NimBLE: GATT procedure initiated: exchange mtu
I (7726) NimBLE: mtu update event; conn_handle=0 cid=4 mtu=256
I (8446) NIMBLE_HIDH: PASSKEY INPUT injected rc=0
I (9636) ESP_HIDH_DEMO: 02:50:f7:f9:55:60 OPEN: nimble
Report Maps: 1
Report Map Length: 111
CCONTROL INPUT REPORT, ID: 3, Length: 2, Permissions: 0x1a, Handle: 37, CCC Handle: 38
I (12776) NIMBLE_HIDH: ENC_CHANGE status=0 encrypted=1 authenticated=1 bonded=1
I (12826) ESP_HIDH_DEMO: 02:50:f7:f9:55:60 INPUT: CCONTROL, MAP: 0, ID: 3, Len: 2, Data:
I (12826) ESP_HIDH_DEMO: 80 00
I (12876) ESP_HIDH_DEMO: 02:50:f7:f9:55:60 INPUT: CCONTROL, MAP: 0, ID: 3, Len: 2, Data:
I (12876) ESP_HIDH_DEMO: 00 00
I (14876) ESP_HIDH_DEMO: 02:50:f7:f9:55:60 INPUT: CCONTROL, MAP: 0, ID: 3, Len: 2, Data:
I (14876) ESP_HIDH_DEMO: 40 00
```
### Keyboard Mode
```text
I (7426) ESP_HIDH_DEMO: SCAN: 2 results
BLE: 68:2e:f7:f9:55:60, RSSI: -57, USAGE: GENERIC, APPEARANCE: 0x03c0, ADDR_TYPE: '0', NAME: ESP Mouse
BLE: 02:50:f7:f9:55:60, RSSI: -30, USAGE: GENERIC, APPEARANCE: 0x03c0, ADDR_TYPE: '0', NAME: ESP Keyboard
I (7446) NimBLE: GAP procedure initiated: connect;
I (7836) NimBLE: Connection established
I (8606) NIMBLE_HIDH: PASSKEY INPUT injected rc=0
I (10086) ESP_HIDH_DEMO: 02:50:f7:f9:55:60 OPEN: nimble
Report Maps: 1
Report Map Length: 65
KEYBOARD INPUT REPORT, ID: 1, Length: 7, Permissions: 0x1a, Handle: 37, CCC Handle: 38
I (12936) NIMBLE_HIDH: ENC_CHANGE status=0 encrypted=1 authenticated=1 bonded=1
I (18636) ESP_HIDH_DEMO: 02:50:f7:f9:55:60 INPUT: KEYBOARD, MAP: 0, ID: 1, Len: 8, Data:
I (18636) ESP_HIDH_DEMO: 00 00 04 00 00 00 00 00
I (18686) ESP_HIDH_DEMO: 02:50:f7:f9:55:60 INPUT: KEYBOARD, MAP: 0, ID: 1, Len: 8, Data:
I (18686) ESP_HIDH_DEMO: 00 00 00 00 00 00 00 00
```
### Mouse Mode
```text
I (7426) ESP_HIDH_DEMO: SCAN: 1 results
BLE: 02:50:f7:f9:55:60, RSSI: -29, USAGE: GENERIC, APPEARANCE: 0x03c0, ADDR_TYPE: '0', NAME: ESP Mouse
I (7436) NimBLE: GAP procedure initiated: connect;
I (7666) NimBLE: Connection established
I (8436) NIMBLE_HIDH: PASSKEY INPUT injected rc=0
I (9716) ESP_HIDH_DEMO: 02:50:f7:f9:55:60 OPEN: nimble
Report Maps: 1
Report Map Length: 52
MOUSE INPUT REPORT, ID: 0, Length: 4, Permissions: 0x1a, Handle: 37, CCC Handle: 38
I (12766) NIMBLE_HIDH: ENC_CHANGE status=0 encrypted=1 authenticated=1 bonded=1
I (15316) ESP_HIDH_DEMO: 02:50:f7:f9:55:60 INPUT: MOUSE, MAP: 0, ID: 0, Len: 4, Data:
I (15316) ESP_HIDH_DEMO: 00 f6 00 00
I (15816) ESP_HIDH_DEMO: 02:50:f7:f9:55:60 INPUT: MOUSE, MAP: 0, ID: 0, Len: 4, Data:
I (15816) ESP_HIDH_DEMO: 00 00 0a 00
```
## Troubleshooting ## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
@@ -2,4 +2,4 @@
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, , 0x6000, nvs, data, nvs, , 0x6000,
phy_init, data, phy, , 0x1000, phy_init, data, phy, , 0x1000,
factory, app, factory, , 0x142000, factory, app, factory, , 0x143000,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, , 0x6000,
4 phy_init, data, phy, , 0x1000,
5 factory, app, factory, , 0x142000, factory, app, factory, , 0x143000,