mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +03:00
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:
@@ -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, ¶m, sizeof(esp_blufi_cb_param_t), NULL, NULL);
|
btc_transfer_context(&msg, ¶m, 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, ¶m, sizeof(esp_blufi_cb_param_t), btc_blufi_cb_deep_copy, btc_blufi_cb_deep_free);
|
btc_transfer_context(&msg, ¶m, 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, ¶m);
|
btc_blufi_cb_to_app(ESP_BLUFI_EVENT_INIT_FINISH, ¶m);
|
||||||
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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
|
Reference in New Issue
Block a user