blob: 63070c3e820051a45dfa4065d571f3d9f81d7179 [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 "garnet/drivers/bluetooth/lib/common/slab_allocator.h"
#include "garnet/drivers/bluetooth/lib/l2cap/fake_channel_test.h"
#include "garnet/drivers/bluetooth/lib/l2cap/fake_layer.h"
#include "garnet/drivers/bluetooth/lib/rfcomm/channel_manager.h"
namespace btlib {
namespace rfcomm {
namespace {
constexpr l2cap::ChannelId kL2CAPChannelId1 = 0x0040;
constexpr l2cap::ChannelId kL2CAPChannelId2 = 0x0041;
constexpr hci::ConnectionHandle kHandle1 = 1;
void DoNothingWithChannel(fbl::RefPtr<Channel> channel,
ServerChannel server_channel) {}
class RFCOMM_ChannelManagerTest : public l2cap::testing::FakeChannelTest {
public:
RFCOMM_ChannelManagerTest() : channel_manager_(nullptr) {}
~RFCOMM_ChannelManagerTest() override = default;
protected:
// Captures the state of the fake remote peer.
struct PeerState {
// Whether this peer supports credit-based flow. This bool also indicates
// whether the session with this peer will have credit-based flow turned on;
// our RFCOMM implementation will always turn on credit-based flow if the
// peer supports it.
bool credit_based_flow;
Role role;
};
void SetUp() override {
l2cap_ = l2cap::testing::FakeLayer::Create();
FXL_DCHECK(l2cap_);
l2cap_->Initialize();
l2cap_->AddACLConnection(kHandle1, hci::Connection::Role::kMaster,
[] { FXL_LOG(WARNING) << "Unimplemented"; },
dispatcher());
// Any new L2CAP channels (incoming our outgoing) opened by our
// ChannelManager will be captured and stored in |handle_to_fake_channel_|.
// Subsequently, all channels will have listeners attached to them, and any
// frames sent from our RFCOMM sessions will be put into the queues in
// |handle_to_incoming_frames_|.
l2cap_->set_channel_callback(
[this](fbl::RefPtr<l2cap::testing::FakeChannel> l2cap_channel) {
FXL_DCHECK(l2cap_channel);
auto handle = l2cap_channel->link_handle();
handle_to_fake_channel_.emplace(handle, l2cap_channel);
l2cap_channel->SetSendCallback(
[this, handle](auto sdu) {
if (handle_to_incoming_frames_.find(handle) ==
handle_to_incoming_frames_.end()) {
handle_to_incoming_frames_.emplace(
handle,
std::queue<std::unique_ptr<const common::ByteBuffer>>());
}
handle_to_incoming_frames_[handle].push(std::move(sdu));
},
dispatcher());
});
channel_manager_ = ChannelManager::Create(l2cap_.get());
FXL_DCHECK(channel_manager_);
}
void TearDown() override {
channel_manager_.release();
l2cap_.reset();
handle_to_peer_state_.clear();
handle_to_fake_channel_.clear();
handle_to_incoming_frames_.clear();
}
void ExpectFrame(hci::ConnectionHandle handle, FrameType type, DLCI dlci) {
auto queue_it = handle_to_incoming_frames_.find(handle);
EXPECT_FALSE(handle_to_incoming_frames_.end() == queue_it);
EXPECT_FALSE(handle_to_peer_state_.find(handle) ==
handle_to_peer_state_.end());
const PeerState& state = handle_to_peer_state_[handle];
auto frame = Frame::Parse(state.credit_based_flow, OppositeRole(state.role),
queue_it->second.front()->view());
queue_it->second.pop();
EXPECT_TRUE(frame);
EXPECT_EQ(type, static_cast<FrameType>(frame->control()));
EXPECT_EQ(dlci, frame->dlci());
}
void ReceiveFrame(hci::ConnectionHandle handle,
std::unique_ptr<Frame> frame) {
auto channel_it = handle_to_fake_channel_.find(handle);
EXPECT_FALSE(channel_it == handle_to_fake_channel_.end());
auto buffer = common::NewSlabBuffer(frame->written_size());
frame->Write(buffer->mutable_view());
channel_it->second->Receive(buffer->view());
}
// Emplace a new PeerState for a new fake peer. Should be called for each fake
// peer which a test is emulating. The returned PeerState should then be
// updated throughout the test (e.g. the multiplexer state should change when
// the multiplexer starts up).
PeerState& AddFakePeerState(hci::ConnectionHandle handle, PeerState state) {
FXL_DCHECK(handle_to_peer_state_.find(handle) ==
handle_to_peer_state_.end());
handle_to_peer_state_.emplace(handle, std::move(state));
return handle_to_peer_state_[handle];
}
fbl::RefPtr<l2cap::testing::FakeChannel> GetFakeChannel(
hci::ConnectionHandle handle) {
FXL_DCHECK(handle_to_fake_channel_.find(handle) !=
handle_to_fake_channel_.end());
return handle_to_fake_channel_[handle];
}
std::unique_ptr<ChannelManager> channel_manager_;
fbl::RefPtr<l2cap::testing::FakeLayer> l2cap_;
std::unordered_map<hci::ConnectionHandle,
std::queue<std::unique_ptr<const common::ByteBuffer>>>
handle_to_incoming_frames_;
// Maps remote peers (represented as connection handles) to the L2CAP channel
// (actually a FakeChannel) over which the corresponding RFCOMM session
// communicates with that peer.
std::unordered_map<hci::ConnectionHandle,
fbl::RefPtr<l2cap::testing::FakeChannel>>
handle_to_fake_channel_;
// Holds the state of the fake peers. Tests must manually update this
// information as needed; for example, if a test mimics mux startup manually,
// it must change its role accordingly, otherwise utility functions like
// ExpectFrame() will not parse frames correctly.
std::unordered_map<hci::ConnectionHandle, PeerState> handle_to_peer_state_;
};
// Expect that registration of an L2CAP channel with the Channel Manager results
// in the L2CAP channel's eventual activation.
TEST_F(RFCOMM_ChannelManagerTest, RegisterL2CAPChannel) {
ChannelOptions l2cap_channel_options(kL2CAPChannelId1);
auto l2cap_channel = CreateFakeChannel(l2cap_channel_options);
EXPECT_TRUE(channel_manager_->RegisterL2CAPChannel(l2cap_channel));
EXPECT_TRUE(l2cap_channel->activated());
}
// Test that command timeouts during multiplexer startup result in the session
// being closed down.
TEST_F(RFCOMM_ChannelManagerTest, MuxStartupAndParamNegotiation_Timeout) {
AddFakePeerState(kHandle1, PeerState{true /*credits*/, Role::kUnassigned});
channel_manager_->OpenRemoteChannel(kHandle1, kMinServerChannel,
&DoNothingWithChannel, dispatcher());
l2cap_->TriggerOutboundChannel(kHandle1, l2cap::kRFCOMM, kL2CAPChannelId1,
kL2CAPChannelId2);
RunLoopUntilIdle();
auto channel = GetFakeChannel(kHandle1);
ExpectFrame(kHandle1, FrameType::kSetAsynchronousBalancedMode,
kMuxControlDLCI);
// Do nothing
RunLoopFor(zx::min(5));
// Expect closedown after timeout
EXPECT_FALSE(channel->activated());
}
// Test successful multiplexer startup (resulting role: responder).
TEST_F(RFCOMM_ChannelManagerTest, MuxStartupAndParamNegotiation_Responder) {
AddFakePeerState(kHandle1, PeerState{true /*credits*/, Role::kUnassigned});
l2cap_->TriggerInboundChannel(kHandle1, l2cap::kRFCOMM, kL2CAPChannelId1,
kL2CAPChannelId2);
RunLoopUntilIdle();
// Receive a multiplexer startup frame on the session
ReceiveFrame(kHandle1, std::make_unique<SetAsynchronousBalancedModeCommand>(
Role::kUnassigned, kMuxControlDLCI));
RunLoopUntilIdle();
ExpectFrame(kHandle1, FrameType::kUnnumberedAcknowledgement, kMuxControlDLCI);
}
// Test successful multiplexer startup (resulting role: initiator)
TEST_F(RFCOMM_ChannelManagerTest, MuxStartupAndParamNegotiation_Initiator) {
auto& state = AddFakePeerState(
kHandle1, PeerState{true /*credits*/, Role::kUnassigned});
bool channel_received = false;
fbl::RefPtr<Channel> channel;
channel_manager_->OpenRemoteChannel(
kHandle1, kMinServerChannel,
[&channel, &channel_received](auto ch, auto server_channel) {
channel_received = true;
channel = ch;
},
dispatcher());
l2cap_->TriggerOutboundChannel(kHandle1, l2cap::kRFCOMM, kL2CAPChannelId1,
kL2CAPChannelId2);
RunLoopUntilIdle();
ExpectFrame(kHandle1, FrameType::kSetAsynchronousBalancedMode,
kMuxControlDLCI);
// Receive a UA on the session
ReceiveFrame(kHandle1, std::make_unique<UnnumberedAcknowledgementResponse>(
Role::kUnassigned, kMuxControlDLCI));
RunLoopUntilIdle();
state.role = Role::kResponder;
{
// Expect a PN command from the session
auto queue_it = handle_to_incoming_frames_.find(kHandle1);
EXPECT_FALSE(queue_it == handle_to_incoming_frames_.end());
EXPECT_EQ(1ul, queue_it->second.size());
auto frame = Frame::Parse(true, OppositeRole(state.role),
queue_it->second.front()->view());
queue_it->second.pop();
EXPECT_EQ(FrameType::kUnnumberedInfoHeaderCheck,
static_cast<FrameType>(frame->control()));
DLCI dlci =
ServerChannelToDLCI(kMinServerChannel, OppositeRole(state.role));
auto mux_command =
static_cast<MuxCommandFrame*>(frame.get())->TakeMuxCommand();
EXPECT_EQ(MuxCommandType::kDLCParameterNegotiation,
mux_command->command_type());
auto params =
static_cast<DLCParameterNegotiationCommand*>(mux_command.get())
->params();
EXPECT_EQ(dlci, params.dlci);
params.credit_based_flow_handshake =
CreditBasedFlowHandshake::kSupportedResponse;
// Receive PN response
ReceiveFrame(kHandle1, std::make_unique<MuxCommandFrame>(
state.role, true,
std::make_unique<DLCParameterNegotiationCommand>(
CommandResponse::kResponse, params)));
RunLoopUntilIdle();
}
EXPECT_TRUE(channel_received);
EXPECT_FALSE(channel);
}
// Test multiplexer startup conflict procedure (resulting role: initiator).
TEST_F(RFCOMM_ChannelManagerTest,
MuxStartupAndParamNegotiation_Conflict_BecomeInitiator) {
auto& state = AddFakePeerState(
kHandle1, PeerState{true /*credits*/, Role::kUnassigned});
bool channel_received = false;
fbl::RefPtr<Channel> channel;
channel_manager_->OpenRemoteChannel(
kHandle1, kMinServerChannel,
[&channel, &channel_received](auto ch, auto server_channel) {
channel_received = true;
channel = ch;
},
dispatcher());
l2cap_->TriggerOutboundChannel(kHandle1, l2cap::kRFCOMM, kL2CAPChannelId1,
kL2CAPChannelId2);
RunLoopUntilIdle();
ExpectFrame(kHandle1, FrameType::kSetAsynchronousBalancedMode,
kMuxControlDLCI);
// Receive a conflicting SABM on the session
ReceiveFrame(kHandle1, std::make_unique<SetAsynchronousBalancedModeCommand>(
state.role, kMuxControlDLCI));
RunLoopUntilIdle();
ExpectFrame(kHandle1, FrameType::kDisconnectedMode, kMuxControlDLCI);
// Wait and expect a SABM
RunLoopFor(zx::sec(5));
ExpectFrame(kHandle1, FrameType::kSetAsynchronousBalancedMode,
kMuxControlDLCI);
// Receive a UA on the session
ReceiveFrame(kHandle1, std::make_unique<UnnumberedAcknowledgementResponse>(
state.role, kMuxControlDLCI));
RunLoopUntilIdle();
state.role = Role::kResponder;
{
// Expect a PN command from the session
auto queue_it = handle_to_incoming_frames_.find(kHandle1);
EXPECT_FALSE(queue_it == handle_to_incoming_frames_.end());
EXPECT_EQ(1ul, queue_it->second.size());
auto frame = Frame::Parse(true, OppositeRole(state.role),
queue_it->second.front()->view());
queue_it->second.pop();
EXPECT_EQ(FrameType::kUnnumberedInfoHeaderCheck,
static_cast<FrameType>(frame->control()));
DLCI dlci =
ServerChannelToDLCI(kMinServerChannel, OppositeRole(state.role));
auto mux_command =
static_cast<MuxCommandFrame*>(frame.get())->TakeMuxCommand();
EXPECT_EQ(MuxCommandType::kDLCParameterNegotiation,
mux_command->command_type());
auto params =
static_cast<DLCParameterNegotiationCommand*>(mux_command.get())
->params();
EXPECT_EQ(dlci, params.dlci);
params.credit_based_flow_handshake =
CreditBasedFlowHandshake::kSupportedResponse;
// Receive PN response
ReceiveFrame(kHandle1, std::make_unique<MuxCommandFrame>(
state.role, true,
std::make_unique<DLCParameterNegotiationCommand>(
CommandResponse::kResponse, params)));
RunLoopUntilIdle();
}
EXPECT_TRUE(channel_received);
EXPECT_FALSE(channel);
}
// Test multiplexer startup conflict procedure (resulting role: responder).
TEST_F(RFCOMM_ChannelManagerTest,
MuxStartupAndParamNegotiation_Conflict_BecomeResponder) {
auto& state = AddFakePeerState(
kHandle1, PeerState{true /*credits*/, Role::kUnassigned});
bool channel_delivered = false;
channel_manager_->OpenRemoteChannel(
kHandle1, kMinServerChannel,
[&channel_delivered](auto channel, auto server_channel) {
channel_delivered = true;
},
dispatcher());
l2cap_->TriggerOutboundChannel(kHandle1, l2cap::kRFCOMM, kL2CAPChannelId1,
kL2CAPChannelId2);
RunLoopUntilIdle();
// Expect initial mux-opening SABM
ExpectFrame(kHandle1, FrameType::kSetAsynchronousBalancedMode,
kMuxControlDLCI);
// Receive a conflicting SABM on the session
state.role = Role::kNegotiating;
ReceiveFrame(kHandle1, std::make_unique<SetAsynchronousBalancedModeCommand>(
state.role, kMuxControlDLCI));
RunLoopUntilIdle();
// Expect a DM frame from the session
ExpectFrame(kHandle1, FrameType::kDisconnectedMode, kMuxControlDLCI);
// Immediately receive another SABM on the session
ReceiveFrame(kHandle1, std::make_unique<SetAsynchronousBalancedModeCommand>(
state.role, kMuxControlDLCI));
RunLoopUntilIdle();
// Expect UA
ExpectFrame(kHandle1, FrameType::kUnnumberedAcknowledgement, kMuxControlDLCI);
state.role = Role::kInitiator;
{
// Expect a PN command from the session
EXPECT_FALSE(handle_to_incoming_frames_.find(kHandle1) ==
handle_to_incoming_frames_.end());
auto& queue = handle_to_incoming_frames_[kHandle1];
EXPECT_EQ(1ul, queue.size());
auto frame = Frame::Parse(state.credit_based_flow, OppositeRole(state.role),
queue.front()->view());
queue.pop();
EXPECT_EQ(FrameType::kUnnumberedInfoHeaderCheck,
static_cast<FrameType>(frame->control()));
DLCI dlci =
ServerChannelToDLCI(kMinServerChannel, OppositeRole(state.role));
auto mux_command =
static_cast<MuxCommandFrame*>(frame.get())->TakeMuxCommand();
EXPECT_EQ(MuxCommandType::kDLCParameterNegotiation,
mux_command->command_type());
auto params =
static_cast<DLCParameterNegotiationCommand*>(mux_command.get())
->params();
EXPECT_EQ(dlci, params.dlci);
params.credit_based_flow_handshake =
CreditBasedFlowHandshake::kSupportedResponse;
// Receive PN response
ReceiveFrame(kHandle1, std::make_unique<MuxCommandFrame>(
state.role, true,
std::make_unique<DLCParameterNegotiationCommand>(
CommandResponse::kResponse, params)));
RunLoopUntilIdle();
}
// EXPECT_TRUE(channel_received);
// EXPECT_FALSE(channel);
}
// Tests whether sessions handle invalid max frame sizes correctly.
TEST_F(RFCOMM_ChannelManagerTest,
MuxStartupAndParamNegotiation_BadPN_InvalidMaxFrameSize) {
auto& state = AddFakePeerState(
kHandle1, PeerState{true /*credits*/, Role::kUnassigned});
bool channel_delivered = false;
channel_manager_->OpenRemoteChannel(
kHandle1, kMinServerChannel,
[&channel_delivered](auto channel, auto server_channel) {
channel_delivered = true;
},
dispatcher());
l2cap_->TriggerOutboundChannel(kHandle1, l2cap::kRFCOMM, kL2CAPChannelId1,
kL2CAPChannelId2);
RunLoopUntilIdle();
ExpectFrame(kHandle1, FrameType::kSetAsynchronousBalancedMode,
kMuxControlDLCI);
// Receive a UA on the session
ReceiveFrame(kHandle1, std::make_unique<UnnumberedAcknowledgementResponse>(
state.role, kMuxControlDLCI));
RunLoopUntilIdle();
state.role = Role::kResponder;
DLCI dlci = ServerChannelToDLCI(kMinServerChannel, OppositeRole(state.role));
{
// Expect a PN command from the session
EXPECT_FALSE(handle_to_incoming_frames_.find(kHandle1) ==
handle_to_incoming_frames_.end());
auto& queue = handle_to_incoming_frames_[kHandle1];
EXPECT_EQ(1ul, queue.size());
auto frame = Frame::Parse(state.credit_based_flow, OppositeRole(state.role),
queue.front()->view());
queue.pop();
EXPECT_EQ(FrameType::kUnnumberedInfoHeaderCheck,
static_cast<FrameType>(frame->control()));
DLCI dlci =
ServerChannelToDLCI(kMinServerChannel, OppositeRole(state.role));
auto mux_command =
static_cast<MuxCommandFrame*>(frame.get())->TakeMuxCommand();
EXPECT_EQ(MuxCommandType::kDLCParameterNegotiation,
mux_command->command_type());
// Create invalid parameters.
auto params =
static_cast<DLCParameterNegotiationCommand*>(mux_command.get())
->params();
EXPECT_EQ(dlci, params.dlci);
params.credit_based_flow_handshake =
CreditBasedFlowHandshake::kSupportedResponse;
// Request a larger max frame size than what was proposed.
params.maximum_frame_size += 1;
// Receive PN response
ReceiveFrame(kHandle1, std::make_unique<MuxCommandFrame>(
OppositeRole(state.role), true,
std::make_unique<DLCParameterNegotiationCommand>(
CommandResponse::kResponse, params)));
RunLoopUntilIdle();
}
ExpectFrame(kHandle1, FrameType::kDisconnect, dlci);
}
// A DM response to a mux SABM shouldn't crash (but shouldn't do anything else).
TEST_F(RFCOMM_ChannelManagerTest,
MuxStartupAndParamNegotiation_RejectMuxStartup) {
AddFakePeerState(kHandle1, PeerState{true /*credits*/, Role::kUnassigned});
bool channel_delivered = false;
channel_manager_->OpenRemoteChannel(
kHandle1, kMinServerChannel,
[&channel_delivered](auto channel, auto server_channel) {
channel_delivered = true;
},
dispatcher());
l2cap_->TriggerOutboundChannel(kHandle1, l2cap::kRFCOMM, kL2CAPChannelId1,
kL2CAPChannelId2);
RunLoopUntilIdle();
ExpectFrame(kHandle1, FrameType::kSetAsynchronousBalancedMode,
kMuxControlDLCI);
// Receive a DM on the session
ReceiveFrame(kHandle1, std::make_unique<DisconnectedModeResponse>(
Role::kUnassigned, kMuxControlDLCI));
RunLoopUntilIdle();
}
} // namespace
} // namespace rfcomm
} // namespace btlib