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/a2dp_offload_manager.h"
16
17 #include <memory>
18
19 #include "pw_bluetooth_sapphire/internal/host/common/host_error.h"
20 #include "pw_bluetooth_sapphire/internal/host/hci-spec/vendor_protocol.h"
21 #include "pw_bluetooth_sapphire/internal/host/testing/controller_test.h"
22 #include "pw_bluetooth_sapphire/internal/host/testing/mock_controller.h"
23 #include "pw_bluetooth_sapphire/internal/host/testing/test_packets.h"
24
25 namespace bt::l2cap {
26 namespace {
27
28 namespace android_hci = bt::hci_spec::vendor::android;
29 namespace android_emb = pw::bluetooth::vendor::android_hci;
30 using namespace bt::testing;
31
32 constexpr hci_spec::ConnectionHandle kTestHandle1 = 0x0001;
33 constexpr ChannelId kLocalId = 0x0040;
34 constexpr ChannelId kRemoteId = 0x9042;
35
BuildConfiguration(android_emb::A2dpCodecType codec=android_emb::A2dpCodecType::SBC)36 A2dpOffloadManager::Configuration BuildConfiguration(
37 android_emb::A2dpCodecType codec = android_emb::A2dpCodecType::SBC) {
38 A2dpOffloadManager::Configuration config;
39 config.codec = codec;
40 config.max_latency = 0xFFFF;
41 config.scms_t_enable.view().enabled().Write(
42 pw::bluetooth::emboss::GenericEnableParam::DISABLE);
43 config.scms_t_enable.view().header().Write(0x00);
44 config.sampling_frequency = android_emb::A2dpSamplingFrequency::HZ_44100;
45 config.bits_per_sample = android_emb::A2dpBitsPerSample::BITS_PER_SAMPLE_16;
46 config.channel_mode = android_emb::A2dpChannelMode::MONO;
47 config.encoded_audio_bit_rate = 0x0;
48
49 switch (codec) {
50 case android_emb::A2dpCodecType::SBC:
51 config.sbc_configuration.view().block_length().Write(
52 android_emb::SbcBlockLen::BLOCK_LEN_4);
53 config.sbc_configuration.view().subbands().Write(
54 android_emb::SbcSubBands::SUBBANDS_4);
55 config.sbc_configuration.view().allocation_method().Write(
56 android_emb::SbcAllocationMethod::SNR);
57 config.sbc_configuration.view().min_bitpool_value().Write(0x00);
58 config.sbc_configuration.view().max_bitpool_value().Write(0xFF);
59 break;
60 case android_emb::A2dpCodecType::AAC:
61 config.aac_configuration.view().object_type().Write(0x00);
62 config.aac_configuration.view().variable_bit_rate().Write(
63 android_emb::AacEnableVariableBitRate::DISABLE);
64 break;
65 case android_emb::A2dpCodecType::LDAC:
66 config.ldac_configuration.view().vendor_id().Write(
67 android_hci::kLdacVendorId);
68 config.ldac_configuration.view().codec_id().Write(
69 android_hci::kLdacCodecId);
70 config.ldac_configuration.view().bitrate_index().Write(
71 android_emb::LdacBitrateIndex::LOW);
72 config.ldac_configuration.view().ldac_channel_mode().stereo().Write(true);
73 break;
74 case android_emb::A2dpCodecType::APTX:
75 case android_emb::A2dpCodecType::APTX_HD:
76 break;
77 }
78
79 return config;
80 }
81
82 using TestingBase = FakeDispatcherControllerTest<MockController>;
83
84 class A2dpOffloadTest : public TestingBase {
85 public:
86 A2dpOffloadTest() = default;
87 ~A2dpOffloadTest() override = default;
88
SetUp()89 void SetUp() override {
90 TestingBase::SetUp();
91
92 offload_mgr_ =
93 std::make_unique<A2dpOffloadManager>(cmd_channel()->AsWeakPtr());
94 }
95
TearDown()96 void TearDown() override { TestingBase::TearDown(); }
97
offload_mgr() const98 A2dpOffloadManager* offload_mgr() const { return offload_mgr_.get(); }
99
100 private:
101 std::unique_ptr<A2dpOffloadManager> offload_mgr_;
102
103 BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(A2dpOffloadTest);
104 };
105
106 class StartA2dpOffloadTest
107 : public A2dpOffloadTest,
108 public ::testing::WithParamInterface<android_emb::A2dpCodecType> {};
109
TEST_P(StartA2dpOffloadTest,StartA2dpOffloadSuccess)110 TEST_P(StartA2dpOffloadTest, StartA2dpOffloadSuccess) {
111 const android_emb::A2dpCodecType codec = GetParam();
112 A2dpOffloadManager::Configuration config = BuildConfiguration(codec);
113
114 const auto command_complete =
115 CommandCompletePacket(android_hci::kA2dpOffloadCommand,
116 pw::bluetooth::emboss::StatusCode::SUCCESS);
117 EXPECT_CMD_PACKET_OUT(
118 test_device(),
119 StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
120 &command_complete);
121
122 std::optional<hci::Result<>> start_result;
123 offload_mgr()->StartA2dpOffload(
124 config,
125 kLocalId,
126 kRemoteId,
127 kTestHandle1,
128 kMaxMTU,
129 [&start_result](auto res) {
130 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
131 start_result = res;
132 });
133 RunUntilIdle();
134 EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
135 EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
136 ASSERT_TRUE(start_result.has_value());
137 EXPECT_TRUE(start_result->is_ok());
138 }
139
140 const std::vector<android_emb::A2dpCodecType> kA2dpCodecTypeParams = {
141 android_emb::A2dpCodecType::SBC,
142 android_emb::A2dpCodecType::AAC,
143 android_emb::A2dpCodecType::LDAC};
144 INSTANTIATE_TEST_SUITE_P(ChannelManagerTest,
145 StartA2dpOffloadTest,
146 ::testing::ValuesIn(kA2dpCodecTypeParams));
147
TEST_F(A2dpOffloadTest,StartA2dpOffloadInvalidConfiguration)148 TEST_F(A2dpOffloadTest, StartA2dpOffloadInvalidConfiguration) {
149 A2dpOffloadManager::Configuration config = BuildConfiguration();
150
151 const auto command_complete = CommandCompletePacket(
152 android_hci::kA2dpOffloadCommand,
153 pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS);
154 EXPECT_CMD_PACKET_OUT(
155 test_device(),
156 StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
157 &command_complete);
158
159 std::optional<hci::Result<>> start_result;
160 offload_mgr()->StartA2dpOffload(
161 config,
162 kLocalId,
163 kRemoteId,
164 kTestHandle1,
165 kMaxMTU,
166 [&start_result](auto res) {
167 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::
168 INVALID_HCI_COMMAND_PARAMETERS),
169 res);
170 start_result = res;
171 });
172 RunUntilIdle();
173 EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
174 ASSERT_TRUE(start_result.has_value());
175 EXPECT_TRUE(start_result->is_error());
176 }
177
TEST_F(A2dpOffloadTest,StartAndStopA2dpOffloadSuccess)178 TEST_F(A2dpOffloadTest, StartAndStopA2dpOffloadSuccess) {
179 A2dpOffloadManager::Configuration config = BuildConfiguration();
180
181 const auto command_complete =
182 CommandCompletePacket(android_hci::kA2dpOffloadCommand,
183 pw::bluetooth::emboss::StatusCode::SUCCESS);
184 EXPECT_CMD_PACKET_OUT(
185 test_device(),
186 StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
187 &command_complete);
188
189 std::optional<hci::Result<>> start_result;
190 offload_mgr()->StartA2dpOffload(
191 config,
192 kLocalId,
193 kRemoteId,
194 kTestHandle1,
195 kMaxMTU,
196 [&start_result](auto res) {
197 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
198 start_result = res;
199 });
200 RunUntilIdle();
201 EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
202 EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
203 ASSERT_TRUE(start_result.has_value());
204 EXPECT_TRUE(start_result->is_ok());
205
206 EXPECT_CMD_PACKET_OUT(
207 test_device(), StopA2dpOffloadRequest(), &command_complete);
208
209 std::optional<hci::Result<>> stop_result;
210 offload_mgr()->RequestStopA2dpOffload(
211 kLocalId, kTestHandle1, [&stop_result](auto res) {
212 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
213 stop_result = res;
214 });
215 RunUntilIdle();
216 EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
217 EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
218 ASSERT_TRUE(stop_result.has_value());
219 EXPECT_TRUE(stop_result->is_ok());
220 }
221
TEST_F(A2dpOffloadTest,StartA2dpOffloadAlreadyStarted)222 TEST_F(A2dpOffloadTest, StartA2dpOffloadAlreadyStarted) {
223 A2dpOffloadManager::Configuration config = BuildConfiguration();
224
225 const auto command_complete =
226 CommandCompletePacket(android_hci::kA2dpOffloadCommand,
227 pw::bluetooth::emboss::StatusCode::SUCCESS);
228 EXPECT_CMD_PACKET_OUT(
229 test_device(),
230 StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
231 &command_complete);
232
233 std::optional<hci::Result<>> start_result;
234 offload_mgr()->StartA2dpOffload(
235 config,
236 kLocalId,
237 kRemoteId,
238 kTestHandle1,
239 kMaxMTU,
240 [&start_result](auto res) {
241 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
242 start_result = res;
243 });
244 RunUntilIdle();
245 EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
246 EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
247 ASSERT_TRUE(start_result.has_value());
248 EXPECT_TRUE(start_result->is_ok());
249
250 start_result.reset();
251 offload_mgr()->StartA2dpOffload(config,
252 kLocalId,
253 kRemoteId,
254 kTestHandle1,
255 kMaxMTU,
256 [&start_result](auto res) {
257 EXPECT_EQ(ToResult(HostError::kInProgress),
258 res);
259 start_result = res;
260 });
261 RunUntilIdle();
262 EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
263 ASSERT_TRUE(start_result.has_value());
264 EXPECT_TRUE(start_result->is_error());
265 }
266
TEST_F(A2dpOffloadTest,StartA2dpOffloadStillStarting)267 TEST_F(A2dpOffloadTest, StartA2dpOffloadStillStarting) {
268 A2dpOffloadManager::Configuration config = BuildConfiguration();
269
270 const auto command_complete =
271 CommandCompletePacket(android_hci::kA2dpOffloadCommand,
272 pw::bluetooth::emboss::StatusCode::SUCCESS);
273 EXPECT_CMD_PACKET_OUT(
274 test_device(),
275 StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
276 &command_complete);
277
278 std::optional<hci::Result<>> start_result;
279 offload_mgr()->StartA2dpOffload(
280 config,
281 kLocalId,
282 kRemoteId,
283 kTestHandle1,
284 kMaxMTU,
285 [&start_result](auto res) {
286 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
287 start_result = res;
288 });
289 EXPECT_FALSE(start_result.has_value());
290
291 offload_mgr()->StartA2dpOffload(config,
292 kLocalId,
293 kRemoteId,
294 kTestHandle1,
295 kMaxMTU,
296 [&start_result](auto res) {
297 EXPECT_EQ(ToResult(HostError::kInProgress),
298 res);
299 start_result = res;
300 });
301 RunUntilIdle();
302 EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
303 EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
304 ASSERT_TRUE(start_result.has_value());
305 EXPECT_TRUE(start_result->is_ok());
306 }
307
TEST_F(A2dpOffloadTest,StartA2dpOffloadStillStopping)308 TEST_F(A2dpOffloadTest, StartA2dpOffloadStillStopping) {
309 A2dpOffloadManager::Configuration config = BuildConfiguration();
310
311 const auto command_complete =
312 CommandCompletePacket(android_hci::kA2dpOffloadCommand,
313 pw::bluetooth::emboss::StatusCode::SUCCESS);
314 EXPECT_CMD_PACKET_OUT(
315 test_device(),
316 StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
317 &command_complete);
318
319 std::optional<hci::Result<>> start_result;
320 offload_mgr()->StartA2dpOffload(
321 config,
322 kLocalId,
323 kRemoteId,
324 kTestHandle1,
325 kMaxMTU,
326 [&start_result](auto res) {
327 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
328 start_result = res;
329 });
330 RunUntilIdle();
331 EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
332 EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
333 ASSERT_TRUE(start_result.has_value());
334 EXPECT_TRUE(start_result->is_ok());
335
336 EXPECT_CMD_PACKET_OUT(
337 test_device(), StopA2dpOffloadRequest(), &command_complete);
338
339 std::optional<hci::Result<>> stop_result;
340 offload_mgr()->RequestStopA2dpOffload(
341 kLocalId, kTestHandle1, [&stop_result](auto res) {
342 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
343 stop_result = res;
344 });
345 EXPECT_FALSE(stop_result.has_value());
346
347 start_result.reset();
348 offload_mgr()->StartA2dpOffload(config,
349 kLocalId,
350 kRemoteId,
351 kTestHandle1,
352 kMaxMTU,
353 [&start_result](auto res) {
354 EXPECT_EQ(ToResult(HostError::kInProgress),
355 res);
356 start_result = res;
357 });
358 RunUntilIdle();
359 EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
360 EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
361 ASSERT_TRUE(start_result.has_value());
362 EXPECT_TRUE(start_result->is_error());
363 ASSERT_TRUE(stop_result.has_value());
364 EXPECT_TRUE(stop_result->is_ok());
365 }
366
TEST_F(A2dpOffloadTest,StopA2dpOffloadStillStarting)367 TEST_F(A2dpOffloadTest, StopA2dpOffloadStillStarting) {
368 A2dpOffloadManager::Configuration config = BuildConfiguration();
369
370 const auto command_complete =
371 CommandCompletePacket(android_hci::kA2dpOffloadCommand,
372 pw::bluetooth::emboss::StatusCode::SUCCESS);
373 EXPECT_CMD_PACKET_OUT(
374 test_device(),
375 StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
376 &command_complete);
377
378 std::optional<hci::Result<>> start_result;
379 offload_mgr()->StartA2dpOffload(
380 config,
381 kLocalId,
382 kRemoteId,
383 kTestHandle1,
384 kMaxMTU,
385 [&start_result](auto res) {
386 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
387 start_result = res;
388 });
389 EXPECT_FALSE(start_result.has_value());
390
391 EXPECT_CMD_PACKET_OUT(
392 test_device(), StopA2dpOffloadRequest(), &command_complete);
393
394 std::optional<hci::Result<>> stop_result;
395 offload_mgr()->RequestStopA2dpOffload(
396 kLocalId, kTestHandle1, [&stop_result](auto res) {
397 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
398 stop_result = res;
399 });
400 RunUntilIdle();
401 EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
402 EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
403 ASSERT_TRUE(start_result.has_value());
404 EXPECT_TRUE(start_result->is_ok());
405 ASSERT_TRUE(stop_result.has_value());
406 EXPECT_TRUE(stop_result->is_ok());
407 }
408
TEST_F(A2dpOffloadTest,StopA2dpOffloadStillStopping)409 TEST_F(A2dpOffloadTest, StopA2dpOffloadStillStopping) {
410 A2dpOffloadManager::Configuration config = BuildConfiguration();
411
412 const auto command_complete =
413 CommandCompletePacket(android_hci::kA2dpOffloadCommand,
414 pw::bluetooth::emboss::StatusCode::SUCCESS);
415 EXPECT_CMD_PACKET_OUT(
416 test_device(),
417 StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
418 &command_complete);
419
420 std::optional<hci::Result<>> start_result;
421 offload_mgr()->StartA2dpOffload(
422 config,
423 kLocalId,
424 kRemoteId,
425 kTestHandle1,
426 kMaxMTU,
427 [&start_result](auto res) {
428 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
429 start_result = res;
430 });
431 RunUntilIdle();
432 EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
433 EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
434 ASSERT_TRUE(start_result.has_value());
435 EXPECT_TRUE(start_result->is_ok());
436
437 EXPECT_CMD_PACKET_OUT(
438 test_device(), StopA2dpOffloadRequest(), &command_complete);
439
440 std::optional<hci::Result<>> stop_result;
441 offload_mgr()->RequestStopA2dpOffload(
442 kLocalId, kTestHandle1, [&stop_result](auto res) {
443 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
444 stop_result = res;
445 });
446 EXPECT_FALSE(stop_result.has_value());
447
448 offload_mgr()->RequestStopA2dpOffload(
449 kLocalId, kTestHandle1, [&stop_result](auto res) {
450 EXPECT_EQ(ToResult(HostError::kInProgress), res);
451 stop_result = res;
452 });
453 RunUntilIdle();
454 EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
455 EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
456 ASSERT_TRUE(stop_result.has_value());
457 EXPECT_TRUE(stop_result->is_ok());
458 }
459
TEST_F(A2dpOffloadTest,StopA2dpOffloadAlreadyStopped)460 TEST_F(A2dpOffloadTest, StopA2dpOffloadAlreadyStopped) {
461 std::optional<hci::Result<>> stop_result;
462 offload_mgr()->RequestStopA2dpOffload(
463 kLocalId, kTestHandle1, [&stop_result](auto res) {
464 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
465 stop_result = res;
466 });
467 RunUntilIdle();
468 EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
469 ASSERT_TRUE(stop_result.has_value());
470 EXPECT_TRUE(stop_result->is_ok());
471 }
472
TEST_F(A2dpOffloadTest,A2dpOffloadOnlyOneChannel)473 TEST_F(A2dpOffloadTest, A2dpOffloadOnlyOneChannel) {
474 A2dpOffloadManager::Configuration config = BuildConfiguration();
475
476 const auto command_complete =
477 CommandCompletePacket(android_hci::kA2dpOffloadCommand,
478 pw::bluetooth::emboss::StatusCode::SUCCESS);
479 EXPECT_CMD_PACKET_OUT(
480 test_device(),
481 StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
482 &command_complete);
483
484 std::optional<hci::Result<>> start_result_0;
485 offload_mgr()->StartA2dpOffload(
486 config,
487 kLocalId,
488 kRemoteId,
489 kTestHandle1,
490 kMaxMTU,
491 [&start_result_0](auto res) {
492 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
493 start_result_0 = res;
494 });
495 RunUntilIdle();
496 EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
497 EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
498 ASSERT_TRUE(start_result_0.has_value());
499 EXPECT_TRUE(start_result_0->is_ok());
500
501 std::optional<hci::Result<>> start_result_1;
502 offload_mgr()->StartA2dpOffload(config,
503 kLocalId + 1,
504 kRemoteId + 1,
505 kTestHandle1,
506 kMaxMTU,
507 [&start_result_1](auto res) {
508 EXPECT_EQ(ToResult(HostError::kInProgress),
509 res);
510 start_result_1 = res;
511 });
512 RunUntilIdle();
513 EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
514 EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId + 1, kTestHandle1));
515 ASSERT_TRUE(start_result_1.has_value());
516 EXPECT_TRUE(start_result_1->is_error());
517 }
518
TEST_F(A2dpOffloadTest,DifferentChannelCannotStopA2dpOffloading)519 TEST_F(A2dpOffloadTest, DifferentChannelCannotStopA2dpOffloading) {
520 A2dpOffloadManager::Configuration config = BuildConfiguration();
521
522 const auto command_complete =
523 CommandCompletePacket(android_hci::kA2dpOffloadCommand,
524 pw::bluetooth::emboss::StatusCode::SUCCESS);
525 EXPECT_CMD_PACKET_OUT(
526 test_device(),
527 StartA2dpOffloadRequest(config, kTestHandle1, kRemoteId, kMaxMTU),
528 &command_complete);
529
530 std::optional<hci::Result<>> start_result;
531 offload_mgr()->StartA2dpOffload(
532 config,
533 kLocalId,
534 kRemoteId,
535 kTestHandle1,
536 kMaxMTU,
537 [&start_result](auto res) {
538 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
539 start_result = res;
540 });
541 RunUntilIdle();
542 EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
543 EXPECT_TRUE(test_device()->AllExpectedCommandPacketsSent());
544 ASSERT_TRUE(start_result.has_value());
545 EXPECT_TRUE(start_result->is_ok());
546
547 std::optional<hci::Result<>> stop_result;
548 offload_mgr()->RequestStopA2dpOffload(
549 kLocalId + 1, kTestHandle1 + 1, [&stop_result](auto res) {
550 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
551 stop_result = res;
552 });
553 RunUntilIdle();
554 EXPECT_TRUE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
555 ASSERT_TRUE(stop_result.has_value());
556 EXPECT_TRUE(stop_result->is_ok());
557
558 EXPECT_CMD_PACKET_OUT(
559 test_device(), StopA2dpOffloadRequest(), &command_complete);
560
561 // Can still stop it from the correct one.
562 stop_result = std::nullopt;
563 offload_mgr()->RequestStopA2dpOffload(
564 kLocalId, kTestHandle1, [&stop_result](auto res) {
565 EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::SUCCESS), res);
566 stop_result = res;
567 });
568 RunUntilIdle();
569 EXPECT_FALSE(offload_mgr()->IsChannelOffloaded(kLocalId, kTestHandle1));
570 ASSERT_TRUE(stop_result.has_value());
571 EXPECT_TRUE(stop_result->is_ok());
572 }
573
574 } // namespace
575 } // namespace bt::l2cap
576