blob: 2deee85e20bf186e745455a63d5749af9c7d0de6 [file] [log] [blame]
/******************************************************************************
*
* Copyright 2018 The Android Open Source Project
*
* 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 "connection_manager.h"
#include <base/functional/bind.h>
#include <base/functional/callback.h>
#include <base/location.h>
#include <base/logging.h>
#include <bluetooth/log.h>
#include <map>
#include <memory>
#include <set>
#include "internal_include/bt_trace.h"
#include "main/shim/le_scanning_manager.h"
#include "os/log.h"
#include "osi/include/alarm.h"
#include "stack/btm/btm_ble_bgconn.h"
#include "stack/include/advertise_data_parser.h"
#include "stack/include/bt_types.h"
#include "stack/include/btm_ble_api.h"
#include "stack/include/btm_log_history.h"
#include "stack/include/main_thread.h"
#include "types/raw_address.h"
#define DIRECT_CONNECT_TIMEOUT (30 * 1000) /* 30 seconds */
using namespace bluetooth;
constexpr char kBtmLogTag[] = "TA";
struct closure_data {
base::OnceClosure user_task;
base::Location posted_from;
};
static void alarm_closure_cb(void* p) {
closure_data* data = (closure_data*)p;
log::verbose("executing timer scheduled at {}", data->posted_from.ToString());
std::move(data->user_task).Run();
delete data;
}
// Periodic alarms are not supported, because we clean up data in callback
void alarm_set_closure(const base::Location& posted_from, alarm_t* alarm,
uint64_t interval_ms, base::OnceClosure user_task) {
closure_data* data = new closure_data;
data->posted_from = posted_from;
data->user_task = std::move(user_task);
log::verbose("scheduling timer {}", data->posted_from.ToString());
alarm_set_on_mloop(alarm, interval_ms, alarm_closure_cb, data);
}
using unique_alarm_ptr = std::unique_ptr<alarm_t, decltype(&alarm_free)>;
namespace connection_manager {
struct tAPPS_CONNECTING {
// ids of clients doing background connection to given device
std::set<tAPP_ID> doing_bg_conn;
std::set<tAPP_ID> doing_targeted_announcements_conn;
bool is_in_accept_list;
// Apps trying to do direct connection.
std::map<tAPP_ID, unique_alarm_ptr> doing_direct_conn;
};
namespace {
// Maps address to apps trying to connect to it
std::map<RawAddress, tAPPS_CONNECTING> bgconn_dev;
int num_of_targeted_announcements_users(void) {
return std::count_if(
bgconn_dev.begin(), bgconn_dev.end(), [](const auto& pair) {
return (!pair.second.is_in_accept_list &&
!pair.second.doing_targeted_announcements_conn.empty());
});
}
bool is_anyone_interested_to_use_accept_list(
const std::map<RawAddress, tAPPS_CONNECTING>::iterator it) {
if (!it->second.doing_targeted_announcements_conn.empty()) {
return (!it->second.doing_direct_conn.empty());
}
return (!it->second.doing_bg_conn.empty() ||
!it->second.doing_direct_conn.empty());
}
bool is_anyone_connecting(
const std::map<RawAddress, tAPPS_CONNECTING>::iterator it) {
return (!it->second.doing_bg_conn.empty() ||
!it->second.doing_direct_conn.empty() ||
!it->second.doing_targeted_announcements_conn.empty());
}
} // namespace
/** background connection device from the list. Returns pointer to the device
* record, or nullptr if not found */
std::set<tAPP_ID> get_apps_connecting_to(const RawAddress& address) {
log::debug("address={}", ADDRESS_TO_LOGGABLE_CSTR(address));
auto it = bgconn_dev.find(address);
return (it != bgconn_dev.end()) ? it->second.doing_bg_conn
: std::set<tAPP_ID>();
}
bool IsTargetedAnnouncement(const uint8_t* p_eir, uint16_t eir_len) {
const uint8_t* p_service_data = p_eir;
uint8_t service_data_len = 0;
while ((p_service_data = AdvertiseDataParser::GetFieldByType(
p_service_data + service_data_len,
eir_len - (p_service_data - p_eir) - service_data_len,
BTM_BLE_AD_TYPE_SERVICE_DATA_TYPE, &service_data_len))) {
uint16_t uuid;
uint8_t announcement_type;
const uint8_t* p_tmp = p_service_data;
if (service_data_len < 3) {
continue;
}
STREAM_TO_UINT16(uuid, p_tmp);
log::debug("Found UUID 0x{:04x}", uuid);
if (uuid != 0x184E && uuid != 0x1853) {
continue;
}
STREAM_TO_UINT8(announcement_type, p_tmp);
log::debug("Found announcement_type 0x{:02x}", announcement_type);
if (announcement_type == 0x01) {
return true;
}
}
return false;
}
static void schedule_direct_connect_add(uint8_t app_id,
const RawAddress& address);
static void target_announcement_observe_results_cb(tBTM_INQ_RESULTS* p_inq,
const uint8_t* p_eir,
uint16_t eir_len) {
auto addr = p_inq->remote_bd_addr;
auto it = bgconn_dev.find(addr);
if (it == bgconn_dev.end() ||
it->second.doing_targeted_announcements_conn.empty()) {
return;
}
if (!IsTargetedAnnouncement(p_eir, eir_len)) {
log::debug("Not a targeted announcement for device {}",
ADDRESS_TO_LOGGABLE_CSTR(addr));
return;
}
log::info("Found targeted announcement for device {}",
ADDRESS_TO_LOGGABLE_CSTR(addr));
if (it->second.is_in_accept_list) {
log::info("Device {} is already connecting",
ADDRESS_TO_LOGGABLE_CSTR(addr));
return;
}
if (BTM_GetHCIConnHandle(addr, BT_TRANSPORT_LE) != 0xFFFF) {
log::debug("Device {} already connected", ADDRESS_TO_LOGGABLE_CSTR(addr));
return;
}
BTM_LogHistory(kBtmLogTag, addr, "Found TA from");
/* Take fist app_id and use it for direct_connect */
auto app_id = *(it->second.doing_targeted_announcements_conn.begin());
/* If scan is ongoing lets stop it */
do_in_main_thread(FROM_HERE,
base::BindOnce(schedule_direct_connect_add, app_id, addr));
}
void target_announcements_filtering_set(bool enable) {
log::debug("enable {}", enable);
BTM_LogHistory(kBtmLogTag, RawAddress::kEmpty,
(enable ? "Start filtering" : "Stop filtering"));
/* Safe to call as if there is no support for filtering, this call will be
* ignored. */
bluetooth::shim::set_target_announcements_filter(enable);
BTM_BleTargetAnnouncementObserve(enable,
target_announcement_observe_results_cb);
}
/** Add a device to the background connection list for targeted announcements.
* Returns
* true if device added to the list, or already in list,
* false otherwise
*/
bool background_connect_targeted_announcement_add(tAPP_ID app_id,
const RawAddress& address) {
log::info("app_id={}, address={}", static_cast<int>(app_id),
ADDRESS_TO_LOGGABLE_CSTR(address));
bool disable_accept_list = false;
auto it = bgconn_dev.find(address);
if (it != bgconn_dev.end()) {
// check if filtering already enabled
if (it->second.doing_targeted_announcements_conn.count(app_id)) {
log::info(
"app_id={}, already doing targeted announcement filtering to "
"address={}",
static_cast<int>(app_id), ADDRESS_TO_LOGGABLE_CSTR(address));
return true;
}
bool targeted_filtering_enabled =
!it->second.doing_targeted_announcements_conn.empty();
// Check if connecting
if (!it->second.doing_direct_conn.empty()) {
log::info("app_id={}, address={}, already in direct connection",
static_cast<int>(app_id), ADDRESS_TO_LOGGABLE_CSTR(address));
} else if (!targeted_filtering_enabled &&
!it->second.doing_bg_conn.empty()) {
// device is already in the acceptlist so we would have to remove it
log::info(
"already doing background connection to address={}. Need to disable "
"it.",
ADDRESS_TO_LOGGABLE_CSTR(address));
disable_accept_list = true;
}
}
if (disable_accept_list) {
BTM_AcceptlistRemove(address);
bgconn_dev[address].is_in_accept_list = false;
}
bgconn_dev[address].doing_targeted_announcements_conn.insert(app_id);
if (bgconn_dev[address].doing_targeted_announcements_conn.size() == 1) {
BTM_LogHistory(kBtmLogTag, address, "Allow connection from");
}
if (num_of_targeted_announcements_users() == 1) {
target_announcements_filtering_set(true);
}
return true;
}
/** Add a device from the background connection list. Returns true if device
* added to the list, or already in list, false otherwise */
bool background_connect_add(uint8_t app_id, const RawAddress& address) {
log::debug("app_id={}, address={}", static_cast<int>(app_id),
ADDRESS_TO_LOGGABLE_CSTR(address));
auto it = bgconn_dev.find(address);
bool in_acceptlist = false;
bool is_targeted_announcement_enabled = false;
if (it != bgconn_dev.end()) {
// device already in the acceptlist, just add interested app to the list
if (it->second.doing_bg_conn.count(app_id)) {
log::debug("app_id={}, already doing background connection to address={}",
static_cast<int>(app_id), ADDRESS_TO_LOGGABLE_CSTR(address));
return true;
}
// Already in acceptlist ?
if (it->second.is_in_accept_list) {
log::debug("app_id={}, address={}, already in accept list",
static_cast<int>(app_id), ADDRESS_TO_LOGGABLE_CSTR(address));
in_acceptlist = true;
} else {
is_targeted_announcement_enabled =
!it->second.doing_targeted_announcements_conn.empty();
}
}
if (!in_acceptlist) {
// the device is not in the acceptlist
if (is_targeted_announcement_enabled) {
log::debug("Targeted announcement enabled, do not add to AcceptList");
} else {
if (!BTM_AcceptlistAdd(address)) {
log::warn("Failed to add device {} to accept list for app {}",
ADDRESS_TO_LOGGABLE_CSTR(address), static_cast<int>(app_id));
return false;
}
bgconn_dev[address].is_in_accept_list = true;
}
}
// create entry for address, and insert app_id.
// new tAPPS_CONNECTING will be default constructed if not exist
bgconn_dev[address].doing_bg_conn.insert(app_id);
return true;
}
/** Removes all registrations for connection for given device.
* Returns true if anything was removed, false otherwise */
bool remove_unconditional(const RawAddress& address) {
log::debug("address={}", ADDRESS_TO_LOGGABLE_CSTR(address));
auto it = bgconn_dev.find(address);
if (it == bgconn_dev.end()) {
log::warn("address {} is not found", ADDRESS_TO_LOGGABLE_CSTR(address));
return false;
}
BTM_AcceptlistRemove(address);
bgconn_dev.erase(it);
return true;
}
/** Remove device from the background connection device list or listening to
* advertising list. Returns true if device was on the list and was
* successfully removed */
bool background_connect_remove(uint8_t app_id, const RawAddress& address) {
log::debug("app_id={}, address={}", static_cast<int>(app_id),
ADDRESS_TO_LOGGABLE_CSTR(address));
auto it = bgconn_dev.find(address);
if (it == bgconn_dev.end()) {
log::warn("address {} is not found", ADDRESS_TO_LOGGABLE_CSTR(address));
return false;
}
bool accept_list_enabled = it->second.is_in_accept_list;
auto num_of_targeted_announcements_before_remove =
it->second.doing_targeted_announcements_conn.size();
bool removed_from_bg_conn = (it->second.doing_bg_conn.erase(app_id) > 0);
bool removed_from_ta =
(it->second.doing_targeted_announcements_conn.erase(app_id) > 0);
if (!removed_from_bg_conn && !removed_from_ta) {
log::warn("Failed to remove background connection app {} for address {}",
static_cast<int>(app_id), ADDRESS_TO_LOGGABLE_CSTR(address));
return false;
}
if (removed_from_ta &&
it->second.doing_targeted_announcements_conn.size() == 0) {
BTM_LogHistory(kBtmLogTag, address, "Ignore connection from");
}
if (is_anyone_connecting(it)) {
log::debug("some device is still connecting, app_id={}, address={}",
static_cast<int>(app_id), ADDRESS_TO_LOGGABLE_CSTR(address));
/* Check which method should be used now.*/
if (!accept_list_enabled) {
/* Accept list was not used */
if (!it->second.doing_targeted_announcements_conn.empty()) {
/* Keep using filtering */
log::debug("Keep using target announcement filtering");
} else if (!it->second.doing_bg_conn.empty()) {
if (!BTM_AcceptlistAdd(address)) {
log::warn("Could not re add device to accept list");
} else {
bgconn_dev[address].is_in_accept_list = true;
}
}
}
return true;
}
bgconn_dev.erase(it);
// no more apps interested - remove from accept list and delete record
if (accept_list_enabled) {
BTM_AcceptlistRemove(address);
return true;
}
if ((num_of_targeted_announcements_before_remove > 0) &&
num_of_targeted_announcements_users() == 0) {
target_announcements_filtering_set(true);
}
return true;
}
bool is_background_connection(const RawAddress& address) {
return bgconn_dev.find(address) != bgconn_dev.end();
}
/** deregister all related background connetion device. */
void on_app_deregistered(uint8_t app_id) {
log::debug("app_id={}", static_cast<int>(app_id));
auto it = bgconn_dev.begin();
auto end = bgconn_dev.end();
/* update the BG conn device list */
while (it != end) {
it->second.doing_bg_conn.erase(app_id);
it->second.doing_direct_conn.erase(app_id);
if (is_anyone_connecting(it)) {
it++;
continue;
}
BTM_AcceptlistRemove(it->first);
it = bgconn_dev.erase(it);
}
}
static void remove_all_clients_with_pending_connections(
const RawAddress& address) {
log::debug("address={}", ADDRESS_TO_LOGGABLE_CSTR(address));
auto it = bgconn_dev.find(address);
while (it != bgconn_dev.end() && !it->second.doing_direct_conn.empty()) {
uint8_t app_id = it->second.doing_direct_conn.begin()->first;
direct_connect_remove(app_id, address);
it = bgconn_dev.find(address);
}
}
void on_connection_complete(const RawAddress& address) {
log::info("Le connection completed to device:{}",
ADDRESS_TO_LOGGABLE_CSTR(address));
remove_all_clients_with_pending_connections(address);
}
void on_connection_timed_out_from_shim(const RawAddress& address) {
log::info("Connection failed {}", ADDRESS_TO_LOGGABLE_CSTR(address));
on_connection_timed_out(0x00, address);
}
/** Reset bg device list. If called after controller reset, set |after_reset|
* to true, as there is no need to wipe controller acceptlist in this case. */
void reset(bool after_reset) {
bgconn_dev.clear();
if (!after_reset) {
target_announcements_filtering_set(false);
BTM_AcceptlistClear();
}
}
void wl_direct_connect_timeout_cb(uint8_t app_id, const RawAddress& address) {
log::debug("app_id={}, address={}", static_cast<int>(app_id),
ADDRESS_TO_LOGGABLE_CSTR(address));
on_connection_timed_out(app_id, address);
// TODO: this would free the timer, from within the timer callback, which is
// bad.
direct_connect_remove(app_id, address, true);
}
/** Add a device to the direct connection list. Returns true if device
* added to the list, false otherwise */
bool direct_connect_add(uint8_t app_id, const RawAddress& address) {
log::debug("app_id={}, address={}", static_cast<int>(app_id),
ADDRESS_TO_LOGGABLE_CSTR(address));
bool in_acceptlist = false;
auto it = bgconn_dev.find(address);
if (it != bgconn_dev.end()) {
// app already trying to connect to this particular device
if (it->second.doing_direct_conn.count(app_id)) {
log::info("direct connect attempt from app_id={} already in progress",
loghex(app_id));
return false;
}
// are we already in the acceptlist ?
if (it->second.is_in_accept_list) {
log::warn("Background connection attempt already in progress app_id={:x}",
app_id);
in_acceptlist = true;
}
}
if (!in_acceptlist) {
if (!BTM_AcceptlistAdd(address, true)) {
// if we can't add to acceptlist, turn parameters back to slow.
log::warn("Unable to add le device to acceptlist");
return false;
}
bgconn_dev[address].is_in_accept_list = true;
}
// Setup a timer
alarm_t* timeout = alarm_new("wl_conn_params_30s");
alarm_set_closure(
FROM_HERE, timeout, DIRECT_CONNECT_TIMEOUT,
base::BindOnce(&wl_direct_connect_timeout_cb, app_id, address));
bgconn_dev[address].doing_direct_conn.emplace(
app_id, unique_alarm_ptr(timeout, &alarm_free));
return true;
}
static void schedule_direct_connect_add(uint8_t app_id,
const RawAddress& address) {
direct_connect_add(app_id, address);
}
bool direct_connect_remove(uint8_t app_id, const RawAddress& address,
bool connection_timeout) {
log::debug("app_id={}, address={}", static_cast<int>(app_id),
ADDRESS_TO_LOGGABLE_CSTR(address));
auto it = bgconn_dev.find(address);
if (it == bgconn_dev.end()) {
log::warn("Unable to find background connection to remove peer:{}",
ADDRESS_TO_LOGGABLE_CSTR(address));
return false;
}
auto app_it = it->second.doing_direct_conn.find(app_id);
if (app_it == it->second.doing_direct_conn.end()) {
log::warn("Unable to find direct connection to remove peer:{}",
ADDRESS_TO_LOGGABLE_CSTR(address));
return false;
}
/* Let see if the device was connected due to Target Announcements.*/
bool is_targeted_announcement_enabled =
!it->second.doing_targeted_announcements_conn.empty();
// this will free the alarm
it->second.doing_direct_conn.erase(app_it);
if (is_anyone_interested_to_use_accept_list(it)) {
if (connection_timeout) {
/* In such case we need to add device back to allow list because,
* when connection timeout out, the lower layer removes device from
* the allow list.
*/
if (!BTM_AcceptlistAdd(address)) {
log::warn(
"Failed to re-add device {} to accept list after connection "
"timeout",
ADDRESS_TO_LOGGABLE_CSTR(address));
}
}
return true;
}
// no more apps interested - remove from acceptlist
BTM_AcceptlistRemove(address);
if (!is_targeted_announcement_enabled) {
bgconn_dev.erase(it);
} else {
it->second.is_in_accept_list = false;
}
return true;
}
void dump(int fd) {
dprintf(fd, "\nconnection_manager state:\n");
if (bgconn_dev.empty()) {
dprintf(fd, "\tno Low Energy connection attempts\n");
return;
}
dprintf(fd, "\tdevices attempting connection: %d", (int)bgconn_dev.size());
for (const auto& entry : bgconn_dev) {
// TODO: confirm whether we need to replace this
dprintf(fd, "\n\t * %s: ", ADDRESS_TO_LOGGABLE_CSTR(entry.first));
if (!entry.second.doing_direct_conn.empty()) {
dprintf(fd, "\n\t\tapps doing direct connect: ");
for (const auto& id : entry.second.doing_direct_conn) {
dprintf(fd, "%d, ", id.first);
}
}
if (!entry.second.doing_bg_conn.empty()) {
dprintf(fd, "\n\t\tapps doing background connect: ");
for (const auto& id : entry.second.doing_bg_conn) {
dprintf(fd, "%d, ", id);
}
}
if (!entry.second.doing_targeted_announcements_conn.empty()) {
dprintf(fd, "\n\t\tapps doing cap announcement connect: ");
for (const auto& id : entry.second.doing_targeted_announcements_conn) {
dprintf(fd, "%d, ", id);
}
}
dprintf(fd, "\n\t\t is in the allow list: %s",
entry.second.is_in_accept_list ? "true" : "false");
}
dprintf(fd, "\n");
}
} // namespace connection_manager