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