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.
1138 lines
39 KiB
1138 lines
39 KiB
/******************************************************************************
|
|
*
|
|
* Copyright 2017 The Android Open Source Project
|
|
* Copyright 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 "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"
|
|
#include "stack/btm/btm_ble_int.h"
|
|
|
|
#include <string.h>
|
|
#include <queue>
|
|
#include <vector>
|
|
|
|
#include <base/bind.h>
|
|
#include <base/bind_helpers.h>
|
|
#include <base/location.h>
|
|
#include <base/logging.h>
|
|
#include <base/memory/weak_ptr.h>
|
|
#include <base/strings/string_number_conversions.h>
|
|
#include <base/time/time.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(const RawAddress& rpa)> 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);
|
|
adv_raddr_timer = nullptr;
|
|
if (timeout_timer) {
|
|
alarm_free(timeout_timer);
|
|
timeout_timer = nullptr;
|
|
}
|
|
}
|
|
};
|
|
|
|
void btm_ble_adv_raddr_timer_timeout(void* data);
|
|
|
|
struct closure_data {
|
|
base::Closure user_task;
|
|
base::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
|
|
static void alarm_set_closure(const base::Location& posted_from, alarm_t* alarm,
|
|
uint64_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;
|
|
base::WeakPtr<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>;
|
|
|
|
BleAdvertisingManager* instance;
|
|
base::WeakPtr<BleAdvertisingManagerImpl> instance_weakptr;
|
|
|
|
class BleAdvertisingManagerImpl
|
|
: public BleAdvertisingManager,
|
|
public BleAdvertiserHciInterface::AdvertisingEventObserver {
|
|
public:
|
|
BleAdvertisingManagerImpl(BleAdvertiserHciInterface* interface)
|
|
: hci_interface(interface), weak_factory_(this) {
|
|
hci_interface->ReadInstanceCount(
|
|
base::Bind(&BleAdvertisingManagerImpl::ReadInstanceCountCb,
|
|
weak_factory_.GetWeakPtr()));
|
|
}
|
|
|
|
~BleAdvertisingManagerImpl() override { 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 GenerateRpa(base::Callback<void(const RawAddress&)> cb) {
|
|
btm_gen_resolvable_private_addr(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,
|
|
const RawAddress& bda) {
|
|
/* Connectable advertising set must be disabled when updating RPA */
|
|
bool restart = p_inst->IsEnabled() && p_inst->IsConnectable();
|
|
|
|
if (!instance_weakptr.get()) return;
|
|
auto hci_interface = instance_weakptr.get()->GetHciInterface();
|
|
|
|
if (restart) {
|
|
p_inst->enable_status = false;
|
|
hci_interface->Enable(false, p_inst->inst_id, 0x00, 0x00,
|
|
base::DoNothing());
|
|
}
|
|
|
|
/* set it to controller */
|
|
hci_interface->SetRandomAddress(
|
|
p_inst->inst_id, bda,
|
|
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,
|
|
base::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,
|
|
const RawAddress& bda) {
|
|
p_inst->own_address = bda;
|
|
|
|
alarm_set_on_mloop(p_inst->adv_raddr_timer,
|
|
btm_get_next_private_addrress_interval_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;
|
|
base::WeakPtr<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 = weak_factory_.GetWeakPtr();
|
|
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 (!c->self) {
|
|
LOG(INFO) << "Stack was shut down";
|
|
return;
|
|
}
|
|
|
|
if (status) {
|
|
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 (!c->self) {
|
|
LOG(INFO) << "Stack was shut down";
|
|
return;
|
|
}
|
|
|
|
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 (!c->self) {
|
|
LOG(INFO) << "Stack was shut down";
|
|
return;
|
|
}
|
|
|
|
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 (!c->self) {
|
|
LOG(INFO) << "Stack was shut down";
|
|
return;
|
|
}
|
|
|
|
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 = weak_factory_.GetWeakPtr();
|
|
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 (!c->self) {
|
|
LOG(INFO) << "Stack was shut down";
|
|
return;
|
|
}
|
|
|
|
if (status != 0) {
|
|
LOG(ERROR) << " 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 (!c->self) {
|
|
LOG(INFO) << "Stack was shut down";
|
|
return;
|
|
}
|
|
|
|
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;
|
|
|
|
if (c->self->adv_inst[c->inst_id].own_address_type == BLE_ADDR_PUBLIC) {
|
|
auto self = c->self;
|
|
self->StartAdvertisingSetAfterAddressPart(std::move(c));
|
|
return;
|
|
}
|
|
|
|
//own_address_type == BLE_ADDR_RANDOM
|
|
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 (!c->self) {
|
|
LOG(INFO) << "Stack was shut down";
|
|
return;
|
|
}
|
|
|
|
if (status != 0) {
|
|
c->self->Unregister(c->inst_id);
|
|
LOG(ERROR) << "setting random address failed, status: " << +status;
|
|
c->cb.Run(0, 0, status);
|
|
return;
|
|
}
|
|
|
|
auto self = c->self;
|
|
self->StartAdvertisingSetAfterAddressPart(std::move(c));
|
|
}, base::Passed(&c)));
|
|
}, base::Passed(&c)));
|
|
}, base::Passed(&c)));
|
|
// clang-format on
|
|
}
|
|
|
|
void StartAdvertisingSetAfterAddressPart(c_type c) {
|
|
c->self->SetData(
|
|
c->inst_id, false, std::move(c->advertise_data),
|
|
Bind(
|
|
[](c_type c, uint8_t status) {
|
|
if (!c->self) {
|
|
LOG(INFO) << "Stack was shut down";
|
|
return;
|
|
}
|
|
|
|
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 (!c->self) {
|
|
LOG(INFO) << "Stack was shut down";
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
auto self = c->self;
|
|
if (c->periodic_params.enable) {
|
|
self->StartAdvertisingSetPeriodicPart(std::move(c));
|
|
} else {
|
|
self->StartAdvertisingSetFinish(std::move(c));
|
|
}
|
|
},
|
|
base::Passed(&c)));
|
|
},
|
|
base::Passed(&c)));
|
|
}
|
|
|
|
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 (!c->self) {
|
|
LOG(INFO) << "Stack was shut down";
|
|
return;
|
|
}
|
|
|
|
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 (!c->self) {
|
|
LOG(INFO) << "Stack was shut down";
|
|
return;
|
|
}
|
|
|
|
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 (!c->self) {
|
|
LOG(INFO) << "Stack was shut down";
|
|
return;
|
|
}
|
|
|
|
if (status != 0) {
|
|
c->self->Unregister(c->inst_id);
|
|
LOG(ERROR) << "enabling periodic advertising failed, status: " << +status;
|
|
c->cb.Run(0, 0, status);
|
|
return;
|
|
}
|
|
|
|
auto self = c->self;
|
|
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);
|
|
base::WeakPtr<BleAdvertisingManagerImpl> self = c->self;
|
|
MultiAdvCb enable_cb = Bind(
|
|
[](c_type c, uint8_t status) {
|
|
if (!c->self) {
|
|
LOG(INFO) << "Stack was shut down";
|
|
return;
|
|
}
|
|
|
|
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, weak_factory_.GetWeakPtr(), inst_id,
|
|
0 /* disable */, std::move(timeout_cb), 0, 0, base::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,
|
|
weak_factory_.GetWeakPtr(), 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,
|
|
weak_factory_.GetWeakPtr(), 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, base::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;
|
|
|
|
// sid must be in range 0x00 to 0x0F. Since no controller supports more than
|
|
// 16 advertisers, it's safe to make sid equal to inst_id.
|
|
uint8_t sid = p_inst->inst_id % 0x0F;
|
|
|
|
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, 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.
|
|
// The TX Power section is a 3 byte section.
|
|
for (size_t i = 0; (i + 2) < data.size();) {
|
|
if (data[i + 1] == 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,
|
|
weak_factory_.GetWeakPtr(), 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;
|
|
|
|
auto dataData = data.data();
|
|
sender.Run(
|
|
inst_id, operation, length, dataData + 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, base::DoNothing());
|
|
}
|
|
|
|
if (p_inst->periodic_enabled) {
|
|
p_inst->periodic_enabled = false;
|
|
GetHciInterface()->SetPeriodicAdvertisingEnable(false, inst_id,
|
|
base::DoNothing());
|
|
}
|
|
|
|
alarm_cancel(p_inst->adv_raddr_timer);
|
|
p_inst->in_use = false;
|
|
GetHciInterface()->RemoveAdvertisingSet(inst_id, base::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() override {
|
|
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, base::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, base::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: " << loghex(status)
|
|
<< ", advertising_handle: " << loghex(advertising_handle)
|
|
<< ", connection_handle: " << loghex(connection_handle);
|
|
|
|
if (status == HCI_ERR_LIMIT_REACHED ||
|
|
status == HCI_ERR_ADVERTISING_TIMEOUT) {
|
|
// 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) {
|
|
// 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, base::DoNothing());
|
|
}
|
|
|
|
} else {
|
|
/* mark directed adv as disabled if adv has been stopped */
|
|
p_inst->in_use = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
base::WeakPtr<BleAdvertisingManagerImpl> GetWeakPtr() {
|
|
return weak_factory_.GetWeakPtr();
|
|
}
|
|
|
|
void CancelAdvAlarms() {
|
|
AdvertisingInstance* p_inst = &adv_inst[0];
|
|
for (uint8_t i = 0; i < inst_count; i++, p_inst++) {
|
|
if (p_inst->timeout_timer) {
|
|
alarm_cancel(p_inst->timeout_timer);
|
|
}
|
|
if (p_inst->adv_raddr_timer) {
|
|
alarm_cancel(p_inst->adv_raddr_timer);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
BleAdvertiserHciInterface* GetHciInterface() { return hci_interface; }
|
|
|
|
BleAdvertiserHciInterface* hci_interface = nullptr;
|
|
std::vector<AdvertisingInstance> adv_inst;
|
|
uint8_t inst_count;
|
|
|
|
// Member variables should appear before the WeakPtrFactory, to ensure
|
|
// that any WeakPtrs are invalidated before its members
|
|
// variable's destructors are executed, rendering them invalid.
|
|
base::WeakPtrFactory<BleAdvertisingManagerImpl> weak_factory_;
|
|
};
|
|
|
|
void btm_ble_adv_raddr_timer_timeout(void* data) {
|
|
BleAdvertisingManagerImpl* ptr = instance_weakptr.get();
|
|
if (ptr) ptr->ConfigureRpa((AdvertisingInstance*)data, base::DoNothing());
|
|
}
|
|
} // namespace
|
|
|
|
void BleAdvertisingManager::Initialize(BleAdvertiserHciInterface* interface) {
|
|
instance = new BleAdvertisingManagerImpl(interface);
|
|
instance_weakptr = ((BleAdvertisingManagerImpl*)instance)->GetWeakPtr();
|
|
}
|
|
|
|
bool BleAdvertisingManager::IsInitialized() { return instance; }
|
|
|
|
base::WeakPtr<BleAdvertisingManager> BleAdvertisingManager::Get() {
|
|
return instance_weakptr;
|
|
};
|
|
|
|
void BleAdvertisingManager::CleanUp() {
|
|
if (instance_weakptr.get()) instance_weakptr.get()->CancelAdvAlarms();
|
|
|
|
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().get());
|
|
|
|
if (BleAdvertiserHciInterface::Get()->QuirkAdvertiserZeroHandle()) {
|
|
// If handle 0 can't be used, register advertiser for it, but never use it.
|
|
BleAdvertisingManager::Get().get()->RegisterAdvertiser(base::DoNothing());
|
|
}
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* 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().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);
|
|
timeout_triggered = false;
|
|
CHECK(!test1.enable_status);
|
|
}
|
|
|
|
// verify that duration and maxExtAdvEvents are properly adjusted when
|
|
// recomputing.
|
|
void testRecomputeTimeout2() {
|
|
auto manager = (BleAdvertisingManagerImpl*)BleAdvertisingManager::Get().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);
|
|
CHECK(test1.enable_status);
|
|
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().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);
|
|
timeout_triggered = false;
|
|
CHECK(!test1.enable_status);
|
|
}
|