• 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/gap/bredr_connection_request.h"
16 
17 #include <gmock/gmock.h>
18 #include <gtest/gtest.h>
19 #include <pw_async/fake_dispatcher_fixture.h>
20 #include <pw_bluetooth/hci_commands.emb.h>
21 #include <pw_chrono/system_clock.h>
22 
23 #include "pw_bluetooth_sapphire/internal/host/common/device_address.h"
24 #include "pw_bluetooth_sapphire/internal/host/testing/controller_test.h"
25 #include "pw_bluetooth_sapphire/internal/host/testing/inspect.h"
26 #include "pw_bluetooth_sapphire/internal/host/testing/mock_controller.h"
27 #include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
28 #include "pw_bluetooth_sapphire/internal/host/testing/test_packets.h"
29 
30 namespace bt::gap {
31 namespace {
32 
33 using namespace inspect::testing;
34 
35 const DeviceAddress kTestAddr(DeviceAddress::Type::kBREDR, {1});
36 const PeerId kPeerId;
37 constexpr hci::Error RetryableError =
38     ToResult(pw::bluetooth::emboss::StatusCode::PAGE_TIMEOUT).error_value();
39 const StaticByteBuffer kCreateConnectionRsp(
40     hci_spec::kCommandStatusEventCode,  // event code
41     0x04,
42     pw::bluetooth::emboss::StatusCode::SUCCESS,  // status
43     0xF0,                                        // num_hci_command_packets
44     LowerBits(hci_spec::kCreateConnection),
45     UpperBits(hci_spec::kCreateConnection));
46 
47 using BrEdrConnectionRequestTests = pw::async::test::FakeDispatcherFixture;
48 
TEST_F(BrEdrConnectionRequestTests,IncomingRequestStatusTracked)49 TEST_F(BrEdrConnectionRequestTests, IncomingRequestStatusTracked) {
50   // A freshly created request is not yet incoming
51   auto req = BrEdrConnectionRequest(dispatcher(),
52                                     kTestAddr,
53                                     kPeerId,
54                                     Peer::InitializingConnectionToken([] {}));
55   EXPECT_FALSE(req.HasIncoming());
56 
57   req.BeginIncoming();
58   // We should now have an incoming request, but still not an outgoing
59   EXPECT_TRUE(req.HasIncoming());
60   EXPECT_FALSE(req.AwaitingOutgoing());
61 
62   // A completed request is no longer incoming
63   req.CompleteIncoming();
64   EXPECT_FALSE(req.HasIncoming());
65 }
66 
TEST_F(BrEdrConnectionRequestTests,CallbacksExecuted)67 TEST_F(BrEdrConnectionRequestTests, CallbacksExecuted) {
68   bool callback_called = false;
69   bool token_destroyed = false;
70   auto req = BrEdrConnectionRequest(
71       dispatcher(),
72       kTestAddr,
73       kPeerId,
74       Peer::InitializingConnectionToken(
75           [&token_destroyed] { token_destroyed = true; }),
76       [&callback_called](auto, auto) { callback_called = true; });
77 
78   // A freshly created request with a callback is awaiting outgoing
79   EXPECT_TRUE(req.AwaitingOutgoing());
80   // Notifying callbacks triggers the callback
81   req.NotifyCallbacks(fit::ok(), [&]() {
82     EXPECT_TRUE(token_destroyed);
83     return nullptr;
84   });
85   EXPECT_TRUE(token_destroyed);
86   EXPECT_TRUE(callback_called);
87 }
88 
89 #ifndef NINSPECT
TEST_F(BrEdrConnectionRequestTests,Inspect)90 TEST_F(BrEdrConnectionRequestTests, Inspect) {
91   // inspector must outlive request
92   inspect::Inspector inspector;
93   BrEdrConnectionRequest req(dispatcher(),
94                              kTestAddr,
95                              kPeerId,
96                              Peer::InitializingConnectionToken([] {}),
97                              [](auto, auto) {});
98   req.BeginIncoming();
99   req.AttachInspect(inspector.GetRoot(), "request_name");
100 
101   auto hierarchy = inspect::ReadFromVmo(inspector.DuplicateVmo());
102   EXPECT_THAT(
103       hierarchy.value(),
104       ChildrenMatch(ElementsAre(NodeMatches(AllOf(
105           NameMatches("request_name"),
106           PropertyList(UnorderedElementsAre(
107               StringIs("peer_id", kPeerId.ToString()),
108               UintIs("callbacks", 1u),
109               BoolIs("has_incoming", true),
110               IntIs("first_create_connection_request_timestamp", -1))))))));
111 }
112 #endif  // NINSPECT
113 
114 using TestingBase =
115     testing::FakeDispatcherControllerTest<testing::MockController>;
116 class BrEdrConnectionRequestLoopTest : public TestingBase {
117  protected:
BrEdrConnectionRequestLoopTest()118   BrEdrConnectionRequestLoopTest()
119       : req_(dispatcher(),
120              kTestAddr,
121              kPeerId,
122              Peer::InitializingConnectionToken([] {}),
__anon87988dee0902(hci::Result<> res, BrEdrConnection* conn) 123              [this](hci::Result<> res, BrEdrConnection* conn) {
124                if (handler_) {
125                  handler_(res, conn);
126                }
127              }) {
128     TestingBase::SetUp();
129 
130     // By default, an outbound ConnectionRequest with a complete handler that
131     // just logs the result.
__anon87988dee0a02(hci::Result<> res, auto ) 132     handler_ = [](hci::Result<> res, auto /*ignore*/) {
133       bt_log(INFO,
134              "gap-bredr-test",
135              "outbound connection request complete: %s",
136              bt_str(res));
137     };
138   }
139 
connection_req()140   BrEdrConnectionRequest& connection_req() { return req_; }
141 
142  private:
143   BrEdrConnectionRequest req_;
144   BrEdrConnectionRequest::OnComplete handler_;
145 };
146 
TEST_F(BrEdrConnectionRequestLoopTest,CreateHciConnectionRequest)147 TEST_F(BrEdrConnectionRequestLoopTest, CreateHciConnectionRequest) {
148   EXPECT_CMD_PACKET_OUT(test_device(),
149                         testing::CreateConnectionPacket(kTestAddr),
150                         &kCreateConnectionRsp);
151 
152   bool failure = false;
153   auto failure_cb = [&failure](hci::Result<> result, PeerId) {
154     if (result.is_error()) {
155       failure = true;
156     }
157   };
158 
159   connection_req().CreateHciConnectionRequest(
160       cmd_channel(),
161       /*clock_offset=*/std::nullopt,
162       /*page_scan_repetition_mode=*/std::nullopt,
163       /*timeout_cb=*/[]() {},
164       std::move(failure_cb),
165       dispatcher());
166 
167   RunUntilIdle();
168 
169   ASSERT_FALSE(failure);
170 }
171 
TEST_F(BrEdrConnectionRequestLoopTest,ShouldntRetryBeforeFirstCreateConnection)172 TEST_F(BrEdrConnectionRequestLoopTest,
173        ShouldntRetryBeforeFirstCreateConnection) {
174   EXPECT_FALSE(connection_req().ShouldRetry(RetryableError));
175 }
TEST_F(BrEdrConnectionRequestLoopTest,RetryableErrorCodeShouldRetryAfterFirstCreateConnection)176 TEST_F(BrEdrConnectionRequestLoopTest,
177        RetryableErrorCodeShouldRetryAfterFirstCreateConnection) {
178   EXPECT_CMD_PACKET_OUT(test_device(),
179                         testing::CreateConnectionPacket(kTestAddr),
180                         &kCreateConnectionRsp);
181 
182   bool failure = false;
183   auto failure_cb = [&failure](hci::Result<> result, PeerId) {
184     if (result.is_error()) {
185       failure = true;
186     }
187   };
188 
189   connection_req().CreateHciConnectionRequest(
190       cmd_channel(),
191       /*clock_offset=*/std::nullopt,
192       /*page_scan_repetition_mode=*/std::nullopt,
193       /*timeout_cb=*/[]() {},
194       std::move(failure_cb),
195       dispatcher());
196 
197   RunFor(std::chrono::seconds(1));
198   ASSERT_FALSE(failure);
199   EXPECT_TRUE(connection_req().ShouldRetry(RetryableError));
200 }
201 
TEST_F(BrEdrConnectionRequestLoopTest,ShouldntRetryWithNonRetriableErrorCode)202 TEST_F(BrEdrConnectionRequestLoopTest, ShouldntRetryWithNonRetriableErrorCode) {
203   EXPECT_CMD_PACKET_OUT(test_device(),
204                         testing::CreateConnectionPacket(kTestAddr),
205                         &kCreateConnectionRsp);
206 
207   bool failure = false;
208   auto failure_cb = [&failure](hci::Result<> result, PeerId) {
209     if (result.is_error()) {
210       failure = true;
211     }
212   };
213 
214   connection_req().CreateHciConnectionRequest(
215       cmd_channel(),
216       /*clock_offset=*/std::nullopt,
217       /*page_scan_repetition_mode=*/std::nullopt,
218       /*timeout_cb=*/[]() {},
219       std::move(failure_cb),
220       dispatcher());
221 
222   RunFor(std::chrono::seconds(1));
223   ASSERT_FALSE(failure);
224   EXPECT_FALSE(connection_req().ShouldRetry(hci::Error(HostError::kCanceled)));
225 }
226 
TEST_F(BrEdrConnectionRequestLoopTest,ShouldntRetryAfterThirtySeconds)227 TEST_F(BrEdrConnectionRequestLoopTest, ShouldntRetryAfterThirtySeconds) {
228   EXPECT_CMD_PACKET_OUT(test_device(),
229                         testing::CreateConnectionPacket(kTestAddr),
230                         &kCreateConnectionRsp);
231 
232   bool failure = false;
233   auto failure_cb = [&failure](hci::Result<> result, PeerId) {
234     if (result.is_error()) {
235       failure = true;
236     }
237   };
238 
239   connection_req().CreateHciConnectionRequest(
240       cmd_channel(),
241       /*clock_offset=*/std::nullopt,
242       /*page_scan_repetition_mode=*/std::nullopt,
243       /*timeout_cb=*/[]() {},
244       std::move(failure_cb),
245       dispatcher());
246   RunFor(std::chrono::seconds(15));
247   ASSERT_FALSE(failure);
248   // Should be OK to retry after 15 seconds
249   EXPECT_TRUE(connection_req().ShouldRetry(RetryableError));
250 
251   EXPECT_CMD_PACKET_OUT(test_device(),
252                         testing::CreateConnectionPacket(kTestAddr),
253                         &kCreateConnectionRsp);
254   connection_req().CreateHciConnectionRequest(
255       cmd_channel(),
256       /*clock_offset=*/std::nullopt,
257       /*page_scan_repetition_mode=*/std::nullopt,
258       /*timeout_cb=*/[]() {},
259       std::move(failure_cb),
260       dispatcher());
261 
262   // Should still be OK to retry, even though we've already retried
263   RunFor(std::chrono::seconds(14));
264   EXPECT_TRUE(connection_req().ShouldRetry(RetryableError));
265 
266   EXPECT_CMD_PACKET_OUT(test_device(),
267                         testing::CreateConnectionPacket(kTestAddr),
268                         &kCreateConnectionRsp);
269   connection_req().CreateHciConnectionRequest(
270       cmd_channel(),
271       /*clock_offset=*/std::nullopt,
272       /*page_scan_repetition_mode=*/std::nullopt,
273       /*timeout_cb=*/[]() {},
274       std::move(failure_cb),
275       dispatcher());
276 
277   RunFor(std::chrono::seconds(1));
278   EXPECT_FALSE(connection_req().ShouldRetry(RetryableError));
279 }
280 
281 }  // namespace
282 }  // namespace bt::gap
283