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