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