blob: c05d018246ef3be6ea3c495835b0dead752e746f [file] [log] [blame]
// Copyright 2017 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/signaling_channel.h"
#include <chrono>
#include <pw_async/dispatcher.h>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fake_channel_test.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
namespace bt::l2cap::internal {
namespace {
using Status = SignalingChannelInterface::Status;
constexpr CommandCode kUnknownCommandCode = 0x00;
constexpr CommandCode kCommandCode = 0xFF;
constexpr hci_spec::ConnectionHandle kTestHandle = 0x0001;
constexpr uint16_t kTestMTU = 100;
constexpr CommandId kMaxCommandId = std::numeric_limits<CommandId>::max();
const auto kTestResponseHandler = [](Status status,
const ByteBuffer& rsp_payload) {
return SignalingChannel::ResponseHandlerAction::kCompleteOutboundTransaction;
};
class TestSignalingChannel : public SignalingChannel {
public:
explicit TestSignalingChannel(Channel::WeakPtr chan,
pw::async::Dispatcher& dispatcher)
: SignalingChannel(std::move(chan),
pw::bluetooth::emboss::ConnectionRole::CENTRAL,
dispatcher) {
set_mtu(kTestMTU);
}
~TestSignalingChannel() override = default;
using PacketCallback = fit::function<void(const SignalingPacket& packet)>;
void set_packet_callback(PacketCallback cb) { packet_cb_ = std::move(cb); }
// Expose GetNextCommandId() as public so it can be called by tests below.
using SignalingChannel::GetNextCommandId;
// Expose ResponderImpl as public so it can be directly tested (rather than
// passed to RequestDelegate).
using SignalingChannel::ResponderImpl;
private:
// SignalingChannel overrides
void DecodeRxUnit(ByteBufferPtr sdu,
const SignalingPacketHandler& cb) override {
BT_ASSERT(sdu);
if (sdu->size()) {
cb(SignalingPacket(sdu.get(), sdu->size() - sizeof(CommandHeader)));
} else {
// Silently drop the packet. See documentation in signaling_channel.h.
}
}
bool IsSupportedResponse(CommandCode code) const override {
switch (code) {
case kCommandRejectCode:
case kEchoResponse:
return true;
}
return false;
}
bool HandlePacket(const SignalingPacket& packet) override {
if (packet_cb_)
packet_cb_(packet);
return SignalingChannel::HandlePacket(packet);
}
PacketCallback packet_cb_;
BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(TestSignalingChannel);
};
class SignalingChannelTest : public testing::FakeChannelTest {
public:
SignalingChannelTest() = default;
~SignalingChannelTest() override = default;
protected:
void SetUp() override {
ChannelOptions options(kLESignalingChannelId);
options.conn_handle = kTestHandle;
fake_channel_inst_ = CreateFakeChannel(options);
sig_ = std::make_unique<TestSignalingChannel>(
fake_channel_inst_->GetWeakPtr(), dispatcher());
}
void TearDown() override {
// Unless a test called DestroySig(), the signaling channel will outlive the
// underlying channel.
fake_channel_inst_ = nullptr;
DestroySig();
}
TestSignalingChannel* sig() const { return sig_.get(); }
void DestroySig() { sig_ = nullptr; }
private:
std::unique_ptr<TestSignalingChannel> sig_;
// Own the fake channel so that its lifetime can span beyond that of |sig_|.
std::unique_ptr<Channel> fake_channel_inst_;
BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(SignalingChannelTest);
};
TEST_F(SignalingChannelTest, IgnoreEmptyFrame) {
bool send_cb_called = false;
auto send_cb = [&send_cb_called](auto) { send_cb_called = true; };
fake_chan()->SetSendCallback(std::move(send_cb), dispatcher());
fake_chan()->Receive(BufferView());
RunUntilIdle();
EXPECT_FALSE(send_cb_called);
}
TEST_F(SignalingChannelTest, Reject) {
constexpr uint8_t kTestId = 14;
// Command Reject packet.
StaticByteBuffer expected(
// Command header
0x01,
kTestId,
0x02,
0x00,
// Reason (Command not understood)
0x00,
0x00);
// A command that TestSignalingChannel does not support.
StaticByteBuffer cmd(
// header
kUnknownCommandCode,
kTestId,
0x04,
0x00,
// data
'L',
'O',
'L',
'Z');
EXPECT_TRUE(ReceiveAndExpect(cmd, expected));
}
TEST_F(SignalingChannelTest, RejectCommandCodeZero) {
constexpr uint8_t kTestId = 14;
// Command Reject packet.
StaticByteBuffer expected(
// Command header
0x01,
kTestId,
0x02,
0x00,
// Reason (Command not understood)
0x00,
0x00);
// A command that TestSignalingChannel does not support.
StaticByteBuffer cmd(
// header
0x00,
kTestId,
0x04,
0x00,
// data
'L',
'O',
'L',
'Z');
EXPECT_TRUE(ReceiveAndExpect(cmd, expected));
}
TEST_F(SignalingChannelTest, RejectNotUnderstoodWithResponder) {
constexpr uint8_t kTestId = 14;
StaticByteBuffer expected(
// Command header (Command Reject, ID, length)
0x01,
kTestId,
0x02,
0x00,
// Reason (Command not understood)
0x00,
0x00);
bool cb_called = false;
auto send_cb = [&expected, &cb_called](auto packet) {
cb_called = true;
EXPECT_TRUE(ContainersEqual(expected, *packet));
};
fake_chan()->SetSendCallback(std::move(send_cb), dispatcher());
TestSignalingChannel::ResponderImpl responder(sig(), kCommandCode, kTestId);
responder.RejectNotUnderstood();
RunUntilIdle();
EXPECT_TRUE(cb_called);
}
TEST_F(SignalingChannelTest, RejectInvalidCIdWithResponder) {
constexpr uint8_t kTestId = 14;
constexpr uint16_t kLocalCId = 0xf00d;
constexpr uint16_t kRemoteCId = 0xcafe;
StaticByteBuffer expected(
// Command header (Command Reject, ID, length)
0x01,
kTestId,
0x06,
0x00,
// Reason (Invalid channel ID)
0x02,
0x00,
// Data (Channel IDs),
LowerBits(kLocalCId),
UpperBits(kLocalCId),
LowerBits(kRemoteCId),
UpperBits(kRemoteCId));
bool cb_called = false;
auto send_cb = [&expected, &cb_called](auto packet) {
cb_called = true;
EXPECT_TRUE(ContainersEqual(expected, *packet));
};
fake_chan()->SetSendCallback(std::move(send_cb), dispatcher());
TestSignalingChannel::ResponderImpl responder(sig(), kCommandCode, kTestId);
responder.RejectInvalidChannelId(kLocalCId, kRemoteCId);
RunUntilIdle();
EXPECT_TRUE(cb_called);
}
TEST_F(SignalingChannelTest, InvalidMTU) {
constexpr uint8_t kTestId = 14;
constexpr uint16_t kTooSmallMTU = 7;
// Command Reject packet.
StaticByteBuffer expected(
// Command header
0x01,
kTestId,
0x04,
0x00,
// Reason (Signaling MTU exceeded)
0x01,
0x00,
// The supported MTU
static_cast<uint8_t>(kTooSmallMTU),
0x00);
// A command that is one octet larger than the MTU.
StaticByteBuffer cmd(
// header
kCommandCode,
kTestId,
0x04,
0x00,
// data
'L',
'O',
'L',
'Z');
sig()->set_mtu(kTooSmallMTU);
EXPECT_TRUE(ReceiveAndExpect(cmd, expected));
}
TEST_F(SignalingChannelTest, HandlePacket) {
constexpr uint8_t kTestId = 14;
// A command that TestSignalingChannel supports.
StaticByteBuffer cmd(
// header
kCommandCode,
kTestId,
0x04,
0x00,
// data
'L',
'O',
'L',
'Z');
bool called = false;
sig()->set_packet_callback([&cmd, &called](auto packet) {
EXPECT_TRUE(ContainersEqual(cmd, packet.data()));
called = true;
});
fake_chan()->Receive(cmd);
RunUntilIdle();
EXPECT_TRUE(called);
}
TEST_F(SignalingChannelTest, UseChannelAfterSignalFree) {
// Destroy the underlying channel's user (SignalingChannel).
DestroySig();
// Ensure that the underlying channel is still alive.
ASSERT_TRUE(fake_chan().is_alive());
// SignalingChannel is expected to deactivate the channel if it doesn't own
// it. Either way, the channel isn't in a state that can receive test data.
EXPECT_FALSE(fake_chan()->activated());
// Ensure that closing the channel (possibly firing callback) is OK.
fake_chan()->Close();
RunUntilIdle();
}
TEST_F(SignalingChannelTest, ValidRequestCommandIds) {
EXPECT_EQ(0x01, sig()->GetNextCommandId());
for (int i = 0; i < kMaxCommandId + 1; i++) {
EXPECT_NE(0x00, sig()->GetNextCommandId());
}
}
TEST_F(SignalingChannelTest, DoNotRejectUnsolicitedResponse) {
constexpr CommandId kTestCmdId = 97;
StaticByteBuffer cmd(
// Command header (Echo Response, length 1)
0x09,
kTestCmdId,
0x01,
0x00,
// Payload
0x23);
size_t send_count = 0;
auto send_cb = [&](auto) { send_count++; };
fake_chan()->SetSendCallback(std::move(send_cb), dispatcher());
fake_chan()->Receive(cmd);
RunUntilIdle();
EXPECT_EQ(0u, send_count);
}
TEST_F(SignalingChannelTest, RejectRemoteResponseWithWrongType) {
constexpr CommandId kReqId = 1;
// Remote's response with the correct ID but wrong type of response.
const StaticByteBuffer rsp_invalid_id(
// Disconnection Response with plausible 4-byte payload.
0x07,
kReqId,
0x04,
0x00,
// Payload
0x0A,
0x00,
0x08,
0x00);
const StaticByteBuffer req_data('P', 'W', 'N');
bool tx_success = false;
fake_chan()->SetSendCallback([&tx_success](auto) { tx_success = true; },
dispatcher());
bool echo_cb_called = false;
EXPECT_TRUE(sig()->SendRequest(
kEchoRequest, req_data, [&echo_cb_called](auto, auto&) {
echo_cb_called = true;
return SignalingChannel::ResponseHandlerAction::
kCompleteOutboundTransaction;
}));
RunUntilIdle();
EXPECT_TRUE(tx_success);
const StaticByteBuffer reject_rsp(
// Command header (Command Rejected)
0x01,
kReqId,
0x02,
0x00,
// Reason (Command not understood)
0x00,
0x00);
bool reject_sent = false;
fake_chan()->SetSendCallback(
[&reject_rsp, &reject_sent](auto cb_packet) {
reject_sent = ContainersEqual(reject_rsp, *cb_packet);
},
dispatcher());
fake_chan()->Receive(rsp_invalid_id);
RunUntilIdle();
EXPECT_FALSE(echo_cb_called);
EXPECT_TRUE(reject_sent);
}
// Ensure that the signaling channel can reuse outgoing command IDs. In the case
// that it's expecting a response on every single valid command ID, requests
// should fail.
TEST_F(SignalingChannelTest, ReuseCommandIdsUntilExhausted) {
int req_count = 0;
constexpr CommandId kRspId = 0x0c;
auto check_header_id = [&req_count, kRspId](auto cb_packet) {
req_count++;
SignalingPacket sent_sig_pkt(cb_packet.get());
if (req_count == kMaxCommandId + 1) {
EXPECT_EQ(kRspId, sent_sig_pkt.header().id);
} else {
EXPECT_EQ(req_count, sent_sig_pkt.header().id);
}
};
fake_chan()->SetSendCallback(std::move(check_header_id), dispatcher());
const StaticByteBuffer req_data('y', 'o', 'o', 'o', 'o', '\0');
for (int i = 0; i < kMaxCommandId; i++) {
EXPECT_TRUE(
sig()->SendRequest(kEchoRequest, req_data, kTestResponseHandler));
}
// All command IDs should be exhausted at this point, so no commands of this
// type should be allowed to be sent.
EXPECT_FALSE(
sig()->SendRequest(kEchoRequest, req_data, kTestResponseHandler));
RunUntilIdle();
EXPECT_EQ(kMaxCommandId, req_count);
// Remote finally responds to a request, but not in order requests were sent.
// This will free a command ID.
const StaticByteBuffer echo_rsp(
// Echo response with no payload.
0x09,
kRspId,
0x00,
0x00);
fake_chan()->Receive(echo_rsp);
RunUntilIdle();
// Request should use freed command ID.
EXPECT_TRUE(sig()->SendRequest(kEchoRequest, req_data, kTestResponseHandler));
RunUntilIdle();
EXPECT_EQ(kMaxCommandId + 1, req_count);
}
// Ensure that a response handler may destroy the signaling channel.
TEST_F(SignalingChannelTest, ResponseHandlerThatDestroysSigDoesNotCrash) {
fake_chan()->SetSendCallback([](auto) {}, dispatcher());
const StaticByteBuffer req_data('h', 'e', 'l', 'l', 'o');
bool rx_success = false;
EXPECT_TRUE(sig()->SendRequest(
kEchoRequest, req_data, [this, &rx_success](Status, const ByteBuffer&) {
rx_success = true;
DestroySig();
return SignalingChannel::ResponseHandlerAction::
kCompleteOutboundTransaction;
}));
constexpr CommandId kReqId = 1;
const StaticByteBuffer echo_rsp(
// Command header (Echo Response, length 1)
kEchoResponse,
kReqId,
0x01,
0x00,
// Payload
0x23);
fake_chan()->Receive(echo_rsp);
RunUntilIdle();
EXPECT_FALSE(sig());
EXPECT_TRUE(rx_success);
}
// Ensure that the signaling channel plumbs a rejection command from remote to
// the appropriate response handler.
TEST_F(SignalingChannelTest, RemoteRejectionPassedToHandler) {
const StaticByteBuffer reject_rsp(
// Command header (Command Rejected)
0x01,
0x01,
0x02,
0x00,
// Reason (Command not understood)
0x00,
0x00);
bool tx_success = false;
fake_chan()->SetSendCallback([&tx_success](auto) { tx_success = true; },
dispatcher());
const StaticByteBuffer req_data('h', 'e', 'l', 'l', 'o');
bool rx_success = false;
EXPECT_TRUE(sig()->SendRequest(
kEchoRequest,
req_data,
[&rx_success, &reject_rsp](Status status, const ByteBuffer& rsp_payload) {
rx_success = true;
EXPECT_EQ(Status::kReject, status);
EXPECT_TRUE(ContainersEqual(reject_rsp.view(sizeof(CommandHeader)),
rsp_payload));
return SignalingChannel::ResponseHandlerAction::
kCompleteOutboundTransaction;
}));
RunUntilIdle();
EXPECT_TRUE(tx_success);
// Remote sends back a rejection.
fake_chan()->Receive(reject_rsp);
RunUntilIdle();
EXPECT_TRUE(rx_success);
}
TEST_F(SignalingChannelTest,
HandlerCompletedByResponseNotCalledAgainAfterRtxTimeout) {
bool tx_success = false;
fake_chan()->SetSendCallback([&tx_success](auto) { tx_success = true; },
dispatcher());
const StaticByteBuffer req_data('h', 'e', 'l', 'l', 'o');
int rx_cb_count = 0;
EXPECT_TRUE(sig()->SendRequest(
kEchoRequest, req_data, [&rx_cb_count](Status status, const ByteBuffer&) {
rx_cb_count++;
EXPECT_EQ(Status::kSuccess, status);
return SignalingChannel::ResponseHandlerAction::
kCompleteOutboundTransaction;
}));
const StaticByteBuffer echo_rsp(
// Echo response with no payload.
0x09,
0x01,
0x00,
0x00);
fake_chan()->Receive(echo_rsp);
RunUntilIdle();
EXPECT_TRUE(tx_success);
EXPECT_EQ(1, rx_cb_count);
RunFor(kSignalingChannelResponseTimeout);
EXPECT_EQ(1, rx_cb_count);
}
// Ensure that the signaling channel calls ResponseHandler with Status::kTimeOut
// after a request times out waiting for a peer response.
TEST_F(SignalingChannelTest,
CallHandlerCalledAfterMaxNumberOfRtxTimeoutRetransmissions) {
size_t send_cb_count = 0;
auto send_cb = [&](auto cb_packet) {
SignalingPacket pkt(cb_packet.get());
EXPECT_EQ(pkt.header().id, 1u);
send_cb_count++;
};
fake_chan()->SetSendCallback(std::move(send_cb), dispatcher());
const StaticByteBuffer req_data('h', 'e', 'l', 'l', 'o');
bool rx_cb_called = false;
EXPECT_TRUE(
sig()->SendRequest(kEchoRequest,
req_data,
[&rx_cb_called](Status status, const ByteBuffer&) {
rx_cb_called = true;
EXPECT_EQ(Status::kTimeOut, status);
return SignalingChannel::ResponseHandlerAction::
kCompleteOutboundTransaction;
}));
RunUntilIdle();
EXPECT_EQ(send_cb_count, 1u);
EXPECT_FALSE(rx_cb_called);
auto timeout = kSignalingChannelResponseTimeout;
for (size_t i = 1; i < kMaxSignalingChannelTransmissions; i++) {
// Ensure retransmission doesn't happen before timeout.
RunFor(timeout - std::chrono::milliseconds(100));
EXPECT_EQ(send_cb_count, i);
RunFor(std::chrono::milliseconds(100));
EXPECT_EQ(send_cb_count, 1 + i);
EXPECT_FALSE(rx_cb_called);
timeout *= 2;
}
send_cb_count = 0;
RunFor(timeout);
EXPECT_EQ(send_cb_count, 0u);
EXPECT_TRUE(rx_cb_called);
}
TEST_F(SignalingChannelTest, TwoResponsesToARetransmittedOutboundRequest) {
size_t send_cb_count = 0;
auto send_cb = [&](auto cb_packet) {
SignalingPacket pkt(cb_packet.get());
EXPECT_EQ(pkt.header().id, 1u);
send_cb_count++;
};
fake_chan()->SetSendCallback(std::move(send_cb), dispatcher());
const StaticByteBuffer req_data('h', 'e', 'l', 'l', 'o');
size_t rx_cb_count = 0;
EXPECT_TRUE(sig()->SendRequest(
kEchoRequest, req_data, [&rx_cb_count](Status status, const ByteBuffer&) {
rx_cb_count++;
EXPECT_EQ(Status::kSuccess, status);
return SignalingChannel::ResponseHandlerAction::
kCompleteOutboundTransaction;
}));
RunUntilIdle();
EXPECT_EQ(1u, send_cb_count);
EXPECT_EQ(0u, rx_cb_count);
RunFor(kSignalingChannelResponseTimeout);
EXPECT_EQ(2u, send_cb_count);
EXPECT_EQ(0u, rx_cb_count);
const StaticByteBuffer echo_rsp(kEchoResponse, 0x01, 0x00, 0x00);
fake_chan()->Receive(echo_rsp);
EXPECT_EQ(2u, send_cb_count);
EXPECT_EQ(1u, rx_cb_count);
// Second response should be ignored as it is unexpected.
fake_chan()->Receive(echo_rsp);
EXPECT_EQ(2u, send_cb_count);
EXPECT_EQ(1u, rx_cb_count);
}
// When the response handler expects more responses, use the longer ERTX timeout
// for the following response.
TEST_F(SignalingChannelTest,
ExpectAdditionalResponseExtendsRtxTimeoutToErtxTimeout) {
bool tx_success = false;
fake_chan()->SetSendCallback([&tx_success](auto) { tx_success = true; },
dispatcher());
const StaticByteBuffer req_data{'h', 'e', 'l', 'l', 'o'};
int rx_cb_calls = 0;
EXPECT_TRUE(sig()->SendRequest(
kEchoRequest, req_data, [&rx_cb_calls](Status status, const ByteBuffer&) {
rx_cb_calls++;
if (rx_cb_calls <= 2) {
EXPECT_EQ(Status::kSuccess, status);
} else {
EXPECT_EQ(Status::kTimeOut, status);
}
return SignalingChannel::ResponseHandlerAction::
kExpectAdditionalResponse;
}));
RunUntilIdle();
EXPECT_TRUE(tx_success);
EXPECT_EQ(0, rx_cb_calls);
const StaticByteBuffer echo_rsp(
// Echo response with no payload.
0x09,
0x01,
0x00,
0x00);
fake_chan()->Receive(echo_rsp);
EXPECT_EQ(1, rx_cb_calls);
// The handler expects more responses so the RTX timer shouldn't have expired.
RunFor(kSignalingChannelResponseTimeout);
fake_chan()->Receive(echo_rsp);
EXPECT_EQ(2, rx_cb_calls);
// The second response should have reset the ERTX timer, so it shouldn't fire
// yet.
RunFor(kSignalingChannelExtendedResponseTimeout -
std::chrono::milliseconds(100));
// If the renewed ERTX timer expires without a third response, receive a
// kTimeOut "response."
RunFor(std::chrono::seconds(1));
EXPECT_EQ(3, rx_cb_calls);
}
TEST_F(SignalingChannelTest, RegisterRequestResponder) {
const StaticByteBuffer remote_req(
// Disconnection Request.
0x06,
0x01,
0x04,
0x00,
// Payload
0x0A,
0x00,
0x08,
0x00);
const BufferView& expected_payload = remote_req.view(sizeof(CommandHeader));
auto expected_rej = StaticByteBuffer(
// Command header (Command rejected, length 2)
0x01,
0x01,
0x02,
0x00,
// Reason (Command not understood)
0x00,
0x00);
// Receive remote's request before a handler is assigned, expecting an
// outbound rejection.
ReceiveAndExpect(remote_req, expected_rej);
// Register the handler.
bool cb_called = false;
sig()->ServeRequest(
kDisconnectionRequest,
[&cb_called, &expected_payload](const ByteBuffer& req_payload,
SignalingChannel::Responder* responder) {
cb_called = true;
EXPECT_TRUE(ContainersEqual(expected_payload, req_payload));
responder->Send(req_payload);
});
const ByteBuffer& local_rsp = StaticByteBuffer(
// Disconnection Response.
0x07,
0x01,
0x04,
0x00,
// Payload
0x0A,
0x00,
0x08,
0x00);
// Receive the same command again.
ReceiveAndExpect(remote_req, local_rsp);
EXPECT_TRUE(cb_called);
}
TEST_F(SignalingChannelTest, DoNotRejectRemoteResponseInvalidId) {
// Request will use ID = 1.
constexpr CommandId kIncorrectId = 2;
// Remote's echo response that has a different ID to what will be in the
// request header.
const StaticByteBuffer rsp_invalid_id(
// Echo response with 4-byte payload.
0x09,
kIncorrectId,
0x04,
0x00,
// Payload
'L',
'3',
'3',
'T');
const BufferView req_data = rsp_invalid_id.view(sizeof(CommandHeader));
bool tx_success = false;
fake_chan()->SetSendCallback([&tx_success](auto) { tx_success = true; },
dispatcher());
bool echo_cb_called = false;
EXPECT_TRUE(sig()->SendRequest(
kEchoRequest, req_data, [&echo_cb_called](auto, auto&) {
echo_cb_called = true;
return SignalingChannel::ResponseHandlerAction::
kCompleteOutboundTransaction;
}));
RunUntilIdle();
EXPECT_TRUE(tx_success);
bool reject_sent = false;
fake_chan()->SetSendCallback(
[&reject_sent](auto cb_packet) { reject_sent = true; }, dispatcher());
fake_chan()->Receive(rsp_invalid_id);
RunUntilIdle();
EXPECT_FALSE(echo_cb_called);
EXPECT_FALSE(reject_sent);
}
} // namespace
} // namespace bt::l2cap::internal