| // Copyright 2018 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 "fake_domain.h" |
| |
| #include <lib/async/cpp/task.h> |
| #include <lib/async/default.h> |
| |
| #include "garnet/drivers/bluetooth/lib/l2cap/fake_channel.h" |
| |
| namespace btlib { |
| |
| using l2cap::testing::FakeChannel; |
| |
| namespace data { |
| namespace testing { |
| |
| void FakeDomain::TriggerLEConnectionParameterUpdate( |
| hci::ConnectionHandle handle, |
| const hci::LEPreferredConnectionParameters& params) { |
| ZX_DEBUG_ASSERT(initialized_); |
| |
| LinkData& link_data = FindLinkData(handle); |
| async::PostTask( |
| link_data.dispatcher, |
| [params, cb = link_data.le_conn_param_cb.share()] { cb(params); }); |
| } |
| |
| void FakeDomain::TriggerOutboundL2capChannel(hci::ConnectionHandle handle, |
| l2cap::PSM psm, |
| l2cap::ChannelId id, |
| l2cap::ChannelId remote_id) { |
| ZX_DEBUG_ASSERT(initialized_); |
| |
| LinkData& link_data = FindLinkData(handle); |
| auto cb_iter = link_data.outbound_conn_cbs.find(psm); |
| ZX_DEBUG_ASSERT_MSG(cb_iter != link_data.outbound_conn_cbs.end(), |
| "no previous call to OpenChannel with PSM %#.4x", psm); |
| |
| std::list<ChannelDelivery>& handlers = cb_iter->second; |
| ZX_DEBUG_ASSERT(!handlers.empty()); |
| |
| l2cap::ChannelCallback& cb = handlers.front().first; |
| auto chan = OpenFakeChannel(&link_data, id, remote_id); |
| async_dispatcher_t* const dispatcher = handlers.front().second; |
| async::PostTask(dispatcher, [cb = std::move(cb), chan = std::move(chan)] { |
| cb(std::move(chan)); |
| }); |
| |
| handlers.pop_front(); |
| if (handlers.empty()) { |
| link_data.outbound_conn_cbs.erase(cb_iter); |
| } |
| } |
| |
| void FakeDomain::TriggerInboundL2capChannel(hci::ConnectionHandle handle, |
| l2cap::PSM psm, l2cap::ChannelId id, |
| l2cap::ChannelId remote_id) { |
| ZX_DEBUG_ASSERT(initialized_); |
| |
| LinkData& link_data = FindLinkData(handle); |
| auto cb_iter = inbound_conn_cbs_.find(psm); |
| ZX_DEBUG_ASSERT_MSG(cb_iter != inbound_conn_cbs_.end(), |
| "no service registered for PSM %#.4x", psm); |
| |
| l2cap::ChannelCallback& cb = cb_iter->second.first; |
| async_dispatcher_t* const dispatcher = cb_iter->second.second; |
| auto chan = OpenFakeChannel(&link_data, id, remote_id); |
| async::PostTask(dispatcher, [cb = cb.share(), chan = std::move(chan)] { |
| cb(std::move(chan)); |
| }); |
| } |
| |
| void FakeDomain::TriggerLinkError(hci::ConnectionHandle handle) { |
| ZX_DEBUG_ASSERT(initialized_); |
| |
| LinkData& link_data = FindLinkData(handle); |
| async::PostTask(link_data.dispatcher, |
| [cb = link_data.link_error_cb.share()] { cb(); }); |
| } |
| |
| void FakeDomain::Initialize() { initialized_ = true; } |
| |
| void FakeDomain::ShutDown() { initialized_ = false; } |
| |
| void FakeDomain::AddACLConnection(hci::ConnectionHandle handle, |
| hci::Connection::Role role, |
| l2cap::LinkErrorCallback link_error_cb, |
| l2cap::SecurityUpgradeCallback security_cb, |
| async_dispatcher_t* dispatcher) { |
| if (!initialized_) |
| return; |
| |
| RegisterInternal(handle, role, hci::Connection::LinkType::kACL, |
| std::move(link_error_cb), dispatcher); |
| } |
| |
| void FakeDomain::AddLEConnection( |
| hci::ConnectionHandle handle, hci::Connection::Role role, |
| l2cap::LinkErrorCallback link_error_cb, |
| l2cap::LEConnectionParameterUpdateCallback conn_param_cb, |
| l2cap::LEFixedChannelsCallback channel_cb, |
| l2cap::SecurityUpgradeCallback security_cb, |
| async_dispatcher_t* dispatcher) { |
| if (!initialized_) |
| return; |
| |
| LinkData* data = |
| RegisterInternal(handle, role, hci::Connection::LinkType::kLE, |
| std::move(link_error_cb), dispatcher); |
| data->le_conn_param_cb = std::move(conn_param_cb); |
| |
| // Open the ATT and SMP fixed channels. |
| auto att = OpenFakeFixedChannel(data, l2cap::kATTChannelId); |
| auto smp = OpenFakeFixedChannel(data, l2cap::kLESMPChannelId); |
| async::PostTask(dispatcher, [att = std::move(att), smp = std::move(smp), |
| cb = std::move(channel_cb)]() mutable { |
| cb(std::move(att), std::move(smp)); |
| }); |
| } |
| |
| void FakeDomain::RemoveConnection(hci::ConnectionHandle handle) { |
| links_.erase(handle); |
| } |
| |
| void FakeDomain::AssignLinkSecurityProperties(hci::ConnectionHandle handle, |
| sm::SecurityProperties security) { |
| // TODO(armansito): implement |
| } |
| |
| void FakeDomain::OpenL2capChannel(hci::ConnectionHandle handle, l2cap::PSM psm, |
| l2cap::ChannelCallback cb, |
| async_dispatcher_t* dispatcher) { |
| ZX_DEBUG_ASSERT(initialized_); |
| |
| LinkData& link_data = FindLinkData(handle); |
| link_data.outbound_conn_cbs[psm].push_back( |
| std::make_pair(std::move(cb), dispatcher)); |
| } |
| |
| void FakeDomain::OpenL2capChannel(hci::ConnectionHandle handle, l2cap::PSM psm, |
| SocketCallback socket_callback, |
| async_dispatcher_t* cb_dispatcher) { |
| ZX_DEBUG_ASSERT(cb_dispatcher); |
| OpenL2capChannel( |
| handle, psm, |
| [this, cb = std::move(socket_callback), |
| cb_dispatcher](auto channel) mutable { |
| zx::socket s = socket_factory_.MakeSocketForChannel(channel); |
| // Called every time the service is connected, cb must be shared. |
| async::PostTask(cb_dispatcher, |
| [s = std::move(s), cb = cb.share(), |
| handle = channel->link_handle()]() mutable { |
| cb(std::move(s), handle); |
| }); |
| }, |
| async_get_default_dispatcher()); |
| } |
| |
| void FakeDomain::RegisterService(l2cap::PSM psm, |
| l2cap::ChannelCallback channel_callback, |
| async_dispatcher_t* dispatcher) { |
| ZX_DEBUG_ASSERT(initialized_); |
| ZX_DEBUG_ASSERT(inbound_conn_cbs_.count(psm) == 0); |
| |
| inbound_conn_cbs_.emplace( |
| psm, std::make_pair(std::move(channel_callback), dispatcher)); |
| } |
| |
| void FakeDomain::RegisterService(l2cap::PSM psm, SocketCallback socket_callback, |
| async_dispatcher_t* cb_dispatcher) { |
| RegisterService( |
| psm, |
| [this, psm, cb = std::move(socket_callback), |
| cb_dispatcher](auto channel) mutable { |
| zx::socket s = socket_factory_.MakeSocketForChannel(channel); |
| // Called every time the service is connected, cb must be shared. |
| async::PostTask(cb_dispatcher, |
| [s = std::move(s), cb = cb.share(), |
| handle = channel->link_handle()]() mutable { |
| cb(std::move(s), handle); |
| }); |
| }, |
| async_get_default_dispatcher()); |
| } |
| |
| void FakeDomain::UnregisterService(l2cap::PSM psm) { |
| ZX_DEBUG_ASSERT(initialized_); |
| |
| inbound_conn_cbs_.erase(psm); |
| } |
| |
| FakeDomain::LinkData* FakeDomain::RegisterInternal( |
| hci::ConnectionHandle handle, hci::Connection::Role role, |
| hci::Connection::LinkType link_type, l2cap::LinkErrorCallback link_error_cb, |
| async_dispatcher_t* dispatcher) { |
| ZX_DEBUG_ASSERT_MSG(links_.find(handle) == links_.end(), |
| "connection handle re-used (handle: %#.4x)", handle); |
| |
| LinkData data; |
| data.handle = handle; |
| data.role = role; |
| data.type = link_type; |
| data.link_error_cb = std::move(link_error_cb); |
| data.dispatcher = dispatcher; |
| |
| auto insert_res = links_.emplace(handle, std::move(data)); |
| return &insert_res.first->second; |
| } |
| |
| fbl::RefPtr<FakeChannel> FakeDomain::OpenFakeChannel( |
| LinkData* link, l2cap::ChannelId id, l2cap::ChannelId remote_id) { |
| auto chan = |
| fbl::AdoptRef(new FakeChannel(id, remote_id, link->handle, link->type)); |
| chan->SetLinkErrorCallback(link->link_error_cb.share(), link->dispatcher); |
| |
| if (chan_cb_) { |
| chan_cb_(chan); |
| } |
| |
| return chan; |
| } |
| |
| fbl::RefPtr<FakeChannel> FakeDomain::OpenFakeFixedChannel(LinkData* link, |
| l2cap::ChannelId id) { |
| return OpenFakeChannel(link, id, id); |
| } |
| |
| FakeDomain::LinkData& FakeDomain::FindLinkData(hci::ConnectionHandle handle) { |
| auto link_iter = links_.find(handle); |
| ZX_DEBUG_ASSERT_MSG(link_iter != links_.end(), |
| "fake link not found (handle: %#.4x)", handle); |
| return link_iter->second; |
| } |
| |
| } // namespace testing |
| } // namespace data |
| } // namespace btlib |