| /**************************************************************************** |
| * wireless/bluetooth/bt_conn.c |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| * |
| * Copyright (c) 2016, Intel Corporation |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * 3. Neither the name of the copyright holder nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
| * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
| * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
| * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <nuttx/kthread.h> |
| #include <nuttx/mm/iob.h> |
| #include <nuttx/wireless/bluetooth/bt_hci.h> |
| #include <nuttx/wireless/bluetooth/bt_core.h> |
| |
| #include "bt_atomic.h" |
| #include "bt_queue.h" |
| #include "bt_hcicore.h" |
| #include "bt_conn.h" |
| #include "bt_l2cap.h" |
| #include "bt_keys.h" |
| #include "bt_smp.h" |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| struct bt_conn_handoff_s |
| { |
| sem_t sync_sem; |
| FAR struct bt_conn_s *conn; |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static struct bt_conn_s g_conns[CONFIG_BLUETOOTH_MAX_CONN]; |
| static struct bt_conn_handoff_s g_conn_handoff = |
| { |
| SEM_INITIALIZER(1), |
| NULL |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| static const char *state2str(enum bt_conn_state_e state) |
| { |
| switch (state) |
| { |
| case BT_CONN_DISCONNECTED: |
| return "disconnected"; |
| |
| case BT_CONN_CONNECT_SCAN: |
| return "connect-scan"; |
| |
| case BT_CONN_CONNECT: |
| return "connect"; |
| |
| case BT_CONN_CONNECTED: |
| return "connected"; |
| |
| case BT_CONN_DISCONNECT: |
| return "disconnect"; |
| |
| default: |
| return "(unknown)"; |
| } |
| } |
| |
| static void bt_conn_reset_rx_state(FAR struct bt_conn_s *conn) |
| { |
| if (!conn->rx_len) |
| { |
| return; |
| } |
| |
| bt_buf_release(conn->rx); |
| conn->rx = NULL; |
| conn->rx_len = 0; |
| } |
| |
| static int conn_tx_kthread(int argc, FAR char *argv[]) |
| { |
| FAR struct bt_conn_s *conn; |
| FAR struct bt_buf_s *buf; |
| struct mq_attr attr; |
| int ret; |
| |
| /* Get the connection instance */ |
| |
| conn = g_conn_handoff.conn; |
| DEBUGASSERT(conn != NULL); |
| nxsem_post(&g_conn_handoff.sync_sem); |
| |
| wlinfo("Started for handle %u\n", conn->handle); |
| |
| while (conn->state == BT_CONN_CONNECTED) |
| { |
| /* Wait until the controller can accept ACL packets */ |
| |
| wlinfo("calling nxsem_wait_uninterruptible()\n"); |
| |
| ret = nxsem_wait_uninterruptible(&g_btdev.le_pkts_sem); |
| if (ret < 0) |
| { |
| wlerr("nxsem_wait_uninterruptible() failed: %d\n", ret); |
| break; |
| } |
| |
| /* Check for disconnection */ |
| |
| if (conn->state != BT_CONN_CONNECTED) |
| { |
| nxsem_post(&g_btdev.le_pkts_sem); |
| break; |
| } |
| |
| /* Get next ACL packet for connection */ |
| |
| ret = bt_queue_receive(&conn->tx_queue, &buf); |
| DEBUGASSERT(ret >= 0 && buf != NULL); |
| UNUSED(ret); |
| |
| if (conn->state != BT_CONN_CONNECTED) |
| { |
| nxsem_post(&g_btdev.le_pkts_sem); |
| bt_buf_release(buf); |
| break; |
| } |
| |
| wlinfo("passing buf %p len %u to driver\n", buf, buf->len); |
| bt_send(g_btdev.btdev, buf); |
| bt_buf_release(buf); |
| } |
| |
| wlinfo("handle %u disconnected - cleaning up\n", conn->handle); |
| |
| /* Give back any allocated buffers */ |
| |
| do |
| { |
| buf = NULL; |
| |
| /* Make sure the thread is not blocked forever on an empty queue. |
| * SIOCBTCONNECT will fail if preceding SIOCBTDISCONNECT does not |
| * result in a successful termination of this thread. |
| */ |
| |
| ret = file_mq_getattr(&conn->tx_queue, &attr); |
| if (ret != OK) |
| { |
| break; |
| } |
| |
| if (attr.mq_curmsgs == 0) |
| { |
| break; |
| } |
| |
| ret = bt_queue_receive(&conn->tx_queue, &buf); |
| if (ret >= 0) |
| { |
| DEBUGASSERT(buf != NULL); |
| bt_buf_release(buf); |
| } |
| } |
| while (ret >= OK); |
| |
| bt_conn_reset_rx_state(conn); |
| |
| wlinfo("handle %u exiting\n", conn->handle); |
| |
| /* Release reference taken when thread was created */ |
| |
| bt_conn_release(conn); |
| return EXIT_SUCCESS; |
| } |
| |
| static int bt_hci_disconnect(FAR struct bt_conn_s *conn, uint8_t reason) |
| { |
| FAR struct bt_buf_s *buf; |
| FAR struct bt_hci_cp_disconnect_s *disconn; |
| int err; |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_DISCONNECT, sizeof(*disconn)); |
| if (!buf) |
| { |
| return -ENOBUFS; |
| } |
| |
| disconn = bt_buf_extend(buf, sizeof(*disconn)); |
| disconn->handle = BT_HOST2LE16(conn->handle); |
| disconn->reason = reason; |
| |
| err = bt_hci_cmd_send(BT_HCI_OP_DISCONNECT, buf); |
| if (err) |
| { |
| return err; |
| } |
| |
| bt_conn_set_state(conn, BT_CONN_DISCONNECT); |
| return 0; |
| } |
| |
| static int bt_hci_connect_le_cancel(FAR struct bt_conn_s *conn) |
| { |
| int err; |
| |
| err = bt_hci_cmd_send(BT_HCI_OP_LE_CREATE_CONN_CANCEL, NULL); |
| if (err) |
| { |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: bt_conn_receive |
| * |
| * Description: |
| * Receive packets from the HCI core on a registered connection. |
| * |
| * Input Parameters: |
| * conn - The registered connection |
| * buf - The buffer structure containing the packet |
| * flags - Packet boundary flags |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void bt_conn_receive(FAR struct bt_conn_s *conn, FAR struct bt_buf_s *buf, |
| uint8_t flags) |
| { |
| FAR struct bt_l2cap_hdr_s *hdr; |
| uint16_t len; |
| |
| wlinfo("handle %u len %u flags %02x\n", conn->handle, buf->len, flags); |
| |
| /* Check packet boundary flags */ |
| |
| switch (flags) |
| { |
| case BT_HCI_ACL_NEW: |
| |
| /* First packet */ |
| |
| hdr = (FAR void *)buf->data; |
| len = BT_LE162HOST(hdr->len); |
| |
| wlinfo("First, len %u final %u\n", buf->len, len); |
| |
| if (conn->rx_len) |
| { |
| wlerr("ERROR: Unexpected first L2CAP frame\n"); |
| bt_conn_reset_rx_state(conn); |
| } |
| |
| conn->rx_len = (sizeof(*hdr) + len) - buf->len; |
| wlinfo("rx_len %u\n", conn->rx_len); |
| if (conn->rx_len) |
| { |
| conn->rx = buf; |
| return; |
| } |
| |
| break; |
| |
| case BT_HCI_ACL_CONTINUATION: |
| |
| /* Continuation */ |
| |
| if (!conn->rx_len) |
| { |
| wlerr("ERROR: Unexpected L2CAP continuation\n"); |
| bt_conn_reset_rx_state(conn); |
| bt_buf_release(buf); |
| return; |
| } |
| |
| if (buf->len > conn->rx_len) |
| { |
| wlerr("ERROR: L2CAP data overflow\n"); |
| bt_conn_reset_rx_state(conn); |
| bt_buf_release(buf); |
| return; |
| } |
| |
| wlinfo("Cont, len %u rx_len %u\n", buf->len, conn->rx_len); |
| |
| if (buf->len > bt_buf_tailroom(conn->rx)) |
| { |
| wlerr("ERROR: Not enough buffer space for L2CAP data\n"); |
| bt_conn_reset_rx_state(conn); |
| bt_buf_release(buf); |
| return; |
| } |
| |
| memcpy(bt_buf_extend(conn->rx, buf->len), buf->data, buf->len); |
| conn->rx_len -= buf->len; |
| bt_buf_release(buf); |
| |
| if (conn->rx_len) |
| { |
| return; |
| } |
| |
| buf = conn->rx; |
| conn->rx = NULL; |
| conn->rx_len = 0; |
| |
| break; |
| |
| default: |
| wlerr("ERROR: Unexpected ACL flags (0x%02x)\n", flags); |
| bt_conn_reset_rx_state(conn); |
| bt_buf_release(buf); |
| return; |
| } |
| |
| hdr = (FAR void *)buf->data; |
| len = BT_LE162HOST(hdr->len); |
| |
| if (sizeof(*hdr) + len != buf->len) |
| { |
| wlerr("ERROR: ACL len mismatch (%u != %u)\n", len, buf->len); |
| bt_buf_release(buf); |
| return; |
| } |
| |
| wlinfo("Successfully parsed %u byte L2CAP packet\n", buf->len); |
| |
| bt_l2cap_receive(conn, buf); |
| } |
| |
| /**************************************************************************** |
| * Name: bt_conn_send |
| * |
| * Description: |
| * Send data over a connection |
| * |
| * Input Parameters: |
| * conn - The registered connection |
| * buf - The buffer structure containing the packet to be sent |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void bt_conn_send(FAR struct bt_conn_s *conn, FAR struct bt_buf_s *buf) |
| { |
| FAR struct bt_hci_acl_hdr_s *hdr; |
| sq_queue_t fraglist; |
| uint16_t len; |
| uint16_t remaining = buf->len; |
| FAR uint8_t *ptr; |
| |
| DEBUGASSERT(conn != NULL && buf != NULL); |
| |
| sq_init(&fraglist); |
| |
| wlwarn("conn handle %u buf len %u\n", conn->handle, buf->len); |
| |
| if (conn->state != BT_CONN_CONNECTED) |
| { |
| wlerr("ERROR: not connected!\n"); |
| return; |
| } |
| |
| len = remaining; |
| if (len > g_btdev.le_mtu) |
| { |
| len = g_btdev.le_mtu; |
| } |
| |
| hdr = bt_buf_provide(buf, sizeof(*hdr)); |
| hdr->handle = BT_HOST2LE16(conn->handle); |
| hdr->len = BT_HOST2LE16(len); |
| |
| buf->len -= remaining - len; |
| ptr = bt_buf_tail(buf); |
| |
| /* Add the fragment to the end of the list */ |
| |
| sq_addlast((FAR sq_entry_t *)buf, &fraglist); |
| remaining -= len; |
| |
| while (remaining) |
| { |
| buf = bt_l2cap_create_pdu(conn); |
| |
| len = remaining; |
| if (len < g_btdev.le_mtu) |
| { |
| len = g_btdev.le_mtu; |
| } |
| |
| /* Copy from original buffer */ |
| |
| memcpy(bt_buf_extend(buf, len), ptr, len); |
| ptr += len; |
| |
| hdr = bt_buf_provide(buf, sizeof(*hdr)); |
| hdr->handle = BT_HOST2LE16(conn->handle | (1 << 12)); |
| hdr->len = BT_HOST2LE16(len); |
| |
| /* Add the fragment to the end of the list */ |
| |
| sq_addlast((FAR sq_entry_t *)buf, &fraglist); |
| remaining -= len; |
| } |
| |
| /* Then send each fragment in the correct order */ |
| |
| while ((buf = (FAR struct bt_buf_s *)sq_remfirst(&fraglist)) != NULL) |
| { |
| bt_queue_send(&conn->tx_queue, buf, BT_NORMAL_PRIO); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: bt_conn_add |
| * |
| * Description: |
| * Add a new connection |
| * |
| * Input Parameters: |
| * peer - The address of the Bluetooth peer |
| * role - Either BT_HCI_ROLE_MASTER or BT_HCI_ROLE_SLAVE |
| * |
| * Returned Value: |
| * A reference to the new connection structure is returned on success. |
| * |
| ****************************************************************************/ |
| |
| FAR struct bt_conn_s *bt_conn_add(FAR const bt_addr_le_t *peer, |
| uint8_t role) |
| { |
| FAR struct bt_conn_s *conn = NULL; |
| int i; |
| |
| for (i = 0; i < CONFIG_BLUETOOTH_MAX_CONN; i++) |
| { |
| if (!bt_addr_le_cmp(&g_conns[i].dst, BT_ADDR_LE_ANY)) |
| { |
| conn = &g_conns[i]; |
| break; |
| } |
| } |
| |
| if (!conn) |
| { |
| return NULL; |
| } |
| |
| memset(conn, 0, sizeof(*conn)); |
| |
| bt_atomic_set(&conn->ref, 1); |
| conn->role = role; |
| bt_addr_le_copy(&conn->dst, peer); |
| |
| return conn; |
| } |
| |
| /**************************************************************************** |
| * Name: bt_conn_set_state |
| * |
| * Description: |
| * Set connection object in certain state and perform actions related to |
| * state change. |
| * |
| * Input Parameters: |
| * conn - The connection whose state will be changed. |
| * state - The new state of the connection. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void bt_conn_set_state(FAR struct bt_conn_s *conn, |
| enum bt_conn_state_e state) |
| { |
| enum bt_conn_state_e old_state; |
| |
| wlinfo("%s -> %s\n", state2str(conn->state), state2str(state)); |
| |
| if (conn->state == state) |
| { |
| wlwarn("no transition\n"); |
| return; |
| } |
| |
| old_state = conn->state; |
| conn->state = state; |
| |
| /* Take a reference for the first state transition after bt_conn_add() and |
| * keep it until reaching DISCONNECTED again. |
| */ |
| |
| if (old_state == BT_CONN_DISCONNECTED) |
| { |
| bt_conn_addref(conn); |
| } |
| |
| switch (conn->state) |
| { |
| case BT_CONN_CONNECTED: |
| { |
| int ret; |
| |
| ret = bt_queue_open(BT_CONN_TX, O_RDWR | O_CREAT | O_CLOEXEC, |
| CONFIG_BLUETOOTH_TXCONN_NMSGS, |
| &conn->tx_queue); |
| DEBUGASSERT(ret >= 0); |
| |
| /* Get exclusive access to the handoff structure. The count will |
| * be zero when we complete this. |
| */ |
| |
| ret = nxsem_wait_uninterruptible(&g_conn_handoff.sync_sem); |
| if (ret >= 0) |
| { |
| /* Start the Tx connection kernel thread */ |
| |
| g_conn_handoff.conn = bt_conn_addref(conn); |
| ret = kthread_create("BT Conn Tx", |
| CONFIG_BLUETOOTH_TXCONN_PRIORITY, |
| CONFIG_BLUETOOTH_TXCONN_STACKSIZE, |
| conn_tx_kthread, NULL); |
| DEBUGASSERT(ret > 0); |
| |
| /* Take the semaphore again. This will force us to wait with |
| * the sem_count at -1. It will be zero again when we |
| * continue. |
| */ |
| |
| ret = nxsem_wait_uninterruptible(&g_conn_handoff.sync_sem); |
| nxsem_post(&g_conn_handoff.sync_sem); |
| } |
| |
| UNUSED(ret); |
| } |
| break; |
| |
| case BT_CONN_DISCONNECTED: |
| |
| /* Send 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) |
| { |
| bt_queue_send(&conn->tx_queue, bt_buf_alloc(BT_DUMMY, NULL, 0), |
| BT_NORMAL_PRIO); |
| } |
| |
| /* Release the reference we took for the very first state |
| * transition. |
| */ |
| |
| bt_conn_release(conn); |
| break; |
| |
| case BT_CONN_CONNECT_SCAN: |
| case BT_CONN_CONNECT: |
| case BT_CONN_DISCONNECT: |
| break; |
| |
| default: |
| wlwarn("no valid (%u) state was set\n", state); |
| break; |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: bt_conn_lookup_handle |
| * |
| * Description: |
| * Look up an existing connection |
| * |
| * Input Parameters: |
| * handle - The handle to be used to perform the lookup |
| * |
| * Returned Value: |
| * A reference to the connection state instance is returned on success. |
| * NULL is returned if the connection is not found. On success, the |
| * caller gets a new reference to the connection object which must be |
| * released with bt_conn_release() once done using the connection. |
| * |
| ****************************************************************************/ |
| |
| FAR struct bt_conn_s *bt_conn_lookup_handle(uint16_t handle) |
| { |
| int i; |
| |
| for (i = 0; i < CONFIG_BLUETOOTH_MAX_CONN; i++) |
| { |
| /* We only care about connections with a valid handle */ |
| |
| if (g_conns[i].state != BT_CONN_CONNECTED && |
| g_conns[i].state != BT_CONN_DISCONNECT) |
| { |
| continue; |
| } |
| |
| if (g_conns[i].handle == handle) |
| { |
| return bt_conn_addref(&g_conns[i]); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /**************************************************************************** |
| * Name: bt_conn_lookup_addr_le |
| * |
| * Description: |
| * Look up an existing connection based on the remote address. |
| * |
| * Input Parameters: |
| * peer - Remote address. |
| * |
| * Returned Value: |
| * A reference to the connection state instance is returned on success. |
| * NULL is returned if the connection is not found. On success, the |
| * caller gets a new reference to the connection object which must be |
| * released with bt_conn_release() once done using the connection. |
| * |
| ****************************************************************************/ |
| |
| FAR struct bt_conn_s *bt_conn_lookup_addr_le(FAR const bt_addr_le_t * peer) |
| { |
| int i; |
| |
| for (i = 0; i < CONFIG_BLUETOOTH_MAX_CONN; i++) |
| { |
| if (!bt_addr_le_cmp(peer, &g_conns[i].dst)) |
| { |
| return bt_conn_addref(&g_conns[i]); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /**************************************************************************** |
| * Name: bt_conn_lookup_state |
| * |
| * Description: |
| * Look up a connection state. For BT_ADDR_LE_ANY, returns the first |
| * connection with the specific state |
| * |
| * Input Parameters: |
| * peer - The peer address to match |
| * state - The connection state to match |
| * |
| * Returned Value: |
| * A reference to the connection state instance is returned on success. |
| * NULL is returned if the connection is not found. On success, the |
| * caller gets a new reference to the connection object which must be |
| * released with bt_conn_release() once done using the connection. |
| * |
| ****************************************************************************/ |
| |
| FAR struct bt_conn_s *bt_conn_lookup_state(FAR const bt_addr_le_t * peer, |
| enum bt_conn_state_e state) |
| { |
| int i; |
| |
| for (i = 0; i < CONFIG_BLUETOOTH_MAX_CONN; i++) |
| { |
| if (!bt_addr_le_cmp(&g_conns[i].dst, BT_ADDR_LE_ANY)) |
| { |
| continue; |
| } |
| |
| if (bt_addr_le_cmp(peer, BT_ADDR_LE_ANY) && |
| bt_addr_le_cmp(peer, &g_conns[i].dst)) |
| { |
| continue; |
| } |
| |
| if (g_conns[i].state == state) |
| { |
| return bt_conn_addref(&g_conns[i]); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /**************************************************************************** |
| * Name: bt_conn_addref |
| * |
| * Description: |
| * Increment the reference count of a connection object. |
| * |
| * Input Parameters: |
| * conn - Connection object. |
| * |
| * Returned Value: |
| * Connection object with incremented reference count. |
| * |
| ****************************************************************************/ |
| |
| FAR struct bt_conn_s *bt_conn_addref(FAR struct bt_conn_s *conn) |
| { |
| bt_atomic_incr(&conn->ref); |
| |
| wlinfo("handle %u ref %" PRId32 "\n", conn->handle, |
| bt_atomic_get(&conn->ref)); |
| |
| return conn; |
| } |
| |
| /**************************************************************************** |
| * Name: bt_conn_release |
| * |
| * Description: |
| * Decrement the reference count of a connection object. |
| * |
| * Input Parameters: |
| * conn - Connection object. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void bt_conn_release(FAR struct bt_conn_s *conn) |
| { |
| bt_atomic_t old_ref; |
| |
| old_ref = bt_atomic_decr(&conn->ref); |
| |
| wlinfo("handle %u ref %" PRId32 "\n", conn->handle, |
| bt_atomic_get(&conn->ref)); |
| |
| if (old_ref > 1) |
| { |
| return; |
| } |
| |
| bt_addr_le_copy(&conn->dst, BT_ADDR_LE_ANY); |
| } |
| |
| /**************************************************************************** |
| * Name: bt_conn_get_dst |
| * |
| * Description: |
| * Get destination (peer) address of a connection. |
| * |
| * Input Parameters: |
| * conn - Connection object. |
| * |
| * Returned Value: |
| * Destination address. |
| * |
| ****************************************************************************/ |
| |
| FAR const bt_addr_le_t *bt_conn_get_dst(FAR const struct bt_conn_s *conn) |
| { |
| return &conn->dst; |
| } |
| |
| /**************************************************************************** |
| * Name: bt_conn_security |
| * |
| * Description: |
| * This function enable security (encryption) for a connection. If device |
| * is already paired with sufficiently strong key encryption will be |
| * enabled. If link is already encrypted with sufficiently strong key this |
| * function does nothing. |
| * |
| * If device is not paired pairing will be initiated. If device is paired |
| * and keys are too weak but input output capabilities allow for strong |
| * enough keys pairing will be initiated. |
| * |
| * This function may return error if required level of security is not |
| * possible to achieve due to local or remote device limitation (eg input |
| * output capabilities). |
| * |
| * Input Parameters: |
| * conn - Connection object. |
| * sec - Requested security level. |
| * |
| * Returned Value: |
| * 0 on success or negative error |
| * |
| ****************************************************************************/ |
| |
| int bt_conn_security(FAR struct bt_conn_s *conn, enum bt_security_e sec) |
| { |
| FAR struct bt_keys_s *keys; |
| |
| if (conn->state != BT_CONN_CONNECTED) |
| { |
| return -ENOTCONN; |
| } |
| |
| /* Store the requested security level */ |
| |
| conn->sec_level = sec; |
| |
| /* Nothing to do */ |
| |
| if (sec == BT_SECURITY_LOW) |
| { |
| return 0; |
| } |
| |
| /* For now we only support Just Works and MITM with passkey (Legacy only) */ |
| |
| if (sec > BT_SECURITY_HIGH) |
| { |
| return -EINVAL; |
| } |
| |
| if (conn->encrypt) |
| { |
| return 0; |
| } |
| |
| if (conn->role == BT_HCI_ROLE_SLAVE) |
| { |
| return bt_smp_send_security_req(conn); |
| } |
| |
| keys = bt_keys_find(BT_KEYS_LTK, &conn->dst); |
| if (keys) |
| { |
| return bt_conn_le_start_encryption(conn, keys->ltk.rand, |
| keys->ltk.ediv, keys->ltk.val); |
| } |
| |
| return bt_smp_send_pairing_req(conn); |
| } |
| |
| /**************************************************************************** |
| * Name:bt_conn_set_auto_conn |
| * |
| * Description: |
| * This function enables/disables automatic connection initiation. |
| * Every time the device looses the connection with peer, this connection |
| * will be re-established if connectible advertisement from peer is |
| * received. |
| * |
| * Input Parameters: |
| * conn - Existing connection object. |
| * auto_conn - boolean value. If true, auto connect is enabled, if false, |
| * auto connect is disabled. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void bt_conn_set_auto_conn(FAR struct bt_conn_s *conn, bool auto_conn) |
| { |
| if (auto_conn) |
| { |
| bt_atomic_setbit(conn->flags, BT_CONN_AUTO_CONNECT); |
| } |
| else |
| { |
| bt_atomic_clrbit(conn->flags, BT_CONN_AUTO_CONNECT); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: bt_conn_disconnect |
| * |
| * Description: |
| * Disconnect an active connection with the specified reason code or cancel |
| * pending outgoing connection. |
| * |
| * Input Parameters: |
| * conn - Connection to disconnect. |
| * reason - Reason code for the disconnection. |
| * |
| * Returned Value: |
| * Zero on success or (negative) error code on failure. |
| * |
| ****************************************************************************/ |
| |
| int bt_conn_disconnect(FAR struct bt_conn_s *conn, uint8_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. |
| */ |
| |
| bt_conn_set_auto_conn(conn, false); |
| |
| switch (conn->state) |
| { |
| case BT_CONN_CONNECT_SCAN: |
| bt_conn_set_state(conn, BT_CONN_DISCONNECTED); |
| bt_le_scan_update(); |
| return 0; |
| |
| case BT_CONN_CONNECT: |
| return bt_hci_connect_le_cancel(conn); |
| |
| case BT_CONN_CONNECTED: |
| return bt_hci_disconnect(conn, reason); |
| |
| case BT_CONN_DISCONNECT: |
| return 0; |
| |
| case BT_CONN_DISCONNECTED: |
| default: |
| return -ENOTCONN; |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: bt_conn_create_le |
| * |
| * Description: |
| * Allows initiate new LE link to remote peer using its address. |
| * Returns a new reference that the the caller is responsible for managing. |
| * |
| * Input Parameters: |
| * peer - Remote address. |
| * |
| * Returned Value: |
| * Valid connection object on success or NULL otherwise. |
| * |
| ****************************************************************************/ |
| |
| FAR struct bt_conn_s *bt_conn_create_le(FAR const bt_addr_le_t *peer) |
| { |
| FAR struct bt_conn_s *conn; |
| |
| /* First check if this connection exists and that it is in a proper |
| * state. |
| */ |
| |
| conn = bt_conn_lookup_addr_le(peer); |
| if (conn != NULL) |
| { |
| switch (conn->state) |
| { |
| case BT_CONN_CONNECT_SCAN: |
| case BT_CONN_CONNECT: |
| case BT_CONN_CONNECTED: |
| return conn; |
| |
| default: |
| bt_conn_release(conn); |
| return NULL; |
| } |
| } |
| |
| /* No.. the connection does not exist. Create it assuming MASTER role |
| * and put it in the BT_CONNECT_SCAN state. |
| */ |
| |
| conn = bt_conn_add(peer, BT_HCI_ROLE_MASTER); |
| if (!conn) |
| { |
| return NULL; |
| } |
| |
| bt_conn_set_state(conn, BT_CONN_CONNECT_SCAN); |
| bt_le_scan_update(); |
| return conn; |
| } |
| |
| /**************************************************************************** |
| * Name: bt_conn_le_start_encryption |
| * |
| * Description: |
| * See the HCI start encryption command. |
| * |
| * NOTE: rand and ediv should be in BT order. |
| * |
| * Input Parameters: |
| * conn - The connection to send the command on. |
| * rand, ediv - Values to use for the encryption key |
| * ltk - |
| * |
| * Returned Value: |
| * Zero is returned on success; a negated errno value is returned on any |
| * failure. |
| * |
| ****************************************************************************/ |
| |
| int bt_conn_le_start_encryption(FAR struct bt_conn_s *conn, uint64_t rand, |
| uint16_t ediv, FAR const uint8_t *ltk) |
| { |
| FAR struct bt_hci_cp_le_start_encryption_s *cp; |
| FAR struct bt_buf_s *buf; |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_LE_START_ENCRYPTION, sizeof(*cp)); |
| if (!buf) |
| { |
| return -ENOBUFS; |
| } |
| |
| cp = bt_buf_extend(buf, sizeof(*cp)); |
| cp->handle = BT_HOST2LE16(conn->handle); |
| cp->rand = rand; |
| cp->ediv = ediv; |
| memcpy(cp->ltk, ltk, sizeof(cp->ltk)); |
| |
| return bt_hci_cmd_send_sync(BT_HCI_OP_LE_START_ENCRYPTION, buf, NULL); |
| } |
| |
| int bt_conn_le_conn_update(FAR struct bt_conn_s *conn, uint16_t min, |
| uint16_t max, uint16_t latency, uint16_t timeout) |
| { |
| FAR struct hci_cp_le_conn_update_s *conn_update; |
| FAR struct bt_buf_s *buf; |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_LE_CONN_UPDATE, sizeof(*conn_update)); |
| if (!buf) |
| { |
| return -ENOBUFS; |
| } |
| |
| conn_update = bt_buf_extend(buf, |
| sizeof(*conn_update)); |
| memset(conn_update, 0, sizeof(*conn_update)); |
| conn_update->handle = BT_HOST2LE16(conn->handle); |
| conn_update->conn_interval_min = BT_HOST2LE16(min); |
| conn_update->conn_interval_max = BT_HOST2LE16(max); |
| conn_update->conn_latency = BT_HOST2LE16(latency); |
| conn_update->supervision_timeout = BT_HOST2LE16(timeout); |
| |
| return bt_hci_cmd_send(BT_HCI_OP_LE_CONN_UPDATE, buf); |
| } |
| |
| /**************************************************************************** |
| * Name: bt_conn_initialize |
| * |
| * Description: |
| * Initialize this module's private data. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void bt_conn_initialize(void) |
| { |
| memset(g_conns, 0, sizeof(g_conns)); |
| } |