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