• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_bluetooth_sapphire/internal/host/l2cap/command_handler.h"
16 
17 #include <pw_assert/check.h>
18 #include <pw_async/fake_dispatcher_fixture.h>
19 
20 #include "pw_bluetooth_sapphire/internal/host/l2cap/fake_signaling_channel.h"
21 #include "pw_bluetooth_sapphire/internal/host/testing/gtest_helpers.h"
22 #include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
23 
24 namespace bt::l2cap::internal {
25 namespace {
26 
27 constexpr ChannelId kLocalCId = 0x0040;
28 constexpr ChannelId kRemoteCId = 0x60a3;
29 
30 struct TestPayload {
31   uint8_t value;
32 };
33 
34 class TestCommandHandler final : public CommandHandler {
35  public:
36   // Inherit ctor
37   using CommandHandler::CommandHandler;
38 
39   // A response that decoding always fails for.
40   class UndecodableResponse final : public CommandHandler::Response {
41    public:
42     using PayloadT = TestPayload;
43     static constexpr const char* kName = "Undecodable Response";
44 
45     using Response::Response;  // Inherit ctor
Decode(const ByteBuffer &)46     bool Decode(const ByteBuffer&) { return false; }
47   };
48 
49   using UndecodableResponseCallback =
50       fit::function<void(const UndecodableResponse& rsp)>;
51 
SendRequestWithUndecodableResponse(CommandCode code,const ByteBuffer & payload,UndecodableResponseCallback cb)52   bool SendRequestWithUndecodableResponse(CommandCode code,
53                                           const ByteBuffer& payload,
54                                           UndecodableResponseCallback cb) {
55     auto on_rsp = BuildResponseHandler<UndecodableResponse>(std::move(cb));
56     return sig()->SendRequest(code, payload, std::move(on_rsp));
57   }
58 };
59 
60 class CommandHandlerTest : public pw::async::test::FakeDispatcherFixture {
61  public:
62   CommandHandlerTest() = default;
63   ~CommandHandlerTest() override = default;
64   BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(CommandHandlerTest);
65 
66  protected:
67   // TestLoopFixture overrides
SetUp()68   void SetUp() override {
69     signaling_channel_ =
70         std::make_unique<testing::FakeSignalingChannel>(dispatcher());
71     command_handler_ = std::make_unique<TestCommandHandler>(
72         fake_sig(), fit::bind_member<&CommandHandlerTest::OnRequestFail>(this));
73     request_fail_callback_ = nullptr;
74     failed_requests_ = 0;
75   }
76 
TearDown()77   void TearDown() override {
78     request_fail_callback_ = nullptr;
79     signaling_channel_ = nullptr;
80     command_handler_ = nullptr;
81   }
82 
fake_sig() const83   testing::FakeSignalingChannel* fake_sig() const {
84     return signaling_channel_.get();
85   }
cmd_handler() const86   TestCommandHandler* cmd_handler() const { return command_handler_.get(); }
failed_requests() const87   size_t failed_requests() const { return failed_requests_; }
88 
set_request_fail_callback(fit::closure request_fail_callback)89   void set_request_fail_callback(fit::closure request_fail_callback) {
90     PW_CHECK(!request_fail_callback_);
91     request_fail_callback_ = std::move(request_fail_callback);
92   }
93 
94  private:
OnRequestFail()95   void OnRequestFail() {
96     failed_requests_++;
97     if (request_fail_callback_) {
98       request_fail_callback_();
99     }
100   }
101 
102   std::unique_ptr<testing::FakeSignalingChannel> signaling_channel_;
103   std::unique_ptr<TestCommandHandler> command_handler_;
104   fit::closure request_fail_callback_;
105   size_t failed_requests_;
106 };
107 
TEST_F(CommandHandlerTest,OutboundDisconReqRspOk)108 TEST_F(CommandHandlerTest, OutboundDisconReqRspOk) {
109   // Disconnect Request payload
110   StaticByteBuffer expected_discon_req(
111       // Destination CID
112       LowerBits(kRemoteCId),
113       UpperBits(kRemoteCId),
114 
115       // Source CID
116       LowerBits(kLocalCId),
117       UpperBits(kLocalCId));
118 
119   // Disconnect Response payload
120   // Channel endpoint roles (source, destination) are relative to requester so
121   // the response's payload should be the same as the request's
122   const ByteBuffer& ok_discon_rsp = expected_discon_req;
123 
124   EXPECT_OUTBOUND_REQ(
125       *fake_sig(),
126       kDisconnectionRequest,
127       expected_discon_req.view(),
128       {SignalingChannel::Status::kSuccess, ok_discon_rsp.view()});
129 
130   bool cb_called = false;
131   CommandHandler::DisconnectionResponseCallback on_discon_rsp =
132       [&cb_called](const CommandHandler::DisconnectionResponse& rsp) {
133         cb_called = true;
134         EXPECT_EQ(SignalingChannel::Status::kSuccess, rsp.status());
135         EXPECT_EQ(kLocalCId, rsp.local_cid());
136         EXPECT_EQ(kRemoteCId, rsp.remote_cid());
137       };
138 
139   EXPECT_TRUE(cmd_handler()->SendDisconnectionRequest(
140       kRemoteCId, kLocalCId, std::move(on_discon_rsp)));
141   RunUntilIdle();
142   EXPECT_TRUE(cb_called);
143 }
144 
TEST_F(CommandHandlerTest,OutboundDisconReqRej)145 TEST_F(CommandHandlerTest, OutboundDisconReqRej) {
146   // Disconnect Request payload
147   StaticByteBuffer expected_discon_req(
148       // Destination CID (relative to requester)
149       LowerBits(kRemoteCId),
150       UpperBits(kRemoteCId),
151 
152       // Source CID (relative to requester)
153       LowerBits(kLocalCId),
154       UpperBits(kLocalCId));
155 
156   // Command Reject payload
157   StaticByteBuffer rej_cid(
158       // Reject Reason (invalid channel ID)
159       LowerBits(static_cast<uint16_t>(RejectReason::kInvalidCID)),
160       UpperBits(static_cast<uint16_t>(RejectReason::kInvalidCID)),
161 
162       // Source CID (relative to rejecter)
163       LowerBits(kRemoteCId),
164       UpperBits(kRemoteCId),
165 
166       // Destination CID (relative to rejecter)
167       LowerBits(kLocalCId),
168       UpperBits(kLocalCId));
169 
170   EXPECT_OUTBOUND_REQ(*fake_sig(),
171                       kDisconnectionRequest,
172                       expected_discon_req.view(),
173                       {SignalingChannel::Status::kReject, rej_cid.view()});
174 
175   bool cb_called = false;
176   CommandHandler::DisconnectionResponseCallback on_discon_rsp =
177       [&cb_called](const CommandHandler::DisconnectionResponse& rsp) {
178         cb_called = true;
179         EXPECT_EQ(SignalingChannel::Status::kReject, rsp.status());
180         EXPECT_EQ(RejectReason::kInvalidCID, rsp.reject_reason());
181         EXPECT_EQ(kLocalCId, rsp.local_cid());
182         EXPECT_EQ(kRemoteCId, rsp.remote_cid());
183       };
184 
185   EXPECT_TRUE(cmd_handler()->SendDisconnectionRequest(
186       kRemoteCId, kLocalCId, std::move(on_discon_rsp)));
187   RunUntilIdle();
188   EXPECT_TRUE(cb_called);
189 }
190 
TEST_F(CommandHandlerTest,OutboundDisconReqRejNotEnoughBytes)191 TEST_F(CommandHandlerTest, OutboundDisconReqRejNotEnoughBytes) {
192   constexpr ChannelId kBadLocalCId = 0x0005;  // Not a dynamic channel
193 
194   // Disconnect Request payload
195   auto expected_discon_req = StaticByteBuffer(
196       // Destination CID
197       LowerBits(kRemoteCId),
198       UpperBits(kRemoteCId),
199 
200       // Source CID
201       LowerBits(kBadLocalCId),
202       UpperBits(kBadLocalCId));
203 
204   // Invalid Command Reject payload (size is too small)
205   auto rej_rsp = StaticByteBuffer(0x01);
206 
207   EXPECT_OUTBOUND_REQ(*fake_sig(),
208                       kDisconnectionRequest,
209                       expected_discon_req.view(),
210                       {SignalingChannel::Status::kReject, rej_rsp.view()});
211 
212   bool cb_called = false;
213   auto on_discon_rsp =
214       [&cb_called](const CommandHandler::DisconnectionResponse&) {
215         cb_called = true;
216       };
217 
218   EXPECT_TRUE(cmd_handler()->SendDisconnectionRequest(
219       kRemoteCId, kBadLocalCId, std::move(on_discon_rsp)));
220   RunUntilIdle();
221   EXPECT_FALSE(cb_called);
222 }
223 
TEST_F(CommandHandlerTest,OutboundDisconReqRejInvalidCIDNotEnoughBytes)224 TEST_F(CommandHandlerTest, OutboundDisconReqRejInvalidCIDNotEnoughBytes) {
225   constexpr ChannelId kBadLocalCId = 0x0005;  // Not a dynamic channel
226 
227   // Disconnect Request payload
228   auto expected_discon_req = StaticByteBuffer(
229       // Destination CID
230       LowerBits(kRemoteCId),
231       UpperBits(kRemoteCId),
232 
233       // Source CID
234       LowerBits(kBadLocalCId),
235       UpperBits(kBadLocalCId));
236 
237   // Command Reject payload (the invalid channel IDs are missing)
238   auto rej_rsp = StaticByteBuffer(
239       // Reject Reason (invalid channel ID)
240       LowerBits(static_cast<uint16_t>(RejectReason::kInvalidCID)),
241       UpperBits(static_cast<uint16_t>(RejectReason::kInvalidCID)));
242 
243   EXPECT_OUTBOUND_REQ(*fake_sig(),
244                       kDisconnectionRequest,
245                       expected_discon_req.view(),
246                       {SignalingChannel::Status::kReject, rej_rsp.view()});
247 
248   bool cb_called = false;
249   auto on_discon_rsp =
250       [&cb_called](const CommandHandler::DisconnectionResponse&) {
251         cb_called = true;
252       };
253 
254   EXPECT_TRUE(cmd_handler()->SendDisconnectionRequest(
255       kRemoteCId, kBadLocalCId, std::move(on_discon_rsp)));
256   RunUntilIdle();
257   EXPECT_FALSE(cb_called);
258 }
259 
TEST_F(CommandHandlerTest,InboundDisconReqRspOk)260 TEST_F(CommandHandlerTest, InboundDisconReqRspOk) {
261   CommandHandler::DisconnectionRequestCallback cb =
262       [](ChannelId local_cid, ChannelId remote_cid, auto responder) {
263         EXPECT_EQ(kLocalCId, local_cid);
264         EXPECT_EQ(kRemoteCId, remote_cid);
265         responder->Send();
266       };
267   cmd_handler()->ServeDisconnectionRequest(std::move(cb));
268 
269   // Disconnection Request payload
270   auto discon_req = StaticByteBuffer(
271       // Destination CID (relative to requester)
272       LowerBits(kLocalCId),
273       UpperBits(kLocalCId),
274 
275       // Source CID (relative to requester)
276       LowerBits(kRemoteCId),
277       UpperBits(kRemoteCId));
278 
279   // Disconnection Response payload is identical to request payload.
280   auto expected_rsp = discon_req;
281 
282   RETURN_IF_FATAL(fake_sig()->ReceiveExpect(
283       kDisconnectionRequest, discon_req, expected_rsp));
284 }
285 
TEST_F(CommandHandlerTest,InboundDisconReqRej)286 TEST_F(CommandHandlerTest, InboundDisconReqRej) {
287   CommandHandler::DisconnectionRequestCallback cb =
288       [](ChannelId local_cid, ChannelId remote_cid, auto responder) {
289         EXPECT_EQ(kLocalCId, local_cid);
290         EXPECT_EQ(kRemoteCId, remote_cid);
291         responder->RejectInvalidChannelId();
292       };
293   cmd_handler()->ServeDisconnectionRequest(std::move(cb));
294 
295   // Disconnection Request payload
296   auto discon_req = StaticByteBuffer(
297       // Destination CID (relative to requester)
298       LowerBits(kLocalCId),
299       UpperBits(kLocalCId),
300 
301       // Source CID (relative to requester)
302       LowerBits(kRemoteCId),
303       UpperBits(kRemoteCId));
304 
305   // Disconnection Response payload
306   auto expected_rsp = discon_req;
307 
308   RETURN_IF_FATAL(fake_sig()->ReceiveExpectRejectInvalidChannelId(
309       kDisconnectionRequest, discon_req, kLocalCId, kRemoteCId));
310 }
311 
TEST_F(CommandHandlerTest,OutboundDisconReqRspPayloadNotEnoughBytes)312 TEST_F(CommandHandlerTest, OutboundDisconReqRspPayloadNotEnoughBytes) {
313   // Disconnect Request payload
314   auto expected_discon_req = StaticByteBuffer(
315       // Destination CID
316       LowerBits(kRemoteCId),
317       UpperBits(kRemoteCId),
318 
319       // Source CID
320       LowerBits(kLocalCId),
321       UpperBits(kLocalCId));
322 
323   // Disconnect Response payload (should include Source CID)
324   auto malformed_discon_rsp = StaticByteBuffer(
325       // Destination CID
326       LowerBits(kRemoteCId),
327       UpperBits(kRemoteCId));
328 
329   EXPECT_OUTBOUND_REQ(
330       *fake_sig(),
331       kDisconnectionRequest,
332       expected_discon_req.view(),
333       {SignalingChannel::Status::kSuccess, malformed_discon_rsp.view()});
334 
335   bool cb_called = false;
336   auto on_discon_cb =
337       [&cb_called](const CommandHandler::DisconnectionResponse&) {
338         cb_called = true;
339       };
340 
341   EXPECT_TRUE(cmd_handler()->SendDisconnectionRequest(
342       kRemoteCId, kLocalCId, std::move(on_discon_cb)));
343   RunUntilIdle();
344   EXPECT_FALSE(cb_called);
345 }
346 
TEST_F(CommandHandlerTest,OutboundReqRspDecodeError)347 TEST_F(CommandHandlerTest, OutboundReqRspDecodeError) {
348   auto payload = StaticByteBuffer(0x00);
349   EXPECT_OUTBOUND_REQ(*fake_sig(),
350                       kDisconnectionRequest,
351                       payload.view(),
352                       {SignalingChannel::Status::kSuccess, payload.view()});
353 
354   bool cb_called = false;
355   auto on_rsp_cb =
356       [&cb_called](const TestCommandHandler::UndecodableResponse&) {
357         cb_called = true;
358       };
359 
360   EXPECT_TRUE(cmd_handler()->SendRequestWithUndecodableResponse(
361       kDisconnectionRequest, payload, std::move(on_rsp_cb)));
362   RunUntilIdle();
363   EXPECT_FALSE(cb_called);
364 }
365 
TEST_F(CommandHandlerTest,OutboundDisconReqRspTimeOut)366 TEST_F(CommandHandlerTest, OutboundDisconReqRspTimeOut) {
367   // Disconnect Request payload
368   auto expected_discon_req = StaticByteBuffer(
369       // Destination CID
370       LowerBits(kRemoteCId),
371       UpperBits(kRemoteCId),
372 
373       // Source CID
374       LowerBits(kLocalCId),
375       UpperBits(kLocalCId));
376 
377   EXPECT_OUTBOUND_REQ(*fake_sig(),
378                       kDisconnectionRequest,
379                       expected_discon_req.view(),
380                       {SignalingChannel::Status::kTimeOut, {}});
381   EXPECT_OUTBOUND_REQ(
382       *fake_sig(), kDisconnectionRequest, expected_discon_req.view());
383 
384   set_request_fail_callback([this]() {
385     // Should still be allowed to send requests even after one failed
386     auto on_discon_rsp = [](auto&) {};
387     EXPECT_TRUE(cmd_handler()->SendDisconnectionRequest(
388         kRemoteCId, kLocalCId, std::move(on_discon_rsp)));
389   });
390 
391   auto on_discon_rsp = [](auto&) { ADD_FAILURE(); };
392 
393   EXPECT_TRUE(cmd_handler()->SendDisconnectionRequest(
394       kRemoteCId, kLocalCId, std::move(on_discon_rsp)));
395 
396   ASSERT_EQ(0u, failed_requests());
397   RETURN_IF_FATAL(RunUntilIdle());
398   EXPECT_EQ(1u, failed_requests());
399 }
400 
TEST_F(CommandHandlerTest,RejectInvalidChannelId)401 TEST_F(CommandHandlerTest, RejectInvalidChannelId) {
402   CommandHandler::DisconnectionRequestCallback cb =
403       [](ChannelId,
404          ChannelId,
405          CommandHandler::DisconnectionResponder* responder) {
406         responder->RejectInvalidChannelId();
407       };
408   cmd_handler()->ServeDisconnectionRequest(std::move(cb));
409 
410   // Disconnection Request payload
411   auto discon_req = StaticByteBuffer(
412       // Destination CID (relative to requester)
413       LowerBits(kLocalCId),
414       UpperBits(kLocalCId),
415 
416       // Source CID (relative to requester)
417       LowerBits(kRemoteCId),
418       UpperBits(kRemoteCId));
419 
420   RETURN_IF_FATAL(fake_sig()->ReceiveExpectRejectInvalidChannelId(
421       kDisconnectionRequest, discon_req, kLocalCId, kRemoteCId));
422 }
423 
424 }  // namespace
425 }  // namespace bt::l2cap::internal
426