// Copyright 2023 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_bluetooth_sapphire/internal/host/l2cap/signaling_channel.h" #include #include #include #include "pw_bluetooth_sapphire/internal/host/l2cap/fake_channel_test.h" #include "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::max(); const auto kTestResponseHandler = [](Status, const ByteBuffer&) { 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 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 { PW_CHECK(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( 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 sig_; // Own the fake channel so that its lifetime can span beyond that of |sig_|. std::unique_ptr 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(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) { 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