/* conn.c - Bluetooth connection handling */ /* * Copyright (c) 2015-2016 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_CONN) #define LOG_MODULE_NAME bt_conn #include "log.h" #include "hci_core.h" #include "conn_internal.h" #include "l2cap_internal.h" #include "keys.h" #include "smp.h" #include "att_internal.h" #include "gatt_internal.h" #if defined(BFLB_BLE) #include "config.h" extern struct k_sem g_poll_sem; #endif struct tx_meta { struct bt_conn_tx *tx; }; #define tx_data(buf) ((struct tx_meta *)net_buf_user_data(buf)) #if !defined(BFLB_DYNAMIC_ALLOC_MEM) NET_BUF_POOL_DEFINE(acl_tx_pool, CONFIG_BT_L2CAP_TX_BUF_COUNT, BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU), sizeof(struct tx_meta), NULL); #else struct net_buf_pool acl_tx_pool; #endif #if CONFIG_BT_L2CAP_TX_FRAG_COUNT > 0 #if defined(BT_CTLR_TX_BUFFER_SIZE) #define FRAG_SIZE BT_L2CAP_BUF_SIZE(BT_CTLR_TX_BUFFER_SIZE - 4) #else #define FRAG_SIZE BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU) #endif #if !defined(BFLB_DYNAMIC_ALLOC_MEM) /* Dedicated pool for fragment buffers in case queued up TX buffers don't * fit the controllers buffer size. We can't use the acl_tx_pool for the * fragmentation, since it's possible that pool is empty and all buffers * are queued up in the TX queue. In such a situation, trying to allocate * another buffer from the acl_tx_pool would result in a deadlock. */ NET_BUF_POOL_FIXED_DEFINE(frag_pool, CONFIG_BT_L2CAP_TX_FRAG_COUNT, FRAG_SIZE, NULL); #else struct net_buf_pool frag_pool; #endif #endif /* CONFIG_BT_L2CAP_TX_FRAG_COUNT > 0 */ /* How long until we cancel HCI_LE_Create_Connection */ #define CONN_TIMEOUT K_SECONDS(CONFIG_BT_CREATE_CONN_TIMEOUT) #if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR) const struct bt_conn_auth_cb *bt_auth; #endif /* CONFIG_BT_SMP || CONFIG_BT_BREDR */ static struct bt_conn conns[CONFIG_BT_MAX_CONN]; static struct bt_conn_cb *callback_list; static struct bt_conn_tx conn_tx[CONFIG_BT_CONN_TX_MAX]; K_FIFO_DEFINE(free_tx); #if defined(CONFIG_BT_BREDR) static struct bt_conn sco_conns[CONFIG_BT_MAX_SCO_CONN]; enum pairing_method { LEGACY, /* Legacy (pre-SSP) pairing */ JUST_WORKS, /* JustWorks pairing */ PASSKEY_INPUT, /* Passkey Entry input */ PASSKEY_DISPLAY, /* Passkey Entry display */ PASSKEY_CONFIRM, /* Passkey confirm */ }; /* based on table 5.7, Core Spec 4.2, Vol.3 Part C, 5.2.2.6 */ static const u8_t ssp_method[4 /* remote */][4 /* local */] = { { JUST_WORKS, JUST_WORKS, PASSKEY_INPUT, JUST_WORKS }, { JUST_WORKS, PASSKEY_CONFIRM, PASSKEY_INPUT, JUST_WORKS }, { PASSKEY_DISPLAY, PASSKEY_DISPLAY, PASSKEY_INPUT, JUST_WORKS }, { JUST_WORKS, JUST_WORKS, JUST_WORKS, JUST_WORKS }, }; #endif /* CONFIG_BT_BREDR */ struct k_sem *bt_conn_get_pkts(struct bt_conn *conn) { #if defined(CONFIG_BT_BREDR) if (conn->type == BT_CONN_TYPE_BR || !bt_dev.le.mtu) { return &bt_dev.br.pkts; } #endif /* CONFIG_BT_BREDR */ return &bt_dev.le.pkts; } static inline const char *state2str(bt_conn_state_t state) { switch (state) { case BT_CONN_DISCONNECTED: return "disconnected"; case BT_CONN_CONNECT_SCAN: return "connect-scan"; case BT_CONN_CONNECT_DIR_ADV: return "connect-dir-adv"; case BT_CONN_CONNECT: return "connect"; case BT_CONN_CONNECTED: return "connected"; case BT_CONN_DISCONNECT: return "disconnect"; default: return "(unknown)"; } } static void notify_connected(struct bt_conn *conn) { struct bt_conn_cb *cb; #if defined(CONFIG_BT_BREDR) if (conn->type == BT_CONN_TYPE_BR && conn->err) { if (atomic_test_bit(bt_dev.flags, BT_DEV_ISCAN)) { atomic_clear_bit(bt_dev.flags, BT_DEV_ISCAN); } if (atomic_test_bit(bt_dev.flags, BT_DEV_PSCAN)) { atomic_clear_bit(bt_dev.flags, BT_DEV_PSCAN); } } #endif for (cb = callback_list; cb; cb = cb->_next) { if (cb->connected) { cb->connected(conn, conn->err); } } if (conn->type == BT_CONN_TYPE_LE && !conn->err) { bt_gatt_connected(conn); } } void notify_disconnected(struct bt_conn *conn) { struct bt_conn_cb *cb; #if defined(CONFIG_BT_BREDR) if (conn->type == BT_CONN_TYPE_BR) { if (atomic_test_bit(bt_dev.flags, BT_DEV_ISCAN)) { atomic_clear_bit(bt_dev.flags, BT_DEV_ISCAN); } if (atomic_test_bit(bt_dev.flags, BT_DEV_PSCAN)) { atomic_clear_bit(bt_dev.flags, BT_DEV_PSCAN); } } #endif for (cb = callback_list; cb; cb = cb->_next) { if (cb->disconnected) { cb->disconnected(conn, conn->err); } } } void notify_le_param_updated(struct bt_conn *conn) { struct bt_conn_cb *cb; /* If new connection parameters meet requirement of pending * parameters don't send slave conn param request anymore on timeout */ if (atomic_test_bit(conn->flags, BT_CONN_SLAVE_PARAM_SET) && conn->le.interval >= conn->le.interval_min && conn->le.interval <= conn->le.interval_max && conn->le.latency == conn->le.pending_latency && conn->le.timeout == conn->le.pending_timeout) { atomic_clear_bit(conn->flags, BT_CONN_SLAVE_PARAM_SET); } for (cb = callback_list; cb; cb = cb->_next) { if (cb->le_param_updated) { cb->le_param_updated(conn, conn->le.interval, conn->le.latency, conn->le.timeout); } } } void notify_le_phy_updated(struct bt_conn *conn, u8_t tx_phy, u8_t rx_phy) { struct bt_conn_cb *cb; for (cb = callback_list; cb; cb = cb->_next) { if (cb->le_phy_updated) { cb->le_phy_updated(conn, tx_phy, rx_phy); } } } bool le_param_req(struct bt_conn *conn, struct bt_le_conn_param *param) { struct bt_conn_cb *cb; if (!bt_le_conn_params_valid(param)) { return false; } for (cb = callback_list; cb; cb = cb->_next) { if (!cb->le_param_req) { continue; } if (!cb->le_param_req(conn, param)) { return false; } /* The callback may modify the parameters so we need to * double-check that it returned valid parameters. */ if (!bt_le_conn_params_valid(param)) { return false; } } /* Default to accepting if there's no app callback */ return true; } static int send_conn_le_param_update(struct bt_conn *conn, const struct bt_le_conn_param *param) { BT_DBG("conn %p features 0x%02x params (%d-%d %d %d)", conn, conn->le.features[0], param->interval_min, param->interval_max, param->latency, param->timeout); /* Use LE connection parameter request if both local and remote support * it; or if local role is master then use LE connection update. */ if ((BT_FEAT_LE_CONN_PARAM_REQ_PROC(bt_dev.le.features) && BT_FEAT_LE_CONN_PARAM_REQ_PROC(conn->le.features) && !atomic_test_bit(conn->flags, BT_CONN_SLAVE_PARAM_L2CAP)) || (conn->role == BT_HCI_ROLE_MASTER)) { int rc; rc = bt_conn_le_conn_update(conn, param); /* store those in case of fallback to L2CAP */ if (rc == 0) { conn->le.pending_latency = param->latency; conn->le.pending_timeout = param->timeout; } return rc; } /* If remote master does not support LL Connection Parameters Request * Procedure */ return bt_l2cap_update_conn_param(conn, param); } static void tx_free(struct bt_conn_tx *tx) { tx->cb = NULL; tx->user_data = NULL; tx->pending_no_cb = 0U; k_fifo_put(&free_tx, tx); } static void tx_notify(struct bt_conn *conn) { BT_DBG("conn %p", conn); while (1) { struct bt_conn_tx *tx; unsigned int key; bt_conn_tx_cb_t cb; void *user_data; key = irq_lock(); if (sys_slist_is_empty(&conn->tx_complete)) { irq_unlock(key); break; } tx = (void *)sys_slist_get_not_empty(&conn->tx_complete); irq_unlock(key); BT_DBG("tx %p cb %p user_data %p", tx, tx->cb, tx->user_data); /* Copy over the params */ cb = tx->cb; user_data = tx->user_data; /* Free up TX notify since there may be user waiting */ tx_free(tx); /* Run the callback, at this point it should be safe to * allocate new buffers since the TX should have been * unblocked by tx_free. */ cb(conn, user_data); } } static void tx_complete_work(struct k_work *work) { struct bt_conn *conn = CONTAINER_OF(work, struct bt_conn, tx_complete_work); BT_DBG("conn %p", conn); tx_notify(conn); } static void conn_update_timeout(struct k_work *work) { struct bt_conn *conn = CONTAINER_OF(work, struct bt_conn, update_work); const struct bt_le_conn_param *param; BT_DBG("conn %p", conn); if (conn->state == BT_CONN_DISCONNECTED) { bt_l2cap_disconnected(conn); #if !defined(BFLB_BLE) notify_disconnected(conn); #endif /* Release the reference we took for the very first * state transition. */ bt_conn_unref(conn); return; } if (conn->type != BT_CONN_TYPE_LE) { return; } if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->role == BT_CONN_ROLE_MASTER) { /* we don't call bt_conn_disconnect as it would also clear * auto connect flag if it was set, instead just cancel * connection directly */ bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_CONN_CANCEL, NULL, NULL); return; } #if defined(CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS) /* if application set own params use those, otherwise use defaults */ if (atomic_test_and_clear_bit(conn->flags, BT_CONN_SLAVE_PARAM_SET)) { param = BT_LE_CONN_PARAM(conn->le.interval_min, conn->le.interval_max, conn->le.pending_latency, conn->le.pending_timeout); send_conn_le_param_update(conn, param); } else { param = BT_LE_CONN_PARAM(CONFIG_BT_PERIPHERAL_PREF_MIN_INT, CONFIG_BT_PERIPHERAL_PREF_MAX_INT, CONFIG_BT_PERIPHERAL_PREF_SLAVE_LATENCY, CONFIG_BT_PERIPHERAL_PREF_TIMEOUT); send_conn_le_param_update(conn, param); } #else /* update only if application set own params */ if (atomic_test_and_clear_bit(conn->flags, BT_CONN_SLAVE_PARAM_SET)) { param = BT_LE_CONN_PARAM(conn->le.interval_min, conn->le.interval_max, conn->le.latency, conn->le.timeout); send_conn_le_param_update(conn, param); } #endif atomic_set_bit(conn->flags, BT_CONN_SLAVE_PARAM_UPDATE); } #if defined(CONFIG_BT_AUDIO) struct bt_conn *iso_conn_new(struct bt_conn *conns, size_t size) { struct bt_conn *conn = NULL; int i; for (i = 0; i < size; i++) { if (atomic_cas(&conns[i].ref, 0, 1)) { conn = &conns[i]; break; } } if (!conn) { return NULL; } (void)memset(conn, 0, offsetof(struct bt_conn, ref)); return conn; } #endif static struct bt_conn *conn_new(void) { struct bt_conn *conn = NULL; int i; for (i = 0; i < ARRAY_SIZE(conns); i++) { if (!atomic_get(&conns[i].ref)) { conn = &conns[i]; break; } } if (!conn) { return NULL; } (void)memset(conn, 0, sizeof(*conn)); k_delayed_work_init(&conn->update_work, conn_update_timeout); k_work_init(&conn->tx_complete_work, tx_complete_work); atomic_set(&conn->ref, 1); return conn; } #if defined(BFLB_BLE) bool le_check_valid_conn(void) { int i; for (i = 0; i < ARRAY_SIZE(conns); i++) { if (atomic_get(&conns[i].ref)) { return true; } } return false; } #if defined(BFLB_HOST_ASSISTANT) void bt_notify_disconnected(void) { int i; for (i = 0; i < ARRAY_SIZE(conns); i++) { if (atomic_get(&conns[i].ref)) { conns[i].err = BT_HCI_ERR_UNSPECIFIED; notify_disconnected(&conns[i]); } } } #endif //#if defined(BFLB_HOST_ASSISTANT) #endif #if defined(CONFIG_BT_BREDR) void bt_sco_cleanup(struct bt_conn *sco_conn) { bt_conn_unref(sco_conn->sco.acl); sco_conn->sco.acl = NULL; bt_conn_unref(sco_conn); } static struct bt_conn *sco_conn_new(void) { struct bt_conn *sco_conn = NULL; int i; for (i = 0; i < ARRAY_SIZE(sco_conns); i++) { if (!atomic_get(&sco_conns[i].ref)) { sco_conn = &sco_conns[i]; break; } } if (!sco_conn) { return NULL; } (void)memset(sco_conn, 0, sizeof(*sco_conn)); atomic_set(&sco_conn->ref, 1); return sco_conn; } struct bt_conn *bt_conn_create_br(const bt_addr_t *peer, const struct bt_br_conn_param *param) { struct bt_hci_cp_connect *cp; struct bt_conn *conn; struct net_buf *buf; conn = bt_conn_lookup_addr_br(peer); if (conn) { switch (conn->state) { case BT_CONN_CONNECT: case BT_CONN_CONNECTED: return conn; default: bt_conn_unref(conn); return NULL; } } conn = bt_conn_add_br(peer); if (!conn) { return NULL; } buf = bt_hci_cmd_create(BT_HCI_OP_CONNECT, sizeof(*cp)); if (!buf) { bt_conn_unref(conn); return NULL; } cp = net_buf_add(buf, sizeof(*cp)); (void)memset(cp, 0, sizeof(*cp)); memcpy(&cp->bdaddr, peer, sizeof(cp->bdaddr)); cp->packet_type = sys_cpu_to_le16(0xcc18); /* DM1 DH1 DM3 DH5 DM5 DH5 */ cp->pscan_rep_mode = 0x02; /* R2 */ cp->allow_role_switch = param->allow_role_switch ? 0x01 : 0x00; cp->clock_offset = 0x0000; /* TODO used cached clock offset */ if (bt_hci_cmd_send_sync(BT_HCI_OP_CONNECT, buf, NULL) < 0) { bt_conn_unref(conn); return NULL; } bt_conn_set_state(conn, BT_CONN_CONNECT); conn->role = BT_CONN_ROLE_MASTER; bt_conn_unref(conn); return conn; } struct bt_conn *bt_conn_create_sco(const bt_addr_t *peer) { struct bt_hci_cp_setup_sync_conn *cp; struct bt_conn *sco_conn; struct net_buf *buf; int link_type; sco_conn = bt_conn_lookup_addr_sco(peer); if (sco_conn) { switch (sco_conn->state) { case BT_CONN_CONNECT: case BT_CONN_CONNECTED: return sco_conn; default: bt_conn_unref(sco_conn); return NULL; } } if (BT_FEAT_LMP_ESCO_CAPABLE(bt_dev.features)) { link_type = BT_HCI_ESCO; } else { link_type = BT_HCI_SCO; } sco_conn = bt_conn_add_sco(peer, link_type); if (!sco_conn) { return NULL; } buf = bt_hci_cmd_create(BT_HCI_OP_SETUP_SYNC_CONN, sizeof(*cp)); if (!buf) { bt_sco_cleanup(sco_conn); return NULL; } cp = net_buf_add(buf, sizeof(*cp)); (void)memset(cp, 0, sizeof(*cp)); BT_ERR("handle : %x", sco_conn->sco.acl->handle); cp->handle = sco_conn->sco.acl->handle; cp->pkt_type = sco_conn->sco.pkt_type; cp->tx_bandwidth = 0x00001f40; cp->rx_bandwidth = 0x00001f40; cp->max_latency = 0x0007; cp->retrans_effort = 0x01; cp->content_format = BT_VOICE_CVSD_16BIT; if (bt_hci_cmd_send_sync(BT_HCI_OP_SETUP_SYNC_CONN, buf, NULL) < 0) { bt_sco_cleanup(sco_conn); return NULL; } bt_conn_set_state(sco_conn, BT_CONN_CONNECT); return sco_conn; } struct bt_conn *bt_conn_lookup_addr_sco(const bt_addr_t *peer) { int i; for (i = 0; i < ARRAY_SIZE(sco_conns); i++) { if (!atomic_get(&sco_conns[i].ref)) { continue; } if (sco_conns[i].type != BT_CONN_TYPE_SCO) { continue; } if (!bt_addr_cmp(peer, &sco_conns[i].sco.acl->br.dst)) { return bt_conn_ref(&sco_conns[i]); } } return NULL; } struct bt_conn *bt_conn_lookup_addr_br(const bt_addr_t *peer) { int i; for (i = 0; i < ARRAY_SIZE(conns); i++) { if (!atomic_get(&conns[i].ref)) { continue; } if (conns[i].type != BT_CONN_TYPE_BR) { continue; } if (!bt_addr_cmp(peer, &conns[i].br.dst)) { return bt_conn_ref(&conns[i]); } } return NULL; } struct bt_conn *bt_conn_add_sco(const bt_addr_t *peer, int link_type) { struct bt_conn *sco_conn = sco_conn_new(); if (!sco_conn) { return NULL; } sco_conn->sco.acl = bt_conn_lookup_addr_br(peer); sco_conn->type = BT_CONN_TYPE_SCO; if (link_type == BT_HCI_SCO) { if (BT_FEAT_LMP_ESCO_CAPABLE(bt_dev.features)) { sco_conn->sco.pkt_type = (bt_dev.br.esco_pkt_type & ESCO_PKT_MASK); } else { sco_conn->sco.pkt_type = (bt_dev.br.esco_pkt_type & SCO_PKT_MASK); } } else if (link_type == BT_HCI_ESCO) { sco_conn->sco.pkt_type = (bt_dev.br.esco_pkt_type & ~EDR_ESCO_PKT_MASK); } return sco_conn; } struct bt_conn *bt_conn_add_br(const bt_addr_t *peer) { struct bt_conn *conn = conn_new(); if (!conn) { return NULL; } bt_addr_copy(&conn->br.dst, peer); conn->type = BT_CONN_TYPE_BR; return conn; } static int pin_code_neg_reply(const bt_addr_t *bdaddr) { struct bt_hci_cp_pin_code_neg_reply *cp; struct net_buf *buf; BT_DBG(""); buf = bt_hci_cmd_create(BT_HCI_OP_PIN_CODE_NEG_REPLY, sizeof(*cp)); if (!buf) { return -ENOBUFS; } cp = net_buf_add(buf, sizeof(*cp)); bt_addr_copy(&cp->bdaddr, bdaddr); return bt_hci_cmd_send_sync(BT_HCI_OP_PIN_CODE_NEG_REPLY, buf, NULL); } static int pin_code_reply(struct bt_conn *conn, const char *pin, u8_t len) { struct bt_hci_cp_pin_code_reply *cp; struct net_buf *buf; BT_DBG(""); buf = bt_hci_cmd_create(BT_HCI_OP_PIN_CODE_REPLY, sizeof(*cp)); if (!buf) { return -ENOBUFS; } cp = net_buf_add(buf, sizeof(*cp)); bt_addr_copy(&cp->bdaddr, &conn->br.dst); cp->pin_len = len; strncpy((char *)cp->pin_code, pin, sizeof(cp->pin_code)); return bt_hci_cmd_send_sync(BT_HCI_OP_PIN_CODE_REPLY, buf, NULL); } int bt_conn_auth_pincode_entry(struct bt_conn *conn, const char *pin) { size_t len; if (!bt_auth) { return -EINVAL; } if (conn->type != BT_CONN_TYPE_BR) { return -EINVAL; } len = strlen(pin); if (len > 16) { return -EINVAL; } if (conn->required_sec_level == BT_SECURITY_L3 && len < 16) { BT_WARN("PIN code for %s is not 16 bytes wide", bt_addr_str(&conn->br.dst)); return -EPERM; } /* Allow user send entered PIN to remote, then reset user state. */ if (!atomic_test_and_clear_bit(conn->flags, BT_CONN_USER)) { return -EPERM; } if (len == 16) { atomic_set_bit(conn->flags, BT_CONN_BR_LEGACY_SECURE); } return pin_code_reply(conn, pin, len); } void bt_conn_pin_code_req(struct bt_conn *conn) { if (bt_auth && bt_auth->pincode_entry) { bool secure = false; if (conn->required_sec_level == BT_SECURITY_L3) { secure = true; } atomic_set_bit(conn->flags, BT_CONN_USER); atomic_set_bit(conn->flags, BT_CONN_BR_PAIRING); bt_auth->pincode_entry(conn, secure); } else { pin_code_neg_reply(&conn->br.dst); } } u8_t bt_conn_get_io_capa(void) { if (!bt_auth) { return BT_IO_NO_INPUT_OUTPUT; } if (bt_auth->passkey_confirm && bt_auth->passkey_display) { return BT_IO_DISPLAY_YESNO; } if (bt_auth->passkey_entry) { return BT_IO_KEYBOARD_ONLY; } if (bt_auth->passkey_display) { return BT_IO_DISPLAY_ONLY; } return BT_IO_NO_INPUT_OUTPUT; } static u8_t ssp_pair_method(const struct bt_conn *conn) { return ssp_method[conn->br.remote_io_capa][bt_conn_get_io_capa()]; } u8_t bt_conn_ssp_get_auth(const struct bt_conn *conn) { /* Validate no bond auth request, and if valid use it. */ if ((conn->br.remote_auth == BT_HCI_NO_BONDING) || ((conn->br.remote_auth == BT_HCI_NO_BONDING_MITM) && (ssp_pair_method(conn) > JUST_WORKS))) { return conn->br.remote_auth; } /* Local & remote have enough IO capabilities to get MITM protection. */ if (ssp_pair_method(conn) > JUST_WORKS) { return conn->br.remote_auth | BT_MITM; } /* No MITM protection possible so ignore remote MITM requirement. */ return (conn->br.remote_auth & ~BT_MITM); } static int ssp_confirm_reply(struct bt_conn *conn) { struct bt_hci_cp_user_confirm_reply *cp; struct net_buf *buf; BT_DBG(""); buf = bt_hci_cmd_create(BT_HCI_OP_USER_CONFIRM_REPLY, sizeof(*cp)); if (!buf) { return -ENOBUFS; } cp = net_buf_add(buf, sizeof(*cp)); bt_addr_copy(&cp->bdaddr, &conn->br.dst); return bt_hci_cmd_send_sync(BT_HCI_OP_USER_CONFIRM_REPLY, buf, NULL); } static int ssp_confirm_neg_reply(struct bt_conn *conn) { struct bt_hci_cp_user_confirm_reply *cp; struct net_buf *buf; BT_DBG(""); buf = bt_hci_cmd_create(BT_HCI_OP_USER_CONFIRM_NEG_REPLY, sizeof(*cp)); if (!buf) { return -ENOBUFS; } cp = net_buf_add(buf, sizeof(*cp)); bt_addr_copy(&cp->bdaddr, &conn->br.dst); return bt_hci_cmd_send_sync(BT_HCI_OP_USER_CONFIRM_NEG_REPLY, buf, NULL); } void bt_conn_ssp_auth_complete(struct bt_conn *conn, u8_t status) { if (!status) { bool bond = !atomic_test_bit(conn->flags, BT_CONN_BR_NOBOND); if (bt_auth && bt_auth->pairing_complete) { bt_auth->pairing_complete(conn, bond); } } else { if (bt_auth && bt_auth->pairing_failed) { bt_auth->pairing_failed(conn, status); } } } void bt_conn_ssp_auth(struct bt_conn *conn, u32_t passkey) { conn->br.pairing_method = ssp_pair_method(conn); /* * If local required security is HIGH then MITM is mandatory. * MITM protection is no achievable when SSP 'justworks' is applied. */ if (conn->required_sec_level > BT_SECURITY_L2 && conn->br.pairing_method == JUST_WORKS) { BT_DBG("MITM protection infeasible for required security"); ssp_confirm_neg_reply(conn); return; } switch (conn->br.pairing_method) { case PASSKEY_CONFIRM: atomic_set_bit(conn->flags, BT_CONN_USER); bt_auth->passkey_confirm(conn, passkey); break; case PASSKEY_DISPLAY: atomic_set_bit(conn->flags, BT_CONN_USER); bt_auth->passkey_display(conn, passkey); break; case PASSKEY_INPUT: atomic_set_bit(conn->flags, BT_CONN_USER); bt_auth->passkey_entry(conn); break; case JUST_WORKS: /* * When local host works as pairing acceptor and 'justworks' * model is applied then notify user about such pairing request. * [BT Core 4.2 table 5.7, Vol 3, Part C, 5.2.2.6] */ if (bt_auth && bt_auth->pairing_confirm && !atomic_test_bit(conn->flags, BT_CONN_BR_PAIRING_INITIATOR)) { atomic_set_bit(conn->flags, BT_CONN_USER); bt_auth->pairing_confirm(conn); break; } ssp_confirm_reply(conn); break; default: break; } } static int ssp_passkey_reply(struct bt_conn *conn, unsigned int passkey) { struct bt_hci_cp_user_passkey_reply *cp; struct net_buf *buf; BT_DBG(""); buf = bt_hci_cmd_create(BT_HCI_OP_USER_PASSKEY_REPLY, sizeof(*cp)); if (!buf) { return -ENOBUFS; } cp = net_buf_add(buf, sizeof(*cp)); bt_addr_copy(&cp->bdaddr, &conn->br.dst); cp->passkey = sys_cpu_to_le32(passkey); return bt_hci_cmd_send_sync(BT_HCI_OP_USER_PASSKEY_REPLY, buf, NULL); } static int ssp_passkey_neg_reply(struct bt_conn *conn) { struct bt_hci_cp_user_passkey_neg_reply *cp; struct net_buf *buf; BT_DBG(""); buf = bt_hci_cmd_create(BT_HCI_OP_USER_PASSKEY_NEG_REPLY, sizeof(*cp)); if (!buf) { return -ENOBUFS; } cp = net_buf_add(buf, sizeof(*cp)); bt_addr_copy(&cp->bdaddr, &conn->br.dst); return bt_hci_cmd_send_sync(BT_HCI_OP_USER_PASSKEY_NEG_REPLY, buf, NULL); } static int bt_hci_connect_br_cancel(struct bt_conn *conn) { struct bt_hci_cp_connect_cancel *cp; struct bt_hci_rp_connect_cancel *rp; struct net_buf *buf, *rsp; int err; buf = bt_hci_cmd_create(BT_HCI_OP_CONNECT_CANCEL, sizeof(*cp)); if (!buf) { return -ENOBUFS; } cp = net_buf_add(buf, sizeof(*cp)); memcpy(&cp->bdaddr, &conn->br.dst, sizeof(cp->bdaddr)); err = bt_hci_cmd_send_sync(BT_HCI_OP_CONNECT_CANCEL, buf, &rsp); if (err) { return err; } rp = (void *)rsp->data; err = rp->status ? -EIO : 0; net_buf_unref(rsp); return err; } static int conn_auth(struct bt_conn *conn) { struct bt_hci_cp_auth_requested *auth; struct net_buf *buf; BT_DBG(""); buf = bt_hci_cmd_create(BT_HCI_OP_AUTH_REQUESTED, sizeof(*auth)); if (!buf) { return -ENOBUFS; } auth = net_buf_add(buf, sizeof(*auth)); auth->handle = sys_cpu_to_le16(conn->handle); atomic_set_bit(conn->flags, BT_CONN_BR_PAIRING_INITIATOR); return bt_hci_cmd_send_sync(BT_HCI_OP_AUTH_REQUESTED, buf, NULL); } #endif /* CONFIG_BT_BREDR */ #if defined(CONFIG_BT_SMP) void bt_conn_identity_resolved(struct bt_conn *conn) { const bt_addr_le_t *rpa; struct bt_conn_cb *cb; if (conn->role == BT_HCI_ROLE_MASTER) { rpa = &conn->le.resp_addr; } else { rpa = &conn->le.init_addr; } for (cb = callback_list; cb; cb = cb->_next) { if (cb->identity_resolved) { cb->identity_resolved(conn, rpa, &conn->le.dst); } } } int bt_conn_le_start_encryption(struct bt_conn *conn, u8_t rand[8], u8_t ediv[2], const u8_t *ltk, size_t len) { struct bt_hci_cp_le_start_encryption *cp; struct net_buf *buf; buf = bt_hci_cmd_create(BT_HCI_OP_LE_START_ENCRYPTION, sizeof(*cp)); if (!buf) { return -ENOBUFS; } cp = net_buf_add(buf, sizeof(*cp)); cp->handle = sys_cpu_to_le16(conn->handle); memcpy(&cp->rand, rand, sizeof(cp->rand)); memcpy(&cp->ediv, ediv, sizeof(cp->ediv)); memcpy(cp->ltk, ltk, len); if (len < sizeof(cp->ltk)) { (void)memset(cp->ltk + len, 0, sizeof(cp->ltk) - len); } return bt_hci_cmd_send_sync(BT_HCI_OP_LE_START_ENCRYPTION, buf, NULL); } #endif /* CONFIG_BT_SMP */ #if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR) u8_t bt_conn_enc_key_size(struct bt_conn *conn) { //GATT/SR/GAR/BV-04-C // if the connection instance is valid if (!conn) { return 0; } if (!conn->encrypt) { return 0; } if (IS_ENABLED(CONFIG_BT_BREDR) && conn->type == BT_CONN_TYPE_BR) { struct bt_hci_cp_read_encryption_key_size *cp; struct bt_hci_rp_read_encryption_key_size *rp; struct net_buf *buf; struct net_buf *rsp; u8_t key_size; buf = bt_hci_cmd_create(BT_HCI_OP_READ_ENCRYPTION_KEY_SIZE, sizeof(*cp)); if (!buf) { return 0; } cp = net_buf_add(buf, sizeof(*cp)); cp->handle = sys_cpu_to_le16(conn->handle); if (bt_hci_cmd_send_sync(BT_HCI_OP_READ_ENCRYPTION_KEY_SIZE, buf, &rsp)) { return 0; } rp = (void *)rsp->data; key_size = rp->status ? 0 : rp->key_size; net_buf_unref(rsp); return key_size; } if (IS_ENABLED(CONFIG_BT_SMP)) { return conn->le.keys ? conn->le.keys->enc_size : 0; } return 0; } void bt_conn_security_changed(struct bt_conn *conn, enum bt_security_err err) { struct bt_conn_cb *cb; for (cb = callback_list; cb; cb = cb->_next) { if (cb->security_changed) { cb->security_changed(conn, conn->sec_level, err); } } #if IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST) if (!err && conn->sec_level >= BT_SECURITY_L2) { bt_keys_update_usage(conn->id, bt_conn_get_dst(conn)); } #endif } static int start_security(struct bt_conn *conn) { #if defined(CONFIG_BT_BREDR) if (conn->type == BT_CONN_TYPE_BR) { if (atomic_test_bit(conn->flags, BT_CONN_BR_PAIRING)) { return -EBUSY; } if (conn->required_sec_level > BT_SECURITY_L3) { return -ENOTSUP; } if (bt_conn_get_io_capa() == BT_IO_NO_INPUT_OUTPUT && conn->required_sec_level > BT_SECURITY_L2) { return -EINVAL; } return conn_auth(conn); } #endif /* CONFIG_BT_BREDR */ if (IS_ENABLED(CONFIG_BT_SMP)) { return bt_smp_start_security(conn); } return -EINVAL; } int bt_conn_set_security(struct bt_conn *conn, bt_security_t sec) { int err; if (conn->state != BT_CONN_CONNECTED) { return -ENOTCONN; } if (IS_ENABLED(CONFIG_BT_SMP_SC_ONLY) && sec < BT_SECURITY_L4) { return -EOPNOTSUPP; } /* nothing to do */ if (conn->sec_level >= sec || conn->required_sec_level >= sec) { return 0; } atomic_set_bit_to(conn->flags, BT_CONN_FORCE_PAIR, sec & BT_SECURITY_FORCE_PAIR); conn->required_sec_level = sec & ~BT_SECURITY_FORCE_PAIR; err = start_security(conn); /* reset required security level in case of error */ if (err) { conn->required_sec_level = conn->sec_level; } return err; } bt_security_t bt_conn_get_security(struct bt_conn *conn) { return conn->sec_level; } #else bt_security_t bt_conn_get_security(struct bt_conn *conn) { return BT_SECURITY_L1; } #endif /* CONFIG_BT_SMP */ void bt_conn_cb_register(struct bt_conn_cb *cb) { cb->_next = callback_list; callback_list = cb; } void bt_conn_reset_rx_state(struct bt_conn *conn) { if (!conn->rx_len) { return; } net_buf_unref(conn->rx); conn->rx = NULL; conn->rx_len = 0U; } void bt_conn_recv(struct bt_conn *conn, struct net_buf *buf, u8_t flags) { struct bt_l2cap_hdr *hdr; u16_t len; /* Make sure we notify any pending TX callbacks before processing * new data for this connection. */ tx_notify(conn); BT_DBG("handle %u len %u flags %02x", conn->handle, buf->len, flags); /* Check packet boundary flags */ switch (flags) { case BT_ACL_START: hdr = (void *)buf->data; len = sys_le16_to_cpu(hdr->len); BT_DBG("First, len %u final %u", buf->len, len); if (conn->rx_len) { BT_ERR("Unexpected first L2CAP frame"); bt_conn_reset_rx_state(conn); } conn->rx_len = (sizeof(*hdr) + len) - buf->len; BT_DBG("rx_len %u", conn->rx_len); if (conn->rx_len) { conn->rx = buf; return; } break; case BT_ACL_CONT: if (!conn->rx_len) { BT_ERR("Unexpected L2CAP continuation"); bt_conn_reset_rx_state(conn); net_buf_unref(buf); return; } if (buf->len > conn->rx_len) { BT_ERR("L2CAP data overflow"); bt_conn_reset_rx_state(conn); net_buf_unref(buf); return; } BT_DBG("Cont, len %u rx_len %u", buf->len, conn->rx_len); if (buf->len > net_buf_tailroom(conn->rx)) { BT_ERR("Not enough buffer space for L2CAP data"); bt_conn_reset_rx_state(conn); net_buf_unref(buf); return; } net_buf_add_mem(conn->rx, buf->data, buf->len); conn->rx_len -= buf->len; net_buf_unref(buf); if (conn->rx_len) { return; } buf = conn->rx; conn->rx = NULL; conn->rx_len = 0U; break; default: BT_ERR("Unexpected ACL flags (0x%02x)", flags); bt_conn_reset_rx_state(conn); net_buf_unref(buf); return; } hdr = (void *)buf->data; len = sys_le16_to_cpu(hdr->len); if (sizeof(*hdr) + len != buf->len) { BT_ERR("ACL len mismatch (%u != %u)", len, buf->len); net_buf_unref(buf); return; } BT_DBG("Successfully parsed %u byte L2CAP packet", buf->len); bt_l2cap_recv(conn, buf); } static struct bt_conn_tx *conn_tx_alloc(void) { /* The TX context always get freed in the system workqueue, * so if we're in the same workqueue but there are no immediate * contexts available, there's no chance we'll get one by waiting. */ #if !defined(BFLB_BLE) if (k_current_get() == &k_sys_work_q.thread) { return k_fifo_get(&free_tx, K_NO_WAIT); } #endif if (IS_ENABLED(CONFIG_BT_DEBUG_CONN)) { struct bt_conn_tx *tx = k_fifo_get(&free_tx, K_NO_WAIT); if (tx) { return tx; } BT_WARN("Unable to get an immediate free conn_tx"); } return k_fifo_get(&free_tx, K_FOREVER); } int bt_conn_send_cb(struct bt_conn *conn, struct net_buf *buf, bt_conn_tx_cb_t cb, void *user_data) { struct bt_conn_tx *tx; BT_DBG("conn handle %u buf len %u cb %p user_data %p", conn->handle, buf->len, cb, user_data); if (conn->state != BT_CONN_CONNECTED) { BT_ERR("not connected!"); net_buf_unref(buf); return -ENOTCONN; } if (cb) { tx = conn_tx_alloc(); if (!tx) { BT_ERR("Unable to allocate TX context"); net_buf_unref(buf); return -ENOBUFS; } /* Verify that we're still connected after blocking */ if (conn->state != BT_CONN_CONNECTED) { BT_WARN("Disconnected while allocating context"); net_buf_unref(buf); tx_free(tx); return -ENOTCONN; } tx->cb = cb; tx->user_data = user_data; tx->pending_no_cb = 0U; tx_data(buf)->tx = tx; } else { tx_data(buf)->tx = NULL; } #if (BFLB_BT_CO_THREAD) if (k_is_current_thread(bt_get_co_thread())) bt_conn_process_tx(conn, buf); else net_buf_put(&conn->tx_queue, buf); #if defined(BFLB_BLE) k_sem_give(&g_poll_sem); #endif #else //BFLB_BT_CO_THREAD net_buf_put(&conn->tx_queue, buf); #if defined(BFLB_BLE) k_sem_give(&g_poll_sem); #endif #endif //BFLB_BT_CO_THREAD return 0; } static bool send_frag(struct bt_conn *conn, struct net_buf *buf, u8_t flags, bool always_consume) { struct bt_conn_tx *tx = tx_data(buf)->tx; struct bt_hci_acl_hdr *hdr; u32_t *pending_no_cb; unsigned int key; int err; BT_DBG("conn %p buf %p len %u flags 0x%02x", conn, buf, buf->len, flags); /* Wait until the controller can accept ACL packets */ k_sem_take(bt_conn_get_pkts(conn), K_FOREVER); /* Check for disconnection while waiting for pkts_sem */ if (conn->state != BT_CONN_CONNECTED) { goto fail; } hdr = net_buf_push(buf, sizeof(*hdr)); hdr->handle = sys_cpu_to_le16(bt_acl_handle_pack(conn->handle, flags)); hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr)); /* Add to pending, it must be done before bt_buf_set_type */ key = irq_lock(); if (tx) { sys_slist_append(&conn->tx_pending, &tx->node); } else { struct bt_conn_tx *tail_tx; tail_tx = (void *)sys_slist_peek_tail(&conn->tx_pending); if (tail_tx) { pending_no_cb = &tail_tx->pending_no_cb; } else { pending_no_cb = &conn->pending_no_cb; } (*pending_no_cb)++; } irq_unlock(key); bt_buf_set_type(buf, BT_BUF_ACL_OUT); err = bt_send(buf); if (err) { BT_ERR("Unable to send to driver (err %d)", err); key = irq_lock(); /* Roll back the pending TX info */ if (tx) { sys_slist_find_and_remove(&conn->tx_pending, &tx->node); } else { __ASSERT_NO_MSG(*pending_no_cb > 0); (*pending_no_cb)--; } irq_unlock(key); goto fail; } return true; fail: k_sem_give(bt_conn_get_pkts(conn)); if (tx) { tx_free(tx); } if (always_consume) { net_buf_unref(buf); } return false; } static inline u16_t conn_mtu(struct bt_conn *conn) { #if defined(CONFIG_BT_BREDR) if (conn->type == BT_CONN_TYPE_BR || !bt_dev.le.mtu) { return bt_dev.br.mtu; } #endif /* CONFIG_BT_BREDR */ return bt_dev.le.mtu; } static struct net_buf *create_frag(struct bt_conn *conn, struct net_buf *buf) { struct net_buf *frag; u16_t frag_len; #if CONFIG_BT_L2CAP_TX_FRAG_COUNT > 0 frag = bt_conn_create_pdu(&frag_pool, 0); #else frag = bt_conn_create_pdu(NULL, 0); #endif if (conn->state != BT_CONN_CONNECTED) { net_buf_unref(frag); return NULL; } /* Fragments never have a TX completion callback */ tx_data(frag)->tx = NULL; frag_len = MIN(conn_mtu(conn), net_buf_tailroom(frag)); net_buf_add_mem(frag, buf->data, frag_len); net_buf_pull(buf, frag_len); return frag; } static bool send_buf(struct bt_conn *conn, struct net_buf *buf) { struct net_buf *frag; BT_DBG("conn %p buf %p len %u", conn, buf, buf->len); /* Send directly if the packet fits the ACL MTU */ if (buf->len <= conn_mtu(conn)) { return send_frag(conn, buf, BT_ACL_START_NO_FLUSH, false); } /* Create & enqueue first fragment */ frag = create_frag(conn, buf); if (!frag) { return false; } if (!send_frag(conn, frag, BT_ACL_START_NO_FLUSH, true)) { return false; } /* * Send the fragments. For the last one simply use the original * buffer (which works since we've used net_buf_pull on it. */ while (buf->len > conn_mtu(conn)) { frag = create_frag(conn, buf); if (!frag) { return false; } if (!send_frag(conn, frag, BT_ACL_CONT, true)) { return false; } } return send_frag(conn, buf, BT_ACL_CONT, false); } static struct k_poll_signal conn_change = K_POLL_SIGNAL_INITIALIZER(conn_change); static void conn_cleanup(struct bt_conn *conn) { struct net_buf *buf; /* Give back any allocated buffers */ while ((buf = net_buf_get(&conn->tx_queue, K_NO_WAIT))) { if (tx_data(buf)->tx) { tx_free(tx_data(buf)->tx); } net_buf_unref(buf); } __ASSERT(sys_slist_is_empty(&conn->tx_pending), "Pending TX packets"); __ASSERT_NO_MSG(conn->pending_no_cb == 0); bt_conn_reset_rx_state(conn); k_delayed_work_submit(&conn->update_work, K_NO_WAIT); #ifdef BFLB_BLE_PATCH_FREE_ALLOCATED_BUFFER_IN_OS k_queue_free(&conn->tx_queue._queue); // k_queue_free(&conn->tx_notify._queue); conn->tx_queue._queue.hdl = NULL; //conn->tx_notify._queue.hdl = NULL; if (conn->update_work.timer.timer.hdl) k_delayed_work_del_timer(&conn->update_work); #endif } int bt_conn_prepare_events(struct k_poll_event events[]) { int i, ev_count = 0; BT_DBG(""); conn_change.signaled = 0U; k_poll_event_init(&events[ev_count++], K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, &conn_change); for (i = 0; i < ARRAY_SIZE(conns); i++) { struct bt_conn *conn = &conns[i]; if (!atomic_get(&conn->ref)) { continue; } if (conn->state == BT_CONN_DISCONNECTED && atomic_test_and_clear_bit(conn->flags, BT_CONN_CLEANUP)) { conn_cleanup(conn); continue; } if (conn->state != BT_CONN_CONNECTED) { continue; } BT_DBG("Adding conn %p to poll list", conn); k_poll_event_init(&events[ev_count], K_POLL_TYPE_FIFO_DATA_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, &conn->tx_queue); events[ev_count++].tag = BT_EVENT_CONN_TX_QUEUE; } return ev_count; } #if (BFLB_BT_CO_THREAD) void bt_conn_process_tx(struct bt_conn *conn, struct net_buf *tx_buf) #else void bt_conn_process_tx(struct bt_conn *conn) #endif { struct net_buf *buf; BT_DBG("conn %p", conn); if (conn->state == BT_CONN_DISCONNECTED && atomic_test_and_clear_bit(conn->flags, BT_CONN_CLEANUP)) { BT_DBG("handle %u disconnected - cleaning up", conn->handle); conn_cleanup(conn); return; } #if (BFLB_BT_CO_THREAD) if (tx_buf) buf = tx_buf; else buf = net_buf_get(&conn->tx_queue, K_NO_WAIT); #else /* Get next ACL packet for connection */ buf = net_buf_get(&conn->tx_queue, K_NO_WAIT); #endif BT_ASSERT(buf); if (!send_buf(conn, buf)) { net_buf_unref(buf); } } struct bt_conn *bt_conn_add_le(u8_t id, const bt_addr_le_t *peer) { struct bt_conn *conn = conn_new(); if (!conn) { return NULL; } conn->id = id; bt_addr_le_copy(&conn->le.dst, peer); #if defined(CONFIG_BT_SMP) conn->sec_level = BT_SECURITY_L1; conn->required_sec_level = BT_SECURITY_L1; #endif /* CONFIG_BT_SMP */ conn->type = BT_CONN_TYPE_LE; conn->le.interval_min = BT_GAP_INIT_CONN_INT_MIN; conn->le.interval_max = BT_GAP_INIT_CONN_INT_MAX; return conn; } static void process_unack_tx(struct bt_conn *conn) { /* Return any unacknowledged packets */ while (1) { struct bt_conn_tx *tx; sys_snode_t *node; unsigned int key; key = irq_lock(); if (conn->pending_no_cb) { conn->pending_no_cb--; irq_unlock(key); k_sem_give(bt_conn_get_pkts(conn)); continue; } node = sys_slist_get(&conn->tx_pending); irq_unlock(key); if (!node) { break; } tx = CONTAINER_OF(node, struct bt_conn_tx, node); key = irq_lock(); conn->pending_no_cb = tx->pending_no_cb; tx->pending_no_cb = 0U; irq_unlock(key); tx_free(tx); k_sem_give(bt_conn_get_pkts(conn)); } } void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state) { bt_conn_state_t old_state; BT_DBG("%s -> %s", state2str(conn->state), state2str(state)); if (conn->state == state) { BT_WARN("no transition"); return; } old_state = conn->state; conn->state = state; /* Actions needed for exiting the old state */ switch (old_state) { case BT_CONN_DISCONNECTED: /* Take a reference for the first state transition after * bt_conn_add_le() and keep it until reaching DISCONNECTED * again. */ bt_conn_ref(conn); break; case BT_CONN_CONNECT: if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->type == BT_CONN_TYPE_LE) { k_delayed_work_cancel(&conn->update_work); } break; default: break; } /* Actions needed for entering the new state */ switch (conn->state) { case BT_CONN_CONNECTED: if (conn->type == BT_CONN_TYPE_SCO) { /* TODO: Notify sco connected */ break; } k_fifo_init(&conn->tx_queue, 20); k_poll_signal_raise(&conn_change, 0); sys_slist_init(&conn->channels); bt_l2cap_connected(conn); notify_connected(conn); break; case BT_CONN_DISCONNECTED: if (conn->type == BT_CONN_TYPE_SCO) { /* TODO: Notify sco disconnected */ bt_conn_unref(conn); break; } /* Notify disconnection and queue a dummy buffer to wake * up and stop the tx thread for states where it was * running. */ if (old_state == BT_CONN_CONNECTED || old_state == BT_CONN_DISCONNECT) { process_unack_tx(conn); tx_notify(conn); /* Cancel Connection Update if it is pending */ if (conn->type == BT_CONN_TYPE_LE) { k_delayed_work_cancel(&conn->update_work); } atomic_set_bit(conn->flags, BT_CONN_CLEANUP); k_poll_signal_raise(&conn_change, 0); /* The last ref will be dropped during cleanup */ } else if (old_state == BT_CONN_CONNECT) { /* conn->err will be set in this case */ notify_connected(conn); bt_conn_unref(conn); } else if (old_state == BT_CONN_CONNECT_SCAN) { /* this indicate LE Create Connection failed */ if (conn->err) { notify_connected(conn); } bt_conn_unref(conn); } else if (old_state == BT_CONN_CONNECT_DIR_ADV) { /* this indicate Directed advertising stopped */ if (conn->err) { notify_connected(conn); } bt_conn_unref(conn); } break; case BT_CONN_CONNECT_SCAN: break; case BT_CONN_CONNECT_DIR_ADV: break; case BT_CONN_CONNECT: if (conn->type == BT_CONN_TYPE_SCO) { break; } /* * Timer is needed only for LE. For other link types controller * will handle connection timeout. */ if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->type == BT_CONN_TYPE_LE) { k_delayed_work_submit(&conn->update_work, CONN_TIMEOUT); } break; case BT_CONN_DISCONNECT: break; default: BT_WARN("no valid (%u) state was set", state); break; } } struct bt_conn *bt_conn_lookup_handle(u16_t handle) { int i; for (i = 0; i < ARRAY_SIZE(conns); i++) { if (!atomic_get(&conns[i].ref)) { continue; } /* We only care about connections with a valid handle */ if (conns[i].state != BT_CONN_CONNECTED && conns[i].state != BT_CONN_DISCONNECT) { continue; } if (conns[i].handle == handle) { return bt_conn_ref(&conns[i]); } } #if defined(CONFIG_BT_BREDR) for (i = 0; i < ARRAY_SIZE(sco_conns); i++) { if (!atomic_get(&sco_conns[i].ref)) { continue; } /* We only care about connections with a valid handle */ if (sco_conns[i].state != BT_CONN_CONNECTED && sco_conns[i].state != BT_CONN_DISCONNECT) { continue; } if (sco_conns[i].handle == handle) { return bt_conn_ref(&sco_conns[i]); } } #endif return NULL; } int bt_conn_addr_le_cmp(const struct bt_conn *conn, const bt_addr_le_t *peer) { /* Check against conn dst address as it may be the identity address */ if (!bt_addr_le_cmp(peer, &conn->le.dst)) { return 0; } /* Check against initial connection address */ if (conn->role == BT_HCI_ROLE_MASTER) { return bt_addr_le_cmp(peer, &conn->le.resp_addr); } return bt_addr_le_cmp(peer, &conn->le.init_addr); } struct bt_conn *bt_conn_lookup_addr_le(u8_t id, const bt_addr_le_t *peer) { int i; for (i = 0; i < ARRAY_SIZE(conns); i++) { if (!atomic_get(&conns[i].ref)) { continue; } if (conns[i].type != BT_CONN_TYPE_LE) { continue; } if (conns[i].id == id && !bt_conn_addr_le_cmp(&conns[i], peer)) { return bt_conn_ref(&conns[i]); } } return NULL; } struct bt_conn *bt_conn_lookup_state_le(const bt_addr_le_t *peer, const bt_conn_state_t state) { int i; for (i = 0; i < ARRAY_SIZE(conns); i++) { if (!atomic_get(&conns[i].ref)) { continue; } if (conns[i].type != BT_CONN_TYPE_LE) { continue; } if (peer && bt_conn_addr_le_cmp(&conns[i], peer)) { continue; } if (conns[i].state == state) { return bt_conn_ref(&conns[i]); } } return NULL; } void bt_conn_foreach(int type, void (*func)(struct bt_conn *conn, void *data), void *data) { int i; for (i = 0; i < ARRAY_SIZE(conns); i++) { if (!atomic_get(&conns[i].ref)) { continue; } if (!(conns[i].type & type)) { continue; } func(&conns[i], data); } #if defined(CONFIG_BT_BREDR) if (type & BT_CONN_TYPE_SCO) { for (i = 0; i < ARRAY_SIZE(sco_conns); i++) { if (!atomic_get(&sco_conns[i].ref)) { continue; } func(&sco_conns[i], data); } } #endif /* defined(CONFIG_BT_BREDR) */ } static void disconnect_all(struct bt_conn *conn, void *data) { u8_t *id = (u8_t *)data; if (conn->id == *id && conn->state == BT_CONN_CONNECTED) { bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); } } void bt_conn_disconnect_all(u8_t id) { bt_conn_foreach(BT_CONN_TYPE_ALL, disconnect_all, &id); } struct bt_conn *bt_conn_ref(struct bt_conn *conn) { atomic_inc(&conn->ref); BT_DBG("handle %u ref %u", conn->handle, atomic_get(&conn->ref)); return conn; } void bt_conn_unref(struct bt_conn *conn) { atomic_dec(&conn->ref); BT_DBG("handle %u ref %u", conn->handle, atomic_get(&conn->ref)); } const bt_addr_le_t *bt_conn_get_dst(const struct bt_conn *conn) { return &conn->le.dst; } int bt_conn_get_info(const struct bt_conn *conn, struct bt_conn_info *info) { info->type = conn->type; info->role = conn->role; info->id = conn->id; switch (conn->type) { case BT_CONN_TYPE_LE: info->le.dst = &conn->le.dst; info->le.src = &bt_dev.id_addr[conn->id]; if (conn->role == BT_HCI_ROLE_MASTER) { info->le.local = &conn->le.init_addr; info->le.remote = &conn->le.resp_addr; } else { info->le.local = &conn->le.resp_addr; info->le.remote = &conn->le.init_addr; } info->le.interval = conn->le.interval; info->le.latency = conn->le.latency; info->le.timeout = conn->le.timeout; return 0; #if defined(CONFIG_BT_BREDR) case BT_CONN_TYPE_BR: info->br.dst = &conn->br.dst; return 0; #endif } return -EINVAL; } int bt_conn_get_remote_dev_info(struct bt_conn_info *info) { int link_num = 0; for (int i = 0; i < ARRAY_SIZE(conns); i++) { if (!atomic_get(&conns[i].ref)) { continue; } bt_conn_get_info(&conns[i], &info[link_num]); link_num++; } return link_num; } static int bt_hci_disconnect(struct bt_conn *conn, u8_t reason) { struct net_buf *buf; struct bt_hci_cp_disconnect *disconn; int err; buf = bt_hci_cmd_create(BT_HCI_OP_DISCONNECT, sizeof(*disconn)); if (!buf) { return -ENOBUFS; } disconn = net_buf_add(buf, sizeof(*disconn)); disconn->handle = sys_cpu_to_le16(conn->handle); disconn->reason = reason; err = bt_hci_cmd_send_sync(BT_HCI_OP_DISCONNECT, buf, NULL); if (err) { return err; } bt_conn_set_state(conn, BT_CONN_DISCONNECT); return 0; } #if defined(CONFIG_BT_STACK_PTS) int pts_bt_conn_le_param_update(struct bt_conn *conn, const struct bt_le_conn_param *param) { BT_DBG("conn %p features 0x%02x params (%d-%d %d %d)", conn, conn->le.features[0], param->interval_min, param->interval_max, param->latency, param->timeout); /* Check if there's a need to update conn params */ if (conn->le.interval >= param->interval_min && conn->le.interval <= param->interval_max && conn->le.latency == param->latency && conn->le.timeout == param->timeout) { return -EALREADY; } /* Cancel any pending update */ k_delayed_work_cancel(&conn->update_work); return bt_l2cap_update_conn_param(conn, param); } #endif int bt_conn_le_param_update(struct bt_conn *conn, const struct bt_le_conn_param *param) { BT_DBG("conn %p features 0x%02x params (%d-%d %d %d)", conn, conn->le.features[0], param->interval_min, param->interval_max, param->latency, param->timeout); /* Check if there's a need to update conn params */ if (conn->le.interval >= param->interval_min && conn->le.interval <= param->interval_max && conn->le.latency == param->latency && conn->le.timeout == param->timeout) { atomic_clear_bit(conn->flags, BT_CONN_SLAVE_PARAM_SET); return -EALREADY; } if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->role == BT_CONN_ROLE_MASTER) { return send_conn_le_param_update(conn, param); } if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) { /* if slave conn param update timer expired just send request */ if (atomic_test_bit(conn->flags, BT_CONN_SLAVE_PARAM_UPDATE)) { return send_conn_le_param_update(conn, param); } /* store new conn params to be used by update timer */ conn->le.interval_min = param->interval_min; conn->le.interval_max = param->interval_max; conn->le.pending_latency = param->latency; conn->le.pending_timeout = param->timeout; atomic_set_bit(conn->flags, BT_CONN_SLAVE_PARAM_SET); } return 0; } int bt_conn_disconnect(struct bt_conn *conn, u8_t reason) { /* Disconnection is initiated by us, so auto connection shall * be disabled. Otherwise the passive scan would be enabled * and we could send LE Create Connection as soon as the remote * starts advertising. */ #if !defined(CONFIG_BT_WHITELIST) if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->type == BT_CONN_TYPE_LE) { bt_le_set_auto_conn(&conn->le.dst, NULL); } #endif /* !defined(CONFIG_BT_WHITELIST) */ switch (conn->state) { case BT_CONN_CONNECT_SCAN: conn->err = reason; bt_conn_set_state(conn, BT_CONN_DISCONNECTED); if (IS_ENABLED(CONFIG_BT_CENTRAL)) { bt_le_scan_update(false); } return 0; case BT_CONN_CONNECT_DIR_ADV: conn->err = reason; bt_conn_set_state(conn, BT_CONN_DISCONNECTED); if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) { /* User should unref connection object when receiving * error in connection callback. */ return bt_le_adv_stop(); } return 0; case BT_CONN_CONNECT: #if defined(CONFIG_BT_BREDR) if (conn->type == BT_CONN_TYPE_BR) { return bt_hci_connect_br_cancel(conn); } #endif /* CONFIG_BT_BREDR */ if (IS_ENABLED(CONFIG_BT_CENTRAL)) { k_delayed_work_cancel(&conn->update_work); return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_CONN_CANCEL, NULL, NULL); } return 0; case BT_CONN_CONNECTED: return bt_hci_disconnect(conn, reason); case BT_CONN_DISCONNECT: return 0; case BT_CONN_DISCONNECTED: default: return -ENOTCONN; } } #if defined(CONFIG_BT_CENTRAL) static void bt_conn_set_param_le(struct bt_conn *conn, const struct bt_le_conn_param *param) { conn->le.interval_min = param->interval_min; conn->le.interval_max = param->interval_max; conn->le.latency = param->latency; conn->le.timeout = param->timeout; #if defined(CONFIG_BT_STACK_PTS) conn->le.own_adder_type = param->own_address_type; #endif } #if defined(CONFIG_BT_WHITELIST) 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; } return 0; } int bt_conn_create_auto_stop(void) { int err; 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; } err = bt_le_auto_conn_cancel(); 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, const struct bt_le_conn_param *param) { struct bt_conn *conn; bt_addr_le_t dst; if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) { return NULL; } if (!bt_le_conn_params_valid(param)) { return NULL; } if (atomic_test_bit(bt_dev.flags, BT_DEV_EXPLICIT_SCAN)) { 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); if (conn) { switch (conn->state) { case BT_CONN_CONNECT_SCAN: bt_conn_set_param_le(conn, param); return conn; case BT_CONN_CONNECT: case BT_CONN_CONNECTED: return conn; case BT_CONN_DISCONNECTED: BT_WARN("Found valid but disconnected conn object"); goto start_scan; default: bt_conn_unref(conn); return NULL; } } if (peer->type == BT_ADDR_LE_PUBLIC_ID || peer->type == BT_ADDR_LE_RANDOM_ID) { bt_addr_le_copy(&dst, peer); dst.type -= BT_ADDR_LE_PUBLIC_ID; } else { bt_addr_le_copy(&dst, bt_lookup_id_addr(BT_ID_DEFAULT, peer)); } /* Only default identity supported for now */ conn = bt_conn_add_le(BT_ID_DEFAULT, &dst); if (!conn) { return NULL; } start_scan: bt_conn_set_param_le(conn, param); bt_conn_set_state(conn, BT_CONN_CONNECT_SCAN); bt_le_scan_update(true); return conn; } #if !defined(CONFIG_BT_WHITELIST) int bt_le_set_auto_conn(const bt_addr_le_t *addr, const struct bt_le_conn_param *param) { struct bt_conn *conn; if (param && !bt_le_conn_params_valid(param)) { return -EINVAL; } /* Only default identity is supported */ conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr); if (!conn) { conn = bt_conn_add_le(BT_ID_DEFAULT, addr); if (!conn) { return -ENOMEM; } } if (param) { bt_conn_set_param_le(conn, param); if (!atomic_test_and_set_bit(conn->flags, BT_CONN_AUTO_CONNECT)) { bt_conn_ref(conn); } } else { if (atomic_test_and_clear_bit(conn->flags, BT_CONN_AUTO_CONNECT)) { bt_conn_unref(conn); if (conn->state == BT_CONN_CONNECT_SCAN) { bt_conn_set_state(conn, BT_CONN_DISCONNECTED); } } } if (conn->state == BT_CONN_DISCONNECTED && atomic_test_bit(bt_dev.flags, BT_DEV_READY)) { if (param) { bt_conn_set_state(conn, BT_CONN_CONNECT_SCAN); } bt_le_scan_update(false); } bt_conn_unref(conn); return 0; } #endif /* !defined(CONFIG_BT_WHITELIST) */ #endif /* CONFIG_BT_CENTRAL */ #if defined(CONFIG_BT_PERIPHERAL) struct bt_conn *bt_conn_create_slave_le(const bt_addr_le_t *peer, const struct bt_le_adv_param *param) { int err; struct bt_conn *conn; struct bt_le_adv_param param_int; memcpy(¶m_int, param, sizeof(param_int)); param_int.options |= (BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_ONE_TIME); conn = bt_conn_lookup_addr_le(param->id, peer); if (conn) { switch (conn->state) { case BT_CONN_CONNECT_DIR_ADV: /* Handle the case when advertising is stopped with * bt_le_adv_stop function */ err = bt_le_adv_start_internal(¶m_int, NULL, 0, NULL, 0, peer); if (err && (err != -EALREADY)) { BT_WARN("Directed advertising could not be" " started: %d", err); bt_conn_unref(conn); return NULL; } __attribute__((fallthrough)); case BT_CONN_CONNECT: case BT_CONN_CONNECTED: return conn; case BT_CONN_DISCONNECTED: BT_WARN("Found valid but disconnected conn object"); goto start_adv; default: bt_conn_unref(conn); return NULL; } } conn = bt_conn_add_le(param->id, peer); if (!conn) { return NULL; } start_adv: bt_conn_set_state(conn, BT_CONN_CONNECT_DIR_ADV); err = bt_le_adv_start_internal(¶m_int, NULL, 0, NULL, 0, peer); if (err) { BT_WARN("Directed advertising could not be started: %d", err); bt_conn_unref(conn); return NULL; } return conn; } #endif /* CONFIG_BT_PERIPHERAL */ int bt_conn_le_conn_update(struct bt_conn *conn, const struct bt_le_conn_param *param) { struct hci_cp_le_conn_update *conn_update; struct net_buf *buf; buf = bt_hci_cmd_create(BT_HCI_OP_LE_CONN_UPDATE, sizeof(*conn_update)); if (!buf) { return -ENOBUFS; } conn_update = net_buf_add(buf, sizeof(*conn_update)); (void)memset(conn_update, 0, sizeof(*conn_update)); conn_update->handle = sys_cpu_to_le16(conn->handle); conn_update->conn_interval_min = sys_cpu_to_le16(param->interval_min); conn_update->conn_interval_max = sys_cpu_to_le16(param->interval_max); conn_update->conn_latency = sys_cpu_to_le16(param->latency); conn_update->supervision_timeout = sys_cpu_to_le16(param->timeout); return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CONN_UPDATE, buf, NULL); } struct net_buf *bt_conn_create_pdu_timeout(struct net_buf_pool *pool, size_t reserve, s32_t timeout) { struct net_buf *buf; /* * PDU must not be allocated from ISR as we block with 'K_FOREVER' * during the allocation */ __ASSERT_NO_MSG(!k_is_in_isr()); if (!pool) { pool = &acl_tx_pool; } if (IS_ENABLED(CONFIG_BT_DEBUG_CONN)) { buf = net_buf_alloc(pool, K_NO_WAIT); if (!buf) { BT_WARN("Unable to allocate buffer with K_NO_WAIT"); buf = net_buf_alloc(pool, timeout); } } else { buf = net_buf_alloc(pool, timeout); } if (!buf) { BT_WARN("Unable to allocate buffer: timeout %d", timeout); return NULL; } reserve += sizeof(struct bt_hci_acl_hdr) + BT_BUF_RESERVE; net_buf_reserve(buf, reserve); return buf; } #if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_BREDR) int bt_conn_auth_cb_register(const struct bt_conn_auth_cb *cb) { if (!cb) { bt_auth = NULL; return 0; } if (bt_auth) { return -EALREADY; } /* The cancel callback must always be provided if the app provides * interactive callbacks. */ if (!cb->cancel && (cb->passkey_display || cb->passkey_entry || cb->passkey_confirm || #if defined(CONFIG_BT_BREDR) cb->pincode_entry || #endif cb->pairing_confirm)) { return -EINVAL; } bt_auth = cb; return 0; } int bt_conn_auth_passkey_entry(struct bt_conn *conn, unsigned int passkey) { if (!bt_auth) { return -EINVAL; } if (IS_ENABLED(CONFIG_BT_SMP) && conn->type == BT_CONN_TYPE_LE) { bt_smp_auth_passkey_entry(conn, passkey); return 0; } #if defined(CONFIG_BT_BREDR) if (conn->type == BT_CONN_TYPE_BR) { /* User entered passkey, reset user state. */ if (!atomic_test_and_clear_bit(conn->flags, BT_CONN_USER)) { return -EPERM; } if (conn->br.pairing_method == PASSKEY_INPUT) { return ssp_passkey_reply(conn, passkey); } } #endif /* CONFIG_BT_BREDR */ return -EINVAL; } int bt_conn_auth_passkey_confirm(struct bt_conn *conn) { if (!bt_auth) { return -EINVAL; } if (IS_ENABLED(CONFIG_BT_SMP) && conn->type == BT_CONN_TYPE_LE) { return bt_smp_auth_passkey_confirm(conn); } #if defined(CONFIG_BT_BREDR) if (conn->type == BT_CONN_TYPE_BR) { /* Allow user confirm passkey value, then reset user state. */ if (!atomic_test_and_clear_bit(conn->flags, BT_CONN_USER)) { return -EPERM; } return ssp_confirm_reply(conn); } #endif /* CONFIG_BT_BREDR */ return -EINVAL; } int bt_conn_auth_cancel(struct bt_conn *conn) { if (!bt_auth) { return -EINVAL; } if (IS_ENABLED(CONFIG_BT_SMP) && conn->type == BT_CONN_TYPE_LE) { return bt_smp_auth_cancel(conn); } #if defined(CONFIG_BT_BREDR) if (conn->type == BT_CONN_TYPE_BR) { /* Allow user cancel authentication, then reset user state. */ if (!atomic_test_and_clear_bit(conn->flags, BT_CONN_USER)) { return -EPERM; } switch (conn->br.pairing_method) { case JUST_WORKS: case PASSKEY_CONFIRM: return ssp_confirm_neg_reply(conn); case PASSKEY_INPUT: return ssp_passkey_neg_reply(conn); case PASSKEY_DISPLAY: return bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL); case LEGACY: return pin_code_neg_reply(&conn->br.dst); default: break; } } #endif /* CONFIG_BT_BREDR */ return -EINVAL; } int bt_conn_auth_pairing_confirm(struct bt_conn *conn) { if (!bt_auth) { return -EINVAL; } switch (conn->type) { #if defined(CONFIG_BT_SMP) case BT_CONN_TYPE_LE: return bt_smp_auth_pairing_confirm(conn); #endif /* CONFIG_BT_SMP */ #if defined(CONFIG_BT_BREDR) case BT_CONN_TYPE_BR: return ssp_confirm_reply(conn); #endif /* CONFIG_BT_BREDR */ default: return -EINVAL; } } #endif /* CONFIG_BT_SMP || CONFIG_BT_BREDR */ u8_t bt_conn_index(struct bt_conn *conn) { u8_t index = conn - conns; __ASSERT(index < CONFIG_BT_MAX_CONN, "Invalid bt_conn pointer"); return index; } struct bt_conn *bt_conn_lookup_id(u8_t id) { struct bt_conn *conn; if (id >= ARRAY_SIZE(conns)) { return NULL; } conn = &conns[id]; if (!atomic_get(&conn->ref)) { return NULL; } return bt_conn_ref(conn); } int bt_conn_init(void) { #if defined(CONFIG_BT_SMP) int err; #endif int i; #if defined(BFLB_BLE) #if defined(BFLB_DYNAMIC_ALLOC_MEM) #if (BFLB_STATIC_ALLOC_MEM) net_buf_init(ACL_TX, &acl_tx_pool, CONFIG_BT_L2CAP_TX_BUF_COUNT, BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU), NULL); #else net_buf_init(&acl_tx_pool, CONFIG_BT_L2CAP_TX_BUF_COUNT, BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU), NULL); #endif #if CONFIG_BT_L2CAP_TX_FRAG_COUNT > 0 #if (BFLB_STATIC_ALLOC_MEM) net_buf_init(FRAG, &frag_pool, CONFIG_BT_L2CAP_TX_FRAG_COUNT, FRAG_SIZE, NULL); #else net_buf_init(&frag_pool, CONFIG_BT_L2CAP_TX_FRAG_COUNT, FRAG_SIZE, NULL); #endif #endif #else //BFLB_DYNAMIC_ALLOC_MEM struct net_buf_pool num_complete_pool; struct net_buf_pool acl_tx_pool; #if CONFIG_BT_L2CAP_TX_FRAG_COUNT > 0 struct net_buf_pool frag_pool; #endif #endif //BFLB_DYNAMIC_ALLOC_MEM k_fifo_init(&free_tx, 20); #endif for (i = 0; i < ARRAY_SIZE(conn_tx); i++) { k_fifo_put(&free_tx, &conn_tx[i]); } bt_att_init(); #if defined(CONFIG_BT_SMP) err = bt_smp_init(); if (err) { return err; } #endif bt_l2cap_init(); /* Initialize background scan */ if (IS_ENABLED(CONFIG_BT_CENTRAL)) { for (i = 0; i < ARRAY_SIZE(conns); i++) { struct bt_conn *conn = &conns[i]; if (!atomic_get(&conn->ref)) { continue; } if (atomic_test_bit(conn->flags, BT_CONN_AUTO_CONNECT)) { /* Only the default identity is supported */ conn->id = BT_ID_DEFAULT; bt_conn_set_state(conn, BT_CONN_CONNECT_SCAN); } } } return 0; }