| // Copyright 2023 The Pigweed Authors |
| // |
| // 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 |
| // |
| // https://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 "pw_bluetooth_sapphire/internal/host/gap/bredr_connection.h" |
| |
| #include <utility> |
| |
| namespace bt::gap { |
| |
| namespace { |
| |
| const char* const kInspectPeerIdPropertyName = "peer_id"; |
| const char* const kInspectPairingStateNodeName = "pairing_state"; |
| |
| } // namespace |
| |
| BrEdrConnection::BrEdrConnection(Peer::WeakPtr peer, |
| std::unique_ptr<hci::BrEdrConnection> link, |
| fit::closure send_auth_request_cb, |
| fit::callback<void()> disconnect_cb, |
| fit::closure on_peer_disconnect_cb, |
| l2cap::ChannelManager* l2cap, |
| hci::Transport::WeakPtr transport, |
| std::optional<Request> request, |
| pw::async::Dispatcher& dispatcher) |
| : peer_id_(peer->identifier()), |
| peer_(std::move(peer)), |
| link_(std::move(link)), |
| request_(std::move(request)), |
| pairing_state_(std::make_unique<PairingState>( |
| peer_, |
| link_.get(), |
| request_ && request_->AwaitingOutgoing(), |
| std::move(send_auth_request_cb), |
| fit::bind_member<&BrEdrConnection::OnPairingStateStatus>(this))), |
| l2cap_(l2cap), |
| sco_manager_( |
| std::make_unique<sco::ScoConnectionManager>(peer_id_, |
| link_->handle(), |
| link_->peer_address(), |
| link_->local_address(), |
| transport)), |
| interrogator_(new BrEdrInterrogator( |
| peer_, link_->handle(), transport->command_channel()->AsWeakPtr())), |
| create_time_(dispatcher.now()), |
| disconnect_cb_(std::move(disconnect_cb)), |
| peer_init_token_(request_->take_peer_init_token()), |
| peer_conn_token_(peer_->MutBrEdr().RegisterConnection()), |
| dispatcher_(dispatcher) { |
| link_->set_peer_disconnect_callback( |
| [peer_disconnect_cb = std::move(on_peer_disconnect_cb)]( |
| const auto& conn, auto /*reason*/) { peer_disconnect_cb(); }); |
| } |
| |
| BrEdrConnection::~BrEdrConnection() { |
| if (auto request = std::exchange(request_, std::nullopt); |
| request.has_value()) { |
| // Connection never completed so signal the requester(s). |
| request->NotifyCallbacks(ToResult(HostError::kNotSupported), |
| [] { return nullptr; }); |
| } |
| |
| sco_manager_.reset(); |
| pairing_state_.reset(); |
| link_.reset(); |
| } |
| |
| void BrEdrConnection::Interrogate(BrEdrInterrogator::ResultCallback callback) { |
| interrogator_->Start(std::move(callback)); |
| } |
| |
| void BrEdrConnection::OnInterrogationComplete() { |
| BT_ASSERT_MSG(!interrogation_complete(), |
| "%s on a connection that's already been interrogated", |
| __FUNCTION__); |
| |
| // Fulfill and clear request so that the dtor does not signal requester(s) |
| // with errors. |
| if (auto request = std::exchange(request_, std::nullopt); |
| request.has_value()) { |
| request->NotifyCallbacks(fit::ok(), [this] { return this; }); |
| } |
| } |
| |
| void BrEdrConnection::AddRequestCallback( |
| BrEdrConnection::Request::OnComplete cb) { |
| if (!request_.has_value()) { |
| cb(fit::ok(), this); |
| return; |
| } |
| |
| BT_ASSERT(request_); |
| request_->AddCallback(std::move(cb)); |
| } |
| |
| void BrEdrConnection::OpenL2capChannel(l2cap::Psm psm, |
| l2cap::ChannelParameters params, |
| l2cap::ChannelCallback cb) { |
| if (!interrogation_complete()) { |
| // Connection is not yet ready for L2CAP; return a null channel. |
| bt_log(INFO, |
| "gap-bredr", |
| "connection not ready; canceling connect to PSM %.4x (peer: %s)", |
| psm, |
| bt_str(peer_id_)); |
| cb(l2cap::Channel::WeakPtr()); |
| return; |
| } |
| |
| bt_log(DEBUG, |
| "gap-bredr", |
| "opening l2cap channel on psm %#.4x (peer: %s)", |
| psm, |
| bt_str(peer_id_)); |
| l2cap_->OpenL2capChannel(link().handle(), psm, params, std::move(cb)); |
| } |
| |
| BrEdrConnection::ScoRequestHandle BrEdrConnection::OpenScoConnection( |
| bt::StaticPacket< |
| pw::bluetooth::emboss::SynchronousConnectionParametersWriter> |
| parameters, |
| sco::ScoConnectionManager::OpenConnectionCallback callback) { |
| return sco_manager_->OpenConnection(std::move(parameters), |
| std::move(callback)); |
| } |
| |
| BrEdrConnection::ScoRequestHandle BrEdrConnection::AcceptScoConnection( |
| std::vector<bt::StaticPacket< |
| pw::bluetooth::emboss::SynchronousConnectionParametersWriter>> |
| parameters, |
| sco::ScoConnectionManager::AcceptConnectionCallback callback) { |
| return sco_manager_->AcceptConnection(std::move(parameters), |
| std::move(callback)); |
| } |
| |
| void BrEdrConnection::AttachInspect(inspect::Node& parent, std::string name) { |
| inspect_node_ = parent.CreateChild(name); |
| inspect_properties_.peer_id = inspect_node_.CreateString( |
| kInspectPeerIdPropertyName, peer_id_.ToString()); |
| |
| pairing_state_->AttachInspect(inspect_node_, kInspectPairingStateNodeName); |
| } |
| |
| void BrEdrConnection::OnPairingStateStatus(hci_spec::ConnectionHandle handle, |
| hci::Result<> status) { |
| if (bt_is_error(status, |
| DEBUG, |
| "gap-bredr", |
| "PairingState error status, disconnecting (peer id: %s)", |
| bt_str(peer_id_))) { |
| if (disconnect_cb_) { |
| disconnect_cb_(); |
| } |
| return; |
| } |
| |
| // Once pairing succeeds for the first time, the transition from Initializing |
| // -> Connected can happen. |
| peer_init_token_.reset(); |
| } |
| |
| pw::chrono::SystemClock::duration BrEdrConnection::duration() const { |
| return dispatcher_.now() - create_time_; |
| } |
| |
| } // namespace bt::gap |