blob: c9c8fad5ce85452128f366cb0c9c21d712b77c72 [file] [log] [blame]
// Copyright 2020 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/l2cap/command_handler.h"
#include "lib/gtest/test_loop_fixture.h"
#include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/fake_signaling_channel.h"
namespace bt::l2cap::internal {
namespace {
constexpr ChannelId kLocalCId = 0x0040;
constexpr ChannelId kRemoteCId = 0x60a3;
struct TestPayload {
uint8_t value;
};
class TestCommandHandler final : public CommandHandler {
public:
// Inherit ctor
using CommandHandler::CommandHandler;
// A response that decoding always fails for.
class UndecodableResponse final : public CommandHandler::Response {
public:
using PayloadT = TestPayload;
static constexpr const char* kName = "Undecodable Response";
using Response::Response; // Inherit ctor
bool Decode(const ByteBuffer& payload_buf) { return false; }
};
using UndecodableResponseCallback = fit::function<void(const UndecodableResponse& rsp)>;
bool SendRequestWithUndecodableResponse(CommandCode code, const ByteBuffer& payload,
UndecodableResponseCallback cb) {
auto on_rsp = BuildResponseHandler<UndecodableResponse>(std::move(cb));
return sig()->SendRequest(code, payload, std::move(on_rsp));
}
};
using TestBase = ::gtest::TestLoopFixture;
class L2CAP_CommandHandlerTest : public TestBase {
public:
L2CAP_CommandHandlerTest() = default;
~L2CAP_CommandHandlerTest() override = default;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(L2CAP_CommandHandlerTest);
protected:
// TestLoopFixture overrides
void SetUp() override {
TestBase::SetUp();
signaling_channel_ = std::make_unique<testing::FakeSignalingChannel>(dispatcher());
command_handler_ = std::make_unique<TestCommandHandler>(
fake_sig(), fit::bind_member(this, &L2CAP_CommandHandlerTest::OnRequestFail));
request_fail_callback_ = nullptr;
failed_requests_ = 0;
}
void TearDown() override {
request_fail_callback_ = nullptr;
signaling_channel_ = nullptr;
command_handler_ = nullptr;
TestBase::TearDown();
}
testing::FakeSignalingChannel* fake_sig() const { return signaling_channel_.get(); }
TestCommandHandler* cmd_handler() const { return command_handler_.get(); }
size_t failed_requests() const { return failed_requests_; }
void set_request_fail_callback(fit::closure request_fail_callback) {
ZX_ASSERT(!request_fail_callback_);
request_fail_callback_ = std::move(request_fail_callback);
}
private:
void OnRequestFail() {
failed_requests_++;
if (request_fail_callback_) {
request_fail_callback_();
}
}
std::unique_ptr<testing::FakeSignalingChannel> signaling_channel_;
std::unique_ptr<TestCommandHandler> command_handler_;
fit::closure request_fail_callback_;
size_t failed_requests_;
};
TEST_F(L2CAP_CommandHandlerTest, OutboundDisconReqRspOk) {
// Disconnect Request payload
auto expected_discon_req = StaticByteBuffer(
// Destination CID
LowerBits(kRemoteCId), UpperBits(kRemoteCId),
// Source CID
LowerBits(kLocalCId), UpperBits(kLocalCId));
// Disconnect Response payload
// Channel endpoint roles (source, destination) are relative to requester so
// the response's payload should be the same as the request's
const ByteBuffer& ok_discon_rsp = expected_discon_req;
EXPECT_OUTBOUND_REQ(*fake_sig(), kDisconnectionRequest, expected_discon_req.view(),
{SignalingChannel::Status::kSuccess, ok_discon_rsp.view()});
bool cb_called = false;
CommandHandler::DisconnectionResponseCallback on_discon_rsp =
[&cb_called](const CommandHandler::DisconnectionResponse& rsp) {
cb_called = true;
EXPECT_EQ(SignalingChannel::Status::kSuccess, rsp.status());
EXPECT_EQ(kLocalCId, rsp.local_cid());
EXPECT_EQ(kRemoteCId, rsp.remote_cid());
};
EXPECT_TRUE(
cmd_handler()->SendDisconnectionRequest(kRemoteCId, kLocalCId, std::move(on_discon_rsp)));
RunLoopUntilIdle();
EXPECT_TRUE(cb_called);
}
TEST_F(L2CAP_CommandHandlerTest, OutboundDisconReqRej) {
// Disconnect Request payload
auto expected_discon_req = StaticByteBuffer(
// Destination CID (relative to requester)
LowerBits(kRemoteCId), UpperBits(kRemoteCId),
// Source CID (relative to requester)
LowerBits(kLocalCId), UpperBits(kLocalCId));
// Command Reject payload
auto rej_cid = StaticByteBuffer(
// Reject Reason (invalid channel ID)
LowerBits(static_cast<uint16_t>(RejectReason::kInvalidCID)),
UpperBits(static_cast<uint16_t>(RejectReason::kInvalidCID)),
// Source CID (relative to rejecter)
LowerBits(kRemoteCId), UpperBits(kRemoteCId),
// Destination CID (relative to rejecter)
LowerBits(kLocalCId), UpperBits(kLocalCId));
EXPECT_OUTBOUND_REQ(*fake_sig(), kDisconnectionRequest, expected_discon_req.view(),
{SignalingChannel::Status::kReject, rej_cid.view()});
bool cb_called = false;
CommandHandler::DisconnectionResponseCallback on_discon_rsp =
[&cb_called](const CommandHandler::DisconnectionResponse& rsp) {
cb_called = true;
EXPECT_EQ(SignalingChannel::Status::kReject, rsp.status());
EXPECT_EQ(RejectReason::kInvalidCID, rsp.reject_reason());
EXPECT_EQ(kLocalCId, rsp.local_cid());
EXPECT_EQ(kRemoteCId, rsp.remote_cid());
};
EXPECT_TRUE(
cmd_handler()->SendDisconnectionRequest(kRemoteCId, kLocalCId, std::move(on_discon_rsp)));
RunLoopUntilIdle();
EXPECT_TRUE(cb_called);
}
TEST_F(L2CAP_CommandHandlerTest, OutboundDisconReqRejNotEnoughBytes) {
constexpr ChannelId kBadLocalCId = 0x0005; // Not a dynamic channel
// Disconnect Request payload
auto expected_discon_req = StaticByteBuffer(
// Destination CID
LowerBits(kRemoteCId), UpperBits(kRemoteCId),
// Source CID
LowerBits(kBadLocalCId), UpperBits(kBadLocalCId));
// Invalid Command Reject payload (size is too small)
auto rej_rsp = StaticByteBuffer(0x01);
EXPECT_OUTBOUND_REQ(*fake_sig(), kDisconnectionRequest, expected_discon_req.view(),
{SignalingChannel::Status::kReject, rej_rsp.view()});
bool cb_called = false;
auto on_discon_rsp = [&cb_called](const CommandHandler::DisconnectionResponse& rsp) {
cb_called = true;
};
EXPECT_TRUE(
cmd_handler()->SendDisconnectionRequest(kRemoteCId, kBadLocalCId, std::move(on_discon_rsp)));
RunLoopUntilIdle();
EXPECT_FALSE(cb_called);
}
TEST_F(L2CAP_CommandHandlerTest, OutboundDisconReqRejInvalidCIDNotEnoughBytes) {
constexpr ChannelId kBadLocalCId = 0x0005; // Not a dynamic channel
// Disconnect Request payload
auto expected_discon_req = StaticByteBuffer(
// Destination CID
LowerBits(kRemoteCId), UpperBits(kRemoteCId),
// Source CID
LowerBits(kBadLocalCId), UpperBits(kBadLocalCId));
// Command Reject payload (the invalid channel IDs are missing)
auto rej_rsp = StaticByteBuffer(
// Reject Reason (invalid channel ID)
LowerBits(static_cast<uint16_t>(RejectReason::kInvalidCID)),
UpperBits(static_cast<uint16_t>(RejectReason::kInvalidCID)));
EXPECT_OUTBOUND_REQ(*fake_sig(), kDisconnectionRequest, expected_discon_req.view(),
{SignalingChannel::Status::kReject, rej_rsp.view()});
bool cb_called = false;
auto on_discon_rsp = [&cb_called](const CommandHandler::DisconnectionResponse& rsp) {
cb_called = true;
};
EXPECT_TRUE(
cmd_handler()->SendDisconnectionRequest(kRemoteCId, kBadLocalCId, std::move(on_discon_rsp)));
RunLoopUntilIdle();
EXPECT_FALSE(cb_called);
}
TEST_F(L2CAP_CommandHandlerTest, InboundDisconReqRspOk) {
CommandHandler::DisconnectionRequestCallback cb = [](ChannelId local_cid, ChannelId remote_cid,
auto responder) {
EXPECT_EQ(kLocalCId, local_cid);
EXPECT_EQ(kRemoteCId, remote_cid);
responder->Send();
};
cmd_handler()->ServeDisconnectionRequest(std::move(cb));
// Disconnection Request payload
auto discon_req = StaticByteBuffer(
// Destination CID (relative to requester)
LowerBits(kLocalCId), UpperBits(kLocalCId),
// Source CID (relative to requester)
LowerBits(kRemoteCId), UpperBits(kRemoteCId));
// Disconnection Response payload is identical to request payload.
auto expected_rsp = discon_req;
RETURN_IF_FATAL(fake_sig()->ReceiveExpect(kDisconnectionRequest, discon_req, expected_rsp));
}
TEST_F(L2CAP_CommandHandlerTest, InboundDisconReqRej) {
CommandHandler::DisconnectionRequestCallback cb = [](ChannelId local_cid, ChannelId remote_cid,
auto responder) {
EXPECT_EQ(kLocalCId, local_cid);
EXPECT_EQ(kRemoteCId, remote_cid);
responder->RejectInvalidChannelId();
};
cmd_handler()->ServeDisconnectionRequest(std::move(cb));
// Disconnection Request payload
auto discon_req = StaticByteBuffer(
// Destination CID (relative to requester)
LowerBits(kLocalCId), UpperBits(kLocalCId),
// Source CID (relative to requester)
LowerBits(kRemoteCId), UpperBits(kRemoteCId));
// Disconnection Response payload
auto expected_rsp = discon_req;
RETURN_IF_FATAL(fake_sig()->ReceiveExpectRejectInvalidChannelId(kDisconnectionRequest, discon_req,
kLocalCId, kRemoteCId));
}
TEST_F(L2CAP_CommandHandlerTest, OutboundDisconReqRspPayloadNotEnoughBytes) {
// Disconnect Request payload
auto expected_discon_req = StaticByteBuffer(
// Destination CID
LowerBits(kRemoteCId), UpperBits(kRemoteCId),
// Source CID
LowerBits(kLocalCId), UpperBits(kLocalCId));
// Disconnect Response payload (should include Source CID)
auto malformed_discon_rsp = StaticByteBuffer(
// Destination CID
LowerBits(kRemoteCId), UpperBits(kRemoteCId));
EXPECT_OUTBOUND_REQ(*fake_sig(), kDisconnectionRequest, expected_discon_req.view(),
{SignalingChannel::Status::kSuccess, malformed_discon_rsp.view()});
bool cb_called = false;
auto on_discon_cb = [&cb_called](const CommandHandler::DisconnectionResponse& rsp) {
cb_called = true;
};
EXPECT_TRUE(
cmd_handler()->SendDisconnectionRequest(kRemoteCId, kLocalCId, std::move(on_discon_cb)));
RunLoopUntilIdle();
EXPECT_FALSE(cb_called);
}
TEST_F(L2CAP_CommandHandlerTest, OutboundReqRspDecodeError) {
auto payload = StaticByteBuffer(0x00);
EXPECT_OUTBOUND_REQ(*fake_sig(), kDisconnectionRequest, payload.view(),
{SignalingChannel::Status::kSuccess, payload.view()});
bool cb_called = false;
auto on_rsp_cb = [&cb_called](const TestCommandHandler::UndecodableResponse& rsp) {
cb_called = true;
};
EXPECT_TRUE(cmd_handler()->SendRequestWithUndecodableResponse(kDisconnectionRequest, payload,
std::move(on_rsp_cb)));
RunLoopUntilIdle();
EXPECT_FALSE(cb_called);
}
TEST_F(L2CAP_CommandHandlerTest, OutboundDisconReqRspTimeOut) {
// Disconnect Request payload
auto expected_discon_req = StaticByteBuffer(
// Destination CID
LowerBits(kRemoteCId), UpperBits(kRemoteCId),
// Source CID
LowerBits(kLocalCId), UpperBits(kLocalCId));
EXPECT_OUTBOUND_REQ(*fake_sig(), kDisconnectionRequest, expected_discon_req.view(),
{SignalingChannel::Status::kTimeOut, {}});
EXPECT_OUTBOUND_REQ(*fake_sig(), kDisconnectionRequest, expected_discon_req.view());
set_request_fail_callback([this]() {
// Should still be allowed to send requests even after one failed
auto on_discon_rsp = [](auto&) {};
EXPECT_TRUE(
cmd_handler()->SendDisconnectionRequest(kRemoteCId, kLocalCId, std::move(on_discon_rsp)));
});
auto on_discon_rsp = [](auto&) { ADD_FAILURE(); };
EXPECT_TRUE(
cmd_handler()->SendDisconnectionRequest(kRemoteCId, kLocalCId, std::move(on_discon_rsp)));
ASSERT_EQ(0u, failed_requests());
RETURN_IF_FATAL(RunLoopUntilIdle());
EXPECT_EQ(1u, failed_requests());
}
TEST_F(L2CAP_CommandHandlerTest, RejectInvalidChannelId) {
CommandHandler::DisconnectionRequestCallback cb =
[](ChannelId local_cid, ChannelId remote_cid,
CommandHandler::DisconnectionResponder* responder) {
responder->RejectInvalidChannelId();
};
cmd_handler()->ServeDisconnectionRequest(std::move(cb));
// Disconnection Request payload
auto discon_req = StaticByteBuffer(
// Destination CID (relative to requester)
LowerBits(kLocalCId), UpperBits(kLocalCId),
// Source CID (relative to requester)
LowerBits(kRemoteCId), UpperBits(kRemoteCId));
RETURN_IF_FATAL(fake_sig()->ReceiveExpectRejectInvalidChannelId(kDisconnectionRequest, discon_req,
kLocalCId, kRemoteCId));
}
} // namespace
} // namespace bt::l2cap::internal