You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1047 lines
36 KiB
1047 lines
36 KiB
/****************************************************************************** |
|
* |
|
* Copyright (C) 2017 The Android Open Source Project |
|
* Copyright (C) 2014 Broadcom Corporation |
|
* |
|
* 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 <base/bind.h> |
|
#include <base/logging.h> |
|
#include <base/strings/string_number_conversions.h> |
|
#include <base/time/time.h> |
|
#include <string.h> |
|
#include <queue> |
|
#include <vector> |
|
|
|
#include "bt_target.h" |
|
#include "device/include/controller.h" |
|
#include "osi/include/alarm.h" |
|
|
|
#include "ble_advertiser.h" |
|
#include "ble_advertiser_hci_interface.h" |
|
#include "btm_int_types.h" |
|
|
|
using base::Bind; |
|
using base::TimeDelta; |
|
using base::TimeTicks; |
|
using RegisterCb = |
|
base::Callback<void(uint8_t /* inst_id */, uint8_t /* status */)>; |
|
using IdTxPowerStatusCb = base::Callback<void( |
|
uint8_t /* inst_id */, int8_t /* tx_power */, uint8_t /* status */)>; |
|
using SetEnableData = BleAdvertiserHciInterface::SetEnableData; |
|
extern void btm_gen_resolvable_private_addr( |
|
base::Callback<void(uint8_t[8])> cb); |
|
|
|
constexpr int ADV_DATA_LEN_MAX = 251; |
|
|
|
namespace { |
|
|
|
bool is_connectable(uint16_t advertising_event_properties) { |
|
return advertising_event_properties & 0x01; |
|
} |
|
|
|
struct AdvertisingInstance { |
|
uint8_t inst_id; |
|
bool in_use; |
|
uint8_t advertising_event_properties; |
|
alarm_t* adv_raddr_timer; |
|
int8_t tx_power; |
|
uint16_t duration; // 1 unit is 10ms |
|
uint8_t maxExtAdvEvents; |
|
alarm_t* timeout_timer; |
|
uint8_t own_address_type; |
|
RawAddress own_address; |
|
MultiAdvCb timeout_cb; |
|
bool address_update_required; |
|
bool periodic_enabled; |
|
uint32_t advertising_interval; // 1 unit is 0.625 ms |
|
|
|
/* When true, advertising set is enabled, or last scheduled call to "LE Set |
|
* Extended Advertising Set Enable" is to enable this advertising set. Any |
|
* command scheduled when in this state will execute when the set is enabled, |
|
* unless enabling fails. |
|
* |
|
* When false, advertising set is disabled, or last scheduled call to "LE Set |
|
* Extended Advertising Set Enable" is to disable this advertising set. Any |
|
* command scheduled when in this state will execute when the set is disabled. |
|
*/ |
|
bool enable_status; |
|
TimeTicks enable_time; |
|
|
|
bool IsEnabled() { return enable_status; } |
|
|
|
bool IsConnectable() { return is_connectable(advertising_event_properties); } |
|
|
|
AdvertisingInstance(int inst_id) |
|
: inst_id(inst_id), |
|
in_use(false), |
|
advertising_event_properties(0), |
|
tx_power(0), |
|
duration(0), |
|
timeout_timer(nullptr), |
|
own_address_type(0), |
|
own_address(RawAddress::kEmpty), |
|
address_update_required(false), |
|
periodic_enabled(false), |
|
enable_status(false) { |
|
adv_raddr_timer = alarm_new_periodic("btm_ble.adv_raddr_timer"); |
|
} |
|
|
|
~AdvertisingInstance() { |
|
alarm_free(adv_raddr_timer); |
|
if (timeout_timer) alarm_free(timeout_timer); |
|
} |
|
}; |
|
|
|
void btm_ble_adv_raddr_timer_timeout(void* data); |
|
|
|
void DoNothing(uint8_t) {} |
|
void DoNothing2(uint8_t, uint8_t) {} |
|
|
|
struct closure_data { |
|
base::Closure user_task; |
|
tracked_objects::Location posted_from; |
|
}; |
|
|
|
static void alarm_closure_cb(void* p) { |
|
closure_data* data = (closure_data*)p; |
|
VLOG(1) << "executing timer scheduled at %s" << data->posted_from.ToString(); |
|
data->user_task.Run(); |
|
delete data; |
|
} |
|
|
|
// Periodic alarms are not supported, because we clean up data in callback |
|
void alarm_set_closure(const tracked_objects::Location& posted_from, |
|
alarm_t* alarm, period_ms_t interval_ms, |
|
base::Closure user_task) { |
|
closure_data* data = new closure_data; |
|
data->posted_from = posted_from; |
|
data->user_task = std::move(user_task); |
|
VLOG(1) << "scheduling timer %s" << data->posted_from.ToString(); |
|
alarm_set_on_mloop(alarm, interval_ms, alarm_closure_cb, data); |
|
} |
|
|
|
class BleAdvertisingManagerImpl; |
|
|
|
/* a temporary type for holding all the data needed in callbacks below*/ |
|
struct CreatorParams { |
|
uint8_t inst_id; |
|
BleAdvertisingManagerImpl* self; |
|
IdTxPowerStatusCb cb; |
|
tBTM_BLE_ADV_PARAMS params; |
|
std::vector<uint8_t> advertise_data; |
|
std::vector<uint8_t> scan_response_data; |
|
tBLE_PERIODIC_ADV_PARAMS periodic_params; |
|
std::vector<uint8_t> periodic_data; |
|
uint16_t duration; |
|
uint8_t maxExtAdvEvents; |
|
RegisterCb timeout_cb; |
|
}; |
|
|
|
using c_type = std::unique_ptr<CreatorParams>; |
|
|
|
class BleAdvertisingManagerImpl |
|
: public BleAdvertisingManager, |
|
public BleAdvertiserHciInterface::AdvertisingEventObserver { |
|
public: |
|
BleAdvertisingManagerImpl(BleAdvertiserHciInterface* interface) { |
|
this->hci_interface = interface; |
|
hci_interface->ReadInstanceCount( |
|
base::Bind(&BleAdvertisingManagerImpl::ReadInstanceCountCb, |
|
base::Unretained(this))); |
|
} |
|
|
|
~BleAdvertisingManagerImpl() { adv_inst.clear(); } |
|
|
|
void GetOwnAddress(uint8_t inst_id, GetAddressCallback cb) override { |
|
cb.Run(adv_inst[inst_id].own_address_type, adv_inst[inst_id].own_address); |
|
} |
|
|
|
void ReadInstanceCountCb(uint8_t instance_count) { |
|
this->inst_count = instance_count; |
|
adv_inst.reserve(inst_count); |
|
/* Initialize adv instance indices and IDs. */ |
|
for (uint8_t i = 0; i < inst_count; i++) { |
|
adv_inst.emplace_back(i); |
|
} |
|
} |
|
|
|
void OnRpaGenerationComplete(base::Callback<void(RawAddress)> cb, |
|
uint8_t rand[8]) { |
|
VLOG(1) << __func__; |
|
|
|
RawAddress bda; |
|
|
|
rand[2] &= (~BLE_RESOLVE_ADDR_MASK); |
|
rand[2] |= BLE_RESOLVE_ADDR_MSB; |
|
|
|
bda.address[2] = rand[0]; |
|
bda.address[1] = rand[1]; |
|
bda.address[0] = rand[2]; |
|
|
|
BT_OCTET16 irk; |
|
BTM_GetDeviceIDRoot(irk); |
|
tSMP_ENC output; |
|
|
|
if (!SMP_Encrypt(irk, BT_OCTET16_LEN, rand, 3, &output)) |
|
LOG_ASSERT(false) << "SMP_Encrypt failed"; |
|
|
|
/* set hash to be LSB of rpAddress */ |
|
bda.address[5] = output.param_buf[0]; |
|
bda.address[4] = output.param_buf[1]; |
|
bda.address[3] = output.param_buf[2]; |
|
|
|
cb.Run(bda); |
|
} |
|
|
|
void GenerateRpa(base::Callback<void(RawAddress)> cb) { |
|
btm_gen_resolvable_private_addr( |
|
Bind(&BleAdvertisingManagerImpl::OnRpaGenerationComplete, |
|
base::Unretained(this), std::move(cb))); |
|
} |
|
|
|
void ConfigureRpa(AdvertisingInstance* p_inst, MultiAdvCb configuredCb) { |
|
/* Connectable advertising set must be disabled when updating RPA */ |
|
bool restart = p_inst->IsEnabled() && p_inst->IsConnectable(); |
|
|
|
// If there is any form of timeout on the set, schedule address update when |
|
// the set stops, because there is no good way to compute new timeout value. |
|
// Maximum duration value is around 10 minutes, so this is safe. |
|
if (restart && (p_inst->duration || p_inst->maxExtAdvEvents)) { |
|
p_inst->address_update_required = true; |
|
configuredCb.Run(0x01); |
|
return; |
|
} |
|
|
|
GenerateRpa(Bind( |
|
[](AdvertisingInstance* p_inst, MultiAdvCb configuredCb, |
|
RawAddress bda) { |
|
/* Connectable advertising set must be disabled when updating RPA */ |
|
bool restart = p_inst->IsEnabled() && p_inst->IsConnectable(); |
|
|
|
auto hci_interface = |
|
((BleAdvertisingManagerImpl*)BleAdvertisingManager::Get()) |
|
->GetHciInterface(); |
|
|
|
if (restart) { |
|
p_inst->enable_status = false; |
|
hci_interface->Enable(false, p_inst->inst_id, 0x00, 0x00, |
|
Bind(DoNothing)); |
|
} |
|
|
|
/* set it to controller */ |
|
hci_interface->SetRandomAddress( |
|
p_inst->inst_id, p_inst->own_address, |
|
Bind( |
|
[](AdvertisingInstance* p_inst, RawAddress bda, |
|
MultiAdvCb configuredCb, uint8_t status) { |
|
p_inst->own_address = bda; |
|
configuredCb.Run(0x00); |
|
}, |
|
p_inst, bda, configuredCb)); |
|
|
|
if (restart) { |
|
p_inst->enable_status = true; |
|
hci_interface->Enable(true, p_inst->inst_id, 0x00, 0x00, |
|
Bind(DoNothing)); |
|
} |
|
}, |
|
p_inst, std::move(configuredCb))); |
|
} |
|
|
|
void RegisterAdvertiser( |
|
base::Callback<void(uint8_t /* inst_id */, uint8_t /* status */)> cb) |
|
override { |
|
AdvertisingInstance* p_inst = &adv_inst[0]; |
|
for (uint8_t i = 0; i < inst_count; i++, p_inst++) { |
|
if (p_inst->in_use) continue; |
|
|
|
p_inst->in_use = true; |
|
|
|
// set up periodic timer to update address. |
|
if (BTM_BleLocalPrivacyEnabled()) { |
|
p_inst->own_address_type = BLE_ADDR_RANDOM; |
|
GenerateRpa(Bind( |
|
[](AdvertisingInstance* p_inst, |
|
base::Callback<void(uint8_t /* inst_id */, uint8_t /* status */)> |
|
cb, |
|
RawAddress bda) { |
|
p_inst->own_address = bda; |
|
|
|
alarm_set_on_mloop(p_inst->adv_raddr_timer, |
|
BTM_BLE_PRIVATE_ADDR_INT_MS, |
|
btm_ble_adv_raddr_timer_timeout, p_inst); |
|
cb.Run(p_inst->inst_id, BTM_BLE_MULTI_ADV_SUCCESS); |
|
}, |
|
p_inst, cb)); |
|
} else { |
|
p_inst->own_address_type = BLE_ADDR_PUBLIC; |
|
p_inst->own_address = *controller_get_interface()->get_address(); |
|
|
|
cb.Run(p_inst->inst_id, BTM_BLE_MULTI_ADV_SUCCESS); |
|
} |
|
return; |
|
} |
|
|
|
LOG(INFO) << "no free advertiser instance"; |
|
cb.Run(0xFF, ADVERTISE_FAILED_TOO_MANY_ADVERTISERS); |
|
} |
|
|
|
void StartAdvertising(uint8_t advertiser_id, MultiAdvCb cb, |
|
tBTM_BLE_ADV_PARAMS* params, |
|
std::vector<uint8_t> advertise_data, |
|
std::vector<uint8_t> scan_response_data, int duration, |
|
MultiAdvCb timeout_cb) override { |
|
/* a temporary type for holding all the data needed in callbacks below*/ |
|
struct CreatorParams { |
|
uint8_t inst_id; |
|
BleAdvertisingManagerImpl* self; |
|
MultiAdvCb cb; |
|
tBTM_BLE_ADV_PARAMS params; |
|
std::vector<uint8_t> advertise_data; |
|
std::vector<uint8_t> scan_response_data; |
|
int duration; |
|
MultiAdvCb timeout_cb; |
|
}; |
|
|
|
std::unique_ptr<CreatorParams> c; |
|
c.reset(new CreatorParams()); |
|
|
|
c->self = this; |
|
c->cb = std::move(cb); |
|
c->params = *params; |
|
c->advertise_data = std::move(advertise_data); |
|
c->scan_response_data = std::move(scan_response_data); |
|
c->duration = duration; |
|
c->timeout_cb = std::move(timeout_cb); |
|
c->inst_id = advertiser_id; |
|
|
|
using c_type = std::unique_ptr<CreatorParams>; |
|
|
|
// this code is intentionally left formatted this way to highlight the |
|
// asynchronous flow |
|
// clang-format off |
|
c->self->SetParameters(c->inst_id, &c->params, Bind( |
|
[](c_type c, uint8_t status, int8_t tx_power) { |
|
if (status != 0) { |
|
LOG(ERROR) << "setting parameters failed, status: " << +status; |
|
c->cb.Run(status); |
|
return; |
|
} |
|
|
|
c->self->adv_inst[c->inst_id].tx_power = tx_power; |
|
|
|
const RawAddress& rpa = c->self->adv_inst[c->inst_id].own_address; |
|
c->self->GetHciInterface()->SetRandomAddress(c->inst_id, rpa, Bind( |
|
[](c_type c, uint8_t status) { |
|
if (status != 0) { |
|
LOG(ERROR) << "setting random address failed, status: " << +status; |
|
c->cb.Run(status); |
|
return; |
|
} |
|
|
|
c->self->SetData(c->inst_id, false, std::move(c->advertise_data), Bind( |
|
[](c_type c, uint8_t status) { |
|
if (status != 0) { |
|
LOG(ERROR) << "setting advertise data failed, status: " << +status; |
|
c->cb.Run(status); |
|
return; |
|
} |
|
|
|
c->self->SetData(c->inst_id, true, std::move(c->scan_response_data), Bind( |
|
[](c_type c, uint8_t status) { |
|
if (status != 0) { |
|
LOG(ERROR) << "setting scan response data failed, status: " << +status; |
|
c->cb.Run(status); |
|
return; |
|
} |
|
|
|
c->self->Enable(c->inst_id, true, c->cb, c->duration, 0, std::move(c->timeout_cb)); |
|
|
|
}, base::Passed(&c))); |
|
}, base::Passed(&c))); |
|
}, base::Passed(&c))); |
|
}, base::Passed(&c))); |
|
// clang-format on |
|
} |
|
|
|
void StartAdvertisingSet(IdTxPowerStatusCb cb, tBTM_BLE_ADV_PARAMS* params, |
|
std::vector<uint8_t> advertise_data, |
|
std::vector<uint8_t> scan_response_data, |
|
tBLE_PERIODIC_ADV_PARAMS* periodic_params, |
|
std::vector<uint8_t> periodic_data, |
|
uint16_t duration, uint8_t maxExtAdvEvents, |
|
RegisterCb timeout_cb) override { |
|
std::unique_ptr<CreatorParams> c; |
|
c.reset(new CreatorParams()); |
|
|
|
c->self = this; |
|
c->cb = std::move(cb); |
|
c->params = *params; |
|
c->advertise_data = std::move(advertise_data); |
|
c->scan_response_data = std::move(scan_response_data); |
|
c->periodic_params = *periodic_params; |
|
c->periodic_data = std::move(periodic_data); |
|
c->duration = duration; |
|
c->maxExtAdvEvents = maxExtAdvEvents; |
|
c->timeout_cb = std::move(timeout_cb); |
|
|
|
// this code is intentionally left formatted this way to highlight the |
|
// asynchronous flow |
|
// clang-format off |
|
c->self->RegisterAdvertiser(Bind( |
|
[](c_type c, uint8_t advertiser_id, uint8_t status) { |
|
if (status != 0) { |
|
LOG(ERROR) << "registering advertiser failed, status: " << +status; |
|
c->cb.Run(0, 0, status); |
|
return; |
|
} |
|
|
|
c->inst_id = advertiser_id; |
|
|
|
c->self->SetParameters(c->inst_id, &c->params, Bind( |
|
[](c_type c, uint8_t status, int8_t tx_power) { |
|
if (status != 0) { |
|
c->self->Unregister(c->inst_id); |
|
LOG(ERROR) << "setting parameters failed, status: " << +status; |
|
c->cb.Run(0, 0, status); |
|
return; |
|
} |
|
|
|
c->self->adv_inst[c->inst_id].tx_power = tx_power; |
|
|
|
const RawAddress& rpa = c->self->adv_inst[c->inst_id].own_address; |
|
c->self->GetHciInterface()->SetRandomAddress(c->inst_id, rpa, Bind( |
|
[](c_type c, uint8_t status) { |
|
if (status != 0) { |
|
c->self->Unregister(c->inst_id); |
|
LOG(ERROR) << "setting random address failed, status: " << +status; |
|
c->cb.Run(0, 0, status); |
|
return; |
|
} |
|
|
|
c->self->SetData(c->inst_id, false, std::move(c->advertise_data), Bind( |
|
[](c_type c, uint8_t status) { |
|
if (status != 0) { |
|
c->self->Unregister(c->inst_id); |
|
LOG(ERROR) << "setting advertise data failed, status: " << +status; |
|
c->cb.Run(0, 0, status); |
|
return; |
|
} |
|
|
|
c->self->SetData(c->inst_id, true, std::move(c->scan_response_data), Bind( |
|
[](c_type c, uint8_t status) { |
|
if (status != 0) { |
|
c->self->Unregister(c->inst_id); |
|
LOG(ERROR) << "setting scan response data failed, status: " << +status; |
|
c->cb.Run(0, 0, status); |
|
return; |
|
} |
|
|
|
if (c->periodic_params.enable) { |
|
c->self->StartAdvertisingSetPeriodicPart(std::move(c)); |
|
} else { |
|
c->self->StartAdvertisingSetFinish(std::move(c)); |
|
} |
|
}, base::Passed(&c))); |
|
}, base::Passed(&c))); |
|
}, base::Passed(&c))); |
|
}, base::Passed(&c))); |
|
}, base::Passed(&c))); |
|
// clang-format on |
|
} |
|
|
|
void StartAdvertisingSetPeriodicPart(c_type c) { |
|
// this code is intentionally left formatted this way to highlight the |
|
// asynchronous flow |
|
// clang-format off |
|
c->self->SetPeriodicAdvertisingParameters(c->inst_id, &c->periodic_params, Bind( |
|
[](c_type c, uint8_t status) { |
|
if (status != 0) { |
|
c->self->Unregister(c->inst_id); |
|
LOG(ERROR) << "setting periodic parameters failed, status: " << +status; |
|
c->cb.Run(0, 0, status); |
|
return; |
|
} |
|
|
|
c->self->SetPeriodicAdvertisingData(c->inst_id, std::move(c->periodic_data), Bind( |
|
[](c_type c, uint8_t status) { |
|
if (status != 0) { |
|
c->self->Unregister(c->inst_id); |
|
LOG(ERROR) << "setting periodic parameters failed, status: " << +status; |
|
c->cb.Run(0, 0, status); |
|
return; |
|
} |
|
|
|
c->self->SetPeriodicAdvertisingEnable(c->inst_id, true, Bind( |
|
[](c_type c, uint8_t status) { |
|
if (status != 0) { |
|
c->self->Unregister(c->inst_id); |
|
LOG(ERROR) << "enabling periodic advertising failed, status: " << +status; |
|
c->cb.Run(0, 0, status); |
|
return; |
|
} |
|
|
|
c->self->StartAdvertisingSetFinish(std::move(c)); |
|
|
|
}, base::Passed(&c))); |
|
}, base::Passed(&c))); |
|
}, base::Passed(&c))); |
|
// clang-format on |
|
} |
|
|
|
void StartAdvertisingSetFinish(c_type c) { |
|
uint8_t inst_id = c->inst_id; |
|
uint16_t duration = c->duration; |
|
uint8_t maxExtAdvEvents = c->maxExtAdvEvents; |
|
RegisterCb timeout_cb = std::move(c->timeout_cb); |
|
BleAdvertisingManagerImpl* self = c->self; |
|
MultiAdvCb enable_cb = Bind( |
|
[](c_type c, uint8_t status) { |
|
if (status != 0) { |
|
c->self->Unregister(c->inst_id); |
|
LOG(ERROR) << "enabling advertiser failed, status: " << +status; |
|
c->cb.Run(0, 0, status); |
|
return; |
|
} |
|
int8_t tx_power = c->self->adv_inst[c->inst_id].tx_power; |
|
c->cb.Run(c->inst_id, tx_power, status); |
|
}, |
|
base::Passed(&c)); |
|
|
|
self->Enable(inst_id, true, std::move(enable_cb), duration, maxExtAdvEvents, |
|
Bind(std::move(timeout_cb), inst_id)); |
|
} |
|
|
|
void EnableWithTimerCb(uint8_t inst_id, MultiAdvCb enable_cb, int duration, |
|
MultiAdvCb timeout_cb, uint8_t status) { |
|
VLOG(1) << __func__ << " inst_id: " << +inst_id; |
|
AdvertisingInstance* p_inst = &adv_inst[inst_id]; |
|
|
|
// Run the regular enable callback |
|
enable_cb.Run(status); |
|
|
|
p_inst->timeout_timer = alarm_new("btm_ble.adv_timeout"); |
|
|
|
base::Closure cb = Bind(&BleAdvertisingManagerImpl::Enable, |
|
base::Unretained(this), inst_id, 0 /* disable */, |
|
std::move(timeout_cb), 0, 0, base::Bind(DoNothing)); |
|
|
|
// schedule disable when the timeout passes |
|
alarm_set_closure(FROM_HERE, p_inst->timeout_timer, duration * 10, |
|
std::move(cb)); |
|
} |
|
|
|
void Enable(uint8_t inst_id, bool enable, MultiAdvCb cb, uint16_t duration, |
|
uint8_t maxExtAdvEvents, MultiAdvCb timeout_cb) override { |
|
VLOG(1) << __func__ << " inst_id: " << +inst_id; |
|
if (inst_id >= inst_count) { |
|
LOG(ERROR) << "bad instance id " << +inst_id; |
|
return; |
|
} |
|
|
|
AdvertisingInstance* p_inst = &adv_inst[inst_id]; |
|
VLOG(1) << __func__ << " enable: " << enable << ", duration: " << +duration; |
|
if (!p_inst->in_use) { |
|
LOG(ERROR) << "Invalid or no active instance"; |
|
cb.Run(BTM_BLE_MULTI_ADV_FAILURE); |
|
return; |
|
} |
|
|
|
if (enable && (duration || maxExtAdvEvents)) { |
|
p_inst->timeout_cb = std::move(timeout_cb); |
|
} |
|
|
|
p_inst->duration = duration; |
|
p_inst->maxExtAdvEvents = maxExtAdvEvents; |
|
|
|
if (enable && p_inst->address_update_required) { |
|
p_inst->address_update_required = false; |
|
ConfigureRpa(p_inst, base::Bind(&BleAdvertisingManagerImpl::EnableFinish, |
|
base::Unretained(this), p_inst, enable, |
|
std::move(cb))); |
|
return; |
|
} |
|
|
|
EnableFinish(p_inst, enable, std::move(cb), 0); |
|
} |
|
|
|
void EnableFinish(AdvertisingInstance* p_inst, bool enable, MultiAdvCb cb, |
|
uint8_t status) { |
|
MultiAdvCb myCb; |
|
if (enable && p_inst->duration) { |
|
// TODO(jpawlowski): HCI implementation that can't do duration should |
|
// emulate it, not EnableWithTimerCb. |
|
myCb = Bind(&BleAdvertisingManagerImpl::EnableWithTimerCb, |
|
base::Unretained(this), p_inst->inst_id, std::move(cb), |
|
p_inst->duration, p_inst->timeout_cb); |
|
} else { |
|
myCb = std::move(cb); |
|
|
|
if (p_inst->timeout_timer) { |
|
alarm_cancel(p_inst->timeout_timer); |
|
alarm_free(p_inst->timeout_timer); |
|
p_inst->timeout_timer = nullptr; |
|
} |
|
} |
|
|
|
if (enable) p_inst->enable_time = TimeTicks::Now(); |
|
p_inst->enable_status = enable; |
|
GetHciInterface()->Enable(enable, p_inst->inst_id, p_inst->duration, |
|
p_inst->maxExtAdvEvents, std::move(myCb)); |
|
} |
|
|
|
void SetParameters(uint8_t inst_id, tBTM_BLE_ADV_PARAMS* p_params, |
|
ParametersCb cb) override { |
|
VLOG(1) << __func__ << " inst_id: " << +inst_id; |
|
if (inst_id >= inst_count) { |
|
LOG(ERROR) << "bad instance id " << +inst_id; |
|
return; |
|
} |
|
|
|
AdvertisingInstance* p_inst = &adv_inst[inst_id]; |
|
if (!p_inst->in_use) { |
|
LOG(ERROR) << "adv instance not in use" << +inst_id; |
|
cb.Run(BTM_BLE_MULTI_ADV_FAILURE, 0); |
|
return; |
|
} |
|
|
|
// TODO: disable only if was enabled, currently no use scenario needs |
|
// that, |
|
// we always set parameters before enabling |
|
// GetHciInterface()->Enable(false, inst_id, Bind(DoNothing)); |
|
p_inst->advertising_event_properties = |
|
p_params->advertising_event_properties; |
|
p_inst->tx_power = p_params->tx_power; |
|
p_inst->advertising_interval = p_params->adv_int_min; |
|
const RawAddress& peer_address = RawAddress::kEmpty; |
|
|
|
GetHciInterface()->SetParameters( |
|
p_inst->inst_id, p_params->advertising_event_properties, |
|
p_params->adv_int_min, p_params->adv_int_max, p_params->channel_map, |
|
p_inst->own_address_type, p_inst->own_address, 0x00, peer_address, |
|
p_params->adv_filter_policy, p_inst->tx_power, |
|
p_params->primary_advertising_phy, 0x00, |
|
p_params->secondary_advertising_phy, 0x01 /* TODO: proper SID */, |
|
p_params->scan_request_notification_enable, cb); |
|
|
|
// TODO: re-enable only if it was enabled, properly call |
|
// SetParamsCallback |
|
// currently no use scenario needs that |
|
// GetHciInterface()->Enable(true, inst_id, BTM_BleUpdateAdvInstParamCb); |
|
} |
|
|
|
void SetData(uint8_t inst_id, bool is_scan_rsp, std::vector<uint8_t> data, |
|
MultiAdvCb cb) override { |
|
VLOG(1) << __func__ << " inst_id: " << +inst_id; |
|
if (inst_id >= inst_count) { |
|
LOG(ERROR) << "bad instance id " << +inst_id; |
|
return; |
|
} |
|
|
|
AdvertisingInstance* p_inst = &adv_inst[inst_id]; |
|
VLOG(1) << "is_scan_rsp = " << is_scan_rsp; |
|
|
|
if (!is_scan_rsp && is_connectable(p_inst->advertising_event_properties)) { |
|
uint8_t flags_val = BTM_GENERAL_DISCOVERABLE; |
|
|
|
if (p_inst->duration) flags_val = BTM_LIMITED_DISCOVERABLE; |
|
|
|
std::vector<uint8_t> flags; |
|
flags.push_back(2); // length |
|
flags.push_back(HCI_EIR_FLAGS_TYPE); |
|
flags.push_back(flags_val); |
|
|
|
data.insert(data.begin(), flags.begin(), flags.end()); |
|
} |
|
|
|
// Find and fill TX Power with the correct value |
|
if (data.size()) { |
|
size_t i = 0; |
|
while (i < data.size()) { |
|
uint8_t type = data[i + 1]; |
|
if (type == HCI_EIR_TX_POWER_LEVEL_TYPE) { |
|
data[i + 2] = adv_inst[inst_id].tx_power; |
|
} |
|
i += data[i] + 1; |
|
} |
|
} |
|
|
|
VLOG(1) << "data is: " << base::HexEncode(data.data(), data.size()); |
|
DivideAndSendData( |
|
inst_id, data, cb, |
|
base::Bind(&BleAdvertisingManagerImpl::SetDataAdvDataSender, |
|
base::Unretained(this), is_scan_rsp)); |
|
} |
|
|
|
void SetDataAdvDataSender(uint8_t is_scan_rsp, uint8_t inst_id, |
|
uint8_t operation, uint8_t length, uint8_t* data, |
|
MultiAdvCb cb) { |
|
if (is_scan_rsp) |
|
GetHciInterface()->SetScanResponseData(inst_id, operation, 0x01, length, |
|
data, cb); |
|
else |
|
GetHciInterface()->SetAdvertisingData(inst_id, operation, 0x01, length, |
|
data, cb); |
|
} |
|
|
|
using DataSender = base::Callback<void( |
|
uint8_t /*inst_id*/, uint8_t /* operation */, uint8_t /* length */, |
|
uint8_t* /* data */, MultiAdvCb /* done */)>; |
|
|
|
void DivideAndSendData(int inst_id, std::vector<uint8_t> data, |
|
MultiAdvCb done_cb, DataSender sender) { |
|
DivideAndSendDataRecursively(true, inst_id, std::move(data), 0, |
|
std::move(done_cb), std::move(sender), 0); |
|
} |
|
|
|
static void DivideAndSendDataRecursively(bool isFirst, int inst_id, |
|
std::vector<uint8_t> data, |
|
int offset, MultiAdvCb done_cb, |
|
DataSender sender, uint8_t status) { |
|
constexpr uint8_t INTERMEDIATE = |
|
0x00; // Intermediate fragment of fragmented data |
|
constexpr uint8_t FIRST = 0x01; // First fragment of fragmented data |
|
constexpr uint8_t LAST = 0x02; // Last fragment of fragmented data |
|
constexpr uint8_t COMPLETE = 0x03; // Complete extended advertising data |
|
|
|
int dataSize = (int)data.size(); |
|
if (status != 0 || (!isFirst && offset == dataSize)) { |
|
/* if we got error writing data, or reached the end of data */ |
|
done_cb.Run(status); |
|
return; |
|
} |
|
|
|
bool moreThanOnePacket = dataSize - offset > ADV_DATA_LEN_MAX; |
|
uint8_t operation = isFirst ? moreThanOnePacket ? FIRST : COMPLETE |
|
: moreThanOnePacket ? INTERMEDIATE : LAST; |
|
int length = moreThanOnePacket ? ADV_DATA_LEN_MAX : dataSize - offset; |
|
int newOffset = offset + length; |
|
|
|
sender.Run( |
|
inst_id, operation, length, data.data() + offset, |
|
Bind(&BleAdvertisingManagerImpl::DivideAndSendDataRecursively, false, |
|
inst_id, std::move(data), newOffset, std::move(done_cb), sender)); |
|
} |
|
|
|
void SetPeriodicAdvertisingParameters(uint8_t inst_id, |
|
tBLE_PERIODIC_ADV_PARAMS* params, |
|
MultiAdvCb cb) override { |
|
VLOG(1) << __func__ << " inst_id: " << +inst_id; |
|
|
|
GetHciInterface()->SetPeriodicAdvertisingParameters( |
|
inst_id, params->min_interval, params->max_interval, |
|
params->periodic_advertising_properties, cb); |
|
} |
|
|
|
void SetPeriodicAdvertisingData(uint8_t inst_id, std::vector<uint8_t> data, |
|
MultiAdvCb cb) override { |
|
VLOG(1) << __func__ << " inst_id: " << +inst_id; |
|
|
|
VLOG(1) << "data is: " << base::HexEncode(data.data(), data.size()); |
|
|
|
DivideAndSendData( |
|
inst_id, data, cb, |
|
base::Bind(&BleAdvertiserHciInterface::SetPeriodicAdvertisingData, |
|
base::Unretained(GetHciInterface()))); |
|
} |
|
|
|
void SetPeriodicAdvertisingEnable(uint8_t inst_id, uint8_t enable, |
|
MultiAdvCb cb) override { |
|
VLOG(1) << __func__ << " inst_id: " << +inst_id << ", enable: " << +enable; |
|
|
|
AdvertisingInstance* p_inst = &adv_inst[inst_id]; |
|
if (!p_inst->in_use) { |
|
LOG(ERROR) << "Invalid or not active instance"; |
|
cb.Run(BTM_BLE_MULTI_ADV_FAILURE); |
|
return; |
|
} |
|
|
|
MultiAdvCb enable_cb = Bind( |
|
[](AdvertisingInstance* p_inst, uint8_t enable, MultiAdvCb cb, |
|
uint8_t status) { |
|
VLOG(1) << "periodc adv enable cb: inst_id: " << +p_inst->inst_id |
|
<< ", enable: " << +enable << ", status: " << std::hex |
|
<< +status; |
|
if (!status) p_inst->periodic_enabled = enable; |
|
|
|
cb.Run(status); |
|
}, |
|
p_inst, enable, std::move(cb)); |
|
|
|
GetHciInterface()->SetPeriodicAdvertisingEnable(enable, inst_id, |
|
std::move(enable_cb)); |
|
} |
|
|
|
void Unregister(uint8_t inst_id) override { |
|
AdvertisingInstance* p_inst = &adv_inst[inst_id]; |
|
|
|
VLOG(1) << __func__ << " inst_id: " << +inst_id; |
|
if (inst_id >= inst_count) { |
|
LOG(ERROR) << "bad instance id " << +inst_id; |
|
return; |
|
} |
|
|
|
if (adv_inst[inst_id].IsEnabled()) { |
|
p_inst->enable_status = false; |
|
GetHciInterface()->Enable(false, inst_id, 0x00, 0x00, Bind(DoNothing)); |
|
} |
|
|
|
if (p_inst->periodic_enabled) { |
|
p_inst->periodic_enabled = false; |
|
GetHciInterface()->SetPeriodicAdvertisingEnable(false, inst_id, |
|
Bind(DoNothing)); |
|
} |
|
|
|
alarm_cancel(p_inst->adv_raddr_timer); |
|
p_inst->in_use = false; |
|
GetHciInterface()->RemoveAdvertisingSet(inst_id, Bind(DoNothing)); |
|
p_inst->address_update_required = false; |
|
} |
|
|
|
void RecomputeTimeout(AdvertisingInstance* inst, TimeTicks now) { |
|
TimeDelta duration = now - inst->enable_time; |
|
bool cb_fired = false; |
|
if (inst->duration) { |
|
int durationDone = (duration.InMilliseconds() / 10); |
|
if (durationDone + 1 >= inst->duration) { |
|
inst->enable_status = false; |
|
inst->timeout_cb.Run(0 /* TODO: STATUS HERE?*/); |
|
cb_fired = true; |
|
} else { |
|
inst->duration = inst->duration - durationDone; |
|
} |
|
} |
|
|
|
if (inst->maxExtAdvEvents && !cb_fired) { |
|
int eventsDone = |
|
(duration.InMilliseconds() / (inst->advertising_interval * 5 / 8)); |
|
|
|
if (eventsDone + 1 >= inst->maxExtAdvEvents) { |
|
inst->enable_status = false; |
|
inst->timeout_cb.Run(0 /* TODO: STATUS HERE?*/); |
|
} else { |
|
inst->maxExtAdvEvents = inst->maxExtAdvEvents - eventsDone; |
|
} |
|
} |
|
} |
|
|
|
void Suspend() { |
|
std::vector<SetEnableData> sets; |
|
|
|
for (AdvertisingInstance& inst : adv_inst) { |
|
if (!inst.in_use || !inst.enable_status) continue; |
|
|
|
if (inst.duration || inst.maxExtAdvEvents) |
|
RecomputeTimeout(&inst, TimeTicks::Now()); |
|
|
|
sets.emplace_back(SetEnableData{.handle = inst.inst_id}); |
|
} |
|
|
|
if (!sets.empty()) GetHciInterface()->Enable(false, sets, Bind(DoNothing)); |
|
} |
|
|
|
void Resume() override { |
|
std::vector<SetEnableData> sets; |
|
|
|
for (const AdvertisingInstance& inst : adv_inst) { |
|
if (inst.in_use && inst.enable_status) { |
|
sets.emplace_back(SetEnableData{ |
|
.handle = inst.inst_id, |
|
.duration = inst.duration, |
|
.max_extended_advertising_events = inst.maxExtAdvEvents}); |
|
} |
|
} |
|
|
|
if (!sets.empty()) GetHciInterface()->Enable(true, sets, Bind(DoNothing)); |
|
} |
|
|
|
void OnAdvertisingSetTerminated( |
|
uint8_t status, uint8_t advertising_handle, uint16_t connection_handle, |
|
uint8_t num_completed_extended_adv_events) override { |
|
AdvertisingInstance* p_inst = &adv_inst[advertising_handle]; |
|
VLOG(1) << __func__ << "status: 0x" << std::hex << +status |
|
<< ", advertising_handle: 0x" << std::hex << +advertising_handle |
|
<< ", connection_handle: 0x" << std::hex << +connection_handle; |
|
|
|
if (status == 0x43 || status == 0x3C) { |
|
// either duration elapsed, or maxExtAdvEvents reached |
|
p_inst->enable_status = false; |
|
|
|
if (p_inst->timeout_cb.is_null()) { |
|
LOG(INFO) << __func__ << "No timeout callback"; |
|
return; |
|
} |
|
|
|
p_inst->timeout_cb.Run(status); |
|
return; |
|
} |
|
|
|
if (BTM_BleLocalPrivacyEnabled() && |
|
advertising_handle <= BTM_BLE_MULTI_ADV_MAX) { |
|
btm_acl_update_conn_addr(connection_handle, p_inst->own_address); |
|
} |
|
|
|
VLOG(1) << "reneabling advertising"; |
|
|
|
if (p_inst->in_use == true) { |
|
// TODO(jpawlowski): we don't really allow to do directed advertising |
|
// right now. This should probably be removed, check with Andre. |
|
if ((p_inst->advertising_event_properties & 0x0C) == 0) { |
|
/* directed advertising bits not set */ |
|
|
|
RecomputeTimeout(p_inst, TimeTicks::Now()); |
|
if (p_inst->enable_status) { |
|
GetHciInterface()->Enable(true, advertising_handle, p_inst->duration, |
|
p_inst->maxExtAdvEvents, Bind(DoNothing)); |
|
} |
|
|
|
} else { |
|
/* mark directed adv as disabled if adv has been stopped */ |
|
p_inst->in_use = false; |
|
} |
|
} |
|
} |
|
|
|
private: |
|
BleAdvertiserHciInterface* GetHciInterface() { return hci_interface; } |
|
|
|
BleAdvertiserHciInterface* hci_interface = nullptr; |
|
std::vector<AdvertisingInstance> adv_inst; |
|
uint8_t inst_count; |
|
}; |
|
|
|
BleAdvertisingManager* instance; |
|
|
|
void btm_ble_adv_raddr_timer_timeout(void* data) { |
|
((BleAdvertisingManagerImpl*)BleAdvertisingManager::Get()) |
|
->ConfigureRpa((AdvertisingInstance*)data, base::Bind(DoNothing)); |
|
} |
|
} // namespace |
|
|
|
void BleAdvertisingManager::Initialize(BleAdvertiserHciInterface* interface) { |
|
instance = new BleAdvertisingManagerImpl(interface); |
|
} |
|
|
|
bool BleAdvertisingManager::IsInitialized() { return instance; } |
|
|
|
BleAdvertisingManager* BleAdvertisingManager::Get() { |
|
CHECK(instance); |
|
return instance; |
|
}; |
|
|
|
void BleAdvertisingManager::CleanUp() { |
|
delete instance; |
|
instance = nullptr; |
|
}; |
|
|
|
/** |
|
* This function initialize the advertising manager. |
|
**/ |
|
void btm_ble_adv_init() { |
|
BleAdvertiserHciInterface::Initialize(); |
|
BleAdvertisingManager::Initialize(BleAdvertiserHciInterface::Get()); |
|
BleAdvertiserHciInterface::Get()->SetAdvertisingEventObserver( |
|
(BleAdvertisingManagerImpl*)BleAdvertisingManager::Get()); |
|
|
|
if (BleAdvertiserHciInterface::Get()->QuirkAdvertiserZeroHandle()) { |
|
// If handle 0 can't be used, register advertiser for it, but never use it. |
|
BleAdvertisingManager::Get()->RegisterAdvertiser(Bind(DoNothing2)); |
|
} |
|
} |
|
|
|
/******************************************************************************* |
|
* |
|
* Function btm_ble_multi_adv_cleanup |
|
* |
|
* Description This function cleans up multi adv control block. |
|
* |
|
* Parameters |
|
* Returns void |
|
* |
|
******************************************************************************/ |
|
void btm_ble_multi_adv_cleanup(void) { |
|
BleAdvertisingManager::CleanUp(); |
|
BleAdvertiserHciInterface::CleanUp(); |
|
} |
|
|
|
// TODO(jpawlowski): Find a nicer way to test RecomputeTimeout without exposing |
|
// AdvertisingInstance |
|
bool timeout_triggered = false; |
|
void test_timeout_cb(uint8_t status) { timeout_triggered = true; } |
|
|
|
// verify that if duration passed, or is about to pass, recomputation will shut |
|
// down the advertiser completly |
|
void testRecomputeTimeout1() { |
|
auto manager = (BleAdvertisingManagerImpl*)BleAdvertisingManager::Get(); |
|
|
|
TimeTicks start = TimeTicks::Now(); |
|
TimeTicks end = start + TimeDelta::FromMilliseconds(111); |
|
AdvertisingInstance test1(0); |
|
test1.enable_status = true; |
|
test1.enable_time = start; |
|
test1.duration = 12 /*120ms*/; |
|
test1.timeout_cb = Bind(&test_timeout_cb); |
|
|
|
manager->RecomputeTimeout(&test1, end); |
|
|
|
CHECK(timeout_triggered == true); |
|
timeout_triggered = false; |
|
CHECK(test1.enable_status == false); |
|
} |
|
|
|
// verify that duration and maxExtAdvEvents are properly adjusted when |
|
// recomputing. |
|
void testRecomputeTimeout2() { |
|
auto manager = (BleAdvertisingManagerImpl*)BleAdvertisingManager::Get(); |
|
|
|
TimeTicks start = TimeTicks::Now(); |
|
TimeTicks end = start + TimeDelta::FromMilliseconds(250); |
|
AdvertisingInstance test1(0); |
|
test1.enable_status = true; |
|
test1.enable_time = start; |
|
test1.duration = 50 /*500ms*/; |
|
test1.maxExtAdvEvents = 50; |
|
test1.advertising_interval = 16 /* 10 ms */; |
|
test1.timeout_cb = Bind(&test_timeout_cb); |
|
|
|
manager->RecomputeTimeout(&test1, end); |
|
|
|
CHECK(timeout_triggered == false); |
|
CHECK(test1.enable_status == true); |
|
CHECK(test1.duration == 25); |
|
CHECK(test1.maxExtAdvEvents == 25); |
|
} |
|
|
|
// verify that if maxExtAdvEvents were sent, or are close to end, recomputation |
|
// wil shut down the advertiser completly |
|
void testRecomputeTimeout3() { |
|
auto manager = (BleAdvertisingManagerImpl*)BleAdvertisingManager::Get(); |
|
|
|
TimeTicks start = TimeTicks::Now(); |
|
TimeTicks end = start + TimeDelta::FromMilliseconds(495); |
|
AdvertisingInstance test1(0); |
|
test1.enable_status = true; |
|
test1.enable_time = start; |
|
test1.maxExtAdvEvents = 50; |
|
test1.advertising_interval = 16 /* 10 ms */; |
|
test1.timeout_cb = Bind(&test_timeout_cb); |
|
|
|
manager->RecomputeTimeout(&test1, end); |
|
|
|
CHECK(timeout_triggered == true); |
|
timeout_triggered = false; |
|
CHECK(test1.enable_status == false); |
|
}
|
|
|