| // 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" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/channel_configuration.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/l2cap_defs.h" |
| |
| namespace bt::l2cap::internal { |
| namespace { |
| |
| ChannelConfiguration::RetransmissionAndFlowControlOption WriteRfcOutboundTimeouts( |
| ChannelConfiguration::RetransmissionAndFlowControlOption rfc_option) { |
| rfc_option.set_rtx_timeout(kErtmReceiverReadyPollTimerDuration.to_msecs()); |
| rfc_option.set_monitor_timeout(kErtmMonitorTimerDuration.to_msecs()); |
| return rfc_option; |
| } |
| |
| constexpr uint16_t kBrEdrDynamicChannelCount = |
| kLastACLDynamicChannelId - kFirstDynamicChannelId + 1; |
| |
| } // namespace |
| |
| BrEdrDynamicChannelRegistry::BrEdrDynamicChannelRegistry(SignalingChannelInterface* sig, |
| DynamicChannelCallback close_cb, |
| ServiceRequestCallback service_request_cb, |
| bool random_channel_ids) |
| : DynamicChannelRegistry(kBrEdrDynamicChannelCount, std::move(close_cb), |
| std::move(service_request_cb), random_channel_ids), |
| state_(0u), |
| 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)); |
| |
| SendInformationRequests(); |
| } |
| |
| DynamicChannelPtr BrEdrDynamicChannelRegistry::MakeOutbound(PSM psm, ChannelId local_cid, |
| ChannelParameters params) { |
| return BrEdrDynamicChannel::MakeOutbound(this, sig_, psm, local_cid, params, PeerSupportsERTM()); |
| } |
| |
| DynamicChannelPtr BrEdrDynamicChannelRegistry::MakeInbound(PSM psm, ChannelId local_cid, |
| ChannelId remote_cid, |
| ChannelParameters params) { |
| return BrEdrDynamicChannel::MakeInbound(this, sig_, psm, local_cid, remote_cid, params, |
| PeerSupportsERTM()); |
| } |
| |
| void BrEdrDynamicChannelRegistry::OnRxConnReq(PSM psm, ChannelId remote_cid, |
| BrEdrCommandHandler::ConnectionResponder* responder) { |
| bt_log(TRACE, "l2cap-bredr", "Got Connection Request for PSM %#.4x from channel %#.4x", psm, |
| remote_cid); |
| |
| if (remote_cid == kInvalidChannelId) { |
| bt_log(DEBUG, "l2cap-bredr", |
| "Invalid source CID; rejecting connection for PSM %#.4x from channel %#.4x", psm, |
| remote_cid); |
| responder->Send(kInvalidChannelId, ConnectionResult::kInvalidSourceCID, |
| ConnectionStatus::kNoInfoAvailable); |
| return; |
| } |
| |
| if (FindChannelByRemoteId(remote_cid) != nullptr) { |
| bt_log(DEBUG, "l2cap-bredr", |
| "Remote CID already in use; rejecting connection for PSM %#.4x from channel %#.4x", psm, |
| remote_cid); |
| responder->Send(kInvalidChannelId, ConnectionResult::kSourceCIDAlreadyAllocated, |
| ConnectionStatus::kNoInfoAvailable); |
| return; |
| } |
| |
| ChannelId local_cid = FindAvailableChannelId(); |
| if (local_cid == kInvalidChannelId) { |
| bt_log(DEBUG, "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(DEBUG, "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, ChannelConfiguration config, |
| BrEdrCommandHandler::ConfigurationResponder* responder) { |
| auto channel = static_cast<BrEdrDynamicChannel*>(FindChannelByLocalId(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, std::move(config), responder); |
| } |
| |
| void BrEdrDynamicChannelRegistry::OnRxDisconReq( |
| ChannelId local_cid, ChannelId remote_cid, |
| BrEdrCommandHandler::DisconnectionResponder* responder) { |
| auto channel = static_cast<BrEdrDynamicChannel*>(FindChannelByLocalId(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(TRACE, "l2cap-bredr", "Got Information Request for type %#.4hx", type); |
| |
| // TODO(fxbug.dev/933): 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 | kExtendedFeaturesBitEnhancedRetransmission; |
| |
| // 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(fxbug.dev/933): Set the bit for SM's fixed channel |
| responder->SendFixedChannelsSupported(channels_supported); |
| break; |
| } |
| |
| default: |
| responder->RejectNotUnderstood(); |
| bt_log(DEBUG, "l2cap-bredr", "Rejecting Information Request type %#.4hx", type); |
| } |
| } |
| |
| void BrEdrDynamicChannelRegistry::OnRxExtendedFeaturesInfoRsp( |
| const BrEdrCommandHandler::InformationResponse& rsp) { |
| if (rsp.status() == BrEdrCommandHandler::Status::kReject) { |
| bt_log(ERROR, "l2cap-bredr", |
| "Extended Features Information Request rejected, reason %#.4hx, disconnecting", |
| rsp.reject_reason()); |
| return; |
| } |
| |
| if (rsp.result() != InformationResult::kSuccess) { |
| bt_log(DEBUG, "l2cap-bredr", |
| "Extended Features Information Response failure result (result: %#.4hx)", |
| static_cast<uint16_t>(rsp.result())); |
| // Treat failure result as if feature mask indicated no ERTM support so that configuration can |
| // fall back to basic mode. |
| ForEach([](DynamicChannel* chan) { |
| static_cast<BrEdrDynamicChannel*>(chan)->SetEnhancedRetransmissionSupport(false); |
| }); |
| return; |
| } |
| |
| if (rsp.type() != InformationType::kExtendedFeaturesSupported) { |
| bt_log(ERROR, "l2cap-bredr", |
| "Incorrect extended features information response type (type: %#.4hx)", rsp.type()); |
| return; |
| } |
| |
| if ((state_ & kExtendedFeaturesReceived) || !(state_ & kExtendedFeaturesSent)) { |
| bt_log(ERROR, "l2cap-bredr", "Unexpected extended features information response (state: %#x)", |
| state_); |
| return; |
| } |
| |
| bt_log(TRACE, "l2cap-bredr", |
| "Received Extended Features Information Response (feature mask: %#.4x)", |
| rsp.extended_features()); |
| |
| state_ |= kExtendedFeaturesReceived; |
| |
| extended_features_ = rsp.extended_features(); |
| |
| // Notify all channels created before extended features received. |
| bool ertm_support = *extended_features_ & kExtendedFeaturesBitEnhancedRetransmission; |
| ForEach([ertm_support](DynamicChannel* chan) { |
| static_cast<BrEdrDynamicChannel*>(chan)->SetEnhancedRetransmissionSupport(ertm_support); |
| }); |
| } |
| |
| void BrEdrDynamicChannelRegistry::SendInformationRequests() { |
| if (!(state_ & kExtendedFeaturesSent)) { |
| BrEdrCommandHandler cmd_handler(sig_); |
| if (!cmd_handler.SendInformationRequest( |
| InformationType::kExtendedFeaturesSupported, [self = GetWeakPtr()](auto& rsp) { |
| if (self) { |
| static_cast<BrEdrDynamicChannelRegistry*>(self.get()) |
| ->OnRxExtendedFeaturesInfoRsp(rsp); |
| } |
| })) { |
| bt_log(ERROR, "l2cap-bredr", "Failed to send Extended Features Information Request"); |
| return; |
| } |
| |
| bt_log(TRACE, "l2cap-bredr", "Sent Extended Features Information Request"); |
| |
| state_ |= kExtendedFeaturesSent; |
| } |
| } |
| |
| std::optional<bool> BrEdrDynamicChannelRegistry::PeerSupportsERTM() const { |
| if (!extended_features_) { |
| return std::nullopt; |
| } |
| return *extended_features_ & kExtendedFeaturesBitEnhancedRetransmission; |
| } |
| |
| BrEdrDynamicChannelPtr BrEdrDynamicChannel::MakeOutbound( |
| DynamicChannelRegistry* registry, SignalingChannelInterface* signaling_channel, PSM psm, |
| ChannelId local_cid, ChannelParameters params, std::optional<bool> peer_supports_ertm) { |
| return std::unique_ptr<BrEdrDynamicChannel>(new BrEdrDynamicChannel( |
| registry, signaling_channel, psm, local_cid, kInvalidChannelId, params, peer_supports_ertm)); |
| } |
| |
| BrEdrDynamicChannelPtr BrEdrDynamicChannel::MakeInbound( |
| DynamicChannelRegistry* registry, SignalingChannelInterface* signaling_channel, PSM psm, |
| ChannelId local_cid, ChannelId remote_cid, ChannelParameters params, |
| std::optional<bool> peer_supports_ertm) { |
| auto channel = std::unique_ptr<BrEdrDynamicChannel>(new BrEdrDynamicChannel( |
| registry, signaling_channel, psm, local_cid, remote_cid, params, peer_supports_ertm)); |
| 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; |
| } |
| |
| auto on_conn_rsp = |
| [self = weak_ptr_factory_.GetWeakPtr()](const BrEdrCommandHandler::ConnectionResponse& rsp) { |
| if (self) { |
| return self->OnRxConnRsp(rsp); |
| } |
| return BrEdrCommandHandler::ResponseHandlerAction::kCompleteOutboundTransaction; |
| }; |
| |
| auto on_conn_rsp_timeout = [this, self = weak_ptr_factory_.GetWeakPtr()] { |
| if (self) { |
| bt_log(WARN, "l2cap-bredr", "Channel %#.4x: Timed out waiting for Connection Response", |
| local_cid()); |
| PassOpenError(); |
| } |
| }; |
| |
| BrEdrCommandHandler cmd_handler(signaling_channel_, std::move(on_conn_rsp_timeout)); |
| if (!cmd_handler.SendConnectionRequest(psm(), local_cid(), std::move(on_conn_rsp))) { |
| bt_log(ERROR, "l2cap-bredr", "Channel %#.4x: Failed to send Connection Request", local_cid()); |
| PassOpenError(); |
| return; |
| } |
| |
| bt_log(TRACE, "l2cap-bredr", "Channel %#.4x: Sent Connection Request", local_cid()); |
| |
| state_ |= kConnRequested; |
| } |
| |
| void BrEdrDynamicChannel::Disconnect(DisconnectDoneCallback done_cb) { |
| ZX_ASSERT(done_cb); |
| if (!IsConnected()) { |
| done_cb(); |
| return; |
| } |
| |
| 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) { |
| done_cb(); |
| return; |
| } |
| |
| auto on_discon_rsp = |
| [local_cid = local_cid(), remote_cid = remote_cid(), self = weak_ptr_factory_.GetWeakPtr(), |
| done_cb = done_cb.share()](const BrEdrCommandHandler::DisconnectionResponse& rsp) mutable { |
| 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(TRACE, "l2cap-bredr", "Channel %#.4x: Got Disconnection Response", local_cid); |
| } |
| |
| if (self) { |
| done_cb(); |
| } |
| }; |
| |
| auto on_discon_rsp_timeout = [local_cid = local_cid(), self = weak_ptr_factory_.GetWeakPtr(), |
| done_cb = done_cb.share()]() mutable { |
| bt_log(WARN, "l2cap-bredr", |
| "Channel %#.4x: Timed out waiting for Disconnection Response; completing disconnection", |
| local_cid); |
| if (self) { |
| done_cb(); |
| } |
| }; |
| |
| BrEdrCommandHandler cmd_handler(signaling_channel_, std::move(on_discon_rsp_timeout)); |
| 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()); |
| done_cb(); |
| return; |
| } |
| |
| bt_log(TRACE, "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() && BothConfigsAccepted() && AcceptedChannelModesAreConsistent(); |
| } |
| |
| ChannelInfo BrEdrDynamicChannel::info() const { |
| ZX_ASSERT(local_config().retransmission_flow_control_option().has_value()); |
| ZX_ASSERT(local_config().mtu_option().has_value()); |
| |
| const auto max_rx_sdu_size = local_config().mtu_option()->mtu(); |
| const auto peer_mtu = remote_config().mtu_option()->mtu(); |
| const auto flush_timeout = parameters_.flush_timeout; |
| if (local_config().retransmission_flow_control_option()->mode() == ChannelMode::kBasic) { |
| const auto max_tx_sdu_size = peer_mtu; |
| return ChannelInfo::MakeBasicMode(max_rx_sdu_size, max_tx_sdu_size, psm(), flush_timeout); |
| } |
| const auto n_frames_in_tx_window = |
| remote_config().retransmission_flow_control_option()->tx_window_size(); |
| const auto max_transmissions = |
| remote_config().retransmission_flow_control_option()->max_transmit(); |
| const auto max_tx_pdu_payload_size = remote_config().retransmission_flow_control_option()->mps(); |
| const auto max_tx_sdu_size = std::min(peer_mtu, max_tx_pdu_payload_size); |
| if (max_tx_pdu_payload_size < peer_mtu) { |
| bt_log(DEBUG, "l2cap-bredr", |
| "Channel %#.4x: reporting MPS of %hu to service to avoid segmenting outbound SDUs, " |
| "which would otherwise be %hu according to MTU", |
| local_cid(), max_tx_sdu_size, peer_mtu); |
| } |
| auto info = ChannelInfo::MakeEnhancedRetransmissionMode( |
| max_rx_sdu_size, max_tx_sdu_size, n_frames_in_tx_window, max_transmissions, |
| max_tx_pdu_payload_size, psm(), flush_timeout); |
| return info; |
| } |
| |
| void BrEdrDynamicChannel::OnRxConfigReq(uint16_t flags, ChannelConfiguration config, |
| BrEdrCommandHandler::ConfigurationResponder* responder) { |
| bool continuation = flags & kConfigurationContinuation; |
| bt_log(TRACE, "l2cap-bredr", "Channel %#.4x: Got Configuration Request (C: %d, options: %s)", |
| local_cid(), continuation, bt_str(config)); |
| |
| if (!IsConnected()) { |
| bt_log(WARN, "l2cap-bredr", "Channel %#.4x: Unexpected Configuration Request, state %x", |
| local_cid(), state_); |
| return; |
| } |
| |
| // Always add options to accumulator, even if C = 0, for later code simplicity. |
| if (remote_config_accum_.has_value()) { |
| remote_config_accum_->Merge(std::move(config)); |
| } else { |
| // TODO(fxbug.dev/40053): if channel is being re-configured, merge with existing configuration |
| remote_config_accum_ = std::move(config); |
| } |
| |
| if (continuation) { |
| // keep responding with success until all options have been received (C flag is 0) |
| responder->Send(remote_cid(), kConfigurationContinuation, ConfigurationResult::kSuccess, |
| ChannelConfiguration::ConfigurationOptions()); |
| bt_log(TRACE, "l2cap-bredr", "Channel %#.4x: Sent Configuration Response (C: 1)", local_cid()); |
| return; |
| } |
| |
| auto req_config = std::exchange(remote_config_accum_, std::nullopt).value(); |
| |
| const auto req_mode = req_config.retransmission_flow_control_option() |
| ? req_config.retransmission_flow_control_option()->mode() |
| : ChannelMode::kBasic; |
| |
| // Record peer support for ERTM even if they haven't sent a Extended Features Mask. |
| if (req_mode == ChannelMode::kEnhancedRetransmission) { |
| SetEnhancedRetransmissionSupport(true); |
| } |
| |
| // Set default config options if not already in request. |
| if (!req_config.mtu_option()) { |
| req_config.set_mtu_option(ChannelConfiguration::MtuOption(kDefaultMTU)); |
| } |
| |
| if (state_ & kRemoteConfigReceived) { |
| // Disconnect if second configuraton request does not contain desired mode. |
| const auto local_mode = local_config_.retransmission_flow_control_option()->mode(); |
| if (req_mode != local_mode) { |
| bt_log(TRACE, "l2cap-bredr", |
| "Channel %#.4x: second configuration request doesn't have desired mode, " |
| "sending unacceptable parameters response and disconnecting (req mode: %#.2x, " |
| "desired: %#.2x)", |
| local_cid(), static_cast<uint8_t>(req_mode), static_cast<uint8_t>(local_mode)); |
| ChannelConfiguration::ConfigurationOptions options; |
| options.push_back(std::make_unique<ChannelConfiguration::RetransmissionAndFlowControlOption>( |
| *local_config().retransmission_flow_control_option())); |
| responder->Send(remote_cid(), 0x0000, ConfigurationResult::kUnacceptableParameters, |
| std::move(options)); |
| PassOpenError(); |
| return; |
| } |
| |
| bt_log(DEBUG, "l2cap-bredr", "Channel %#.4x: Reconfiguring, state %x", local_cid(), state_); |
| } |
| |
| state_ |= kRemoteConfigReceived; |
| |
| // Reject request if it contains unknown options. |
| // See Core Spec v5.1, Volume 3, Section 4.5: Configuration Options |
| if (!req_config.unknown_options().empty()) { |
| ChannelConfiguration::ConfigurationOptions unknown_options; |
| std::string unknown_string; |
| for (auto& option : req_config.unknown_options()) { |
| unknown_options.push_back(std::make_unique<ChannelConfiguration::UnknownOption>(option)); |
| unknown_string += std::string(" ") + option.ToString(); |
| } |
| |
| bt_log(DEBUG, "l2cap-bredr", |
| "Channel %#.4x: config request contained unknown options (options: %s)\n", local_cid(), |
| unknown_string.c_str()); |
| |
| responder->Send(remote_cid(), 0x0000, ConfigurationResult::kUnknownOptions, |
| std::move(unknown_options)); |
| return; |
| } |
| |
| auto unacceptable_config = CheckForUnacceptableConfigReqOptions(req_config); |
| auto unacceptable_options = unacceptable_config.Options(); |
| if (!unacceptable_options.empty()) { |
| responder->Send(remote_cid(), 0x0000, ConfigurationResult::kUnacceptableParameters, |
| std::move(unacceptable_options)); |
| bt_log(TRACE, "l2cap-bredr", |
| "Channel %#.4x: Sent unacceptable parameters configuration response (options: %s)", |
| local_cid(), bt_str(unacceptable_config)); |
| return; |
| } |
| |
| // TODO(fxbug.dev/1059): Defer accepting config req using a Pending response |
| state_ |= kRemoteConfigAccepted; |
| |
| ChannelConfiguration response_config; |
| |
| // Successful response should include actual MTU local device will use. This must be min(received |
| // MTU, local outgoing MTU capability). Currently, we accept any MTU. |
| // TODO(fxbug.dev/41376): determine the upper bound of what we are actually capable of sending |
| uint16_t actual_mtu = req_config.mtu_option()->mtu(); |
| response_config.set_mtu_option(ChannelConfiguration::MtuOption(actual_mtu)); |
| req_config.set_mtu_option(response_config.mtu_option()); |
| |
| if (req_mode == ChannelMode::kEnhancedRetransmission) { |
| auto outbound_rfc_option = |
| WriteRfcOutboundTimeouts(req_config.retransmission_flow_control_option().value()); |
| response_config.set_retransmission_flow_control_option(std::move(outbound_rfc_option)); |
| } else { |
| response_config.set_retransmission_flow_control_option( |
| req_config.retransmission_flow_control_option()); |
| } |
| |
| responder->Send(remote_cid(), 0x0000, ConfigurationResult::kSuccess, response_config.Options()); |
| |
| bt_log(TRACE, "l2cap-bredr", "Channel %#.4x: Sent Configuration Response (options: %s)", |
| local_cid(), bt_str(response_config)); |
| |
| // Save accepted options. |
| remote_config_.Merge(std::move(req_config)); |
| |
| if (!remote_config_.retransmission_flow_control_option()) { |
| remote_config_.set_retransmission_flow_control_option( |
| ChannelConfiguration::RetransmissionAndFlowControlOption::MakeBasicMode()); |
| } |
| |
| if (BothConfigsAccepted() && !AcceptedChannelModesAreConsistent()) { |
| bt_log(WARN, "l2cap-bredr", |
| "Channel %#.4x: inconsistent channel mode negotiation (local mode: %#.2x, remote " |
| "mode: %#.2x); falling back to Basic Mode", |
| local_cid(), |
| static_cast<uint8_t>(local_config().retransmission_flow_control_option()->mode()), |
| static_cast<uint8_t>(remote_config().retransmission_flow_control_option()->mode())); |
| |
| // The most applicable guidance for the case where the peer send conflicting modes is in Core |
| // Spec v5.0 Vol 3, Part A, Sec 5.4: "If the mode proposed by the remote device has a higher |
| // precedence (according to the state 1 precedence) then the algorithm will operate such that |
| // creation of a channel using the remote device's mode has higher priority than disconnecting |
| // the channel." |
| // |
| // Note also that, "In state 1, Basic L2CAP mode has the highest precedence and shall take |
| // precedence over Enhanced Retransmission mode..." |
| // |
| // So, if we are to continue the connection, it makes the most sense to use Basic Mode. |
| local_config_.set_retransmission_flow_control_option( |
| ChannelConfiguration::RetransmissionAndFlowControlOption::MakeBasicMode()); |
| remote_config_.set_retransmission_flow_control_option( |
| ChannelConfiguration::RetransmissionAndFlowControlOption::MakeBasicMode()); |
| PassOpenResult(); |
| return; |
| } |
| |
| if (IsOpen()) { |
| set_opened(); |
| PassOpenResult(); |
| } |
| } |
| |
| void BrEdrDynamicChannel::OnRxDisconReq(BrEdrCommandHandler::DisconnectionResponder* responder) { |
| bt_log(TRACE, "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(DEBUG, "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(TRACE, "l2cap-bredr", "Channel %#.4x: Sent Connection Response", local_cid()); |
| state_ |= kConnResponded; |
| |
| UpdateLocalConfigForErtm(); |
| if (!IsWaitingForPeerErtmSupport()) { |
| TrySendLocalConfig(); |
| } |
| } |
| |
| BrEdrDynamicChannel::BrEdrDynamicChannel(DynamicChannelRegistry* registry, |
| SignalingChannelInterface* signaling_channel, PSM psm, |
| ChannelId local_cid, ChannelId remote_cid, |
| ChannelParameters params, |
| std::optional<bool> peer_supports_ertm) |
| : DynamicChannel(registry, psm, local_cid, remote_cid), |
| signaling_channel_(signaling_channel), |
| parameters_(params), |
| state_(0u), |
| peer_supports_ertm_(peer_supports_ertm), |
| weak_ptr_factory_(this) { |
| ZX_DEBUG_ASSERT(signaling_channel_); |
| ZX_DEBUG_ASSERT(local_cid != kInvalidChannelId); |
| |
| UpdateLocalConfigForErtm(); |
| } |
| |
| 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::UpdateLocalConfigForErtm() { |
| local_config_.set_mtu_option(ChannelConfiguration::MtuOption(CalculateLocalMtu())); |
| |
| if (ShouldRequestEnhancedRetransmission()) { |
| // Core Spec v5.0 Vol 3, Part A, Sec 8.6.2.1 "When configuring a channel over an ACL-U logical |
| // link the values sent in a Configuration Request packet for Retransmission timeout and Monitor |
| // timeout shall be 0." |
| auto option = |
| ChannelConfiguration::RetransmissionAndFlowControlOption::MakeEnhancedRetransmissionMode( |
| /*tx_window_size=*/kErtmMaxUnackedInboundFrames, |
| /*max_transmit=*/kErtmMaxInboundRetransmissions, /*rtx_timeout=*/0, |
| /*monitor_timeout=*/0, |
| /*mps=*/kMaxInboundPduPayloadSize); |
| local_config_.set_retransmission_flow_control_option(option); |
| } else { |
| local_config_.set_retransmission_flow_control_option( |
| ChannelConfiguration::RetransmissionAndFlowControlOption::MakeBasicMode()); |
| } |
| } |
| |
| uint16_t BrEdrDynamicChannel::CalculateLocalMtu() const { |
| const bool request_ertm = ShouldRequestEnhancedRetransmission(); |
| const auto kDefaultPreferredMtu = request_ertm ? kMaxInboundPduPayloadSize : kMaxMTU; |
| uint16_t mtu = parameters_.max_rx_sdu_size.value_or(kDefaultPreferredMtu); |
| if (mtu < kMinACLMTU) { |
| bt_log(WARN, "l2cap-bredr", |
| "Channel %#.4x: preferred MTU channel parameter below minimum allowed, using minimum " |
| "instead (mtu param: %#x, min mtu: %#x)", |
| local_cid(), mtu, kMinACLMTU); |
| mtu = kMinACLMTU; |
| } |
| if (request_ertm && mtu > kMaxInboundPduPayloadSize) { |
| bt_log(DEBUG, "l2cap-bredr", |
| "Channel %#.4x: preferred MTU channel parameter above MPS; using MPS instead to avoid " |
| "segmentation (mtu param: %#x, max pdu: %#x)", |
| local_cid(), mtu, kMaxInboundPduPayloadSize); |
| mtu = kMaxInboundPduPayloadSize; |
| } |
| return mtu; |
| } |
| |
| bool BrEdrDynamicChannel::ShouldRequestEnhancedRetransmission() const { |
| return parameters_.mode && *parameters_.mode == ChannelMode::kEnhancedRetransmission && |
| peer_supports_ertm_.value_or(false); |
| } |
| |
| bool BrEdrDynamicChannel::IsWaitingForPeerErtmSupport() { |
| const auto local_mode = parameters_.mode.value_or(ChannelMode::kBasic); |
| return !peer_supports_ertm_.has_value() && (local_mode != ChannelMode::kBasic); |
| } |
| |
| void BrEdrDynamicChannel::TrySendLocalConfig() { |
| if (state_ & kLocalConfigSent) { |
| return; |
| } |
| |
| ZX_ASSERT(!IsWaitingForPeerErtmSupport()); |
| |
| SendLocalConfig(); |
| } |
| |
| void BrEdrDynamicChannel::SendLocalConfig() { |
| auto on_config_rsp_timeout = [this, self = weak_ptr_factory_.GetWeakPtr()] { |
| if (self) { |
| bt_log(WARN, "l2cap-bredr", "Channel %#.4x: Timed out waiting for Configuration Response", |
| local_cid()); |
| PassOpenError(); |
| } |
| }; |
| |
| BrEdrCommandHandler cmd_handler(signaling_channel_, std::move(on_config_rsp_timeout)); |
| |
| auto request_config = local_config_; |
| |
| // Don't send Retransmission & Flow Control option for basic mode |
| if (request_config.retransmission_flow_control_option()->mode() == ChannelMode::kBasic) { |
| request_config.set_retransmission_flow_control_option(std::nullopt); |
| } |
| |
| if (!cmd_handler.SendConfigurationRequest( |
| remote_cid(), 0, request_config.Options(), |
| [self = weak_ptr_factory_.GetWeakPtr()](auto& rsp) { |
| if (self) { |
| return self->OnRxConfigRsp(rsp); |
| } |
| return ResponseHandlerAction::kCompleteOutboundTransaction; |
| })) { |
| bt_log(ERROR, "l2cap-bredr", "Channel %#.4x: Failed to send Configuration Request", |
| local_cid()); |
| PassOpenError(); |
| return; |
| } |
| |
| bt_log(TRACE, "l2cap-bredr", "Channel %#.4x: Sent Configuration Request (options: %s)", |
| local_cid(), bt_str(request_config)); |
| |
| state_ |= kLocalConfigSent; |
| } |
| |
| bool BrEdrDynamicChannel::BothConfigsAccepted() const { |
| return (state_ & kLocalConfigAccepted) && (state_ & kRemoteConfigAccepted); |
| } |
| |
| bool BrEdrDynamicChannel::AcceptedChannelModesAreConsistent() const { |
| ZX_ASSERT(BothConfigsAccepted()); |
| auto local_mode = local_config_.retransmission_flow_control_option()->mode(); |
| auto remote_mode = remote_config_.retransmission_flow_control_option()->mode(); |
| return local_mode == remote_mode; |
| } |
| |
| ChannelConfiguration BrEdrDynamicChannel::CheckForUnacceptableConfigReqOptions( |
| const ChannelConfiguration& config) const { |
| // TODO(fxbug.dev/40053): reject reconfiguring MTU if mode is Enhanced Retransmission or Streaming |
| // mode. |
| ChannelConfiguration unacceptable; |
| |
| // Reject MTUs below minimum size |
| if (config.mtu_option()->mtu() < kMinACLMTU) { |
| bt_log(DEBUG, "l2cap", |
| "Channel %#.4x: config request contains MTU below minimum (mtu: %hu, min: %hu)", |
| local_cid(), config.mtu_option()->mtu(), kMinACLMTU); |
| // Respond back with a proposed MTU value of the required minimum (Core Spec v5.1, Vol 3, Part |
| // A, Section 5.1: "It is implementation specific whether the local device continues the |
| // configuration process or disconnects the channel.") |
| unacceptable.set_mtu_option(ChannelConfiguration::MtuOption(kMinACLMTU)); |
| } |
| |
| const auto req_mode = config.retransmission_flow_control_option() |
| ? config.retransmission_flow_control_option()->mode() |
| : ChannelMode::kBasic; |
| const auto local_mode = local_config().retransmission_flow_control_option()->mode(); |
| switch (req_mode) { |
| case ChannelMode::kBasic: |
| // Local device must accept, as basic mode has highest precedence. |
| if (local_mode == ChannelMode::kEnhancedRetransmission) { |
| bt_log(DEBUG, "l2cap-bredr", |
| "Channel %#.4x: accepting peer basic mode configuration option when preferred mode " |
| "was ERTM", |
| local_cid()); |
| } |
| break; |
| case ChannelMode::kEnhancedRetransmission: |
| // Basic mode has highest precedence, so if local mode is basic, reject ERTM and send local |
| // mode. |
| if (local_mode == ChannelMode::kBasic) { |
| bt_log(DEBUG, "l2cap-bredr", |
| "Channel %#.4x: rejecting peer ERTM mode configuration option because preferred " |
| "mode is basic", |
| local_cid()); |
| unacceptable.set_retransmission_flow_control_option( |
| ChannelConfiguration::RetransmissionAndFlowControlOption::MakeBasicMode()); |
| break; |
| } |
| unacceptable.set_retransmission_flow_control_option(CheckForUnacceptableErtmOptions(config)); |
| break; |
| default: |
| bt_log(DEBUG, "l2cap-bredr", |
| "Channel %#.4x: rejecting unsupported retransmission and flow control configuration " |
| "option (mode: %#.2x)", |
| local_cid(), static_cast<uint8_t>(req_mode)); |
| |
| // All other modes are lower precedence than what local device supports, so send local mode. |
| if (local_mode == ChannelMode::kEnhancedRetransmission) { |
| // Retransmission & Flow Control fields for ERTM are not negotiable, so do not propose |
| // acceptable values per Core Spec v5.0, Vol 3, Part A, Sec 7.1.4. |
| unacceptable.set_retransmission_flow_control_option( |
| ChannelConfiguration::RetransmissionAndFlowControlOption:: |
| MakeEnhancedRetransmissionMode( |
| /*tx_window_size=*/0, |
| /*max_transmit=*/0, /*rtx_timeout=*/0, |
| /*monitor_timeout=*/0, |
| /*mps=*/0)); |
| } else { |
| unacceptable.set_retransmission_flow_control_option( |
| local_config().retransmission_flow_control_option()); |
| } |
| } |
| |
| return unacceptable; |
| } |
| |
| std::optional<ChannelConfiguration::RetransmissionAndFlowControlOption> |
| BrEdrDynamicChannel::CheckForUnacceptableErtmOptions(const ChannelConfiguration& config) const { |
| ZX_ASSERT(config.retransmission_flow_control_option()->mode() == |
| ChannelMode::kEnhancedRetransmission); |
| ZX_ASSERT(local_config().retransmission_flow_control_option()->mode() == |
| ChannelMode::kEnhancedRetransmission); |
| |
| std::optional<ChannelConfiguration::RetransmissionAndFlowControlOption> unacceptable_rfc_option; |
| const auto& peer_rfc_option = config.retransmission_flow_control_option().value(); |
| |
| // TxWindow has a range of 1 to 63 (Core Spec v5.0, Vol 3, Part A, Sec 5.4). |
| if (peer_rfc_option.tx_window_size() < kErtmMinUnackedInboundFrames) { |
| bt_log(DEBUG, "l2cap-bredr", "Channel %#.4x: rejecting too-small ERTM TxWindow of %hhu", |
| local_cid(), peer_rfc_option.tx_window_size()); |
| unacceptable_rfc_option = unacceptable_rfc_option.value_or(peer_rfc_option); |
| unacceptable_rfc_option->set_tx_window_size(kErtmMinUnackedInboundFrames); |
| } else if (peer_rfc_option.tx_window_size() > kErtmMaxUnackedInboundFrames) { |
| bt_log(DEBUG, "l2cap-bredr", "Channel %#.4x: rejecting too-small ERTM TxWindow of %hhu", |
| local_cid(), peer_rfc_option.tx_window_size()); |
| unacceptable_rfc_option = unacceptable_rfc_option.value_or(peer_rfc_option); |
| unacceptable_rfc_option->set_tx_window_size(kErtmMaxUnackedInboundFrames); |
| } |
| |
| // NOTE(fxbug.dev/1033): MPS must be large enough to fit the largest SDU in the minimum MTU case, |
| // because ERTM does not segment in the outbound direction. |
| if (peer_rfc_option.mps() < kMinACLMTU) { |
| bt_log(DEBUG, "l2cap-bredr", "Channel %#.4x: rejecting too-small ERTM MPS of %hu", local_cid(), |
| peer_rfc_option.mps()); |
| unacceptable_rfc_option = unacceptable_rfc_option.value_or(peer_rfc_option); |
| unacceptable_rfc_option->set_mps(kMinACLMTU); |
| } |
| |
| return unacceptable_rfc_option; |
| } |
| |
| bool BrEdrDynamicChannel::TryRecoverFromUnacceptableParametersConfigRsp( |
| const ChannelConfiguration& rsp_config) { |
| // Check if channel mode was unacceptable. |
| if (rsp_config.retransmission_flow_control_option()) { |
| // Check if peer rejected basic mode. Do not disconnect, in case peer will accept resending |
| // basic mode (as is the case with PTS test L2CAP/COS/CFD/BV-02-C). |
| if (local_config_.retransmission_flow_control_option()->mode() == ChannelMode::kBasic) { |
| bt_log(WARN, "l2cap-bredr", |
| "Channel %#.4x: Peer rejected basic mode with unacceptable " |
| "parameters result (rsp mode: %#.2x)", |
| local_cid(), |
| static_cast<uint8_t>(rsp_config.retransmission_flow_control_option()->mode())); |
| } |
| |
| // Core Spec v5.1, Vol 3, Part A, Sec 5.4: |
| // If the mode in the remote device's negative Configuration Response does |
| // not match the mode in the remote device's Configuration Request then the |
| // local device shall disconnect the channel. |
| if (state_ & kRemoteConfigAccepted) { |
| const auto rsp_mode = rsp_config.retransmission_flow_control_option()->mode(); |
| const auto remote_mode = remote_config_.retransmission_flow_control_option()->mode(); |
| if (rsp_mode != remote_mode) { |
| bt_log(ERROR, "l2cap-bredr", |
| "Channel %#.4x: Unsuccussful config: mode in unacceptable parameters config " |
| "response does not match mode in remote config request (rsp mode: %#.2x, req mode: " |
| "%#.2x)", |
| local_cid(), static_cast<uint8_t>(rsp_mode), static_cast<uint8_t>(remote_mode)); |
| return false; |
| } |
| } |
| |
| bt_log(TRACE, "l2cap-bredr", |
| "Channel %#.4x: Attempting to recover from unacceptable parameters config response by " |
| "falling back to basic mode and resending config request", |
| local_cid()); |
| |
| // Fall back to basic mode and try sending config again. |
| // TODO(fxbug.dev/46992): limit the number of retries |
| peer_supports_ertm_ = false; |
| UpdateLocalConfigForErtm(); |
| SendLocalConfig(); |
| return true; |
| } |
| |
| // Other unacceptable parameters cannot be recovered from. |
| bt_log( |
| WARN, "l2cap-bredr", |
| "Channel %#.4x: Unsuccessful config: could not recover from unacceptable parameters config " |
| "response", |
| local_cid()); |
| return false; |
| } |
| |
| BrEdrDynamicChannel::ResponseHandlerAction 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 ResponseHandlerAction::kCompleteOutboundTransaction; |
| } |
| |
| 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 ResponseHandlerAction::kCompleteOutboundTransaction; |
| } |
| |
| if ((state_ & kConnResponded) || !(state_ & kConnRequested)) { |
| bt_log(ERROR, "l2cap-bredr", "Channel %#.4x: Unexpected Connection Response, state %#x", |
| local_cid(), state_); |
| PassOpenError(); |
| return ResponseHandlerAction::kCompleteOutboundTransaction; |
| } |
| |
| if (rsp.result() == ConnectionResult::kPending) { |
| bt_log(TRACE, "l2cap-bredr", "Channel %#.4x: Remote is pending open, status %#.4hx", |
| local_cid(), rsp.conn_status()); |
| |
| if (rsp.remote_cid() == kInvalidChannelId) { |
| return ResponseHandlerAction::kExpectAdditionalResponse; |
| } |
| |
| // If the remote provides a channel ID, then we store it. It can be used for |
| // disconnection from this point forward. |
| if (!SetRemoteChannelId(rsp.remote_cid())) { |
| bt_log(ERROR, "l2cap-bredr", "Channel %#.4x: Remote channel ID %#.4x is not unique", |
| local_cid(), rsp.remote_cid()); |
| PassOpenError(); |
| return ResponseHandlerAction::kCompleteOutboundTransaction; |
| } |
| |
| bt_log(TRACE, "l2cap-bredr", "Channel %#.4x: Got remote channel ID %#.4x", local_cid(), |
| remote_cid()); |
| return ResponseHandlerAction::kExpectAdditionalResponse; |
| } |
| |
| 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 ResponseHandlerAction::kCompleteOutboundTransaction; |
| } |
| |
| 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 ResponseHandlerAction::kCompleteOutboundTransaction; |
| } |
| |
| // 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; |
| |
| if (!SetRemoteChannelId(rsp.remote_cid())) { |
| bt_log(ERROR, "l2cap-bredr", "Channel %#.4x: Remote channel ID %#.4x is not unique", |
| local_cid(), rsp.remote_cid()); |
| PassOpenError(); |
| return ResponseHandlerAction::kCompleteOutboundTransaction; |
| } |
| |
| bt_log(TRACE, "l2cap-bredr", "Channel %#.4x: Got remote channel ID %#.4x", local_cid(), |
| rsp.remote_cid()); |
| |
| UpdateLocalConfigForErtm(); |
| if (!IsWaitingForPeerErtmSupport()) { |
| TrySendLocalConfig(); |
| } |
| return ResponseHandlerAction::kCompleteOutboundTransaction; |
| } |
| |
| BrEdrDynamicChannel::ResponseHandlerAction 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 ResponseHandlerAction::kCompleteOutboundTransaction; |
| } |
| |
| // Pending responses may contain return values and adjustments to non-negotiated values. |
| if (rsp.result() == ConfigurationResult::kPending) { |
| bt_log(TRACE, "l2cap-bredr", "Channel %#.4x: remote pending config (options: %s)", local_cid(), |
| bt_str(rsp.config())); |
| |
| if (rsp.config().mtu_option()) { |
| local_config_.set_mtu_option(rsp.config().mtu_option()); |
| } |
| |
| return ResponseHandlerAction::kExpectAdditionalResponse; |
| } |
| |
| if (rsp.result() == ConfigurationResult::kUnacceptableParameters) { |
| bt_log(DEBUG, "l2cap-bredr", |
| "Channel %#.4x: Received unacceptable parameters config response (options: %s)", |
| local_cid(), bt_str(rsp.config())); |
| |
| if (!TryRecoverFromUnacceptableParametersConfigRsp(rsp.config())) { |
| PassOpenError(); |
| } |
| return ResponseHandlerAction::kCompleteOutboundTransaction; |
| } |
| |
| if (rsp.result() != ConfigurationResult::kSuccess) { |
| bt_log(ERROR, "l2cap-bredr", "Channel %#.4x: unsuccessful config (result: %#.4hx, options: %s)", |
| local_cid(), rsp.result(), bt_str(rsp.config())); |
| PassOpenError(); |
| return ResponseHandlerAction::kCompleteOutboundTransaction; |
| } |
| |
| 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 ResponseHandlerAction::kCompleteOutboundTransaction; |
| } |
| |
| state_ |= kLocalConfigAccepted; |
| |
| bt_log(TRACE, "l2cap-bredr", "Channel %#.4x: Got Configuration Response (options: %s)", |
| local_cid(), bt_str(rsp.config())); |
| |
| if (rsp.config().mtu_option()) { |
| local_config_.set_mtu_option(rsp.config().mtu_option()); |
| } |
| |
| if (BothConfigsAccepted() && !AcceptedChannelModesAreConsistent()) { |
| bt_log(WARN, "l2cap-bredr", |
| "Channel %#.4x: inconsistent channel mode negotiation (local mode: %#.2x, remote " |
| "mode: %#.2x); falling back to Basic Mode", |
| local_cid(), |
| static_cast<uint8_t>(local_config().retransmission_flow_control_option()->mode()), |
| static_cast<uint8_t>(remote_config().retransmission_flow_control_option()->mode())); |
| |
| // See spec justification in OnRxConfigReq. |
| local_config_.set_retransmission_flow_control_option( |
| ChannelConfiguration::RetransmissionAndFlowControlOption::MakeBasicMode()); |
| remote_config_.set_retransmission_flow_control_option( |
| ChannelConfiguration::RetransmissionAndFlowControlOption::MakeBasicMode()); |
| PassOpenResult(); |
| return ResponseHandlerAction::kCompleteOutboundTransaction; |
| } |
| |
| if (IsOpen()) { |
| set_opened(); |
| PassOpenResult(); |
| } |
| |
| return ResponseHandlerAction::kCompleteOutboundTransaction; |
| } |
| |
| void BrEdrDynamicChannel::SetEnhancedRetransmissionSupport(bool supported) { |
| peer_supports_ertm_ = supported; |
| |
| UpdateLocalConfigForErtm(); |
| |
| // Don't send local config before connection response. |
| if (state_ & kConnResponded) { |
| TrySendLocalConfig(); |
| } |
| } |
| |
| } // namespace bt::l2cap::internal |