When developing Bluetooth applications, you typically run into
some errors. If you are an experienced Bluetooth developer,
you would typically have an HCI error lookup table in your memory.
Others might not.
This commit utilizes defines CONFIG_BT_DEBUG_HCI_ERR_TO_STR
and utilizes bt_hci_err_to_str() to print out HCI error strings
when enabled to improve the user experience.
Several alternatives where considered. This approach was chosen
as it had the best balance between readability, code size, and
implementation complexity.
The alternatives are listed below as a reference.
1. Macro defined format specifier:
```c
#define HCI_ERR_FMT "%s"
#define BT_HCI_ERR_TO_STR(err) (err)
#define HCI_ERR_FMT "%d"
#define BT_HCI_ERR_TO_STR(err) bt_hci_err_to_str((err))
LOG_INF("The event contained " HCI_ERR_FMT " as status",
BT_HCI_ERR_TO_STR(err));
```
Advantage: Space efficient: Code size does not increase
Disadvantage: Code becomes hard to read
2. Format specifier to always include both integer and string:
```c
static inline const char bt_hci_err_to_str(err)
{
return "";
}
LOG_INF("The event contained %s(0x%02x) as status",
bt_hci_err_to_str(err), err);
```
Advantage: Simple to use, implement, and read,
Disadvantage: Increases code size when CONFIG_BT_DEBUG_HCI_ERR_TO_STR
is disabled. The compiler seems unable to optimize away the unused
format specifier. Note: The size increase is only present when
logging is enabled.
3. Always print as string, allocate a stack variable when printing:
```c
const char *bt_hci_err_to_str(char *dst, size_t dst_size, uint8_t err)
{
snprintf(dst, dst_size, 0x%02x, err);
return dst;
}
LOG_INF("The event contained %s as status", BT_HCI_ERR_TO_STR(err));
```
Advantage: Very easy to read.
Disadvantage: Printing error codes becomes slow as it involves calling
snprint.
4. Implement a custom printf specifier, for example E.
This requires a global CONFIG_ERR_AS_STR as I assume we cannot have
one specifier for each type of error code.
Also, I assume we cannot start adding specifiers for each subsystem.
```c
#define BT_HCI_ERR_TO_STR(err) (err)
#define BT_HCI_ERR_TO_STR(err) bt_hci_err_to_str((err))
LOG_INF("The event contained %E as status", BT_HCI_ERR_TO_STR(err));
```
Advantage: Both efficient code and readable code.
Disadvantage: This requires a global CONFIG_ERR_AS_STR as I assume
we cannot have one specifier for each type of error code.
Also, I assume we cannot start adding specifiers for each subsystem.
That is, this approach is hard to implement correctly in a scalable
way.
Signed-off-by: Rubin Gerritsen <rubin.gerritsen@nordicsemi.no>
263 lines
5.8 KiB
C
263 lines
5.8 KiB
C
/*
|
|
* Copyright (c) 2017 Nordic Semiconductor ASA
|
|
* Copyright (c) 2015 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <zephyr/sys/check.h>
|
|
#include <zephyr/bluetooth/hci.h>
|
|
|
|
#include "ecc.h"
|
|
#include "hci_core.h"
|
|
|
|
#define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(bt_ecc);
|
|
|
|
static uint8_t pub_key[BT_PUB_KEY_LEN];
|
|
static sys_slist_t pub_key_cb_slist;
|
|
static bt_dh_key_cb_t dh_key_cb;
|
|
|
|
static const uint8_t debug_public_key[BT_PUB_KEY_LEN] = {
|
|
/* X */
|
|
0xe6, 0x9d, 0x35, 0x0e, 0x48, 0x01, 0x03, 0xcc,
|
|
0xdb, 0xfd, 0xf4, 0xac, 0x11, 0x91, 0xf4, 0xef,
|
|
0xb9, 0xa5, 0xf9, 0xe9, 0xa7, 0x83, 0x2c, 0x5e,
|
|
0x2c, 0xbe, 0x97, 0xf2, 0xd2, 0x03, 0xb0, 0x20,
|
|
/* Y */
|
|
0x8b, 0xd2, 0x89, 0x15, 0xd0, 0x8e, 0x1c, 0x74,
|
|
0x24, 0x30, 0xed, 0x8f, 0xc2, 0x45, 0x63, 0x76,
|
|
0x5c, 0x15, 0x52, 0x5a, 0xbf, 0x9a, 0x32, 0x63,
|
|
0x6d, 0xeb, 0x2a, 0x65, 0x49, 0x9c, 0x80, 0xdc
|
|
};
|
|
|
|
bool bt_pub_key_is_debug(uint8_t *cmp_pub_key)
|
|
{
|
|
return memcmp(cmp_pub_key, debug_public_key, BT_PUB_KEY_LEN) == 0;
|
|
}
|
|
|
|
int bt_pub_key_gen(struct bt_pub_key_cb *new_cb)
|
|
{
|
|
struct bt_pub_key_cb *cb;
|
|
int err;
|
|
|
|
/*
|
|
* We check for both "LE Read Local P-256 Public Key" and
|
|
* "LE Generate DH Key" support here since both commands are needed for
|
|
* ECC support. If "LE Generate DH Key" is not supported then there
|
|
* is no point in reading local public key.
|
|
*/
|
|
if (!BT_CMD_TEST(bt_dev.supported_commands, 34, 1) ||
|
|
!BT_CMD_TEST(bt_dev.supported_commands, 34, 2)) {
|
|
LOG_WRN("ECC HCI commands not available");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_USE_DEBUG_KEYS)) {
|
|
if (!BT_CMD_TEST(bt_dev.supported_commands, 41, 2)) {
|
|
LOG_WRN("ECC Debug keys HCI command not available");
|
|
} else {
|
|
atomic_set_bit(bt_dev.flags, BT_DEV_HAS_PUB_KEY);
|
|
__ASSERT_NO_MSG(new_cb->func != NULL);
|
|
new_cb->func(debug_public_key);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (!new_cb) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&pub_key_cb_slist, cb, node) {
|
|
if (cb == new_cb) {
|
|
LOG_WRN("Callback already registered");
|
|
return -EALREADY;
|
|
}
|
|
}
|
|
|
|
sys_slist_prepend(&pub_key_cb_slist, &new_cb->node);
|
|
|
|
if (atomic_test_and_set_bit(bt_dev.flags, BT_DEV_PUB_KEY_BUSY)) {
|
|
return 0;
|
|
}
|
|
|
|
atomic_clear_bit(bt_dev.flags, BT_DEV_HAS_PUB_KEY);
|
|
|
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_P256_PUBLIC_KEY, NULL, NULL);
|
|
if (err) {
|
|
|
|
LOG_ERR("Sending LE P256 Public Key command failed");
|
|
atomic_clear_bit(bt_dev.flags, BT_DEV_PUB_KEY_BUSY);
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&pub_key_cb_slist, cb, node) {
|
|
if (cb->func) {
|
|
cb->func(NULL);
|
|
}
|
|
}
|
|
|
|
sys_slist_init(&pub_key_cb_slist);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void bt_pub_key_hci_disrupted(void)
|
|
{
|
|
struct bt_pub_key_cb *cb;
|
|
|
|
atomic_clear_bit(bt_dev.flags, BT_DEV_PUB_KEY_BUSY);
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&pub_key_cb_slist, cb, node) {
|
|
if (cb->func) {
|
|
cb->func(NULL);
|
|
}
|
|
}
|
|
|
|
sys_slist_init(&pub_key_cb_slist);
|
|
}
|
|
|
|
const uint8_t *bt_pub_key_get(void)
|
|
{
|
|
if (IS_ENABLED(CONFIG_BT_USE_DEBUG_KEYS) &&
|
|
BT_CMD_TEST(bt_dev.supported_commands, 41, 2)) {
|
|
return debug_public_key;
|
|
}
|
|
|
|
if (atomic_test_bit(bt_dev.flags, BT_DEV_HAS_PUB_KEY)) {
|
|
return pub_key;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int hci_generate_dhkey_v1(const uint8_t *remote_pk)
|
|
{
|
|
struct bt_hci_cp_le_generate_dhkey *cp;
|
|
struct net_buf *buf;
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_GENERATE_DHKEY, sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
memcpy(cp->key, remote_pk, sizeof(cp->key));
|
|
|
|
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_GENERATE_DHKEY, buf, NULL);
|
|
}
|
|
|
|
static int hci_generate_dhkey_v2(const uint8_t *remote_pk, uint8_t key_type)
|
|
{
|
|
struct bt_hci_cp_le_generate_dhkey_v2 *cp;
|
|
struct net_buf *buf;
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_GENERATE_DHKEY_V2, sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
memcpy(cp->key, remote_pk, sizeof(cp->key));
|
|
cp->key_type = key_type;
|
|
|
|
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_GENERATE_DHKEY_V2, buf, NULL);
|
|
}
|
|
|
|
int bt_dh_key_gen(const uint8_t remote_pk[BT_PUB_KEY_LEN], bt_dh_key_cb_t cb)
|
|
{
|
|
int err;
|
|
|
|
if (dh_key_cb == cb) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
if (dh_key_cb || atomic_test_bit(bt_dev.flags, BT_DEV_PUB_KEY_BUSY)) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (!atomic_test_bit(bt_dev.flags, BT_DEV_HAS_PUB_KEY)) {
|
|
return -EADDRNOTAVAIL;
|
|
}
|
|
|
|
dh_key_cb = cb;
|
|
|
|
if (IS_ENABLED(CONFIG_BT_USE_DEBUG_KEYS) &&
|
|
BT_CMD_TEST(bt_dev.supported_commands, 41, 2)) {
|
|
err = hci_generate_dhkey_v2(remote_pk,
|
|
BT_HCI_LE_KEY_TYPE_DEBUG);
|
|
} else {
|
|
err = hci_generate_dhkey_v1(remote_pk);
|
|
}
|
|
|
|
if (err) {
|
|
dh_key_cb = NULL;
|
|
LOG_WRN("Failed to generate DHKey (err %d)", err);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void bt_hci_evt_le_pkey_complete(struct net_buf *buf)
|
|
{
|
|
struct bt_hci_evt_le_p256_public_key_complete *evt = (void *)buf->data;
|
|
struct bt_pub_key_cb *cb;
|
|
|
|
LOG_DBG("status: 0x%02x %s", evt->status, bt_hci_err_to_str(evt->status));
|
|
|
|
atomic_clear_bit(bt_dev.flags, BT_DEV_PUB_KEY_BUSY);
|
|
|
|
if (!evt->status) {
|
|
memcpy(pub_key, evt->key, BT_PUB_KEY_LEN);
|
|
atomic_set_bit(bt_dev.flags, BT_DEV_HAS_PUB_KEY);
|
|
}
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&pub_key_cb_slist, cb, node) {
|
|
if (cb->func) {
|
|
cb->func(evt->status ? NULL : pub_key);
|
|
}
|
|
}
|
|
|
|
sys_slist_init(&pub_key_cb_slist);
|
|
}
|
|
|
|
void bt_hci_evt_le_dhkey_complete(struct net_buf *buf)
|
|
{
|
|
struct bt_hci_evt_le_generate_dhkey_complete *evt = (void *)buf->data;
|
|
|
|
LOG_DBG("status: 0x%02x %s", evt->status, bt_hci_err_to_str(evt->status));
|
|
|
|
if (dh_key_cb) {
|
|
bt_dh_key_cb_t cb = dh_key_cb;
|
|
|
|
dh_key_cb = NULL;
|
|
cb(evt->status ? NULL : evt->dhkey);
|
|
}
|
|
}
|
|
|
|
#ifdef ZTEST_UNITTEST
|
|
uint8_t const *bt_ecc_get_public_key(void)
|
|
{
|
|
return pub_key;
|
|
}
|
|
|
|
uint8_t const *bt_ecc_get_internal_debug_public_key(void)
|
|
{
|
|
return debug_public_key;
|
|
}
|
|
|
|
sys_slist_t *bt_ecc_get_pub_key_cb_slist(void)
|
|
{
|
|
return &pub_key_cb_slist;
|
|
}
|
|
|
|
bt_dh_key_cb_t *bt_ecc_get_dh_key_cb(void)
|
|
{
|
|
return &dh_key_cb;
|
|
}
|
|
#endif /* ZTEST_UNITTEST */
|