blob: f33ecb49d7f3e9e9f81f0b71d4245e8744be2dd4 [file] [log] [blame]
// 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