| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/fake_adapter.h" |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/link_type.h" |
| |
| namespace bt::gap::testing { |
| |
| FakeAdapter::FakeAdapter(pw::async::Dispatcher& pw_dispatcher) |
| : init_state_(InitState::kNotInitialized), |
| fake_le_(std::make_unique<FakeLowEnergy>(this)), |
| fake_bredr_(std::make_unique<FakeBrEdr>()), |
| heap_dispatcher_(pw_dispatcher), |
| peer_cache_(pw_dispatcher), |
| weak_self_(this) {} |
| |
| bool FakeAdapter::Initialize(InitializeCallback callback, |
| fit::closure transport_closed_callback) { |
| init_state_ = InitState::kInitializing; |
| (void)heap_dispatcher_.Post( |
| [this, cb = std::move(callback)](pw::async::Context /*ctx*/, |
| pw::Status status) mutable { |
| if (status.ok()) { |
| init_state_ = InitState::kInitialized; |
| cb(/*success=*/true); |
| } |
| }); |
| return true; |
| } |
| |
| void FakeAdapter::ShutDown() { init_state_ = InitState::kNotInitialized; } |
| |
| FakeAdapter::FakeBrEdr::~FakeBrEdr() { |
| for (auto& chan : channels_) { |
| chan.second->Close(); |
| } |
| } |
| |
| void FakeAdapter::FakeBrEdr::OpenL2capChannel( |
| PeerId peer_id, |
| l2cap::Psm psm, |
| BrEdrSecurityRequirements security_requirements, |
| l2cap::ChannelParameters params, |
| l2cap::ChannelCallback cb) { |
| l2cap::ChannelInfo info( |
| params.mode.value_or(l2cap::RetransmissionAndFlowControlMode::kBasic), |
| params.max_rx_sdu_size.value_or(l2cap::kDefaultMTU), |
| /*max_tx_sdu_size=*/l2cap::kDefaultMTU, |
| /*n_frames_in_tx_window=*/0, |
| /*max_transmissions=*/0, |
| /*max_tx_pdu_payload_size=*/0, |
| psm, |
| params.flush_timeout); |
| l2cap::ChannelId local_id = next_channel_id_++; |
| auto channel = std::make_unique<l2cap::testing::FakeChannel>( |
| /*id=*/local_id, |
| /*remote_id=*/l2cap::kFirstDynamicChannelId, |
| /*handle=*/1, |
| bt::LinkType::kACL, |
| info); |
| l2cap::testing::FakeChannel::WeakPtr weak_fake_channel = channel->AsWeakPtr(); |
| l2cap::Channel::WeakPtr weak_channel = channel->GetWeakPtr(); |
| channels_.emplace(local_id, std::move(channel)); |
| if (channel_cb_) { |
| channel_cb_(weak_fake_channel); |
| } |
| cb(weak_channel); |
| } |
| |
| void FakeAdapter::FakeLowEnergy::UpdateRandomAddress(DeviceAddress& address) { |
| random_ = address; |
| // Notify the callback about the change in address. |
| if (address_changed_callback_) { |
| address_changed_callback_(); |
| } |
| } |
| |
| void FakeAdapter::FakeLowEnergy::Connect( |
| PeerId peer_id, |
| ConnectionResultCallback callback, |
| LowEnergyConnectionOptions connection_options) { |
| connections_[peer_id] = Connection{peer_id, connection_options}; |
| |
| auto bondable_cb = [connection_options]() { |
| return connection_options.bondable_mode; |
| }; |
| auto security_cb = []() { return sm::SecurityProperties(); }; |
| auto role_cb = []() { |
| return pw::bluetooth::emboss::ConnectionRole::CENTRAL; |
| }; |
| auto handle = std::make_unique<LowEnergyConnectionHandle>( |
| peer_id, |
| /*handle=*/1, |
| /*release_cb=*/[](auto) {}, |
| std::move(bondable_cb), |
| std::move(security_cb), |
| std::move(role_cb)); |
| callback(fit::ok(std::move(handle))); |
| } |
| |
| bool FakeAdapter::FakeLowEnergy::Disconnect(PeerId peer_id) { |
| return connections_.erase(peer_id); |
| } |
| |
| void FakeAdapter::FakeLowEnergy::StartAdvertising( |
| AdvertisingData data, |
| AdvertisingData scan_rsp, |
| AdvertisingInterval interval, |
| bool extended_pdu, |
| bool anonymous, |
| bool include_tx_power_level, |
| std::optional<ConnectableAdvertisingParameters> connectable, |
| AdvertisingStatusCallback status_callback) { |
| // status_callback is currently not called because its parameters can only be |
| // constructed by LowEnergyAdvertisingManager. |
| |
| RegisteredAdvertisement adv{.data = std::move(data), |
| .scan_rsp = std::move(scan_rsp), |
| .connectable = std::move(connectable), |
| .interval = interval, |
| .extended_pdu = extended_pdu, |
| .anonymous = anonymous, |
| .include_tx_power_level = include_tx_power_level}; |
| AdvertisementId adv_id = next_advertisement_id_; |
| next_advertisement_id_ = AdvertisementId(next_advertisement_id_.value() + 1); |
| advertisements_.emplace(adv_id, std::move(adv)); |
| } |
| |
| void FakeAdapter::FakeLowEnergy::EnablePrivacy(bool enabled) { |
| privacy_enabled_ = enabled; |
| if (!enabled && random_.has_value()) { |
| random_.reset(); |
| if (address_changed_callback_) { |
| address_changed_callback_(); |
| } |
| } |
| } |
| |
| FakeAdapter::FakeBrEdr::RegistrationHandle |
| FakeAdapter::FakeBrEdr::RegisterService(std::vector<sdp::ServiceRecord> records, |
| l2cap::ChannelParameters chan_params, |
| ServiceConnectCallback conn_cb) { |
| auto handle = next_service_handle_++; |
| registered_services_.emplace( |
| handle, |
| RegisteredService{std::move(records), chan_params, std::move(conn_cb)}); |
| return handle; |
| } |
| |
| bool FakeAdapter::FakeBrEdr::UnregisterService(RegistrationHandle handle) { |
| return registered_services_.erase(handle); |
| } |
| |
| void FakeAdapter::SetLocalName(std::string name, |
| hci::ResultFunction<> callback) { |
| local_name_ = name; |
| callback(fit::ok()); |
| } |
| |
| void FakeAdapter::SetDeviceClass(DeviceClass dev_class, |
| hci::ResultFunction<> callback) { |
| device_class_ = dev_class; |
| callback(fit::ok()); |
| } |
| |
| BrEdrConnectionManager::SearchId FakeAdapter::FakeBrEdr::AddServiceSearch( |
| const UUID& uuid, |
| std::unordered_set<sdp::AttributeId> attributes, |
| SearchCallback callback) { |
| auto handle = next_search_handle_++; |
| registered_searches_.emplace( |
| handle, |
| RegisteredSearch{uuid, std::move(attributes), std::move(callback)}); |
| return SearchId(handle); |
| } |
| |
| void FakeAdapter::FakeBrEdr::TriggerServiceFound( |
| PeerId peer_id, |
| UUID uuid, |
| std::map<sdp::AttributeId, sdp::DataElement> attributes) { |
| for (auto it = registered_searches_.begin(); it != registered_searches_.end(); |
| it++) { |
| if (it->second.uuid == uuid) { |
| it->second.callback(peer_id, attributes); |
| } |
| } |
| } |
| |
| } // namespace bt::gap::testing |