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