blob: 4018f5ac0ef4d61f586e9d7ab5bb5f02ecaab296 [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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/bredr_dynamic_channel.h"
#include <endian.h>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/assert.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/channel_configuration.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
#pragma clang diagnostic ignored "-Wswitch-enum"
namespace bt::l2cap::internal {
namespace {
ChannelConfiguration::RetransmissionAndFlowControlOption
WriteRfcOutboundTimeouts(
ChannelConfiguration::RetransmissionAndFlowControlOption rfc_option) {
rfc_option.set_rtx_timeout(kErtmReceiverReadyPollTimerMsecs);
rfc_option.set_monitor_timeout(kErtmMonitorTimerMsecs);
return rfc_option;
}
constexpr uint16_t kBrEdrDynamicChannelCount =
kLastACLDynamicChannelId - kFirstDynamicChannelId + 1;
const uint8_t kMaxNumBasicConfigRequests = 2;
} // 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) {
BT_DEBUG_ASSERT(sig_);
BrEdrCommandHandler cmd_handler(sig_);
cmd_handler.ServeConnectionRequest(
fit::bind_member<&BrEdrDynamicChannelRegistry::OnRxConnReq>(this));
cmd_handler.ServeConfigurationRequest(
fit::bind_member<&BrEdrDynamicChannelRegistry::OnRxConfigReq>(this));
cmd_handler.ServeDisconnectionRequest(
fit::bind_member<&BrEdrDynamicChannelRegistry::OnRxDisconReq>(this));
cmd_handler.ServeInformationRequest(
fit::bind_member<&BrEdrDynamicChannelRegistry::OnRxInfoReq>(this));
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",
static_cast<unsigned short>(type));
// TODO(https://fxbug.dev/42175069): 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 | kExtendedFeaturesBitFCSOption |
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(https://fxbug.dev/42175069): 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",
static_cast<unsigned short>(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",
static_cast<unsigned short>(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)",
static_cast<unsigned short>(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) {
bt_log(
DEBUG, "l2cap-bredr", "Skipping sending info requests, already sent");
return;
}
BrEdrCommandHandler cmd_handler(sig_);
auto on_rx_info_rsp = [self = GetWeakPtr(), this](auto& rsp) {
if (self.is_alive()) {
OnRxExtendedFeaturesInfoRsp(rsp);
}
};
if (!cmd_handler.SendInformationRequest(
InformationType::kExtendedFeaturesSupported,
std::move(on_rx_info_rsp))) {
bt_log(ERROR,
"l2cap-bredr",
"Failed to send Extended Features Information Request");
return;
}
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_self_.GetWeakPtr()](
const BrEdrCommandHandler::ConnectionResponse& rsp) {
if (self.is_alive()) {
return self->OnRxConnRsp(rsp);
}
return BrEdrCommandHandler::ResponseHandlerAction::
kCompleteOutboundTransaction;
};
auto on_conn_rsp_timeout = [this, self = weak_self_.GetWeakPtr()] {
if (self.is_alive()) {
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) {
BT_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_self_.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.is_alive()) {
done_cb();
}
};
auto on_discon_rsp_timeout = [local_cid = local_cid(),
self = weak_self_.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.is_alive()) {
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(WARN,
"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 {
BT_ASSERT(local_config().retransmission_flow_control_option().has_value());
BT_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() ==
RetransmissionAndFlowControlMode::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(https://fxbug.dev/42115983): 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()
: RetransmissionAndFlowControlMode::kBasic;
// Record peer support for ERTM even if they haven't sent a Extended Features
// Mask.
if (req_mode == RetransmissionAndFlowControlMode::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 configuration 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(https://fxbug.dev/42057179): 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(https://fxbug.dev/42117452): 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 == RetransmissionAndFlowControlMode::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_self_(this) {
BT_DEBUG_ASSERT(signaling_channel_);
BT_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() {
BT_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 ==
RetransmissionAndFlowControlMode::kEnhancedRetransmission &&
peer_supports_ertm_.value_or(false);
}
bool BrEdrDynamicChannel::IsWaitingForPeerErtmSupport() {
const auto local_mode =
parameters_.mode.value_or(RetransmissionAndFlowControlMode::kBasic);
return !peer_supports_ertm_.has_value() &&
(local_mode != RetransmissionAndFlowControlMode::kBasic);
}
void BrEdrDynamicChannel::TrySendLocalConfig() {
if (state_ & kLocalConfigSent) {
return;
}
BT_ASSERT(!IsWaitingForPeerErtmSupport());
SendLocalConfig();
}
void BrEdrDynamicChannel::SendLocalConfig() {
auto on_config_rsp_timeout = [this, self = weak_self_.GetWeakPtr()] {
if (self.is_alive()) {
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() ==
RetransmissionAndFlowControlMode::kBasic) {
request_config.set_retransmission_flow_control_option(std::nullopt);
}
if (!request_config.retransmission_flow_control_option()) {
num_basic_config_requests_++;
}
if (!cmd_handler.SendConfigurationRequest(
remote_cid(),
0,
request_config.Options(),
[self = weak_self_.GetWeakPtr()](auto& rsp) {
if (self.is_alive()) {
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 {
BT_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(https://fxbug.dev/42115983): 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()
: RetransmissionAndFlowControlMode::kBasic;
const auto local_mode =
local_config().retransmission_flow_control_option()->mode();
switch (req_mode) {
case RetransmissionAndFlowControlMode::kBasic:
// Local device must accept, as basic mode has highest precedence.
if (local_mode ==
RetransmissionAndFlowControlMode::kEnhancedRetransmission) {
bt_log(DEBUG,
"l2cap-bredr",
"Channel %#.4x: accepting peer basic mode configuration option "
"when preferred mode "
"was ERTM",
local_cid());
}
break;
case RetransmissionAndFlowControlMode::kEnhancedRetransmission:
// Basic mode has highest precedence, so if local mode is basic, reject
// ERTM and send local mode.
if (local_mode == RetransmissionAndFlowControlMode::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 ==
RetransmissionAndFlowControlMode::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 {
BT_ASSERT(config.retransmission_flow_control_option()->mode() ==
RetransmissionAndFlowControlMode::kEnhancedRetransmission);
BT_ASSERT(local_config().retransmission_flow_control_option()->mode() ==
RetransmissionAndFlowControlMode::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(https://fxbug.dev/42054330): 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() ==
RetransmissionAndFlowControlMode::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: Unsuccessful 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 up to
// kMaxNumBasicConfigRequests times
peer_supports_ertm_ = false;
if (num_basic_config_requests_ == kMaxNumBasicConfigRequests) {
bt_log(WARN,
"l2cap-bredr",
"Channel %#.4x: Peer rejected config request. Channel's limit of "
"%#.2x basic mode config request attempts has been met",
local_cid(),
kMaxNumBasicConfigRequests);
return 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(),
static_cast<unsigned short>(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(),
static_cast<unsigned short>(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(),
static_cast<unsigned short>(rsp.result()),
static_cast<unsigned int>(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(),
static_cast<unsigned short>(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(),
static_cast<unsigned short>(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