mirror of
https://github.com/google/pebble.git
synced 2025-04-30 15:21:41 -04:00
1237 lines
46 KiB
C
1237 lines
46 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "gap_le_connect.h"
|
|
|
|
#include "ble_log.h"
|
|
#include "comm/bluetooth_analytics.h"
|
|
#include "comm/bt_conn_mgr.h"
|
|
#include "comm/bt_lock.h"
|
|
#include "gap_le_advert.h"
|
|
#include "gap_le_connect_params.h"
|
|
#include "gap_le_connection.h"
|
|
#include "gap_le_task.h"
|
|
#include "kernel/events.h"
|
|
#include "kernel/pbl_malloc.h"
|
|
#include "services/common/bluetooth/bluetooth_persistent_storage.h"
|
|
#include "services/normal/bluetooth/ble_hrm.h"
|
|
#include "system/hexdump.h"
|
|
#include "system/logging.h"
|
|
#include "system/passert.h"
|
|
|
|
#include <bluetooth/gap_le_connect.h>
|
|
#include <bluetooth/pebble_pairing_service.h>
|
|
#include <btutil/bt_device.h>
|
|
#include <btutil/sm_util.h>
|
|
|
|
#if BLE_MASTER_CONNECT_SUPPORT // FIXME: Shouldn't be needed after PBL-32761
|
|
extern unsigned int bt_stack_id(void);
|
|
#endif
|
|
|
|
//! About this module
|
|
//! -----------------
|
|
//! - Manages initiating connections to other BLE devices as a Master.
|
|
//! - Handles inbound connection events as Slave as well.
|
|
//! - Programs the Bluetooth controller's white-list with the device(s) to
|
|
//! initiate connections to.
|
|
//! - Uses the Bluetooth controller operations "LE Create Connection" and
|
|
//! "LE Create Connection Cancel" to start/stop initiating, using the
|
|
//! white-list as set of devices to look out for.
|
|
//! - Exposes an internal API that lets clients register "connection intents".
|
|
//! - Connection intents survive airplane mode. This keeps the application logic
|
|
//! simpler for developers. Otherwise they would have to watch the air-plane
|
|
//! mode state and re-register the connection intent.
|
|
//! - Clients are currently identified by PebbleTask (later by app UUID?).
|
|
//! - Clients do not have to worry about connection intents from other clients.
|
|
//! because the module virtualizes the connection events. For example, if
|
|
//! a client uses the API to initiate a connection, but a connection has
|
|
//! already been created (by another client), it will still get a connection
|
|
//! event (pretty much immediately) as if the device just connected.
|
|
//!
|
|
//! BT 4.1 Questions
|
|
//! ----------------
|
|
//! - What happens when LE Create Connection is sent for device that is already
|
|
//! connected as master?
|
|
//!
|
|
//! - What happens when whitelisting a resolvable address, then connecting and
|
|
//! finding out the device is already connected?
|
|
|
|
//! Represents a client (task) that (co-)owns an intent to connect.
|
|
typedef struct {
|
|
//! True if the client has registered this intent, false if not.
|
|
//! Because we're using a fixed size array, this is needed to indicate whether
|
|
//! the array element is used or not.
|
|
bool is_used;
|
|
|
|
//! True if the system should handle pairing / encryption reestablishment
|
|
//! transparently first, before sending the connection event.
|
|
bool is_pairing_required;
|
|
|
|
//! True if the intent should be kept around until the client calls
|
|
//! gap_le_connect_cancel(), false if the intent should be removed
|
|
//! when the slave device disconnects.
|
|
bool auto_reconnect;
|
|
|
|
//! True if a connection event has been sent. False if a disconnection event
|
|
//! has been sent or if an event has never been sent. In other words,
|
|
//! clients start off as disconnected.
|
|
//! For clients that have is_pairing_required set to true, the connection
|
|
//! event only gets sent after pairing/encryption is established.
|
|
bool connected;
|
|
} GAPLEConnectionClient;
|
|
|
|
//! Data structure to hold cached bonding info
|
|
typedef struct {
|
|
BTBondingID id;
|
|
BTDeviceInternal device; // containing identity address, not connection address
|
|
SMIdentityResolvingKey irk;
|
|
} GAPLEConnectionIntentBonding;
|
|
|
|
//! Intent to connect.
|
|
//! Each intent is "owned" by one or more clients.
|
|
typedef struct {
|
|
ListNode node;
|
|
|
|
//! The device to connect to.
|
|
//! @note When using a bonding, its address will be set to the last known connection address.
|
|
BTDeviceInternal device;
|
|
|
|
//! Array of clients (tasks). It's fixed in size for simplicity.
|
|
//! It's not using PebbleTask to save some RAM.
|
|
GAPLEConnectionClient client[GAPLEClientNum];
|
|
|
|
//! True when `bonding` exists
|
|
bool is_bonding_based;
|
|
|
|
//! The optional bonding info for the device to connect to. NULL when unused.
|
|
GAPLEConnectionIntentBonding bonding[];
|
|
} GAPLEConnectionIntent;
|
|
|
|
_Static_assert(offsetof(GAPLEConnectionIntent, node) == 0,
|
|
"ListNode must be the first field in GAPLEConnectionIntent");
|
|
|
|
typedef enum {
|
|
GAPLEConnectionEventDisconnected,
|
|
GAPLEConnectionEventConnectedNotEncrypted,
|
|
GAPLEConnectionEventConnectedAndEncrypted,
|
|
} GAPLEConnectionEvent;
|
|
|
|
//! Value indicating the current BLE connectivity role to the phone, from Pebble's point of view.
|
|
typedef enum {
|
|
GAPLERoleSlave,
|
|
GAPLERoleMaster,
|
|
} GAPLERole;
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
// Static Variables -- MUST be protected with bt_lock/unlock!
|
|
|
|
//! The list of connection intents.
|
|
static GAPLEConnectionIntent * s_intents;
|
|
|
|
//! True if there is a pending LE Create Connection call, false if not.
|
|
static bool s_has_pending_create_connection;
|
|
|
|
//! True if the device is currently connected as LE Slave (4.0)
|
|
static bool s_is_connected_as_slave;
|
|
|
|
//! TODO: Implement role-switching (PBL-20368)
|
|
//! This is just a placeholder / stop-gap for now that is always set to GAPLERoleSlave, so that we
|
|
//! don't accidentally act as a master (perform LE Create Connection).
|
|
static const GAPLERole s_current_role = GAPLERoleSlave;
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
// Function prototypes
|
|
|
|
typedef void (*IntentApply)(GAPLEConnectionIntent *intent, void *data);
|
|
static void prv_apply_function_to_intents_matching_connection(const GAPLEConnection *connection,
|
|
IntentApply fp, void *data);
|
|
|
|
static bool prv_intent_matches_connection(const GAPLEConnectionIntent *intent,
|
|
const GAPLEConnection *connection);
|
|
static void prv_start_connecting_if_needed(void);
|
|
static GAPLEConnectionIntent * prv_get_intent_by_device(const BTDeviceInternal *device);
|
|
static void prv_intent_remove_and_free(GAPLEConnectionIntent *intent);
|
|
static bool prv_is_intent_used(const GAPLEConnectionIntent *intent);
|
|
static bool prv_is_intent_requiring_encryption(const GAPLEConnectionIntent *intent);
|
|
static bool prv_is_intent_using_whitelist(const GAPLEConnectionIntent *intent);
|
|
static BTBondingID prv_get_bonding_id_for_intent(const GAPLEConnectionIntent *intent);
|
|
static void prv_mutate_whitelist(const BTDeviceInternal *device, bool is_adding);
|
|
static void prv_mutate_whitelist_safely(const BTDeviceInternal *device,
|
|
bool is_adding);
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
// TODO: This is basically only used by the Settings/Bluetooth UI to refresh the list.
|
|
// Need to fix this up when addressing https://pebbletechnology.atlassian.net/browse/PBL-5254
|
|
static void prv_put_legacy_connection_event(const BTDeviceInternal *device, bool is_connected) {
|
|
PebbleEvent event = {
|
|
.type = PEBBLE_BT_CONNECTION_EVENT,
|
|
.bluetooth = {
|
|
.connection = {
|
|
.is_ble = true,
|
|
.device = *device,
|
|
},
|
|
},
|
|
};
|
|
|
|
if (is_connected) {
|
|
event.bluetooth.connection.state = PebbleBluetoothConnectionEventStateConnected;
|
|
} else {
|
|
event.bluetooth.connection.state = PebbleBluetoothConnectionEventStateDisconnected;
|
|
}
|
|
event_put(&event);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
static void prv_put_connection_event(PebbleTaskBitset task_mask,
|
|
const BTDeviceInternal *device,
|
|
uint8_t hci_reason,
|
|
bool connected,
|
|
BTBondingID bonding_id) {
|
|
PebbleEvent pebble_event = {
|
|
.type = PEBBLE_BLE_CONNECTION_EVENT,
|
|
.task_mask = task_mask,
|
|
.bluetooth = {
|
|
.le = {
|
|
.connection = {
|
|
.bt_device_bits = device->opaque.opaque_64,
|
|
.hci_reason = hci_reason,
|
|
.connected = connected,
|
|
.bonding_id = bonding_id,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
event_put(&pebble_event);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
static void prv_build_task_mask_cb(GAPLEConnectionIntent *intent, void *data) {
|
|
PebbleTaskBitset *task_mask = (PebbleTaskBitset *) data;
|
|
for (GAPLEClient c = 0; c < GAPLEClientNum; ++c) {
|
|
GAPLEConnectionClient *client = &intent->client[c];
|
|
if (client->is_used) {
|
|
*task_mask &= ~gap_le_pebble_task_bit_for_client(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
//! extern'd for gatt.c, used to determine to what tasks a "Buffer Empty" event should be sent.
|
|
//! Helper function to build a PebbleTaskBitset task_mask of the clients' tasks that are virtually
|
|
//! connected to specified real connection and therefore need to receive events for it.
|
|
//! bt_lock is assumed to be taken before calling this function.
|
|
PebbleTaskBitset gap_le_connect_task_mask_for_connection(const GAPLEConnection *connection) {
|
|
const PebbleTaskBitset task_mask_none = ~0;
|
|
PebbleTaskBitset task_mask = task_mask_none;
|
|
prv_apply_function_to_intents_matching_connection(connection, prv_build_task_mask_cb, &task_mask);
|
|
return task_mask;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
//! Updates the state of the client (as kept by this module) and
|
|
//! sends an event to notify client tasks of any state change. Client tasks
|
|
//! that have already been notified, will not be notified again.
|
|
//! @note Upon disconnection, this function also removes and free's the intent
|
|
//! if there are no more clients that want to auto-reconnect. The caller of this
|
|
//! function should therefore not attempt to access the intent after this
|
|
//! function returns.
|
|
//! bt_lock is assumed to be taken before calling this function.
|
|
//! @return false if the intent has been cleaned-up by this function and should
|
|
//! not be accessed any longer after returning.
|
|
static bool prv_update_clients(GAPLEConnectionIntent *intent,
|
|
uint8_t hci_reason,
|
|
GAPLEConnectionEvent event) {
|
|
const BTDeviceInternal *device = &intent->device;
|
|
const bool connected = (event == GAPLEConnectionEventConnectedNotEncrypted ||
|
|
event == GAPLEConnectionEventConnectedAndEncrypted);
|
|
|
|
// Mask to mask out all tasks
|
|
const PebbleTaskBitset task_mask_none = ~0;
|
|
|
|
// Un-mask tasks that not to be notified of the new state:
|
|
PebbleTaskBitset task_mask = task_mask_none;
|
|
for (GAPLEClient c = 0; c < GAPLEClientNum; ++c) {
|
|
GAPLEConnectionClient *client = &intent->client[c];
|
|
|
|
if (!client->is_used) {
|
|
continue;
|
|
}
|
|
|
|
// When auto-reconnection is disabled, the client is "done" after the
|
|
// first disconnection.
|
|
if (event == GAPLEConnectionEventDisconnected && !client->auto_reconnect) {
|
|
// (One-shot) intents should survive air plane mode toggles:
|
|
if (hci_reason != GAPLEConnectHCIReasonExtensionAirPlaneMode) {
|
|
client->is_used = false;
|
|
}
|
|
}
|
|
|
|
if (client->connected != connected) {
|
|
if (client->is_pairing_required &&
|
|
event == GAPLEConnectionEventConnectedNotEncrypted) {
|
|
// If is_pairing_required is true, "connected & not encrypted" is an
|
|
// in-between state that should not be reported to the client.
|
|
continue;
|
|
}
|
|
// The new state needs to be communicated with this client.
|
|
task_mask &= ~gap_le_pebble_task_bit_for_client(c);
|
|
|
|
// Update the local state for the client. An event is sent shortly after.
|
|
intent->client[c].connected = connected;
|
|
}
|
|
}
|
|
|
|
if (task_mask != task_mask_none) {
|
|
// Send event to the client(s) that need to be notified:
|
|
const BTBondingID bonding_id = prv_get_bonding_id_for_intent(intent);
|
|
prv_put_connection_event(task_mask, device, hci_reason, connected, bonding_id);
|
|
}
|
|
|
|
|
|
// Clean up unused intent:
|
|
if (!prv_is_intent_used(intent)) {
|
|
prv_intent_remove_and_free(intent);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void bt_driver_handle_le_connection_handle_update_address_and_irk(const BleAddressAndIRKChange *e) {
|
|
bt_lock();
|
|
{
|
|
GAPLEConnection *connection = gap_le_connection_by_device(&e->device);
|
|
if (!connection) {
|
|
PBL_LOG(LOG_LEVEL_ERROR,
|
|
"Got address & IRK update for nonexistent connection. "
|
|
"Old addr:"BT_DEVICE_ADDRESS_FMT, BT_DEVICE_ADDRESS_XPLODE(e->device.address));
|
|
goto unlock;
|
|
}
|
|
if (e->is_address_updated) {
|
|
connection->device = e->new_device;
|
|
PBL_LOG(LOG_LEVEL_INFO,
|
|
"Updated address to "BT_DEVICE_ADDRESS_FMT". Updated IRK: %c",
|
|
BT_DEVICE_ADDRESS_XPLODE(connection->device.address), e->is_resolved ? 'Y' : 'N');
|
|
} else if (e->is_resolved) {
|
|
PBL_LOG(LOG_LEVEL_INFO, "Updated IRK: %c", e->is_resolved ? 'Y' : 'N');
|
|
}
|
|
const SMIdentityResolvingKey *remote_irk = e->is_resolved ? &e->irk : NULL;
|
|
if (connection->irk) {
|
|
PBL_LOG(LOG_LEVEL_WARNING, "Connection already has IRK!?");
|
|
}
|
|
gap_le_connection_set_irk(connection, remote_irk);
|
|
}
|
|
unlock:
|
|
bt_unlock();
|
|
}
|
|
|
|
void bt_driver_handle_peer_version_info_event(const BleRemoteVersionInfoReceivedEvent *e) {
|
|
bt_lock();
|
|
|
|
GAPLEConnection *connection = gap_le_connection_by_device(&e->peer_address);
|
|
if (connection) {
|
|
const BleRemoteVersionInfo *info = &e->remote_version_info;
|
|
connection->remote_version_info = *info;
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Remote Vers Info: VersNr: %d, CompId: 0x%x, SubVersNr: 0x%x",
|
|
(int)info->version_number, (int)info->company_identifier, (int)info->subversion_number);
|
|
}
|
|
|
|
bt_unlock();
|
|
}
|
|
|
|
//! bt_lock is assumed to be taken before calling this function.
|
|
void bt_driver_handle_le_connection_complete_event(const BleConnectionCompleteEvent *event) {
|
|
bt_lock();
|
|
const BleConnectionParams *params = &event->conn_params;
|
|
PBL_LOG(LOG_LEVEL_INFO,
|
|
"LE Conn Compl: addr="BT_DEVICE_ADDRESS_FMT", is_random_addr=%u,",
|
|
BT_DEVICE_ADDRESS_XPLODE(event->peer_address.address),
|
|
event->peer_address.is_random_address);
|
|
PBL_LOG(LOG_LEVEL_INFO,
|
|
" hdl=%u, status=0x%02x, master=%u, %u, slave lat=%u, "
|
|
"supervision timeout=%u, is_resolved=%c",
|
|
event->handle, event->status, event->is_master, params->conn_interval_1_25ms,
|
|
params->slave_latency_events, params->supervision_timeout_10ms,
|
|
event->is_resolved ? 'Y' : 'N');
|
|
|
|
// When an "LE Connection Complete" event is received, the
|
|
// "LE Create Connection" operation is stopped, so update our state:
|
|
s_has_pending_create_connection = false;
|
|
|
|
switch (event->status) {
|
|
case HciStatusCode_Success: {
|
|
// New connection! Update our records:
|
|
const bool local_is_master = event->is_master;
|
|
|
|
if (!local_is_master) {
|
|
s_is_connected_as_slave = true;
|
|
gap_le_advert_handle_connect_as_slave();
|
|
|
|
prv_put_legacy_connection_event(&event->peer_address, true /* connected */);
|
|
}
|
|
|
|
if (gap_le_connection_is_connected(&event->peer_address)) {
|
|
// We have seen this crop up for cases where the phone has disconnected due to a timeout
|
|
// but the watch has not yet. In practice, I think the only way it could happen is if a
|
|
// user is sitting in the Bluetooth settings menu and walking in and out of range. If it
|
|
// does take place, let's trigger a disconnect to try and put us back into a sane state
|
|
PBL_LOG(LOG_LEVEL_ERROR,
|
|
"Not adding connection for device. It is already connected .. disconnecting");
|
|
bt_driver_gap_le_disconnect(&event->peer_address);
|
|
break;
|
|
}
|
|
|
|
const SMIdentityResolvingKey *remote_irk = event->is_resolved ? &event->irk : NULL;
|
|
GAPLEConnection *connection = gap_le_connection_add(&event->peer_address, remote_irk,
|
|
local_is_master);
|
|
// Cache the BLE connection parameters
|
|
connection->conn_params = *params;
|
|
|
|
bool found_match = false;
|
|
GAPLEConnectionIntent *intent = s_intents;
|
|
while (intent) {
|
|
GAPLEConnectionIntent *next = (GAPLEConnectionIntent *) intent->node.next;
|
|
if (prv_intent_matches_connection(intent, connection)) {
|
|
found_match = true;
|
|
|
|
if (intent->is_bonding_based) {
|
|
// Update connection address:
|
|
intent->device = event->peer_address;
|
|
|
|
// FIXME:
|
|
// Find and assign bonding_id even if there is no intent.
|
|
// https://pebbletechnology.atlassian.net/browse/PBL-20972
|
|
connection->bonding_id = intent->bonding->id;
|
|
}
|
|
|
|
prv_update_clients(intent, HciStatusCode_Success,
|
|
GAPLEConnectionEventConnectedNotEncrypted);
|
|
|
|
if (prv_is_intent_using_whitelist(intent)) {
|
|
// Remove from white-list, because the device is connected now.
|
|
prv_mutate_whitelist(&event->peer_address, false /* remove */);
|
|
}
|
|
|
|
if (local_is_master && prv_is_intent_requiring_encryption(intent)) {
|
|
// TODO: kick off pairing
|
|
}
|
|
}
|
|
intent = next;
|
|
}
|
|
|
|
if (!local_is_master) { // At the moment we don't grab analytics for connections we generate
|
|
bluetooth_analytics_handle_connect(&event->peer_address, &event->conn_params);
|
|
}
|
|
|
|
|
|
if (!found_match) {
|
|
// There is no connection intent from our end. This could be the phone that is connecting
|
|
// for the first time. Let the connection watchdog (TODO: PBL-11236) take care of
|
|
// disconnecting at some point, if the connection ends up being unused.
|
|
PBL_LOG(LOG_LEVEL_INFO, "No intent for connection");
|
|
bluetooth_analytics_handle_no_intent_for_connection();
|
|
}
|
|
|
|
#if RECOVERY_FW
|
|
// In PRF, stick to shortest connection interval indefinitely:
|
|
conn_mgr_set_ble_conn_response_time(connection, BtConsumerPRF,
|
|
ResponseTimeMin, MAX_PERIOD_RUN_FOREVER);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case HciStatusCode_UnknownConnectionIdentifier: {
|
|
// Happens if "Connection Create" was cancelled.
|
|
// See Bluetooth Spec 4.0, Volume 2, Part E, Chapter 7.8.13.
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
PBL_LOG(LOG_LEVEL_ERROR, "Connection Complete Event status: 0x%x",
|
|
event->status);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Continue initiating connections to disconnected devices:
|
|
prv_start_connecting_if_needed();
|
|
bt_unlock();
|
|
}
|
|
|
|
//! bt_lock is assumed to be taken before calling this function.
|
|
void bt_driver_handle_le_disconnection_complete_event(const BleDisconnectionCompleteEvent *event) {
|
|
bt_lock();
|
|
|
|
switch (event->status) {
|
|
case HciStatusCode_Success: {
|
|
// Disconnection! Update our records:
|
|
GAPLEConnection *connection = gap_le_connection_by_device(&event->peer_address);
|
|
#if CAPABILITY_HAS_BUILTIN_HRM
|
|
ble_hrm_handle_disconnection(connection);
|
|
#endif
|
|
const bool local_is_master = connection->local_is_master;
|
|
|
|
PBL_LOG(LOG_LEVEL_INFO, "LE Disconn: addr="BT_DEVICE_ADDRESS_FMT", is_random_addr=%u,",
|
|
BT_DEVICE_ADDRESS_XPLODE(event->peer_address.address),
|
|
event->peer_address.is_random_address);
|
|
PBL_LOG(LOG_LEVEL_INFO, " hdl=%u, status=0x%02x, reason=0x%02x, master=%u",
|
|
event->handle, event->status, event->reason, local_is_master);
|
|
|
|
bluetooth_analytics_handle_disconnect(local_is_master);
|
|
|
|
bluetooth_analytics_handle_connection_disconnection_event(
|
|
AnalyticsEvent_BtLeDisconnect, event->reason, &connection->remote_version_info);
|
|
|
|
if (!local_is_master) {
|
|
s_is_connected_as_slave = false;
|
|
gap_le_advert_handle_disconnect_as_slave();
|
|
|
|
prv_put_legacy_connection_event(&event->peer_address, false /* disconnected */);
|
|
}
|
|
|
|
GAPLEConnectionIntent *intent = s_intents;
|
|
while (intent) {
|
|
GAPLEConnectionIntent *next = (GAPLEConnectionIntent *) intent->node.next;
|
|
if (prv_intent_matches_connection(intent, connection)) {
|
|
// Notify clients:
|
|
if (prv_update_clients(intent, event->reason,
|
|
GAPLEConnectionEventDisconnected)) {
|
|
// Only if the intent hasn't been cleaned up by now:
|
|
if (prv_is_intent_using_whitelist(intent)) {
|
|
// Add to white-list, because the device is disconnected now and we
|
|
// need to start connecting again:
|
|
prv_mutate_whitelist_safely(&event->peer_address, true /* add */);
|
|
}
|
|
|
|
if (intent->is_bonding_based) {
|
|
// Clear out connection address (more for debugging than any else):
|
|
intent->device = (const BTDeviceInternal) {};
|
|
}
|
|
}
|
|
}
|
|
intent = next;
|
|
}
|
|
|
|
gap_le_connection_remove(&event->peer_address);
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
PBL_LOG(LOG_LEVEL_ERROR, "Disconnection Complete Event status: 0x%x",
|
|
event->status);
|
|
break;
|
|
}
|
|
}
|
|
bt_unlock();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
//! Convenience function to apply a function to each intent matching or resolving to a device
|
|
//! bt_lock is assumed to be taken before calling this function.
|
|
static void prv_apply_function_to_intents_matching_connection(const GAPLEConnection *connection,
|
|
IntentApply fp, void *data) {
|
|
GAPLEConnectionIntent *intent = s_intents;
|
|
while (intent) {
|
|
GAPLEConnectionIntent *next = (GAPLEConnectionIntent *) intent->node.next;
|
|
if (prv_intent_matches_connection(intent, connection)) {
|
|
fp(intent, data);
|
|
}
|
|
intent = next;
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
//! Helper for prv_handle_encryption_change
|
|
|
|
static void prv_send_clients_encrypted_event(GAPLEConnectionIntent *intent, void *unused_data) {
|
|
prv_update_clients(intent, HciStatusCode_Success,
|
|
GAPLEConnectionEventConnectedAndEncrypted);
|
|
}
|
|
|
|
//! bt_lock is assumed to be taken before calling this function.
|
|
void bt_driver_handle_le_encryption_change_event(const BleEncryptionChange *event) {
|
|
bt_lock();
|
|
const bool is_encrypted = (event->encryption_enabled);
|
|
if (!is_encrypted) {
|
|
// The "Encryption Change" event can only enable encryption, there's no inverse,
|
|
// so there must be an error:
|
|
PBL_LOG(LOG_LEVEL_ERROR, "Encryption change failed: %u", event->status);
|
|
goto unlock;
|
|
}
|
|
|
|
// Bluetopia doesn't set the 'is_random_address' field in the encryption change event, so using
|
|
// gap_le_connection_by_device() will fail.
|
|
GAPLEConnection *connection = gap_le_connection_by_addr(&event->dev_address);
|
|
if (connection->is_encrypted) {
|
|
PBL_LOG(LOG_LEVEL_INFO, "Encryption refreshed!");
|
|
goto unlock;
|
|
}
|
|
|
|
const bool local_is_master = connection->local_is_master;
|
|
connection->is_encrypted = true;
|
|
|
|
if (!local_is_master) {
|
|
PBL_LOG(LOG_LEVEL_INFO, "Hurray! LE Security established.");
|
|
bluetooth_analytics_handle_encryption_change();
|
|
bt_driver_pebble_pairing_service_handle_status_change(connection);
|
|
}
|
|
|
|
prv_apply_function_to_intents_matching_connection(connection,
|
|
prv_send_clients_encrypted_event, NULL);
|
|
unlock:
|
|
bt_unlock();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
//! Wrappers around Bluetopia HCI / GAP calls - Not compiled at the moment, fix in PBL-32761
|
|
//! bt_lock is assumed to be taken before calling these functions.
|
|
|
|
static void prv_start_connecting(void) {
|
|
#if !BLE_MASTER_CONNECT_SUPPORT // PBL-32761
|
|
PBL_LOG(LOG_LEVEL_WARNING, "Watch driven BLE connection unimplemented");
|
|
#else
|
|
if (s_has_pending_create_connection) {
|
|
PBL_LOG(LOG_LEVEL_ERROR, "Already connecting...");
|
|
return;
|
|
}
|
|
|
|
BLE_GAP_LOG_DEBUG("Starting connecting..");
|
|
unsigned int stack_id = bt_stack_id();
|
|
// See Bluetooth Spec 4.0, Volume 2, Part E, Chapter 7.8.12:
|
|
const GAP_LE_Address_Type_t local_addr_type = BleAddressType_Random;
|
|
GAP_LE_Connection_Parameters_t connection_params = {
|
|
.Connection_Interval_Min = 40,
|
|
.Connection_Interval_Max = 60,
|
|
.Slave_Latency = 0,
|
|
.Supervision_Timeout = 6000,
|
|
.Minimum_Connection_Length = 0,
|
|
.Maximum_Connection_Length = 40950,
|
|
};
|
|
const int r = GAP_LE_Create_Connection(stack_id, 10240, 10240, fpWhiteList,
|
|
0 /* fpWhiteList ignores remote addr type */,
|
|
NULL /* fpWhiteList ignores remote addr */,
|
|
local_addr_type, &connection_params,
|
|
gap_le_connect_bluetopia_connection_callback,
|
|
0 /* callback context: unused */);
|
|
if (r) {
|
|
PBL_LOG(LOG_LEVEL_ERROR, "GAP_LE_Create_Connection (r=%d)", r);
|
|
} else {
|
|
s_has_pending_create_connection = true;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void prv_stop_connecting(void) {
|
|
#if !BLE_MASTER_CONNECT_SUPPORT // PBL-32761
|
|
PBL_LOG(LOG_LEVEL_WARNING, "Watch driven BLE connection cancel unimplemented");
|
|
#else
|
|
if (!s_has_pending_create_connection) {
|
|
return;
|
|
}
|
|
unsigned int stack_id = bt_stack_id();
|
|
BLE_GAP_LOG_DEBUG("Stopping connecting...");
|
|
// See Bluetooth Spec 4.0, Volume 2, Part E, Chapter 7.8.13:
|
|
const int r = GAP_LE_Cancel_Create_Connection(stack_id);
|
|
if (r) {
|
|
PBL_LOG(LOG_LEVEL_ERROR, "GAP_LE_Cancel_Create_Connection (r=%d)", r);
|
|
} else {
|
|
// Update the state right away (don't wait for the Connection Complete event
|
|
// with HCI_ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER status):
|
|
s_has_pending_create_connection = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void prv_mutate_whitelist(const BTDeviceInternal *device, bool is_adding) {
|
|
#if !BLE_MASTER_CONNECT_SUPPORT // PBL-32761
|
|
PBL_LOG(LOG_LEVEL_WARNING, "BLE whitelist mutation unimplemented");
|
|
#else
|
|
unsigned int stack_id = bt_stack_id();
|
|
BLE_GAP_LOG_DEBUG("Mutating white-list (adding=%u): " BD_ADDR_FMT,
|
|
is_adding, BT_DEVICE_ADDRESS_XPLODE(device->address));
|
|
// See Bluetooth Spec 4.0, Volume 2, Part E, Chapter 7.8.15:
|
|
uint8_t status = 0;
|
|
const uint8_t addr_type = device->is_random_address ? 0x01 : 0x00;
|
|
__typeof__(&HCI_LE_Add_Device_To_White_List) mutator =
|
|
(is_adding ? HCI_LE_Add_Device_To_White_List : HCI_LE_Remove_Device_From_White_List);
|
|
const int r = mutator(stack_id, addr_type,
|
|
BTDeviceAddressToBDADDR(device->address), &status);
|
|
if (r) {
|
|
PBL_LOG(LOG_LEVEL_ERROR,
|
|
"HCI_LE_..._Device_To_White_List (is_adding=%u, r=%d, status=0x%x)",
|
|
is_adding, r, status);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
//! Helpers to manage the s_intents list.
|
|
//! bt_lock() is expected to be taken by the caller for each of these.
|
|
|
|
static bool prv_intent_matches_connection(const GAPLEConnectionIntent *intent,
|
|
const GAPLEConnection *connection) {
|
|
if (intent->is_bonding_based) {
|
|
// If the bonding-based intent is connected, the `device` is set to the connection address,
|
|
// if it's not connected, it's all zeroes.
|
|
if (bt_device_equal(&connection->device.opaque, &intent->device.opaque)) {
|
|
return true;
|
|
}
|
|
if (!connection->irk) {
|
|
// are we looking for a bonding which did not exchange an irk?
|
|
if (sm_is_pairing_info_irk_not_used(&intent->bonding->irk)) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Bonding does not have irk ... comparing identity address");
|
|
return (0 == memcmp(&connection->device.opaque, &intent->bonding->device.opaque,
|
|
sizeof(connection->device.opaque)));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
return (0 == memcmp(connection->irk, &intent->bonding->irk, sizeof(*connection->irk)));
|
|
} else {
|
|
return bt_device_equal(&connection->device.opaque, &intent->device.opaque);
|
|
}
|
|
}
|
|
|
|
static void prv_intent_remove_and_free(GAPLEConnectionIntent *intent) {
|
|
list_remove(&intent->node, (ListNode **) &s_intents, NULL);
|
|
kernel_free(intent);
|
|
}
|
|
|
|
static bool prv_intent_filter_by_device(ListNode *node, void *data) {
|
|
const BTDeviceInternal *target_device = (const BTDeviceInternal *) data;
|
|
const GAPLEConnectionIntent *intent = (const GAPLEConnectionIntent *) node;
|
|
if (intent->is_bonding_based) {
|
|
return false;
|
|
}
|
|
return bt_device_equal(&target_device->opaque, &intent->device.opaque);
|
|
}
|
|
|
|
static GAPLEConnectionIntent * prv_get_intent_by_device(const BTDeviceInternal *device) {
|
|
return (GAPLEConnectionIntent *) list_find(&s_intents->node,
|
|
prv_intent_filter_by_device,
|
|
(void *) device);
|
|
}
|
|
|
|
static bool prv_intent_filter_by_bonding_id(ListNode *node, void *data) {
|
|
const BTBondingID bonding_id = (BTBondingID) (uintptr_t) data;
|
|
const GAPLEConnectionIntent *intent = (const GAPLEConnectionIntent *) node;
|
|
if (!intent->is_bonding_based) {
|
|
return false;
|
|
}
|
|
return (intent->bonding->id == bonding_id);
|
|
}
|
|
|
|
static GAPLEConnectionIntent * prv_get_intent_by_bonding_id(BTBondingID bonding_id) {
|
|
return (GAPLEConnectionIntent *) list_find(&s_intents->node,
|
|
prv_intent_filter_by_bonding_id,
|
|
(void *) (uintptr_t) bonding_id);
|
|
}
|
|
|
|
static bool prv_intent_filter_disconnected(ListNode *node, void *data) {
|
|
const GAPLEConnectionIntent *intent = (const GAPLEConnectionIntent *) node;
|
|
return !gap_le_connection_is_connected(&intent->device);
|
|
}
|
|
|
|
static bool prv_has_intents_for_disconnected_devices(void) {
|
|
return list_find(&s_intents->node, prv_intent_filter_disconnected, NULL);
|
|
}
|
|
|
|
static uint32_t prv_intents_count(void) {
|
|
return list_count(&s_intents->node);
|
|
}
|
|
|
|
static bool prv_is_intent_used(const GAPLEConnectionIntent *intent) {
|
|
return (intent->client[GAPLEClientKernel].is_used |
|
|
intent->client[GAPLEClientApp].is_used);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
static bool prv_is_intent_requiring_encryption(const GAPLEConnectionIntent *intent) {
|
|
return (intent->client[GAPLEClientKernel].is_pairing_required ||
|
|
intent->client[GAPLEClientApp].is_pairing_required);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
static bool prv_is_intent_using_whitelist(const GAPLEConnectionIntent *intent) {
|
|
// TODO: If the bonding does not contain a valid IRK, perhaps we should use and whitelist the
|
|
// identity address and treat it as a normal connection intent?
|
|
// See note in BT spec "Note: An all zero Identity Resolving Key data field indicates that a
|
|
// device does not have a valid resolvable private address." in Security Manager chapter.
|
|
return (!intent->is_bonding_based);
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
static BTBondingID prv_get_bonding_id_for_intent(const GAPLEConnectionIntent *intent) {
|
|
if (intent->is_bonding_based) {
|
|
return intent->bonding->id;
|
|
}
|
|
return BT_BONDING_ID_INVALID;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
static void prv_start_connecting_if_needed(void) {
|
|
if (s_current_role == GAPLERoleSlave) {
|
|
return;
|
|
}
|
|
if (prv_has_intents_for_disconnected_devices()) {
|
|
prv_start_connecting();
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
//! Adds or removes a device to/from the Bluetooth controller's whitelist.
|
|
//! Stops and (re)starts the LE Create Connection operation as necessary.
|
|
static void prv_mutate_whitelist_safely(const BTDeviceInternal *device,
|
|
bool is_adding) {
|
|
// If there are already connection intents, cancel connecting briefly,
|
|
// otherwise it's illegal to modify the white-list.
|
|
prv_stop_connecting();
|
|
|
|
// Mutate white-list:
|
|
prv_mutate_whitelist(device, is_adding);
|
|
|
|
// Start/continue connecting:
|
|
prv_start_connecting_if_needed();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
// Helper data structure for prv_register_intent
|
|
|
|
struct RegisterIntentRequest {
|
|
bool is_bonding_based;
|
|
union {
|
|
const BTDeviceInternal *device;
|
|
GAPLEConnectionIntentBonding bonding;
|
|
};
|
|
};
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
//! Registers a connection intent for a client task
|
|
//! bt_lock() is expected to be taken by the caller
|
|
static BTErrno prv_register_intent(struct RegisterIntentRequest *request,
|
|
bool auto_reconnect,
|
|
bool is_pairing_required,
|
|
GAPLEClient c) {
|
|
// Check if the max count wasn't exceeded:
|
|
const uint32_t prev_num_intents = prv_intents_count();
|
|
if (prev_num_intents >= GAP_LE_CONNECT_MASTER_MAX_CONNECTION_INTENTS) {
|
|
return BTErrnoNotEnoughResources;
|
|
}
|
|
|
|
bool is_already_connected = false;
|
|
bool is_already_encrypted = false;
|
|
bool local_is_master = false;
|
|
|
|
const BTDeviceInternal *connected_device = NULL;
|
|
GAPLEConnectionIntent *intent;
|
|
|
|
if (request->is_bonding_based) {
|
|
const GAPLEConnection *connection = gap_le_connection_find_by_irk(&request->bonding.irk);
|
|
if (!connection) {
|
|
if (sm_is_pairing_info_irk_not_used(&request->bonding.irk)) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "register_intent: IRK not used, searching by addr");
|
|
connection = gap_le_connection_by_device(&request->bonding.device);
|
|
}
|
|
}
|
|
if (connection) {
|
|
is_already_connected = true;
|
|
is_already_encrypted = connection->is_encrypted;
|
|
local_is_master = connection->local_is_master;
|
|
|
|
connected_device = &connection->device;
|
|
}
|
|
intent = prv_get_intent_by_bonding_id(request->bonding.id);
|
|
} else {
|
|
is_already_connected = gap_le_connection_is_connected(request->device);
|
|
intent = prv_get_intent_by_device(request->device);
|
|
}
|
|
|
|
const bool is_existing_intent = (intent != NULL);
|
|
if (is_existing_intent) {
|
|
if (intent->client[c].is_used) {
|
|
return BTErrnoInvalidState;
|
|
}
|
|
} else {
|
|
// Create intent for device and add to list:
|
|
const size_t alloc_size = sizeof(GAPLEConnectionIntent) +
|
|
(request->is_bonding_based ? sizeof(GAPLEConnectionIntentBonding) : 0);
|
|
intent = (GAPLEConnectionIntent *) kernel_malloc(alloc_size);
|
|
if (!intent) {
|
|
return BTErrnoNotEnoughResources;
|
|
}
|
|
memset(intent, 0, alloc_size);
|
|
s_intents = (GAPLEConnectionIntent *) list_prepend(&s_intents->node,
|
|
&intent->node);
|
|
|
|
if (request->is_bonding_based) {
|
|
// Create bonding info cache if it has not been created yet:
|
|
intent->is_bonding_based = true,
|
|
*intent->bonding = request->bonding;
|
|
if (connected_device) {
|
|
intent->device = *connected_device;
|
|
}
|
|
} else {
|
|
intent->device = *request->device;
|
|
// Append to hardware white-list of BT chip if not connected:
|
|
if (!is_already_connected) {
|
|
prv_mutate_whitelist_safely(request->device, true /* add */);
|
|
}
|
|
}
|
|
}
|
|
|
|
intent->client[c].is_used = true;
|
|
intent->client[c].auto_reconnect = auto_reconnect;
|
|
intent->client[c].is_pairing_required = is_pairing_required;
|
|
intent->client[c].connected = false; // starting state
|
|
|
|
if (!is_already_connected) {
|
|
return BTErrnoOK;
|
|
}
|
|
|
|
if (is_pairing_required && !is_already_encrypted) {
|
|
if (local_is_master) {
|
|
// TODO:
|
|
// - Check if pairing process is on-going, if so, do nothing
|
|
// - If not on-going, kick it off (we're the master)
|
|
// See https://pebbletechnology.atlassian.net/browse/PBL-6850
|
|
} else {
|
|
// Pebble is slave, the other side should start pairing.
|
|
// Connection watchdog should take care of disconnecting after timeout in case pairing
|
|
// does not happen in a timely manner
|
|
// TODO: https://pebbletechnology.atlassian.net/browse/PBL-11236
|
|
}
|
|
return BTErrnoOK;
|
|
}
|
|
|
|
// Notify client of the virtual connection:
|
|
prv_update_clients(intent, HciStatusCode_Success,
|
|
is_already_encrypted ? GAPLEConnectionEventConnectedAndEncrypted :
|
|
GAPLEConnectionEventConnectedNotEncrypted);
|
|
|
|
return BTErrnoOK;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
//! Unregisters a connection intent for a client task
|
|
//! bt_lock() is expected to be taken by the caller
|
|
static BTErrno prv_unregister_intent(GAPLEConnectionIntent *intent,
|
|
GAPLEClient c,
|
|
bool should_send_disconnection_event,
|
|
uint8_t hci_reason) {
|
|
if (!intent->client[c].is_used) {
|
|
// No intent that is owned by the given client
|
|
return BTErrnoInvalidParameter;
|
|
}
|
|
|
|
// Only send disconnection event if a connection event has been sent to the
|
|
// client in the past:
|
|
const bool is_connected_virtual = intent->client[c].connected;
|
|
should_send_disconnection_event &= is_connected_virtual;
|
|
|
|
const BTDeviceInternal *device = &intent->device;
|
|
const bool is_connected_real = gap_le_connection_is_connected(device);
|
|
|
|
const BTBondingID bonding_id = prv_get_bonding_id_for_intent(intent);
|
|
|
|
// Flag as unused:
|
|
intent->client[c].is_used = false;
|
|
|
|
bool should_remove_and_free = false;
|
|
|
|
if (!prv_is_intent_used(intent)) {
|
|
should_remove_and_free = true;
|
|
|
|
if (is_connected_real) {
|
|
// Disconnect the device because no one is using it
|
|
const int result = bt_driver_gap_le_disconnect(device);
|
|
if (result != 0) {
|
|
PBL_LOG(LOG_LEVEL_ERROR, "Ble disconnect failed: %d", result);
|
|
}
|
|
} else {
|
|
if (prv_is_intent_using_whitelist(intent)) {
|
|
// Remove from white-list:
|
|
prv_mutate_whitelist_safely(device, false /* remove */);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (should_send_disconnection_event) {
|
|
// Send virtual disconnection event:
|
|
const PebbleTaskBitset task_mask = ~gap_le_pebble_task_bit_for_client(c);
|
|
prv_put_connection_event(task_mask, device, hci_reason, false /* connected */, bonding_id);
|
|
}
|
|
|
|
if (should_remove_and_free) {
|
|
// Delete the intent:
|
|
prv_intent_remove_and_free(intent);
|
|
}
|
|
|
|
return BTErrnoOK;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
void gap_le_connect_handle_bonding_change(BTBondingID bonding_id, BtPersistBondingOp op) {
|
|
// Load from flash outside of the bt_lock() block:
|
|
GAPLEConnectionIntentBonding updated_bonding;
|
|
if (op == BtPersistBondingOpDidChange) {
|
|
if (!bt_persistent_storage_get_ble_pairing_by_id(bonding_id, &updated_bonding.irk,
|
|
&updated_bonding.device, NULL)) {
|
|
WTF;
|
|
}
|
|
}
|
|
|
|
bt_lock();
|
|
GAPLEConnectionIntent *intent = prv_get_intent_by_bonding_id(bonding_id);
|
|
if (!intent) {
|
|
goto unlock;
|
|
}
|
|
switch (op) {
|
|
case BtPersistBondingOpDidAdd:
|
|
// FIXME:
|
|
break;
|
|
case BtPersistBondingOpDidChange:
|
|
*intent->bonding = updated_bonding;
|
|
break;
|
|
case BtPersistBondingOpWillDelete:
|
|
for (GAPLEClient c = GAPLEClientKernel; c < GAPLEClientNum; ++c) {
|
|
if (!intent->client[c].is_used) {
|
|
continue;
|
|
}
|
|
prv_unregister_intent(intent, c, true /* should_send_disconnection_event */,
|
|
GAPLEConnectHCIReasonExtensionUserRemovedBonding);
|
|
}
|
|
break;
|
|
default:
|
|
WTF;
|
|
}
|
|
unlock:
|
|
bt_unlock();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
BTErrno gap_le_connect_connect(const BTDeviceInternal *device, bool auto_reconnect,
|
|
bool is_pairing_required, GAPLEClient client) {
|
|
if (!device || client >= GAPLEClientNum) {
|
|
return BTErrnoInvalidParameter;
|
|
}
|
|
bt_lock();
|
|
struct RegisterIntentRequest request = {
|
|
.is_bonding_based = false,
|
|
.device = device,
|
|
};
|
|
BTErrno ret_value = prv_register_intent(&request, auto_reconnect, is_pairing_required, client);
|
|
bt_unlock();
|
|
return ret_value;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
BTErrno gap_le_connect_cancel(const BTDeviceInternal *device,
|
|
GAPLEClient client) {
|
|
if (!device || client >= GAPLEClientNum) {
|
|
return BTErrnoInvalidParameter;
|
|
}
|
|
bt_lock();
|
|
// Find the intent for the device:
|
|
GAPLEConnectionIntent *intent = prv_get_intent_by_device(device);
|
|
BTErrno ret_value;
|
|
if (!intent) {
|
|
// No intent for given device
|
|
ret_value = BTErrnoInvalidParameter;
|
|
} else {
|
|
ret_value = prv_unregister_intent(intent, client, true /* should_send_disconnection_event */,
|
|
GAPLEConnectHCIReasonExtensionCancelConnect);
|
|
}
|
|
bt_unlock();
|
|
return ret_value;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
BTErrno gap_le_connect_connect_by_bonding(BTBondingID bonding_id, bool auto_reconnect,
|
|
bool is_pairing_required, GAPLEClient client) {
|
|
if (bonding_id == BT_BONDING_ID_INVALID || client >= GAPLEClientNum) {
|
|
return BTErrnoInvalidParameter;
|
|
}
|
|
struct RegisterIntentRequest request = {
|
|
.is_bonding_based = true,
|
|
.bonding = {
|
|
.id = bonding_id,
|
|
},
|
|
};
|
|
// Get the IRK and device from the bonding storage,
|
|
// outside of bt_lock(), because it uses flash.
|
|
if (!bt_persistent_storage_get_ble_pairing_by_id(bonding_id, &request.bonding.irk,
|
|
&request.bonding.device, NULL)) {
|
|
return BTErrnoInvalidParameter;
|
|
}
|
|
bt_lock();
|
|
BTErrno ret_value = prv_register_intent(&request, auto_reconnect, is_pairing_required, client);
|
|
bt_unlock();
|
|
return ret_value;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
BTErrno gap_le_connect_cancel_by_bonding(BTBondingID bonding_id, GAPLEClient client) {
|
|
if (bonding_id == BT_BONDING_ID_INVALID || client >= GAPLEClientNum) {
|
|
return BTErrnoInvalidParameter;
|
|
}
|
|
bt_lock();
|
|
// Find the intent for the device:
|
|
GAPLEConnectionIntent *intent = prv_get_intent_by_bonding_id(bonding_id);
|
|
BTErrno ret_value;
|
|
if (!intent) {
|
|
// No intent for given device
|
|
ret_value = BTErrnoInvalidParameter;
|
|
} else {
|
|
ret_value = prv_unregister_intent(intent, client, true /* should_send_disconnection_event */,
|
|
GAPLEConnectHCIReasonExtensionCancelConnect);
|
|
}
|
|
bt_unlock();
|
|
return ret_value;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
void gap_le_connect_cancel_all(GAPLEClient client) {
|
|
bt_lock();
|
|
{
|
|
BLE_GAP_LOG_DEBUG("Cancel connecting all for client %u...", client);
|
|
|
|
GAPLEConnectionIntent *intent = s_intents;
|
|
while (intent) {
|
|
GAPLEConnectionIntent *next = (GAPLEConnectionIntent *) intent->node.next;
|
|
prv_unregister_intent(intent, client, false /* should_send_disconnection_event */,
|
|
GAPLEConnectHCIReasonExtensionCancelConnect);
|
|
intent = next;
|
|
}
|
|
}
|
|
bt_unlock();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
bool gap_le_connect_is_connected_as_slave(void) {
|
|
bool connected;
|
|
bt_lock();
|
|
{
|
|
connected = s_is_connected_as_slave;
|
|
}
|
|
bt_unlock();
|
|
return connected;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
void gap_le_connect_init(void) {
|
|
bt_lock();
|
|
{
|
|
GAPLEConnectionIntent *intent = s_intents;
|
|
while (intent) {
|
|
GAPLEConnectionIntent *next = (GAPLEConnectionIntent *) intent->node.next;
|
|
prv_mutate_whitelist(&intent->device, true /* add */);
|
|
intent = next;
|
|
}
|
|
|
|
prv_start_connecting_if_needed();
|
|
}
|
|
bt_unlock();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
|
|
void gap_le_connect_deinit(void) {
|
|
bt_lock();
|
|
{
|
|
s_has_pending_create_connection = false;
|
|
|
|
// Going into air-plane mode, send virtual disconnection events:
|
|
GAPLEConnectionIntent *intent = s_intents;
|
|
while (intent) {
|
|
GAPLEConnectionIntent *next = (GAPLEConnectionIntent *) intent->node.next;
|
|
prv_update_clients(intent, GAPLEConnectHCIReasonExtensionAirPlaneMode,
|
|
GAPLEConnectionEventDisconnected);
|
|
intent = next;
|
|
}
|
|
|
|
if (s_is_connected_as_slave) {
|
|
// The BT controller will not send an etLE_Disconnection_Complete event
|
|
// when going to airplane mode while being connected.
|
|
// Stop analytics stopwatches manually:
|
|
bluetooth_analytics_handle_disconnect(false);
|
|
s_is_connected_as_slave = false;
|
|
}
|
|
}
|
|
bt_unlock();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------------------------------
|
|
// For unit testing
|
|
|
|
bool gap_le_connect_has_pending_create_connection(void) {
|
|
bt_lock();
|
|
const bool ret_value = s_has_pending_create_connection;
|
|
bt_unlock();
|
|
return ret_value;
|
|
}
|
|
|
|
bool gap_le_connect_has_connection_intent(const BTDeviceInternal *device,
|
|
GAPLEClient c) {
|
|
bt_lock();
|
|
bool ret_value = true;
|
|
const GAPLEConnectionIntent *intent = prv_get_intent_by_device(device);
|
|
if (intent) {
|
|
if (!intent->client[c].is_used) {
|
|
// Not all specified clients own the intent
|
|
ret_value = false;
|
|
}
|
|
} else {
|
|
// Intent not found
|
|
ret_value = false;
|
|
}
|
|
bt_unlock();
|
|
return ret_value;
|
|
}
|
|
|
|
bool gap_le_connect_has_connection_intent_for_bonding(BTBondingID bonding_id,
|
|
GAPLEClient c) {
|
|
bt_lock();
|
|
bool ret_value = true;
|
|
const GAPLEConnectionIntent *intent = prv_get_intent_by_bonding_id(bonding_id);
|
|
if (intent) {
|
|
if (!intent->client[c].is_used) {
|
|
// Not all specified clients own the intent
|
|
ret_value = false;
|
|
}
|
|
} else {
|
|
// Intent not found
|
|
ret_value = false;
|
|
}
|
|
bt_unlock();
|
|
return ret_value;
|
|
}
|
|
|
|
uint32_t gap_le_connect_connection_intents_count(void) {
|
|
bt_lock();
|
|
const uint32_t count = prv_intents_count();
|
|
bt_unlock();
|
|
return count;
|
|
}
|