Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1298 lines
27 KiB

/*
* Copyright (c) 2017-2021 Nordic Semiconductor ASA
* Copyright (c) 2015-2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/byteorder.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/buf.h>
#include "common/bt_str.h"
#include "host/hci_core.h"
#include "host/conn_internal.h"
#include "host/keys.h"
#include "sco_internal.h"
#define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_br);
#define RSSI_INVALID 127
enum __packed resolve_name_state {
RESOLVE_REMOTE_NAME_PENDING,
RESOLVE_REMOTE_NAME_RESOLVING,
RESOLVE_REMOTE_NAME_RESOLVED,
};
struct bt_br_discovery_result *discovery_results;
static size_t discovery_results_size;
static size_t discovery_results_count;
static sys_slist_t discovery_cbs = SYS_SLIST_STATIC_INIT(&discovery_cbs);
static int reject_conn(const bt_addr_t *bdaddr, uint8_t reason)
{
struct bt_hci_cp_reject_conn_req *cp;
struct net_buf *buf;
int err;
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
return -ENOBUFS;
}
cp = net_buf_add(buf, sizeof(*cp));
bt_addr_copy(&cp->bdaddr, bdaddr);
cp->reason = reason;
err = bt_hci_cmd_send_sync(BT_HCI_OP_REJECT_CONN_REQ, buf, NULL);
if (err) {
return err;
}
return 0;
}
static int accept_conn(const bt_addr_t *bdaddr)
{
struct bt_hci_cp_accept_conn_req *cp;
struct net_buf *buf;
int err;
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
return -ENOBUFS;
}
cp = net_buf_add(buf, sizeof(*cp));
bt_addr_copy(&cp->bdaddr, bdaddr);
cp->role = BT_HCI_ROLE_PERIPHERAL;
err = bt_hci_cmd_send_sync(BT_HCI_OP_ACCEPT_CONN_REQ, buf, NULL);
if (err) {
return err;
}
return 0;
}
void bt_hci_conn_req(struct net_buf *buf)
{
struct bt_hci_evt_conn_request *evt = (void *)buf->data;
struct bt_conn *conn;
LOG_DBG("conn req from %s, type 0x%02x", bt_addr_str(&evt->bdaddr), evt->link_type);
if (evt->link_type != BT_HCI_ACL) {
uint8_t err;
err = bt_esco_conn_req(evt);
if (err != BT_HCI_ERR_SUCCESS) {
reject_conn(&evt->bdaddr, err);
}
return;
}
conn = bt_conn_add_br(&evt->bdaddr);
if (!conn) {
reject_conn(&evt->bdaddr, BT_HCI_ERR_INSUFFICIENT_RESOURCES);
return;
}
accept_conn(&evt->bdaddr);
conn->role = BT_HCI_ROLE_PERIPHERAL;
bt_conn_set_state(conn, BT_CONN_INITIATING);
bt_conn_unref(conn);
}
static bool br_sufficient_key_size(struct bt_conn *conn)
{
struct bt_hci_cp_read_encryption_key_size *cp;
struct bt_hci_rp_read_encryption_key_size *rp;
struct net_buf *buf, *rsp;
uint8_t key_size;
int err;
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
LOG_ERR("Failed to allocate command buffer");
return false;
}
cp = net_buf_add(buf, sizeof(*cp));
cp->handle = sys_cpu_to_le16(conn->handle);
err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_ENCRYPTION_KEY_SIZE, buf, &rsp);
if (err) {
LOG_ERR("Failed to read encryption key size (err %d)", err);
return false;
}
if (rsp->len < sizeof(*rp)) {
LOG_ERR("Too small command complete for encryption key size");
net_buf_unref(rsp);
return false;
}
rp = (void *)rsp->data;
key_size = rp->key_size;
net_buf_unref(rsp);
LOG_DBG("Encryption key size is %u", key_size);
if (conn->sec_level == BT_SECURITY_L4) {
return key_size == BT_HCI_ENCRYPTION_KEY_SIZE_MAX;
}
return key_size >= CONFIG_BT_BR_MIN_ENC_KEY_SIZE;
}
bool bt_br_update_sec_level(struct bt_conn *conn)
{
if (!conn->encrypt) {
conn->sec_level = BT_SECURITY_L1;
return true;
}
if (conn->br.link_key) {
if (conn->br.link_key->flags & BT_LINK_KEY_AUTHENTICATED) {
if (conn->encrypt == BT_HCI_ENCRYPTION_ON_BR_AES_CCM) {
conn->sec_level = BT_SECURITY_L4;
} else {
conn->sec_level = BT_SECURITY_L3;
}
} else {
conn->sec_level = BT_SECURITY_L2;
}
} else {
LOG_WRN("No BR/EDR link key found");
conn->sec_level = BT_SECURITY_L2;
}
if (!br_sufficient_key_size(conn)) {
LOG_ERR("Encryption key size is not sufficient");
bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL);
return false;
}
if (conn->required_sec_level > conn->sec_level) {
LOG_ERR("Failed to set required security level");
bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL);
return false;
}
return true;
}
void bt_hci_synchronous_conn_complete(struct net_buf *buf)
{
struct bt_hci_evt_sync_conn_complete *evt = (void *)buf->data;
struct bt_conn *sco_conn;
uint16_t handle = sys_le16_to_cpu(evt->handle);
LOG_DBG("status 0x%02x, handle %u, type 0x%02x", evt->status, handle, evt->link_type);
sco_conn = bt_conn_lookup_addr_sco(&evt->bdaddr);
if (!sco_conn) {
LOG_ERR("Unable to find conn for %s", bt_addr_str(&evt->bdaddr));
return;
}
if (evt->status) {
sco_conn->err = evt->status;
bt_conn_set_state(sco_conn, BT_CONN_DISCONNECTED);
bt_conn_unref(sco_conn);
return;
}
sco_conn->handle = handle;
bt_conn_set_state(sco_conn, BT_CONN_CONNECTED);
bt_conn_unref(sco_conn);
}
void bt_hci_conn_complete(struct net_buf *buf)
{
struct bt_hci_evt_conn_complete *evt = (void *)buf->data;
struct bt_conn *conn;
struct bt_hci_cp_read_remote_features *cp;
uint16_t handle = sys_le16_to_cpu(evt->handle);
LOG_DBG("status 0x%02x, handle %u, type 0x%02x", evt->status, handle, evt->link_type);
conn = bt_conn_lookup_addr_br(&evt->bdaddr);
if (!conn) {
LOG_ERR("Unable to find conn for %s", bt_addr_str(&evt->bdaddr));
return;
}
if (evt->status) {
conn->err = evt->status;
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
bt_conn_unref(conn);
return;
}
conn->handle = handle;
conn->err = 0U;
conn->encrypt = evt->encr_enabled;
if (!bt_br_update_sec_level(conn)) {
bt_conn_unref(conn);
return;
}
bt_conn_set_state(conn, BT_CONN_CONNECTED);
atomic_set_bit_to(conn->flags, BT_CONN_BR_BONDABLE, bt_get_bondable());
bt_conn_connected(conn);
bt_conn_unref(conn);
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
return;
}
cp = net_buf_add(buf, sizeof(*cp));
cp->handle = evt->handle;
bt_hci_cmd_send_sync(BT_HCI_OP_READ_REMOTE_FEATURES, buf, NULL);
}
static int request_name(const bt_addr_t *addr, uint8_t pscan, uint16_t offset)
{
struct bt_hci_cp_remote_name_request *cp;
struct net_buf *buf;
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
return -ENOBUFS;
}
cp = net_buf_add(buf, sizeof(*cp));
bt_addr_copy(&cp->bdaddr, addr);
cp->pscan_rep_mode = pscan;
cp->reserved = 0x00; /* reserved, should be set to 0x00 */
cp->clock_offset = offset;
return bt_hci_cmd_send_sync(BT_HCI_OP_REMOTE_NAME_REQUEST, buf, NULL);
}
#define EIR_SHORT_NAME 0x08
#define EIR_COMPLETE_NAME 0x09
static bool eir_has_name(const uint8_t *eir)
{
int len = 240;
while (len) {
if (len < 2) {
break;
}
/* Look for early termination */
if (!eir[0]) {
break;
}
/* Check if field length is correct */
if (eir[0] > len - 1) {
break;
}
switch (eir[1]) {
case EIR_SHORT_NAME:
case EIR_COMPLETE_NAME:
if (eir[0] > 1) {
return true;
}
break;
default:
break;
}
/* Parse next AD Structure */
len -= eir[0] + 1;
eir += eir[0] + 1;
}
return false;
}
void bt_br_discovery_reset(void)
{
discovery_results = NULL;
discovery_results_size = 0;
discovery_results_count = 0;
}
static bool check_request_name(void)
{
int i;
bool resolving_names = false;
for (i = 0; i < discovery_results_count; i++) {
struct bt_br_discovery_priv *priv;
priv = &discovery_results[i]._priv;
if (eir_has_name(discovery_results[i].eir)) {
continue;
}
if (priv->resolve_state != RESOLVE_REMOTE_NAME_PENDING) {
continue;
}
if (request_name(&discovery_results[i].addr, priv->pscan_rep_mode,
priv->clock_offset)) {
priv->resolve_state = RESOLVE_REMOTE_NAME_RESOLVED;
continue;
}
priv->resolve_state = RESOLVE_REMOTE_NAME_RESOLVING;
resolving_names = true;
break;
}
return resolving_names;
}
static void report_discovery_results(void)
{
int i;
struct bt_br_discovery_cb *listener, *next;
for (i = 0; i < discovery_results_count; i++) {
struct bt_br_discovery_priv *priv;
priv = &discovery_results[i]._priv;
priv->resolve_state = RESOLVE_REMOTE_NAME_PENDING;
}
if (check_request_name()) {
return;
}
atomic_clear_bit(bt_dev.flags, BT_DEV_INQUIRY);
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&discovery_cbs, listener, next, node) {
if (listener->timeout) {
listener->timeout(discovery_results, discovery_results_count);
}
}
bt_br_discovery_reset();
}
void bt_hci_inquiry_complete(struct net_buf *buf)
{
struct bt_hci_evt_inquiry_complete *evt = (void *)buf->data;
if (evt->status) {
LOG_ERR("Failed to complete inquiry");
}
report_discovery_results();
}
static struct bt_br_discovery_result *get_result_slot(const bt_addr_t *addr, int8_t rssi)
{
struct bt_br_discovery_result *result = NULL;
size_t i;
/* check if already present in results */
for (i = 0; i < discovery_results_count; i++) {
if (bt_addr_eq(addr, &discovery_results[i].addr)) {
return &discovery_results[i];
}
}
/* Pick a new slot (if available) */
if (discovery_results_count < discovery_results_size) {
bt_addr_copy(&discovery_results[discovery_results_count].addr, addr);
return &discovery_results[discovery_results_count++];
}
/* ignore if invalid RSSI */
if (rssi == RSSI_INVALID) {
return NULL;
}
/*
* Pick slot with smallest RSSI that is smaller then passed RSSI
* TODO handle TX if present
*/
for (i = 0; i < discovery_results_size; i++) {
if (discovery_results[i].rssi > rssi) {
continue;
}
if (!result || result->rssi > discovery_results[i].rssi) {
result = &discovery_results[i];
}
}
if (result) {
LOG_DBG("Reusing slot (old %s rssi %d dBm)", bt_addr_str(&result->addr),
result->rssi);
bt_addr_copy(&result->addr, addr);
}
return result;
}
void bt_hci_inquiry_result_with_rssi(struct net_buf *buf)
{
uint8_t num_reports = net_buf_pull_u8(buf);
if (!atomic_test_bit(bt_dev.flags, BT_DEV_INQUIRY)) {
return;
}
LOG_DBG("number of results: %u", num_reports);
while (num_reports--) {
struct bt_hci_evt_inquiry_result_with_rssi *evt;
struct bt_br_discovery_result *result;
struct bt_br_discovery_priv *priv;
struct bt_br_discovery_cb *listener, *next;
if (buf->len < sizeof(*evt)) {
LOG_ERR("Unexpected end to buffer");
return;
}
evt = net_buf_pull_mem(buf, sizeof(*evt));
LOG_DBG("%s rssi %d dBm", bt_addr_str(&evt->addr), evt->rssi);
result = get_result_slot(&evt->addr, evt->rssi);
if (!result) {
return;
}
priv = &result->_priv;
priv->pscan_rep_mode = evt->pscan_rep_mode;
priv->clock_offset = evt->clock_offset;
memcpy(result->cod, evt->cod, 3);
result->rssi = evt->rssi;
/* we could reuse slot so make sure EIR is cleared */
(void)memset(result->eir, 0, sizeof(result->eir));
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&discovery_cbs, listener, next, node) {
if (listener->recv) {
listener->recv(result);
}
}
}
}
void bt_hci_extended_inquiry_result(struct net_buf *buf)
{
struct bt_hci_evt_extended_inquiry_result *evt = (void *)buf->data;
struct bt_br_discovery_result *result;
struct bt_br_discovery_priv *priv;
struct bt_br_discovery_cb *listener, *next;
if (!atomic_test_bit(bt_dev.flags, BT_DEV_INQUIRY)) {
return;
}
LOG_DBG("%s rssi %d dBm", bt_addr_str(&evt->addr), evt->rssi);
result = get_result_slot(&evt->addr, evt->rssi);
if (!result) {
return;
}
priv = &result->_priv;
priv->pscan_rep_mode = evt->pscan_rep_mode;
priv->clock_offset = evt->clock_offset;
result->rssi = evt->rssi;
memcpy(result->cod, evt->cod, 3);
memcpy(result->eir, evt->eir, sizeof(result->eir));
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&discovery_cbs, listener, next, node) {
if (listener->recv) {
listener->recv(result);
}
}
}
void bt_hci_remote_name_request_complete(struct net_buf *buf)
{
struct bt_hci_evt_remote_name_req_complete *evt = (void *)buf->data;
struct bt_br_discovery_result *result;
struct bt_br_discovery_priv *priv;
int eir_len = 240;
uint8_t *eir;
struct bt_br_discovery_cb *listener, *next;
result = get_result_slot(&evt->bdaddr, RSSI_INVALID);
if (!result) {
return;
}
priv = &result->_priv;
priv->resolve_state = RESOLVE_REMOTE_NAME_RESOLVED;
if (evt->status) {
goto check_names;
}
eir = result->eir;
while (eir_len) {
if (eir_len < 2) {
break;
}
/* Look for early termination */
if (!eir[0]) {
size_t name_len;
eir_len -= 2;
/* name is null terminated */
name_len = strlen((const char *)evt->name);
if (name_len > eir_len) {
eir[0] = eir_len + 1;
eir[1] = EIR_SHORT_NAME;
} else {
eir[0] = name_len + 1;
eir[1] = EIR_SHORT_NAME;
}
memcpy(&eir[2], evt->name, eir[0] - 1);
break;
}
/* Check if field length is correct */
if (eir[0] > eir_len - 1) {
break;
}
/* next EIR Structure */
eir_len -= eir[0] + 1;
eir += eir[0] + 1;
}
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&discovery_cbs, listener, next, node) {
if (listener->recv) {
listener->recv(result);
}
}
check_names:
/* if still need to request name */
if (check_request_name()) {
return;
}
/* all names resolved, report discovery results */
atomic_clear_bit(bt_dev.flags, BT_DEV_INQUIRY);
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&discovery_cbs, listener, next, node) {
if (listener->timeout) {
listener->timeout(discovery_results, discovery_results_count);
}
}
}
void bt_hci_read_remote_features_complete(struct net_buf *buf)
{
struct bt_hci_evt_remote_features *evt = (void *)buf->data;
uint16_t handle = sys_le16_to_cpu(evt->handle);
struct bt_hci_cp_read_remote_ext_features *cp;
struct bt_conn *conn;
LOG_DBG("status 0x%02x handle %u", evt->status, handle);
conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_BR);
if (!conn) {
LOG_ERR("Can't find conn for handle %u", handle);
return;
}
if (evt->status) {
goto done;
}
memcpy(conn->br.features[0], evt->features, sizeof(evt->features));
if (!BT_FEAT_EXT_FEATURES(conn->br.features)) {
goto done;
}
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
goto done;
}
/* Read remote host features (page 1) */
cp = net_buf_add(buf, sizeof(*cp));
cp->handle = evt->handle;
cp->page = 0x01;
bt_hci_cmd_send_sync(BT_HCI_OP_READ_REMOTE_EXT_FEATURES, buf, NULL);
done:
bt_conn_unref(conn);
}
void bt_hci_read_remote_ext_features_complete(struct net_buf *buf)
{
struct bt_hci_evt_remote_ext_features *evt = (void *)buf->data;
uint16_t handle = sys_le16_to_cpu(evt->handle);
struct bt_conn *conn;
LOG_DBG("status 0x%02x handle %u", evt->status, handle);
conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_BR);
if (!conn) {
LOG_ERR("Can't find conn for handle %u", handle);
return;
}
if (!evt->status && evt->page == 0x01) {
memcpy(conn->br.features[1], evt->features, sizeof(conn->br.features[1]));
}
bt_conn_unref(conn);
}
void bt_hci_role_change(struct net_buf *buf)
{
struct bt_hci_evt_role_change *evt = (void *)buf->data;
struct bt_conn *conn;
LOG_DBG("status 0x%02x role %u addr %s", evt->status, evt->role, bt_addr_str(&evt->bdaddr));
if (evt->status) {
return;
}
conn = bt_conn_lookup_addr_br(&evt->bdaddr);
if (!conn) {
LOG_ERR("Can't find conn for %s", bt_addr_str(&evt->bdaddr));
return;
}
if (evt->role) {
conn->role = BT_CONN_ROLE_PERIPHERAL;
} else {
conn->role = BT_CONN_ROLE_CENTRAL;
}
bt_conn_unref(conn);
}
static int read_ext_features(void)
{
int i;
/* Read Local Supported Extended Features */
for (i = 1; i < LMP_FEAT_PAGES_COUNT; i++) {
struct bt_hci_cp_read_local_ext_features *cp;
struct bt_hci_rp_read_local_ext_features *rp;
struct net_buf *buf, *rsp;
int err;
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
return -ENOBUFS;
}
cp = net_buf_add(buf, sizeof(*cp));
cp->page = i;
err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_LOCAL_EXT_FEATURES, buf, &rsp);
if (err) {
return err;
}
rp = (void *)rsp->data;
memcpy(&bt_dev.features[i], rp->ext_features, sizeof(bt_dev.features[i]));
if (rp->max_page <= i) {
net_buf_unref(rsp);
break;
}
net_buf_unref(rsp);
}
return 0;
}
void device_supported_pkt_type(void)
{
/* Device supported features and sco packet types */
if (BT_FEAT_LMP_SCO_CAPABLE(bt_dev.features)) {
bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_SCO_HV1);
}
if (BT_FEAT_HV2_PKT(bt_dev.features)) {
bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_SCO_HV2);
}
if (BT_FEAT_HV3_PKT(bt_dev.features)) {
bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_SCO_HV3);
}
if (BT_FEAT_LMP_ESCO_CAPABLE(bt_dev.features)) {
bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_EV3);
}
if (BT_FEAT_EV4_PKT(bt_dev.features)) {
bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_EV4);
}
if (BT_FEAT_EV5_PKT(bt_dev.features)) {
bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_EV5);
}
if (BT_FEAT_2EV3_PKT(bt_dev.features)) {
bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_2EV3);
}
if (BT_FEAT_3EV3_PKT(bt_dev.features)) {
bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_3EV3);
}
if (BT_FEAT_3SLOT_PKT(bt_dev.features)) {
bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_2EV5 | HCI_PKT_TYPE_ESCO_3EV5);
}
}
static void read_buffer_size_complete(struct net_buf *buf)
{
struct bt_hci_rp_read_buffer_size *rp = (void *)buf->data;
uint16_t pkts;
LOG_DBG("status 0x%02x", rp->status);
bt_dev.br.mtu = sys_le16_to_cpu(rp->acl_max_len);
pkts = sys_le16_to_cpu(rp->acl_max_num);
LOG_DBG("ACL BR/EDR buffers: pkts %u mtu %u", pkts, bt_dev.br.mtu);
k_sem_init(&bt_dev.br.pkts, pkts, pkts);
}
int bt_br_init(void)
{
struct net_buf *buf;
struct bt_hci_cp_write_ssp_mode *ssp_cp;
struct bt_hci_cp_write_inquiry_mode *inq_cp;
struct bt_hci_write_local_name *name_cp;
int err;
/* Read extended local features */
if (BT_FEAT_EXT_FEATURES(bt_dev.features)) {
err = read_ext_features();
if (err) {
return err;
}
}
/* Add local supported packet types to bt_dev */
device_supported_pkt_type();
/* Get BR/EDR buffer size */
err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_BUFFER_SIZE, NULL, &buf);
if (err) {
return err;
}
read_buffer_size_complete(buf);
net_buf_unref(buf);
/* Set SSP mode */
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
return -ENOBUFS;
}
ssp_cp = net_buf_add(buf, sizeof(*ssp_cp));
ssp_cp->mode = 0x01;
err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_SSP_MODE, buf, NULL);
if (err) {
return err;
}
/* Enable Inquiry results with RSSI or extended Inquiry */
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
return -ENOBUFS;
}
inq_cp = net_buf_add(buf, sizeof(*inq_cp));
inq_cp->mode = 0x02;
err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_INQUIRY_MODE, buf, NULL);
if (err) {
return err;
}
/* Set local name */
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
return -ENOBUFS;
}
name_cp = net_buf_add(buf, sizeof(*name_cp));
strncpy((char *)name_cp->local_name, CONFIG_BT_DEVICE_NAME, sizeof(name_cp->local_name));
err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_LOCAL_NAME, buf, NULL);
if (err) {
return err;
}
/* Set Class of device */
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
return -ENOBUFS;
}
net_buf_add_le24(buf, CONFIG_BT_COD);
err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_CLASS_OF_DEVICE, buf, NULL);
if (err) {
return err;
}
/* Set page timeout*/
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
return -ENOBUFS;
}
net_buf_add_le16(buf, CONFIG_BT_PAGE_TIMEOUT);
err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_PAGE_TIMEOUT, buf, NULL);
if (err) {
return err;
}
/* Enable BR/EDR SC if supported */
if (BT_FEAT_SC(bt_dev.features)) {
struct bt_hci_cp_write_sc_host_supp *sc_cp;
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
return -ENOBUFS;
}
sc_cp = net_buf_add(buf, sizeof(*sc_cp));
sc_cp->sc_support = 0x01;
err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_SC_HOST_SUPP, buf, NULL);
if (err) {
return err;
}
}
return 0;
}
static int br_start_inquiry(const struct bt_br_discovery_param *param)
{
const uint8_t iac[3] = {0x33, 0x8b, 0x9e};
struct bt_hci_op_inquiry *cp;
struct net_buf *buf;
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
return -ENOBUFS;
}
cp = net_buf_add(buf, sizeof(*cp));
cp->length = param->length;
cp->num_rsp = 0xff; /* we limit discovery only by time */
memcpy(cp->lap, iac, 3);
if (param->limited) {
cp->lap[0] = 0x00;
}
return bt_hci_cmd_send_sync(BT_HCI_OP_INQUIRY, buf, NULL);
}
static bool valid_br_discov_param(const struct bt_br_discovery_param *param, size_t num_results)
{
if (!num_results || num_results > 255) {
return false;
}
if (!param->length || param->length > 0x30) {
return false;
}
return true;
}
int bt_br_discovery_start(const struct bt_br_discovery_param *param,
struct bt_br_discovery_result *results, size_t cnt)
{
int err;
LOG_DBG("");
if (!valid_br_discov_param(param, cnt)) {
return -EINVAL;
}
if (atomic_test_bit(bt_dev.flags, BT_DEV_INQUIRY)) {
return -EALREADY;
}
err = br_start_inquiry(param);
if (err) {
return err;
}
atomic_set_bit(bt_dev.flags, BT_DEV_INQUIRY);
(void)memset(results, 0, sizeof(*results) * cnt);
discovery_results = results;
discovery_results_size = cnt;
discovery_results_count = 0;
return 0;
}
int bt_br_discovery_stop(void)
{
int err;
int i;
LOG_DBG("");
if (!atomic_test_bit(bt_dev.flags, BT_DEV_INQUIRY)) {
return -EALREADY;
}
err = bt_hci_cmd_send_sync(BT_HCI_OP_INQUIRY_CANCEL, NULL, NULL);
if (err) {
return err;
}
for (i = 0; i < discovery_results_count; i++) {
struct bt_br_discovery_priv *priv;
struct bt_hci_cp_remote_name_cancel *cp;
struct net_buf *buf;
priv = &discovery_results[i]._priv;
if (priv->resolve_state != RESOLVE_REMOTE_NAME_RESOLVING) {
continue;
}
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
continue;
}
cp = net_buf_add(buf, sizeof(*cp));
bt_addr_copy(&cp->bdaddr, &discovery_results[i].addr);
bt_hci_cmd_send_sync(BT_HCI_OP_REMOTE_NAME_CANCEL, buf, NULL);
}
atomic_clear_bit(bt_dev.flags, BT_DEV_INQUIRY);
discovery_results = NULL;
discovery_results_size = 0;
discovery_results_count = 0;
return 0;
}
void bt_br_discovery_cb_register(struct bt_br_discovery_cb *cb)
{
sys_slist_append(&discovery_cbs, &cb->node);
}
void bt_br_discovery_cb_unregister(struct bt_br_discovery_cb *cb)
{
sys_slist_find_and_remove(&discovery_cbs, &cb->node);
}
static int write_scan_enable(uint8_t scan)
{
struct net_buf *buf;
int err;
LOG_DBG("type %u", scan);
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
return -ENOBUFS;
}
net_buf_add_u8(buf, scan);
err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_SCAN_ENABLE, buf, NULL);
if (err) {
return err;
}
atomic_set_bit_to(bt_dev.flags, BT_DEV_ISCAN, (scan & BT_BREDR_SCAN_INQUIRY));
atomic_set_bit_to(bt_dev.flags, BT_DEV_PSCAN, (scan & BT_BREDR_SCAN_PAGE));
return 0;
}
int bt_br_set_connectable(bool enable)
{
if (enable) {
if (atomic_test_bit(bt_dev.flags, BT_DEV_PSCAN)) {
return -EALREADY;
} else {
return write_scan_enable(BT_BREDR_SCAN_PAGE);
}
} else {
if (!atomic_test_bit(bt_dev.flags, BT_DEV_PSCAN)) {
return -EALREADY;
} else {
return write_scan_enable(BT_BREDR_SCAN_DISABLED);
}
}
}
#define BT_LIAC 0x9e8b00
#define BT_GIAC 0x9e8b33
static int bt_br_write_current_iac_lap(bool limited)
{
struct bt_hci_cp_write_current_iac_lap *iac_lap;
struct net_buf *buf;
uint8_t param_len;
uint8_t num_current_iac = limited ? 2 : 1;
LOG_DBG("limited discoverable mode? %s", limited ? "Yes" : "No");
param_len = sizeof(*iac_lap) + (num_current_iac * sizeof(struct bt_hci_iac_lap));
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
return -ENOBUFS;
}
iac_lap = net_buf_add(buf, param_len);
iac_lap->num_current_iac = num_current_iac;
sys_put_le24(BT_GIAC, iac_lap->lap[0].iac);
if (num_current_iac > 1) {
sys_put_le24(BT_LIAC, iac_lap->lap[1].iac);
}
return bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_CURRENT_IAC_LAP, buf, NULL);
}
#define BT_COD_MAJOR_SVC_CLASS_LIMITED_DISCOVER BIT(13)
static int bt_br_read_cod(uint32_t *cod)
{
struct net_buf *rsp;
struct bt_hci_rp_read_class_of_device *rp;
int err;
LOG_DBG("Read COD");
/* Read Class of device */
err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_CLASS_OF_DEVICE, NULL, &rsp);
if (err) {
LOG_WRN("Fail to read COD");
return err;
}
if (!rsp || (rsp->len < sizeof(*rp))) {
LOG_WRN("Invalid response");
return -EIO;
}
rp = (void *)rsp->data;
*cod = sys_get_le24(rp->class_of_device);
LOG_DBG("Current COD %06x", *cod);
net_buf_unref(rsp);
return 0;
}
static int bt_br_write_cod(uint32_t cod)
{
struct net_buf *buf;
/* Set Class of device */
buf = bt_hci_cmd_alloc(K_FOREVER);
if (!buf) {
return -ENOBUFS;
}
net_buf_add_le24(buf, cod);
return bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_CLASS_OF_DEVICE, buf, NULL);
}
static int bt_br_update_cod(bool limited)
{
int err;
uint32_t cod;
LOG_DBG("Update COD");
err = bt_br_read_cod(&cod);
if (err) {
return err;
}
if (limited) {
cod |= BT_COD_MAJOR_SVC_CLASS_LIMITED_DISCOVER;
} else {
cod &= ~BT_COD_MAJOR_SVC_CLASS_LIMITED_DISCOVER;
}
err = bt_br_write_cod(cod);
return err;
}
static void bt_br_limited_discoverable_timeout_handler(struct k_work *work)
{
int err;
if (!atomic_test_bit(bt_dev.flags, BT_DEV_LIMITED_DISCOVERABLE_MODE)) {
LOG_INF("Limited discoverable mode has been disabled");
return;
}
err = bt_br_set_discoverable(false, false);
if (err) {
LOG_WRN("Disable discoverable failure (err %d)", err);
}
}
/* Work used for limited discoverable mode time span */
static K_WORK_DELAYABLE_DEFINE(bt_br_limited_discoverable_timeout,
bt_br_limited_discoverable_timeout_handler);
int bt_br_set_discoverable(bool enable, bool limited)
{
int err;
if (enable) {
if (atomic_test_bit(bt_dev.flags, BT_DEV_ISCAN)) {
return -EALREADY;
}
if (!atomic_test_bit(bt_dev.flags, BT_DEV_PSCAN)) {
return -EPERM;
}
err = bt_br_write_current_iac_lap(limited);
if (err) {
return err;
}
err = bt_br_update_cod(limited);
if (err) {
return err;
}
err = write_scan_enable(BT_BREDR_SCAN_INQUIRY | BT_BREDR_SCAN_PAGE);
if (!err && (limited == true)) {
atomic_set_bit(bt_dev.flags, BT_DEV_LIMITED_DISCOVERABLE_MODE);
k_work_reschedule(&bt_br_limited_discoverable_timeout,
K_SECONDS(CONFIG_BT_LIMITED_DISCOVERABLE_DURATION));
}
return err;
}
if (!atomic_test_bit(bt_dev.flags, BT_DEV_ISCAN)) {
return -EALREADY;
}
err = write_scan_enable(BT_BREDR_SCAN_PAGE);
if (err) {
return err;
}
if (atomic_test_bit(bt_dev.flags, BT_DEV_LIMITED_DISCOVERABLE_MODE)) {
err = bt_br_write_current_iac_lap(false);
if (err) {
return err;
}
err = bt_br_update_cod(false);
if (err) {
return err;
}
atomic_clear_bit(bt_dev.flags, BT_DEV_LIMITED_DISCOVERABLE_MODE);
k_work_cancel_delayable(&bt_br_limited_discoverable_timeout);
}
return 0;
}
bool bt_br_bond_exists(const bt_addr_t *addr)
{
struct bt_keys_link_key *key = bt_keys_find_link_key(addr);
/* if there are any keys stored then device is bonded */
return key != NULL;
}
static void unpair(const bt_addr_t *addr)
{
struct bt_conn *conn = bt_conn_lookup_addr_br(addr);
struct bt_conn_auth_info_cb *listener, *next;
if (conn) {
bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
bt_conn_unref(conn);
}
bt_keys_link_key_clear_addr(addr);
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&bt_auth_info_cbs, listener,
next, node) {
if (listener->br_bond_deleted) {
listener->br_bond_deleted(addr);
}
}
}
static void bt_br_unpair_remote(const struct bt_br_bond_info *info, void *data)
{
ARG_UNUSED(data);
unpair(&info->addr);
}
int bt_br_unpair(const bt_addr_t *addr)
{
if (!addr || bt_addr_eq(addr, BT_ADDR_ANY)) {
bt_br_foreach_bond(bt_br_unpair_remote, NULL);
} else {
unpair(addr);
}
return 0;
}