Bluetooth: Host: Add whitelist support in Bluetooth Host API
Add whitelist support in the bluetooth host. Supported features: - Advertising with whitelist on scan requests, connect request ,or both - Scanning with whitelist - Creating connections using a whitelist (Auto connection procedure). Signed-off-by: Joakim Andersson <joakim.andersson@nordicsemi.no>
This commit is contained in:
parent
d075c91634
commit
a463d117f6
7 changed files with 449 additions and 26 deletions
|
@ -288,6 +288,13 @@ enum {
|
||||||
* reading its Central Address Resolution characteristic.
|
* reading its Central Address Resolution characteristic.
|
||||||
*/
|
*/
|
||||||
BT_LE_ADV_OPT_DIR_ADDR_RPA = BIT(5),
|
BT_LE_ADV_OPT_DIR_ADDR_RPA = BIT(5),
|
||||||
|
|
||||||
|
/* Use whitelist to filter devices that can request scan response data.
|
||||||
|
*/
|
||||||
|
BT_LE_ADV_OPT_FILTER_SCAN_REQ = BIT(6),
|
||||||
|
|
||||||
|
/* Use whitelist to filter devices that can connect. */
|
||||||
|
BT_LE_ADV_OPT_FILTER_CONN = BIT(7),
|
||||||
};
|
};
|
||||||
|
|
||||||
/** LE Advertising Parameters. */
|
/** LE Advertising Parameters. */
|
||||||
|
@ -400,14 +407,23 @@ int bt_le_adv_stop(void);
|
||||||
typedef void bt_le_scan_cb_t(const bt_addr_le_t *addr, s8_t rssi,
|
typedef void bt_le_scan_cb_t(const bt_addr_le_t *addr, s8_t rssi,
|
||||||
u8_t adv_type, struct net_buf_simple *buf);
|
u8_t adv_type, struct net_buf_simple *buf);
|
||||||
|
|
||||||
|
enum {
|
||||||
|
/* Filter duplicates. */
|
||||||
|
BT_LE_SCAN_FILTER_DUPLICATE = BIT(0),
|
||||||
|
|
||||||
|
/* Filter using whitelist. */
|
||||||
|
BT_LE_SCAN_FILTER_WHITELIST = BIT(1),
|
||||||
|
|
||||||
|
/* Filter using extended filter policies. */
|
||||||
|
BT_LE_SCAN_FILTER_EXTENDED = BIT(2),
|
||||||
|
};
|
||||||
|
|
||||||
/** LE scan parameters */
|
/** LE scan parameters */
|
||||||
struct bt_le_scan_param {
|
struct bt_le_scan_param {
|
||||||
/** Scan type (BT_HCI_LE_SCAN_ACTIVE or BT_HCI_LE_SCAN_PASSIVE) */
|
/** Scan type (BT_HCI_LE_SCAN_ACTIVE or BT_HCI_LE_SCAN_PASSIVE) */
|
||||||
u8_t type;
|
u8_t type;
|
||||||
|
|
||||||
/** Duplicate filtering (BT_HCI_LE_SCAN_FILTER_DUP_ENABLE or
|
/** Bit-field of scanning filter options. */
|
||||||
* BT_HCI_LE_SCAN_FILTER_DUP_DISABLE)
|
|
||||||
*/
|
|
||||||
u8_t filter_dup;
|
u8_t filter_dup;
|
||||||
|
|
||||||
/** Scan interval (N * 0.625 ms) */
|
/** Scan interval (N * 0.625 ms) */
|
||||||
|
@ -420,7 +436,7 @@ struct bt_le_scan_param {
|
||||||
/** Helper to declare scan parameters inline
|
/** Helper to declare scan parameters inline
|
||||||
*
|
*
|
||||||
* @param _type Scan Type (BT_HCI_LE_SCAN_ACTIVE/BT_HCI_LE_SCAN_PASSIVE)
|
* @param _type Scan Type (BT_HCI_LE_SCAN_ACTIVE/BT_HCI_LE_SCAN_PASSIVE)
|
||||||
* @param _filter Filter Duplicates
|
* @param _filter Filter options
|
||||||
* @param _interval Scan Interval (N * 0.625 ms)
|
* @param _interval Scan Interval (N * 0.625 ms)
|
||||||
* @param _window Scan Window (N * 0.625 ms)
|
* @param _window Scan Window (N * 0.625 ms)
|
||||||
*/
|
*/
|
||||||
|
@ -434,7 +450,7 @@ struct bt_le_scan_param {
|
||||||
|
|
||||||
/** Helper macro to enable active scanning to discover new devices. */
|
/** Helper macro to enable active scanning to discover new devices. */
|
||||||
#define BT_LE_SCAN_ACTIVE BT_LE_SCAN_PARAM(BT_HCI_LE_SCAN_ACTIVE, \
|
#define BT_LE_SCAN_ACTIVE BT_LE_SCAN_PARAM(BT_HCI_LE_SCAN_ACTIVE, \
|
||||||
BT_HCI_LE_SCAN_FILTER_DUP_ENABLE, \
|
BT_LE_SCAN_FILTER_DUPLICATE, \
|
||||||
BT_GAP_SCAN_FAST_INTERVAL, \
|
BT_GAP_SCAN_FAST_INTERVAL, \
|
||||||
BT_GAP_SCAN_FAST_WINDOW)
|
BT_GAP_SCAN_FAST_WINDOW)
|
||||||
|
|
||||||
|
@ -444,7 +460,7 @@ struct bt_le_scan_param {
|
||||||
* (e.g., UUID) are known to be placed in Advertising Data.
|
* (e.g., UUID) are known to be placed in Advertising Data.
|
||||||
*/
|
*/
|
||||||
#define BT_LE_SCAN_PASSIVE BT_LE_SCAN_PARAM(BT_HCI_LE_SCAN_PASSIVE, \
|
#define BT_LE_SCAN_PASSIVE BT_LE_SCAN_PARAM(BT_HCI_LE_SCAN_PASSIVE, \
|
||||||
BT_HCI_LE_SCAN_FILTER_DUP_ENABLE, \
|
BT_LE_SCAN_FILTER_DUPLICATE, \
|
||||||
BT_GAP_SCAN_FAST_INTERVAL, \
|
BT_GAP_SCAN_FAST_INTERVAL, \
|
||||||
BT_GAP_SCAN_FAST_WINDOW)
|
BT_GAP_SCAN_FAST_WINDOW)
|
||||||
|
|
||||||
|
@ -470,6 +486,48 @@ int bt_le_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb);
|
||||||
*/
|
*/
|
||||||
int bt_le_scan_stop(void);
|
int bt_le_scan_stop(void);
|
||||||
|
|
||||||
|
/** @brief Add device (LE) to whitelist.
|
||||||
|
*
|
||||||
|
* Add peer device LE address to the whitelist.
|
||||||
|
*
|
||||||
|
* @note The whitelist cannot be modified when an LE role is using
|
||||||
|
* the whitelist, i.e advertiser or scanner using a whitelist or automatic
|
||||||
|
* connecting to devices using whitelist.
|
||||||
|
*
|
||||||
|
* @param addr Bluetooth LE identity address.
|
||||||
|
*
|
||||||
|
* @return Zero on success or error code otherwise, positive in case
|
||||||
|
* of protocol error or negative (POSIX) in case of stack internal error.
|
||||||
|
*/
|
||||||
|
int bt_le_whitelist_add(const bt_addr_le_t *addr);
|
||||||
|
|
||||||
|
/** @brief Remove device (LE) from whitelist.
|
||||||
|
*
|
||||||
|
* Remove peer device LE address from the whitelist.
|
||||||
|
*
|
||||||
|
* @note The whitelist cannot be modified when an LE role is using
|
||||||
|
* the whitelist, i.e advertiser or scanner using a whitelist or automatic
|
||||||
|
* connecting to devices using whitelist.
|
||||||
|
*
|
||||||
|
* @param addr Bluetooth LE identity address.
|
||||||
|
*
|
||||||
|
* @return Zero on success or error code otherwise, positive in case
|
||||||
|
* of protocol error or negative (POSIX) in case of stack internal error.
|
||||||
|
*/
|
||||||
|
int bt_le_whitelist_rem(const bt_addr_le_t *addr);
|
||||||
|
|
||||||
|
/** @brief Clear whitelist.
|
||||||
|
*
|
||||||
|
* Clear all devices from the whitelist.
|
||||||
|
*
|
||||||
|
* @note The whitelist cannot be modified when an LE role is using
|
||||||
|
* the whitelist, i.e advertiser or scanner using a whitelist or automatic
|
||||||
|
* connecting to devices using whitelist.
|
||||||
|
*
|
||||||
|
* @return Zero on success or error code otherwise, positive in case
|
||||||
|
* of protocol error or negative (POSIX) in case of stack internal error.
|
||||||
|
*/
|
||||||
|
int bt_le_whitelist_clear(void);
|
||||||
|
|
||||||
/** @brief Set (LE) channel map.
|
/** @brief Set (LE) channel map.
|
||||||
*
|
*
|
||||||
|
|
|
@ -209,6 +209,8 @@ int bt_conn_disconnect(struct bt_conn *conn, u8_t reason);
|
||||||
* Allows initiate new LE link to remote peer using its address.
|
* Allows initiate new LE link to remote peer using its address.
|
||||||
* Returns a new reference that the the caller is responsible for managing.
|
* Returns a new reference that the the caller is responsible for managing.
|
||||||
*
|
*
|
||||||
|
* This uses the General Connection Establishment procedure.
|
||||||
|
*
|
||||||
* @param peer Remote address.
|
* @param peer Remote address.
|
||||||
* @param param Initial connection parameters.
|
* @param param Initial connection parameters.
|
||||||
*
|
*
|
||||||
|
@ -217,6 +219,22 @@ int bt_conn_disconnect(struct bt_conn *conn, u8_t reason);
|
||||||
struct bt_conn *bt_conn_create_le(const bt_addr_le_t *peer,
|
struct bt_conn *bt_conn_create_le(const bt_addr_le_t *peer,
|
||||||
const struct bt_le_conn_param *param);
|
const struct bt_le_conn_param *param);
|
||||||
|
|
||||||
|
/** @brief Automatically connect to remote devices in whitelist.
|
||||||
|
*
|
||||||
|
* This uses the Auto Connection Establishment procedure.
|
||||||
|
*
|
||||||
|
* @param param Initial connection parameters.
|
||||||
|
*
|
||||||
|
* @return Zero on success or (negative) error code on failure.
|
||||||
|
*/
|
||||||
|
int bt_conn_create_auto_le(const struct bt_le_conn_param *param);
|
||||||
|
|
||||||
|
/** @brief Stop automatic connect creation.
|
||||||
|
*
|
||||||
|
* @return Zero on success or (negative) error code on failure.
|
||||||
|
*/
|
||||||
|
int bt_conn_create_auto_stop(void);
|
||||||
|
|
||||||
/** @brief Automatically connect to remote device if it's in range.
|
/** @brief Automatically connect to remote device if it's in range.
|
||||||
*
|
*
|
||||||
* This function enables/disables automatic connection initiation.
|
* This function enables/disables automatic connection initiation.
|
||||||
|
|
|
@ -752,6 +752,11 @@ struct bt_hci_cp_le_set_random_address {
|
||||||
/* Needed in advertising reports when getting info about */
|
/* Needed in advertising reports when getting info about */
|
||||||
#define BT_LE_ADV_SCAN_RSP 0x04
|
#define BT_LE_ADV_SCAN_RSP 0x04
|
||||||
|
|
||||||
|
#define BT_LE_ADV_FP_NO_WHITELIST 0x00
|
||||||
|
#define BT_LE_ADV_FP_WHITELIST_SCAN_REQ 0x01
|
||||||
|
#define BT_LE_ADV_FP_WHITELIST_CONN_IND 0x02
|
||||||
|
#define BT_LE_ADV_FP_WHITELIST_BOTH 0x03
|
||||||
|
|
||||||
#define BT_HCI_OP_LE_SET_ADV_PARAM BT_OP(BT_OGF_LE, 0x0006)
|
#define BT_HCI_OP_LE_SET_ADV_PARAM BT_OP(BT_OGF_LE, 0x0006)
|
||||||
struct bt_hci_cp_le_set_adv_param {
|
struct bt_hci_cp_le_set_adv_param {
|
||||||
u16_t min_interval;
|
u16_t min_interval;
|
||||||
|
@ -794,6 +799,9 @@ struct bt_hci_cp_le_set_adv_enable {
|
||||||
#define BT_HCI_LE_SCAN_PASSIVE 0x00
|
#define BT_HCI_LE_SCAN_PASSIVE 0x00
|
||||||
#define BT_HCI_LE_SCAN_ACTIVE 0x01
|
#define BT_HCI_LE_SCAN_ACTIVE 0x01
|
||||||
|
|
||||||
|
#define BT_HCI_LE_SCAN_FP_NO_WHITELIST 0x00
|
||||||
|
#define BT_HCI_LE_SCAN_FP_USE_WHITELIST 0x01
|
||||||
|
|
||||||
struct bt_hci_cp_le_set_scan_param {
|
struct bt_hci_cp_le_set_scan_param {
|
||||||
u8_t scan_type;
|
u8_t scan_type;
|
||||||
u16_t interval;
|
u16_t interval;
|
||||||
|
@ -816,6 +824,10 @@ struct bt_hci_cp_le_set_scan_enable {
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
#define BT_HCI_OP_LE_CREATE_CONN BT_OP(BT_OGF_LE, 0x000d)
|
#define BT_HCI_OP_LE_CREATE_CONN BT_OP(BT_OGF_LE, 0x000d)
|
||||||
|
|
||||||
|
#define BT_HCI_LE_CREATE_CONN_FP_DIRECT 0x00
|
||||||
|
#define BT_HCI_LE_CREATE_CONN_FP_WHITELIST 0x01
|
||||||
|
|
||||||
struct bt_hci_cp_le_create_conn {
|
struct bt_hci_cp_le_create_conn {
|
||||||
u16_t scan_interval;
|
u16_t scan_interval;
|
||||||
u16_t scan_window;
|
u16_t scan_window;
|
||||||
|
|
|
@ -177,6 +177,24 @@ config BT_SETTINGS_CCC_STORE_ON_WRITE
|
||||||
workqueue stack space.
|
workqueue stack space.
|
||||||
endif # BT_SETTINGS
|
endif # BT_SETTINGS
|
||||||
|
|
||||||
|
config BT_WHITELIST
|
||||||
|
bool "Enable whitelist support"
|
||||||
|
help
|
||||||
|
This option enables the whitelist API. This takes advantage of the
|
||||||
|
whitelisting feature of a BLE controller.
|
||||||
|
The whitelist is a global list and the same whitelist is used
|
||||||
|
by both scanner and advertiser. The whitelist cannot be modified while
|
||||||
|
it is in use.
|
||||||
|
|
||||||
|
An Advertiser can whitelist which peers can connect or request scan
|
||||||
|
response data.
|
||||||
|
A scanner can whitelist advertiser for which it will generate
|
||||||
|
advertising reports.
|
||||||
|
Connections can be established automatically for whitelisted peers.
|
||||||
|
|
||||||
|
This options deprecates the bt_le_set_auto_conn API in favor of the
|
||||||
|
bt_conn_create_aute_le API.
|
||||||
|
|
||||||
if BT_CONN
|
if BT_CONN
|
||||||
|
|
||||||
if BT_HCI_ACL_FLOW_CONTROL
|
if BT_HCI_ACL_FLOW_CONTROL
|
||||||
|
|
|
@ -1871,10 +1871,12 @@ int bt_conn_disconnect(struct bt_conn *conn, u8_t reason)
|
||||||
* and we could send LE Create Connection as soon as the remote
|
* and we could send LE Create Connection as soon as the remote
|
||||||
* starts advertising.
|
* starts advertising.
|
||||||
*/
|
*/
|
||||||
|
#if !defined(CONFIG_BT_WHITELIST)
|
||||||
if (IS_ENABLED(CONFIG_BT_CENTRAL) &&
|
if (IS_ENABLED(CONFIG_BT_CENTRAL) &&
|
||||||
conn->type == BT_CONN_TYPE_LE) {
|
conn->type == BT_CONN_TYPE_LE) {
|
||||||
bt_le_set_auto_conn(&conn->le.dst, NULL);
|
bt_le_set_auto_conn(&conn->le.dst, NULL);
|
||||||
}
|
}
|
||||||
|
#endif /* !defined(CONFIG_BT_WHITELIST) */
|
||||||
|
|
||||||
switch (conn->state) {
|
switch (conn->state) {
|
||||||
case BT_CONN_CONNECT_SCAN:
|
case BT_CONN_CONNECT_SCAN:
|
||||||
|
@ -1927,6 +1929,148 @@ static void bt_conn_set_param_le(struct bt_conn *conn,
|
||||||
conn->le.timeout = param->timeout;
|
conn->le.timeout = param->timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_WHITELIST)
|
||||||
|
int bt_le_whitelist_add(const bt_addr_le_t *addr)
|
||||||
|
{
|
||||||
|
struct bt_hci_cp_le_add_dev_to_wl *cp;
|
||||||
|
struct net_buf *buf;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (!(bt_dev.le.wl_entries < bt_dev.le.wl_size)) {
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_ADD_DEV_TO_WL, sizeof(*cp));
|
||||||
|
if (!buf) {
|
||||||
|
return -ENOBUFS;
|
||||||
|
}
|
||||||
|
|
||||||
|
cp = net_buf_add(buf, sizeof(*cp));
|
||||||
|
bt_addr_le_copy(&cp->addr, addr);
|
||||||
|
|
||||||
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_ADD_DEV_TO_WL, buf, NULL);
|
||||||
|
if (err) {
|
||||||
|
BT_ERR("Failed to add device to whitelist");
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
bt_dev.le.wl_entries++;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_le_whitelist_rem(const bt_addr_le_t *addr)
|
||||||
|
{
|
||||||
|
struct bt_hci_cp_le_rem_dev_from_wl *cp;
|
||||||
|
struct net_buf *buf;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_REM_DEV_FROM_WL, sizeof(*cp));
|
||||||
|
if (!buf) {
|
||||||
|
return -ENOBUFS;
|
||||||
|
}
|
||||||
|
|
||||||
|
cp = net_buf_add(buf, sizeof(*cp));
|
||||||
|
bt_addr_le_copy(&cp->addr, addr);
|
||||||
|
|
||||||
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_REM_DEV_FROM_WL, buf, NULL);
|
||||||
|
if (err) {
|
||||||
|
BT_ERR("Failed to remove device from whitelist");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
bt_dev.le.wl_entries--;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_le_whitelist_clear(void)
|
||||||
|
{
|
||||||
|
int err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_CLEAR_WL, NULL, NULL);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
BT_ERR("Failed to clear whitelist");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
bt_dev.le.wl_entries = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_conn_create_auto_le(const struct bt_le_conn_param *param)
|
||||||
|
{
|
||||||
|
struct bt_conn *conn;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bt_le_conn_params_valid(param)) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (atomic_test_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (atomic_test_bit(bt_dev.flags, BT_DEV_AUTO_CONN)) {
|
||||||
|
return -EALREADY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bt_dev.le.wl_entries) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Don't start initiator if we have general discovery procedure. */
|
||||||
|
conn = bt_conn_lookup_state_le(NULL, BT_CONN_CONNECT_SCAN);
|
||||||
|
if (conn) {
|
||||||
|
bt_conn_unref(conn);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Don't start initiator if we have direct discovery procedure. */
|
||||||
|
conn = bt_conn_lookup_state_le(NULL, BT_CONN_CONNECT);
|
||||||
|
if (conn) {
|
||||||
|
bt_conn_unref(conn);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bt_le_auto_conn(param);
|
||||||
|
if (err) {
|
||||||
|
BT_ERR("Failed to start whitelist scan");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_set_bit(bt_dev.flags, BT_DEV_AUTO_CONN);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bt_conn_create_auto_stop(void)
|
||||||
|
{
|
||||||
|
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!atomic_test_bit(bt_dev.flags, BT_DEV_AUTO_CONN)) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_CONN_CANCEL, NULL,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
atomic_clear_bit(bt_dev.flags, BT_DEV_AUTO_CONN);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
BT_ERR("Failed to stop initiator");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* defined(CONFIG_BT_WHITELIST) */
|
||||||
|
|
||||||
struct bt_conn *bt_conn_create_le(const bt_addr_le_t *peer,
|
struct bt_conn *bt_conn_create_le(const bt_addr_le_t *peer,
|
||||||
const struct bt_le_conn_param *param)
|
const struct bt_le_conn_param *param)
|
||||||
{
|
{
|
||||||
|
@ -1945,6 +2089,11 @@ struct bt_conn *bt_conn_create_le(const bt_addr_le_t *peer,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_WHITELIST) &&
|
||||||
|
atomic_test_bit(bt_dev.flags, BT_DEV_AUTO_CONN)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, peer);
|
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, peer);
|
||||||
if (conn) {
|
if (conn) {
|
||||||
switch (conn->state) {
|
switch (conn->state) {
|
||||||
|
@ -1985,6 +2134,7 @@ struct bt_conn *bt_conn_create_le(const bt_addr_le_t *peer,
|
||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(CONFIG_BT_WHITELIST)
|
||||||
int bt_le_set_auto_conn(const bt_addr_le_t *addr,
|
int bt_le_set_auto_conn(const bt_addr_le_t *addr,
|
||||||
const struct bt_le_conn_param *param)
|
const struct bt_le_conn_param *param)
|
||||||
{
|
{
|
||||||
|
@ -2034,6 +2184,7 @@ int bt_le_set_auto_conn(const bt_addr_le_t *addr,
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
#endif /* !defined(CONFIG_BT_WHITELIST) */
|
||||||
#endif /* CONFIG_BT_CENTRAL */
|
#endif /* CONFIG_BT_CENTRAL */
|
||||||
|
|
||||||
#if defined(CONFIG_BT_PERIPHERAL)
|
#if defined(CONFIG_BT_PERIPHERAL)
|
||||||
|
|
|
@ -193,6 +193,15 @@ static inline void handle_event(u8_t event, struct net_buf *buf,
|
||||||
buf->len, bt_hex(buf->data, buf->len));
|
buf->len, bt_hex(buf->data, buf->len));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool is_wl_empty(void)
|
||||||
|
{
|
||||||
|
#if defined(CONFIG_BT_WHITELIST)
|
||||||
|
return !bt_dev.le.wl_entries;
|
||||||
|
#else
|
||||||
|
return true;
|
||||||
|
#endif /* defined(CONFIG_BT_WHITELIST) */
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL)
|
#if defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL)
|
||||||
static void report_completed_packet(struct net_buf *buf)
|
static void report_completed_packet(struct net_buf *buf)
|
||||||
{
|
{
|
||||||
|
@ -686,6 +695,69 @@ static void hci_num_completed_packets(struct net_buf *buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(CONFIG_BT_CENTRAL)
|
#if defined(CONFIG_BT_CENTRAL)
|
||||||
|
#if defined(CONFIG_BT_WHITELIST)
|
||||||
|
int bt_le_auto_conn(const struct bt_le_conn_param *conn_param)
|
||||||
|
{
|
||||||
|
struct net_buf *buf;
|
||||||
|
struct bt_hci_cp_le_create_conn *cp;
|
||||||
|
u8_t own_addr_type;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING)) {
|
||||||
|
err = set_le_scan_enable(BT_HCI_LE_SCAN_DISABLE);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_PRIVACY)) {
|
||||||
|
err = le_set_private_addr(BT_ID_DEFAULT);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
if (BT_FEAT_LE_PRIVACY(bt_dev.le.features)) {
|
||||||
|
own_addr_type = BT_HCI_OWN_ADDR_RPA_OR_RANDOM;
|
||||||
|
} else {
|
||||||
|
own_addr_type = BT_ADDR_LE_RANDOM;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const bt_addr_le_t *addr = &bt_dev.id_addr[BT_ID_DEFAULT];
|
||||||
|
|
||||||
|
/* If Static Random address is used as Identity address we
|
||||||
|
* need to restore it before creating connection. Otherwise
|
||||||
|
* NRPA used for active scan could be used for connection.
|
||||||
|
*/
|
||||||
|
if (addr->type == BT_ADDR_LE_RANDOM) {
|
||||||
|
set_random_address(&addr->a);
|
||||||
|
}
|
||||||
|
|
||||||
|
own_addr_type = addr->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_CREATE_CONN, sizeof(*cp));
|
||||||
|
if (!buf) {
|
||||||
|
return -ENOBUFS;
|
||||||
|
}
|
||||||
|
|
||||||
|
cp = net_buf_add(buf, sizeof(*cp));
|
||||||
|
(void)memset(cp, 0, sizeof(*cp));
|
||||||
|
|
||||||
|
cp->filter_policy = BT_HCI_LE_CREATE_CONN_FP_WHITELIST;
|
||||||
|
cp->own_addr_type = own_addr_type;
|
||||||
|
|
||||||
|
/* User Initiated procedure use fast scan parameters. */
|
||||||
|
cp->scan_interval = sys_cpu_to_le16(BT_GAP_SCAN_FAST_INTERVAL);
|
||||||
|
cp->scan_window = sys_cpu_to_le16(BT_GAP_SCAN_FAST_WINDOW);
|
||||||
|
|
||||||
|
cp->conn_interval_min = sys_cpu_to_le16(conn_param->interval_min);
|
||||||
|
cp->conn_interval_max = sys_cpu_to_le16(conn_param->interval_max);
|
||||||
|
cp->conn_latency = sys_cpu_to_le16(conn_param->latency);
|
||||||
|
cp->supervision_timeout = sys_cpu_to_le16(conn_param->timeout);
|
||||||
|
|
||||||
|
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_CONN, buf, NULL);
|
||||||
|
}
|
||||||
|
#endif /* defined(CONFIG_BT_WHITELIST) */
|
||||||
|
|
||||||
static int hci_le_create_conn(const struct bt_conn *conn)
|
static int hci_le_create_conn(const struct bt_conn *conn)
|
||||||
{
|
{
|
||||||
struct net_buf *buf;
|
struct net_buf *buf;
|
||||||
|
@ -790,12 +862,12 @@ static void hci_disconn_complete(struct net_buf *buf)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(CONFIG_BT_CENTRAL)
|
#if defined(CONFIG_BT_CENTRAL) && !defined(CONFIG_BT_WHITELIST)
|
||||||
if (atomic_test_bit(conn->flags, BT_CONN_AUTO_CONNECT)) {
|
if (atomic_test_bit(conn->flags, BT_CONN_AUTO_CONNECT)) {
|
||||||
bt_conn_set_state(conn, BT_CONN_CONNECT_SCAN);
|
bt_conn_set_state(conn, BT_CONN_CONNECT_SCAN);
|
||||||
bt_le_scan_update(false);
|
bt_le_scan_update(false);
|
||||||
}
|
}
|
||||||
#endif /* CONFIG_BT_CENTRAL */
|
#endif /* defined(CONFIG_BT_CENTRAL) && !defined(CONFIG_BT_WHITELIST) */
|
||||||
|
|
||||||
bt_conn_unref(conn);
|
bt_conn_unref(conn);
|
||||||
|
|
||||||
|
@ -996,13 +1068,14 @@ static void enh_conn_complete(struct bt_hci_evt_le_enh_conn_complete *evt)
|
||||||
*/
|
*/
|
||||||
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
|
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
|
||||||
|
|
||||||
|
#if !defined(CONFIG_BT_WHITELIST)
|
||||||
/* Check if device is marked for autoconnect. */
|
/* Check if device is marked for autoconnect. */
|
||||||
if (atomic_test_bit(conn->flags,
|
if (atomic_test_bit(conn->flags,
|
||||||
BT_CONN_AUTO_CONNECT)) {
|
BT_CONN_AUTO_CONNECT)) {
|
||||||
bt_conn_set_state(conn,
|
bt_conn_set_state(conn,
|
||||||
BT_CONN_CONNECT_SCAN);
|
BT_CONN_CONNECT_SCAN);
|
||||||
}
|
}
|
||||||
|
#endif /* !defined(CONFIG_BT_WHITELIST) */
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1028,14 +1101,24 @@ static void enh_conn_complete(struct bt_hci_evt_le_enh_conn_complete *evt)
|
||||||
|
|
||||||
conn = find_pending_connect(&id_addr);
|
conn = find_pending_connect(&id_addr);
|
||||||
|
|
||||||
if (evt->role == BT_CONN_ROLE_SLAVE) {
|
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) &&
|
||||||
|
evt->role == BT_HCI_ROLE_SLAVE) {
|
||||||
/*
|
/*
|
||||||
* clear advertising even if we are not able to add connection
|
* clear advertising even if we are not able to add connection
|
||||||
* object to keep host in sync with controller state
|
* object to keep host in sync with controller state
|
||||||
*/
|
*/
|
||||||
atomic_clear_bit(bt_dev.flags, BT_DEV_ADVERTISING);
|
atomic_clear_bit(bt_dev.flags, BT_DEV_ADVERTISING);
|
||||||
|
|
||||||
/* only for slave we may need to add new connection */
|
/* for slave we may need to add new connection */
|
||||||
|
if (!conn) {
|
||||||
|
conn = bt_conn_add_le(&id_addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_CENTRAL) &&
|
||||||
|
IS_ENABLED(CONFIG_BT_WHITELIST) &&
|
||||||
|
evt->role == BT_HCI_ROLE_MASTER) {
|
||||||
|
/* for whitelist initiator me may need to add new connection. */
|
||||||
if (!conn) {
|
if (!conn) {
|
||||||
conn = bt_conn_add_le(&id_addr);
|
conn = bt_conn_add_le(&id_addr);
|
||||||
}
|
}
|
||||||
|
@ -1059,7 +1142,8 @@ static void enh_conn_complete(struct bt_hci_evt_le_enh_conn_complete *evt)
|
||||||
* or responder address. Only slave needs to be updated. For master all
|
* or responder address. Only slave needs to be updated. For master all
|
||||||
* was set during outgoing connection creation.
|
* was set during outgoing connection creation.
|
||||||
*/
|
*/
|
||||||
if (conn->role == BT_HCI_ROLE_SLAVE) {
|
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) &&
|
||||||
|
conn->role == BT_HCI_ROLE_SLAVE) {
|
||||||
conn->id = bt_dev.adv_id;
|
conn->id = bt_dev.adv_id;
|
||||||
bt_addr_le_copy(&conn->le.init_addr, &peer_addr);
|
bt_addr_le_copy(&conn->le.init_addr, &peer_addr);
|
||||||
|
|
||||||
|
@ -1086,7 +1170,14 @@ static void enh_conn_complete(struct bt_hci_evt_le_enh_conn_complete *evt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conn->role == BT_HCI_ROLE_MASTER) {
|
if (IS_ENABLED(CONFIG_BT_CENTRAL) &&
|
||||||
|
conn->role == BT_HCI_ROLE_MASTER) {
|
||||||
|
if (IS_ENABLED(CONFIG_BT_WHITELIST) &&
|
||||||
|
atomic_test_bit(bt_dev.flags, BT_DEV_AUTO_CONN)) {
|
||||||
|
conn->id = BT_ID_DEFAULT;
|
||||||
|
atomic_clear_bit(bt_dev.flags, BT_DEV_AUTO_CONN);
|
||||||
|
}
|
||||||
|
|
||||||
bt_addr_le_copy(&conn->le.resp_addr, &peer_addr);
|
bt_addr_le_copy(&conn->le.resp_addr, &peer_addr);
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_BT_PRIVACY)) {
|
if (IS_ENABLED(CONFIG_BT_PRIVACY)) {
|
||||||
|
@ -3274,7 +3365,13 @@ static int start_le_scan(u8_t scan_type, u16_t interval, u16_t window)
|
||||||
*/
|
*/
|
||||||
set_param.interval = sys_cpu_to_le16(interval);
|
set_param.interval = sys_cpu_to_le16(interval);
|
||||||
set_param.window = sys_cpu_to_le16(window);
|
set_param.window = sys_cpu_to_le16(window);
|
||||||
set_param.filter_policy = 0x00;
|
|
||||||
|
if (IS_ENABLED(CONFIG_BT_WHITELIST) &&
|
||||||
|
atomic_test_bit(bt_dev.flags, BT_DEV_SCAN_WL)) {
|
||||||
|
set_param.filter_policy = BT_HCI_LE_SCAN_FP_USE_WHITELIST;
|
||||||
|
} else {
|
||||||
|
set_param.filter_policy = BT_HCI_LE_SCAN_FP_NO_WHITELIST;
|
||||||
|
}
|
||||||
|
|
||||||
if (IS_ENABLED(CONFIG_BT_PRIVACY)) {
|
if (IS_ENABLED(CONFIG_BT_PRIVACY)) {
|
||||||
err = le_set_private_addr(BT_ID_DEFAULT);
|
err = le_set_private_addr(BT_ID_DEFAULT);
|
||||||
|
@ -3878,6 +3975,29 @@ static void le_read_supp_states_complete(struct net_buf *buf)
|
||||||
bt_dev.le.states = sys_get_le64(rp->le_states);
|
bt_dev.le.states = sys_get_le64(rp->le_states);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_SMP)
|
||||||
|
static void le_read_resolving_list_size_complete(struct net_buf *buf)
|
||||||
|
{
|
||||||
|
struct bt_hci_rp_le_read_rl_size *rp = (void *)buf->data;
|
||||||
|
|
||||||
|
BT_DBG("Resolving List size %u", rp->rl_size);
|
||||||
|
|
||||||
|
bt_dev.le.rl_size = rp->rl_size;
|
||||||
|
}
|
||||||
|
#endif /* defined(CONFIG_BT_SMP) */
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_WHITELIST)
|
||||||
|
static void le_read_wl_size_complete(struct net_buf *buf)
|
||||||
|
{
|
||||||
|
struct bt_hci_rp_le_read_wl_size *rp =
|
||||||
|
(struct bt_hci_rp_le_read_wl_size *)buf->data;
|
||||||
|
|
||||||
|
BT_DBG("Whitelist size %u", rp->wl_size);
|
||||||
|
|
||||||
|
bt_dev.le.wl_size = rp->wl_size;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static int common_init(void)
|
static int common_init(void)
|
||||||
{
|
{
|
||||||
struct net_buf *rsp;
|
struct net_buf *rsp;
|
||||||
|
@ -4111,24 +4231,27 @@ static int le_init(void)
|
||||||
|
|
||||||
#if defined(CONFIG_BT_SMP)
|
#if defined(CONFIG_BT_SMP)
|
||||||
if (BT_FEAT_LE_PRIVACY(bt_dev.le.features)) {
|
if (BT_FEAT_LE_PRIVACY(bt_dev.le.features)) {
|
||||||
struct bt_hci_rp_le_read_rl_size *rp;
|
|
||||||
|
|
||||||
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_RL_SIZE, NULL,
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_RL_SIZE, NULL,
|
||||||
&rsp);
|
&rsp);
|
||||||
if (err) {
|
if (err) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
le_read_resolving_list_size_complete(rsp);
|
||||||
rp = (void *)rsp->data;
|
|
||||||
|
|
||||||
BT_DBG("Resolving List size %u", rp->rl_size);
|
|
||||||
|
|
||||||
bt_dev.le.rl_size = rp->rl_size;
|
|
||||||
|
|
||||||
net_buf_unref(rsp);
|
net_buf_unref(rsp);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_WHITELIST)
|
||||||
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_WL_SIZE, NULL,
|
||||||
|
&rsp);
|
||||||
|
if (err) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
le_read_wl_size_complete(rsp);
|
||||||
|
net_buf_unref(rsp);
|
||||||
|
#endif /* defined(CONFIG_BT_WHITELIST) */
|
||||||
|
|
||||||
return le_set_event_mask();
|
return le_set_event_mask();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5299,6 +5422,13 @@ static bool valid_adv_param(const struct bt_le_adv_param *param, bool dir_adv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_wl_empty() &&
|
||||||
|
((param->options & BT_LE_ADV_OPT_FILTER_SCAN_REQ) ||
|
||||||
|
(param->options & BT_LE_ADV_OPT_FILTER_CONN))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ((param->options & BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY) || !dir_adv) {
|
if ((param->options & BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY) || !dir_adv) {
|
||||||
if (param->interval_min > param->interval_max ||
|
if (param->interval_min > param->interval_max ||
|
||||||
param->interval_min < 0x0020 ||
|
param->interval_min < 0x0020 ||
|
||||||
|
@ -5441,6 +5571,21 @@ int bt_le_adv_start_internal(const struct bt_le_adv_param *param,
|
||||||
atomic_clear_bit(bt_dev.flags, BT_DEV_RPA_VALID);
|
atomic_clear_bit(bt_dev.flags, BT_DEV_RPA_VALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_WHITELIST)
|
||||||
|
if ((param->options & BT_LE_ADV_OPT_FILTER_SCAN_REQ) &&
|
||||||
|
(param->options & BT_LE_ADV_OPT_FILTER_CONN)) {
|
||||||
|
set_param.filter_policy = BT_LE_ADV_FP_WHITELIST_BOTH;
|
||||||
|
} else if (param->options & BT_LE_ADV_OPT_FILTER_SCAN_REQ) {
|
||||||
|
set_param.filter_policy = BT_LE_ADV_FP_WHITELIST_SCAN_REQ;
|
||||||
|
} else if (param->options & BT_LE_ADV_OPT_FILTER_CONN) {
|
||||||
|
set_param.filter_policy = BT_LE_ADV_FP_WHITELIST_CONN_IND;
|
||||||
|
} else {
|
||||||
|
#else
|
||||||
|
{
|
||||||
|
#endif /* defined(CONFIG_BT_WHITELIST) */
|
||||||
|
set_param.filter_policy = BT_LE_ADV_FP_NO_WHITELIST;
|
||||||
|
}
|
||||||
|
|
||||||
/* Set which local identity address we're advertising with */
|
/* Set which local identity address we're advertising with */
|
||||||
bt_dev.adv_id = param->id;
|
bt_dev.adv_id = param->id;
|
||||||
id_addr = &bt_dev.id_addr[param->id];
|
id_addr = &bt_dev.id_addr[param->id];
|
||||||
|
@ -5595,8 +5740,13 @@ static bool valid_le_scan_param(const struct bt_le_scan_param *param)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param->filter_dup != BT_HCI_LE_SCAN_FILTER_DUP_DISABLE &&
|
if (param->filter_dup &
|
||||||
param->filter_dup != BT_HCI_LE_SCAN_FILTER_DUP_ENABLE) {
|
~(BT_LE_SCAN_FILTER_DUPLICATE | BT_LE_SCAN_FILTER_WHITELIST)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_wl_empty() &&
|
||||||
|
param->filter_dup & BT_LE_SCAN_FILTER_WHITELIST) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5642,7 +5792,12 @@ int bt_le_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic_set_bit_to(bt_dev.flags, BT_DEV_SCAN_FILTER_DUP,
|
atomic_set_bit_to(bt_dev.flags, BT_DEV_SCAN_FILTER_DUP,
|
||||||
param->filter_dup);
|
param->filter_dup & BT_LE_SCAN_FILTER_DUPLICATE);
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_WHITELIST)
|
||||||
|
atomic_set_bit_to(bt_dev.flags, BT_DEV_SCAN_WL,
|
||||||
|
param->filter_dup & BT_LE_SCAN_FILTER_WHITELIST);
|
||||||
|
#endif /* defined(CONFIG_BT_WHITELIST) */
|
||||||
|
|
||||||
err = start_le_scan(param->type, param->interval, param->window);
|
err = start_le_scan(param->type, param->interval, param->window);
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|
|
@ -43,6 +43,8 @@ enum {
|
||||||
BT_DEV_EXPLICIT_SCAN,
|
BT_DEV_EXPLICIT_SCAN,
|
||||||
BT_DEV_ACTIVE_SCAN,
|
BT_DEV_ACTIVE_SCAN,
|
||||||
BT_DEV_SCAN_FILTER_DUP,
|
BT_DEV_SCAN_FILTER_DUP,
|
||||||
|
BT_DEV_SCAN_WL,
|
||||||
|
BT_DEV_AUTO_CONN,
|
||||||
|
|
||||||
BT_DEV_RPA_VALID,
|
BT_DEV_RPA_VALID,
|
||||||
|
|
||||||
|
@ -83,6 +85,13 @@ struct bt_dev_le {
|
||||||
*/
|
*/
|
||||||
u8_t rl_entries;
|
u8_t rl_entries;
|
||||||
#endif /* CONFIG_BT_SMP */
|
#endif /* CONFIG_BT_SMP */
|
||||||
|
|
||||||
|
#if defined(CONFIG_BT_WHITELIST)
|
||||||
|
/* Size of the controller whitelist. */
|
||||||
|
u8_t wl_size;
|
||||||
|
/* Number of entries in the resolving list. */
|
||||||
|
u8_t wl_entries;
|
||||||
|
#endif /* CONFIG_BT_WHITELIST */
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined(CONFIG_BT_BREDR)
|
#if defined(CONFIG_BT_BREDR)
|
||||||
|
@ -185,6 +194,8 @@ bool bt_le_conn_params_valid(const struct bt_le_conn_param *param);
|
||||||
|
|
||||||
int bt_le_scan_update(bool fast_scan);
|
int bt_le_scan_update(bool fast_scan);
|
||||||
|
|
||||||
|
int bt_le_auto_conn(const struct bt_le_conn_param *conn_param);
|
||||||
|
|
||||||
bool bt_addr_le_is_bonded(u8_t id, const bt_addr_le_t *addr);
|
bool bt_addr_le_is_bonded(u8_t id, const bt_addr_le_t *addr);
|
||||||
const bt_addr_le_t *bt_lookup_id_addr(u8_t id, const bt_addr_le_t *addr);
|
const bt_addr_le_t *bt_lookup_id_addr(u8_t id, const bt_addr_le_t *addr);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue