| // 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 "bredr_dynamic_channel.h" |
| |
| #include <endian.h> |
| #include <zircon/assert.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/common/log.h" |
| |
| namespace bt { |
| namespace l2cap { |
| namespace internal { |
| |
| BrEdrDynamicChannelRegistry::BrEdrDynamicChannelRegistry( |
| SignalingChannelInterface* sig, DynamicChannelCallback close_cb, |
| ServiceRequestCallback service_request_cb) |
| : DynamicChannelRegistry(kLastACLDynamicChannelId, std::move(close_cb), |
| std::move(service_request_cb)), |
| sig_(sig) { |
| ZX_DEBUG_ASSERT(sig_); |
| BrEdrCommandHandler cmd_handler(sig_); |
| cmd_handler.ServeConnectionRequest( |
| fit::bind_member(this, &BrEdrDynamicChannelRegistry::OnRxConnReq)); |
| cmd_handler.ServeConfigurationRequest( |
| fit::bind_member(this, &BrEdrDynamicChannelRegistry::OnRxConfigReq)); |
| cmd_handler.ServeDisconnectionRequest( |
| fit::bind_member(this, &BrEdrDynamicChannelRegistry::OnRxDisconReq)); |
| cmd_handler.ServeInformationRequest( |
| fit::bind_member(this, &BrEdrDynamicChannelRegistry::OnRxInfoReq)); |
| } |
| |
| DynamicChannelPtr BrEdrDynamicChannelRegistry::MakeOutbound( |
| PSM psm, ChannelId local_cid) { |
| return BrEdrDynamicChannel::MakeOutbound(this, sig_, psm, local_cid); |
| } |
| |
| DynamicChannelPtr BrEdrDynamicChannelRegistry::MakeInbound( |
| PSM psm, ChannelId local_cid, ChannelId remote_cid) { |
| return BrEdrDynamicChannel::MakeInbound(this, sig_, psm, local_cid, |
| remote_cid); |
| } |
| |
| void BrEdrDynamicChannelRegistry::OnRxConnReq( |
| PSM psm, ChannelId remote_cid, |
| BrEdrCommandHandler::ConnectionResponder* responder) { |
| bt_log(SPEW, "l2cap-bredr", |
| "Got Connection Request for PSM %#.4x from channel %#.4x", psm, |
| remote_cid); |
| |
| // TODO(NET-1320): Check if remote ID is already in use and if so, respond |
| // with kSourceCIDAlreadyAllocated |
| |
| ChannelId local_cid = FindAvailableChannelId(); |
| if (local_cid == kInvalidChannelId) { |
| bt_log(TRACE, "l2cap-bredr", |
| "Out of IDs; rejecting connection for PSM %#.4x from channel %#.4x", |
| psm, remote_cid); |
| responder->Send(kInvalidChannelId, ConnectionResult::kNoResources, |
| ConnectionStatus::kNoInfoAvailable); |
| return; |
| } |
| |
| auto dyn_chan = RequestService(psm, local_cid, remote_cid); |
| if (!dyn_chan) { |
| bt_log(TRACE, "l2cap-bredr", |
| "Rejecting connection for unsupported PSM %#.4x from channel %#.4x", |
| psm, remote_cid); |
| responder->Send(kInvalidChannelId, ConnectionResult::kPSMNotSupported, |
| ConnectionStatus::kNoInfoAvailable); |
| return; |
| } |
| |
| static_cast<BrEdrDynamicChannel*>(dyn_chan)->CompleteInboundConnection( |
| responder); |
| } |
| |
| void BrEdrDynamicChannelRegistry::OnRxConfigReq( |
| ChannelId local_cid, uint16_t flags, const ByteBuffer& options, |
| BrEdrCommandHandler::ConfigurationResponder* responder) { |
| auto channel = static_cast<BrEdrDynamicChannel*>(FindChannel(local_cid)); |
| if (channel == nullptr) { |
| bt_log(WARN, "l2cap-bredr", "ID %#.4x not found for Configuration Request", |
| local_cid); |
| responder->RejectInvalidChannelId(); |
| return; |
| } |
| |
| channel->OnRxConfigReq(flags, options, responder); |
| } |
| |
| void BrEdrDynamicChannelRegistry::OnRxDisconReq( |
| ChannelId local_cid, ChannelId remote_cid, |
| BrEdrCommandHandler::DisconnectionResponder* responder) { |
| auto channel = static_cast<BrEdrDynamicChannel*>(FindChannel(local_cid)); |
| if (channel == nullptr || channel->remote_cid() != remote_cid) { |
| bt_log(WARN, "l2cap-bredr", |
| "ID %#.4x not found for Disconnection Request (remote ID %#.4x)", |
| local_cid, remote_cid); |
| responder->RejectInvalidChannelId(); |
| return; |
| } |
| |
| channel->OnRxDisconReq(responder); |
| } |
| |
| void BrEdrDynamicChannelRegistry::OnRxInfoReq( |
| InformationType type, |
| BrEdrCommandHandler::InformationResponder* responder) { |
| bt_log(SPEW, "l2cap-bredr", "Got Information Request for type %#.4hx", type); |
| |
| // TODO(NET-1074): The responses here will likely remain hardcoded magics, but |
| // maybe they should live elsewhere. |
| switch (type) { |
| case InformationType::kConnectionlessMTU: { |
| responder->SendNotSupported(); |
| break; |
| } |
| |
| case InformationType::kExtendedFeaturesSupported: { |
| const ExtendedFeatures extended_features = |
| kExtendedFeaturesBitFixedChannels; |
| |
| // Express support for the Fixed Channel Supported feature |
| responder->SendExtendedFeaturesSupported(extended_features); |
| break; |
| } |
| |
| case InformationType::kFixedChannelsSupported: { |
| const FixedChannelsSupported channels_supported = |
| kFixedChannelsSupportedBitSignaling; |
| |
| // Express support for the ACL-U Signaling Channel (as required) |
| // TODO(NET-1074): Set the bit for SM's fixed channel |
| responder->SendFixedChannelsSupported(channels_supported); |
| break; |
| } |
| |
| default: |
| responder->RejectNotUnderstood(); |
| bt_log(TRACE, "l2cap-bredr", "Rejecting Information Request type %#.4hx", |
| type); |
| } |
| } |
| |
| BrEdrDynamicChannelPtr BrEdrDynamicChannel::MakeOutbound( |
| DynamicChannelRegistry* registry, |
| SignalingChannelInterface* signaling_channel, PSM psm, |
| ChannelId local_cid) { |
| return std::unique_ptr<BrEdrDynamicChannel>(new BrEdrDynamicChannel( |
| registry, signaling_channel, psm, local_cid, kInvalidChannelId)); |
| } |
| |
| BrEdrDynamicChannelPtr BrEdrDynamicChannel::MakeInbound( |
| DynamicChannelRegistry* registry, |
| SignalingChannelInterface* signaling_channel, PSM psm, ChannelId local_cid, |
| ChannelId remote_cid) { |
| auto channel = std::unique_ptr<BrEdrDynamicChannel>(new BrEdrDynamicChannel( |
| registry, signaling_channel, psm, local_cid, remote_cid)); |
| channel->state_ |= kConnRequested; |
| return channel; |
| } |
| |
| void BrEdrDynamicChannel::Open(fit::closure open_result_cb) { |
| open_result_cb_ = std::move(open_result_cb); |
| |
| if (state_ & kConnRequested) { |
| return; |
| } |
| |
| BrEdrCommandHandler cmd_handler(signaling_channel_); |
| if (!cmd_handler.SendConnectionRequest( |
| psm(), local_cid(), |
| fit::bind_member(this, &BrEdrDynamicChannel::OnRxConnRsp))) { |
| bt_log(ERROR, "l2cap-bredr", |
| "Channel %#.4x: Failed to send Connection Request", local_cid()); |
| PassOpenError(); |
| return; |
| } |
| |
| bt_log(SPEW, "l2cap-bredr", "Channel %#.4x: Sent Connection Request", |
| local_cid()); |
| |
| state_ |= kConnRequested; |
| } |
| |
| void BrEdrDynamicChannel::Disconnect() { |
| ZX_DEBUG_ASSERT((state_ & kDisconnected) == 0); |
| |
| state_ |= kDisconnected; |
| |
| // Don't send disconnect request if the peer never responded (also can't, |
| // because we don't have their end's ID). |
| if (remote_cid() == kInvalidChannelId) { |
| return; |
| } |
| |
| // This response handler can't hold references to this object, which is about |
| // to be destroyed. |
| // |
| // TODO(NET-1373): Destroying this channel and allowing this channel ID to be |
| // recycled should wait until either this response is received or RTX timeout. |
| // For now, don't wait for the response at all to avoid a hang on non- |
| // response. |
| auto on_discon_rsp = |
| [local_cid = local_cid(), remote_cid = remote_cid()]( |
| const BrEdrCommandHandler::DisconnectionResponse& rsp) { |
| if (rsp.local_cid() != local_cid || rsp.remote_cid() != remote_cid) { |
| bt_log(WARN, "l2cap-bredr", |
| "Channel %#.4x: Got Disconnection Response with ID %#.4x/" |
| "remote ID %#.4x on channel with remote ID %#.4x", |
| local_cid, rsp.local_cid(), rsp.remote_cid(), remote_cid); |
| } else { |
| bt_log(SPEW, "l2cap-bredr", |
| "Channel %#.4x: Got Disconnection Response", local_cid); |
| } |
| return false; |
| }; |
| |
| BrEdrCommandHandler cmd_handler(signaling_channel_); |
| if (!cmd_handler.SendDisconnectionRequest(remote_cid(), local_cid(), |
| std::move(on_discon_rsp))) { |
| bt_log(ERROR, "l2cap-bredr", |
| "Channel %#.4x: Failed to send Disconnection Request", local_cid()); |
| return; |
| } |
| |
| bt_log(SPEW, "l2cap-bredr", "Channel %#.4x: Sent Disconnection Request", |
| local_cid()); |
| } |
| |
| bool BrEdrDynamicChannel::IsConnected() const { |
| // Remote-initiated channels have remote_cid_ already set. |
| return (state_ & kConnRequested) && (state_ & kConnResponded) && |
| (remote_cid() != kInvalidChannelId) && !(state_ & kDisconnected); |
| } |
| |
| bool BrEdrDynamicChannel::IsOpen() const { |
| return IsConnected() && (state_ & kLocalConfigAccepted) && |
| (state_ & kRemoteConfigAccepted); |
| } |
| |
| void BrEdrDynamicChannel::OnRxConfigReq( |
| uint16_t flags, const ByteBuffer& options, |
| BrEdrCommandHandler::ConfigurationResponder* responder) { |
| bt_log(SPEW, "l2cap-bredr", "Channel %#.4x: Got Configuration Request", |
| local_cid()); |
| |
| if (!IsConnected()) { |
| bt_log(WARN, "l2cap-bredr", |
| "Channel %#.4x: Unexpected Configuration Request, state %x", |
| local_cid(), state_); |
| return; |
| } |
| |
| if (state_ & kRemoteConfigReceived) { |
| bt_log(TRACE, "l2cap-bredr", "Channel %#.4x: Reconfiguring, state %x", |
| local_cid(), state_); |
| } |
| |
| state_ |= kRemoteConfigReceived; |
| |
| // TODO(NET-1084): Defer accepting config req using a Pending response |
| state_ |= kRemoteConfigAccepted; |
| responder->Send(remote_cid(), 0x0000, ConfigurationResult::kSuccess, |
| BufferView()); |
| |
| bt_log(SPEW, "l2cap-bredr", "Channel %#.4x: Sent Configuration Response", |
| local_cid()); |
| |
| if (IsOpen()) { |
| set_opened(); |
| PassOpenResult(); |
| } |
| } |
| |
| void BrEdrDynamicChannel::OnRxDisconReq( |
| BrEdrCommandHandler::DisconnectionResponder* responder) { |
| bt_log(SPEW, "l2cap-bredr", "Channel %#.4x: Got Disconnection Request", |
| local_cid()); |
| |
| // Unconnected channels only exist if they are waiting for a Connection |
| // Response from the peer or are disconnected yet undestroyed for some reason. |
| // Getting a Disconnection Request implies some error condition or misbehavior |
| // but the reaction should still be to terminate this channel. |
| if (!IsConnected()) { |
| bt_log(WARN, "l2cap-bredr", |
| "Channel %#.4x: Unexpected Disconnection Request", local_cid()); |
| } |
| |
| state_ |= kDisconnected; |
| responder->Send(); |
| if (opened()) { |
| OnDisconnected(); |
| } else { |
| PassOpenError(); |
| } |
| } |
| |
| void BrEdrDynamicChannel::CompleteInboundConnection( |
| BrEdrCommandHandler::ConnectionResponder* responder) { |
| bt_log(TRACE, "l2cap-bredr", |
| "Channel %#.4x: connected for PSM %#.4x from remote channel %#.4x", |
| local_cid(), psm(), remote_cid()); |
| |
| responder->Send(local_cid(), ConnectionResult::kSuccess, |
| ConnectionStatus::kNoInfoAvailable); |
| bt_log(SPEW, "l2cap-bredr", "Channel %#.4x: Sent Connection Response", |
| local_cid()); |
| state_ |= kConnResponded; |
| TrySendLocalConfig(); |
| } |
| |
| BrEdrDynamicChannel::BrEdrDynamicChannel( |
| DynamicChannelRegistry* registry, |
| SignalingChannelInterface* signaling_channel, PSM psm, ChannelId local_cid, |
| ChannelId remote_cid) |
| : DynamicChannel(registry, psm, local_cid, remote_cid), |
| signaling_channel_(signaling_channel), |
| state_(0u), |
| weak_ptr_factory_(this) { |
| ZX_DEBUG_ASSERT(signaling_channel_); |
| ZX_DEBUG_ASSERT(local_cid != kInvalidChannelId); |
| } |
| |
| void BrEdrDynamicChannel::PassOpenResult() { |
| if (open_result_cb_) { |
| // Guard against use-after-free if this object's owner destroys it while |
| // running |open_result_cb_|. |
| auto cb = std::move(open_result_cb_); |
| cb(); |
| } |
| } |
| |
| // This only checks that the channel had failed to open before passing to the |
| // client. The channel may still be connected, in case it's useful to perform |
| // channel configuration at this point. |
| void BrEdrDynamicChannel::PassOpenError() { |
| ZX_ASSERT(!IsOpen()); |
| PassOpenResult(); |
| } |
| |
| void BrEdrDynamicChannel::TrySendLocalConfig() { |
| if (state_ & kLocalConfigSent) { |
| return; |
| } |
| |
| BrEdrCommandHandler cmd_handler(signaling_channel_); |
| if (!cmd_handler.SendConfigurationRequest( |
| remote_cid(), 0, BufferView(), |
| [self = weak_ptr_factory_.GetWeakPtr()](auto& rsp) { |
| if (self) { |
| return self->OnRxConfigRsp(rsp); |
| } |
| return false; |
| })) { |
| bt_log(ERROR, "l2cap-bredr", |
| "Channel %#.4x: Failed to send Configuration Request", local_cid()); |
| PassOpenError(); |
| return; |
| } |
| |
| bt_log(SPEW, "l2cap-bredr", "Channel %#.4x: Sent Configuration Request", |
| local_cid()); |
| |
| state_ |= kLocalConfigSent; |
| } |
| |
| bool BrEdrDynamicChannel::OnRxConnRsp( |
| const BrEdrCommandHandler::ConnectionResponse& rsp) { |
| if (rsp.status() == BrEdrCommandHandler::Status::kReject) { |
| bt_log(ERROR, "l2cap-bredr", |
| "Channel %#.4x: Connection Request rejected reason %#.4hx", |
| local_cid(), rsp.reject_reason()); |
| PassOpenError(); |
| return false; |
| } |
| |
| if (rsp.local_cid() != local_cid()) { |
| bt_log( |
| ERROR, "l2cap-bredr", |
| "Channel %#.4x: Got Connection Response for another channel ID %#.4x", |
| local_cid(), rsp.local_cid()); |
| PassOpenError(); |
| return false; |
| } |
| |
| if ((state_ & kConnResponded) || !(state_ & kConnRequested)) { |
| bt_log(ERROR, "l2cap-bredr", |
| "Channel %#.4x: Unexpected Connection Response, state %#x", |
| local_cid(), state_); |
| PassOpenError(); |
| return false; |
| } |
| |
| if (rsp.result() == ConnectionResult::kPending) { |
| bt_log(SPEW, "l2cap-bredr", |
| "Channel %#.4x: Remote is pending open, status %#.4hx", local_cid(), |
| rsp.conn_status()); |
| |
| // If the remote provides a channel ID, then we store it. It can be used for |
| // disconnection from this point forward. |
| if (rsp.remote_cid() != kInvalidChannelId) { |
| set_remote_cid(rsp.remote_cid()); |
| bt_log(SPEW, "l2cap-bredr", "Channel %#.4x: Got remote channel ID %#.4x", |
| local_cid(), remote_cid()); |
| } |
| return true; // Final connection result still expected |
| } |
| |
| if (rsp.result() != ConnectionResult::kSuccess) { |
| bt_log(ERROR, "l2cap-bredr", |
| "Channel %#.4x: Unsuccessful Connection Response result %#.4hx, " |
| "status %#.4x", |
| local_cid(), rsp.result(), rsp.status()); |
| PassOpenError(); |
| return false; |
| } |
| |
| if (rsp.remote_cid() < kFirstDynamicChannelId) { |
| bt_log(ERROR, "l2cap-bredr", |
| "Channel %#.4x: received Connection Response with invalid channel " |
| "ID %#.4x, disconnecting", |
| local_cid(), remote_cid()); |
| |
| // This results in sending a Disconnection Request for non-zero remote IDs, |
| // which is probably what we want because there's no other way to send back |
| // a failure in this case. |
| PassOpenError(); |
| return false; |
| } |
| |
| // TODO(xow): To be stricter, we can disconnect if the remote ID changes on us |
| // during connection like this, but not sure if that would be beneficial. |
| if (remote_cid() != kInvalidChannelId && remote_cid() != rsp.remote_cid()) { |
| bt_log(WARN, "l2cap-bredr", |
| "Channel %#.4x: using new remote ID %#.4x after previous Connection " |
| "Response provided %#.4x", |
| local_cid(), rsp.remote_cid(), remote_cid()); |
| } |
| |
| state_ |= kConnResponded; |
| set_remote_cid(rsp.remote_cid()); |
| |
| bt_log(SPEW, "l2cap-bredr", "Channel %#.4x: Got remote channel ID %#.4x", |
| local_cid(), remote_cid()); |
| |
| TrySendLocalConfig(); |
| return false; |
| } |
| |
| bool BrEdrDynamicChannel::OnRxConfigRsp( |
| const BrEdrCommandHandler::ConfigurationResponse& rsp) { |
| if (rsp.status() == BrEdrCommandHandler::Status::kReject) { |
| bt_log(ERROR, "l2cap-bredr", |
| "Channel %#.4x: Configuration Request rejected, reason %#.4hx, " |
| "disconnecting", |
| local_cid(), rsp.reject_reason()); |
| |
| // Configuration Request being rejected is fatal because the remote is not |
| // trying to negotiate parameters (any more). |
| PassOpenError(); |
| return false; |
| } |
| |
| if (rsp.result() == ConfigurationResult::kPending) { |
| bt_log(SPEW, "l2cap-bredr", "Channel %#.4x: remote pending config", |
| local_cid()); |
| return true; |
| } |
| |
| if (rsp.result() != ConfigurationResult::kSuccess) { |
| bt_log(ERROR, "l2cap-bredr", |
| "Channel %#.4x: unsuccessful config reason %#.4hx", local_cid(), |
| rsp.result()); |
| PassOpenError(); |
| return false; |
| } |
| |
| if (rsp.local_cid() != local_cid()) { |
| bt_log(ERROR, "l2cap-bredr", |
| "Channel %#.4x: dropping Configuration Response for %#.4x", |
| local_cid(), rsp.local_cid()); |
| PassOpenError(); |
| return false; |
| } |
| |
| state_ |= kLocalConfigAccepted; |
| |
| bt_log(SPEW, "l2cap-bredr", "Channel %#.4x: Got Configuration Response", |
| local_cid()); |
| |
| if (IsOpen()) { |
| set_opened(); |
| PassOpenResult(); |
| } |
| |
| return false; |
| } |
| |
| } // namespace internal |
| } // namespace l2cap |
| } // namespace bt |