1 // Copyright 2024 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/iso/iso_stream.h"
16
17 #include "pw_bluetooth/hci_data.emb.h"
18 #include "pw_bluetooth_sapphire/internal/host/testing/controller_test.h"
19 #include "pw_bluetooth_sapphire/internal/host/testing/mock_controller.h"
20 #include "pw_bluetooth_sapphire/internal/host/testing/test_packets.h"
21
22 namespace bt::iso {
23 namespace {
24
25 using pw::bluetooth::emboss::IsoDataPacketStatus;
26 using pw::bluetooth::emboss::IsoDataPbFlag;
27
28 constexpr hci_spec::CigIdentifier kCigId = 0x22;
29 constexpr hci_spec::CisIdentifier kCisId = 0x42;
30
31 constexpr hci_spec::ConnectionHandle kCisHandleId = 0x59e;
32
33 constexpr size_t kMaxControllerSDUFragmentSize = 100;
34 constexpr size_t kMaxControllerPacketCount = 9;
35
36 constexpr size_t kSduHeaderSize = 4;
37
38 using MockControllerTestBase =
39 bt::testing::FakeDispatcherControllerTest<bt::testing::MockController>;
40
41 class IsoStreamTest : public MockControllerTestBase {
42 public:
43 IsoStreamTest() = default;
44 ~IsoStreamTest() override = default;
45
SetUp()46 void SetUp() override {
47 MockControllerTestBase::SetUp(
48 pw::bluetooth::Controller::FeaturesBits::kHciIso);
49 hci::DataBufferInfo iso_buffer_info(kMaxControllerSDUFragmentSize,
50 kMaxControllerPacketCount);
51 transport()->InitializeIsoDataChannel(iso_buffer_info);
52 iso_stream_ = IsoStream::Create(
53 kCigId,
54 kCisId,
55 kCisHandleId,
56 /*on_established_cb=*/
57 [this](pw::bluetooth::emboss::StatusCode status,
58 std::optional<WeakSelf<IsoStream>::WeakPtr>,
59 const std::optional<CisEstablishedParameters>& parameters) {
60 ASSERT_FALSE(establishment_status_.has_value());
61 establishment_status_ = status;
62 established_parameters_ = parameters;
63 },
64 transport()->command_channel()->AsWeakPtr(),
65 /*on_closed_cb=*/
66 [this]() {
67 ASSERT_FALSE(closed_);
68 closed_ = true;
69 },
70 transport()->iso_data_channel());
71 }
72
73 protected:
74 // Send an HCI_LE_CIS_Established event with the provided status
75 void EstablishCis(pw::bluetooth::emboss::StatusCode status);
76
77 // Call IsoStream::SetupDataPath().
78 // |cmd_complete_status| is nullopt if we do not expect an
79 // LE_Setup_ISO_Data_Path command to be generated, otherwise it should be set
80 // to the status code we want to generate in the response frame.
81 // |expected_cb_result| should be set to the expected result of the callback
82 // from IsoStream::SetupDataPath.
83 void SetupDataPath(pw::bluetooth::emboss::DataPathDirection direction,
84 const std::optional<std::vector<uint8_t>>& codec_config,
85 const std::optional<pw::bluetooth::emboss::StatusCode>&
86 cmd_complete_status,
87 iso::IsoStream::SetupDataPathError expected_cb_result,
88 bool generate_mismatched_cid = false);
89
RegisterStream()90 void RegisterStream() {
91 transport()->iso_data_channel()->RegisterConnection(
92 kCisHandleId, iso_stream()->GetWeakPtr());
93 }
94
95 bool HandleCompleteIncomingSDU(const pw::span<const std::byte>& complete_sdu);
96
iso_stream()97 IsoStream* iso_stream() { return iso_stream_.get(); }
98
complete_incoming_sdus()99 std::queue<std::vector<std::byte>>* complete_incoming_sdus() {
100 return &complete_incoming_sdus_;
101 }
102
establishment_status()103 std::optional<pw::bluetooth::emboss::StatusCode> establishment_status() {
104 return establishment_status_;
105 }
106
established_parameters()107 std::optional<CisEstablishedParameters> established_parameters() {
108 return established_parameters_;
109 }
110
closed()111 bool closed() { return closed_; }
112
113 protected:
114 bool accept_incoming_sdus_ = true;
115
116 private:
117 std::unique_ptr<IsoStream> iso_stream_;
118 std::optional<pw::bluetooth::emboss::StatusCode> establishment_status_;
119 std::optional<CisEstablishedParameters> established_parameters_;
120 std::queue<std::vector<std::byte>> complete_incoming_sdus_;
121 bool closed_ = false;
122 };
123
LECisEstablishedPacketWithDefaultValues(pw::bluetooth::emboss::StatusCode status)124 static DynamicByteBuffer LECisEstablishedPacketWithDefaultValues(
125 pw::bluetooth::emboss::StatusCode status) {
126 return testing::LECisEstablishedEventPacket(
127 status,
128 kCisHandleId,
129 /*cig_sync_delay_us=*/0x123456, // Must be in [0x0000ea, 0x7fffff]
130 /*cis_sync_delay_us=*/0x7890ab, // Must be in [0x0000ea, 0x7fffff]
131 /*transport_latency_c_to_p_us=*/0x654321, // Must be in [0x0000ea,
132 // 0x7fffff]
133 /*transport_latency_p_to_c_us=*/0x0fedcb, // Must be in [0x0000ea,
134 // 0x7fffff]
135 /*phy_c_to_p=*/pw::bluetooth::emboss::IsoPhyType::LE_2M,
136 /*phy_c_to_p=*/pw::bluetooth::emboss::IsoPhyType::LE_CODED,
137 /*nse=*/0x10, // Must be in [0x01, 0x1f]
138 /*bn_c_to_p=*/0x05, // Must be in [0x00, 0x0f]
139 /*bn_p_to_c=*/0x0f, // Must be in [0x00, 0x0f]
140 /*ft_c_to_p=*/0x01, // Must be in [0x01, 0xff]
141 /*ft_p_to_c=*/0xff, // Must be in [0x01, 0xff]
142 /*max_pdu_c_to_p=*/0x0042, // Must be in [0x0000, 0x00fb]
143 /*max_pdu_p_to_c=*/0x00fb, // Must be in [0x0000, 0x00fb]
144 /*iso_interval=*/0x0222 // Must be in [0x0004, 0x0c80]
145 );
146 }
147
EstablishCis(pw::bluetooth::emboss::StatusCode status)148 void IsoStreamTest::EstablishCis(pw::bluetooth::emboss::StatusCode status) {
149 DynamicByteBuffer le_cis_established_packet =
150 LECisEstablishedPacketWithDefaultValues(status);
151 test_device()->SendCommandChannelPacket(le_cis_established_packet);
152 RunUntilIdle();
153 ASSERT_TRUE(establishment_status().has_value());
154 EXPECT_EQ(*(establishment_status()), status);
155 if (status == pw::bluetooth::emboss::StatusCode::SUCCESS) {
156 EXPECT_TRUE(established_parameters().has_value());
157 } else {
158 EXPECT_FALSE(established_parameters().has_value());
159 }
160 }
161
GenerateCodecId()162 bt::StaticPacket<pw::bluetooth::emboss::CodecIdWriter> GenerateCodecId() {
163 const uint16_t kCompanyId = 0x1234;
164 bt::StaticPacket<pw::bluetooth::emboss::CodecIdWriter> codec_id;
165 auto codec_id_view = codec_id.view();
166 codec_id_view.coding_format().Write(pw::bluetooth::emboss::CodingFormat::LC3);
167 codec_id_view.company_id().Write(kCompanyId);
168 return codec_id;
169 }
170
SetupDataPath(pw::bluetooth::emboss::DataPathDirection direction,const std::optional<std::vector<uint8_t>> & codec_configuration,const std::optional<pw::bluetooth::emboss::StatusCode> & cmd_complete_status,iso::IsoStream::SetupDataPathError expected_cb_result,bool generate_mismatched_cid)171 void IsoStreamTest::SetupDataPath(
172 pw::bluetooth::emboss::DataPathDirection direction,
173 const std::optional<std::vector<uint8_t>>& codec_configuration,
174 const std::optional<pw::bluetooth::emboss::StatusCode>& cmd_complete_status,
175 iso::IsoStream::SetupDataPathError expected_cb_result,
176 bool generate_mismatched_cid) {
177 const uint32_t kControllerDelay = 1234; // Must be < 4000000
178 std::optional<iso::IsoStream::SetupDataPathError> actual_cb_result;
179
180 if (cmd_complete_status.has_value()) {
181 auto setup_data_path_packet =
182 testing::LESetupIsoDataPathPacket(kCisHandleId,
183 direction,
184 /*HCI*/ 0,
185 GenerateCodecId(),
186 kControllerDelay,
187 codec_configuration);
188 hci_spec::ConnectionHandle cis_handle =
189 kCisHandleId + (generate_mismatched_cid ? 1 : 0);
190 auto setup_data_path_complete_packet =
191 testing::LESetupIsoDataPathResponse(*cmd_complete_status, cis_handle);
192 EXPECT_CMD_PACKET_OUT(test_device(),
193 setup_data_path_packet,
194 &setup_data_path_complete_packet);
195 }
196
197 iso_stream()->SetupDataPath(
198 direction,
199 GenerateCodecId(),
200 codec_configuration,
201 kControllerDelay,
202 [&actual_cb_result](iso::IsoStream::SetupDataPathError result) {
203 actual_cb_result = result;
204 },
205 fit::bind_member<&IsoStreamTest::HandleCompleteIncomingSDU>(this));
206 RunUntilIdle();
207 ASSERT_TRUE(actual_cb_result.has_value());
208 EXPECT_EQ(expected_cb_result, *actual_cb_result);
209 }
210
HandleCompleteIncomingSDU(const pw::span<const std::byte> & sdu)211 bool IsoStreamTest::HandleCompleteIncomingSDU(
212 const pw::span<const std::byte>& sdu) {
213 if (!accept_incoming_sdus_) {
214 return false;
215 }
216 std::vector<std::byte> new_sdu(sdu.size());
217 std::copy(sdu.begin(), sdu.end(), new_sdu.begin());
218 complete_incoming_sdus_.emplace(std::move(new_sdu));
219 return true;
220 }
221
TEST_F(IsoStreamTest,CisEstablishedSuccessfully)222 TEST_F(IsoStreamTest, CisEstablishedSuccessfully) {
223 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
224 }
225
TEST_F(IsoStreamTest,CisEstablishmentFailed)226 TEST_F(IsoStreamTest, CisEstablishmentFailed) {
227 EstablishCis(pw::bluetooth::emboss::StatusCode::MEMORY_CAPACITY_EXCEEDED);
228 }
229
TEST_F(IsoStreamTest,ClosedCallsCloseCallback)230 TEST_F(IsoStreamTest, ClosedCallsCloseCallback) {
231 EXPECT_FALSE(closed());
232 iso_stream()->Close();
233 EXPECT_TRUE(closed());
234 }
235
TEST_F(IsoStreamTest,SetupDataPathSuccessfully)236 TEST_F(IsoStreamTest, SetupDataPathSuccessfully) {
237 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
238 SetupDataPath(pw::bluetooth::emboss::DataPathDirection::OUTPUT,
239 /*codec_configuration=*/std::nullopt,
240 pw::bluetooth::emboss::StatusCode::SUCCESS,
241 iso::IsoStream::SetupDataPathError::kSuccess);
242 }
243
TEST_F(IsoStreamTest,SetupDataPathBeforeCisEstablished)244 TEST_F(IsoStreamTest, SetupDataPathBeforeCisEstablished) {
245 SetupDataPath(pw::bluetooth::emboss::DataPathDirection::OUTPUT,
246 /*codec_configuration=*/std::nullopt,
247 /*cmd_complete_status=*/std::nullopt,
248 iso::IsoStream::SetupDataPathError::kCisNotEstablished);
249 }
250
TEST_F(IsoStreamTest,SetupInputDataPathTwice)251 TEST_F(IsoStreamTest, SetupInputDataPathTwice) {
252 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
253 SetupDataPath(
254 pw::bluetooth::emboss::DataPathDirection::INPUT,
255 /*codec_configuration=*/std::nullopt,
256 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
257 iso::IsoStream::SetupDataPathError::kSuccess);
258 SetupDataPath(pw::bluetooth::emboss::DataPathDirection::INPUT,
259 /*codec_configuration=*/std::nullopt,
260 /*cmd_complete_status=*/std::nullopt,
261 iso::IsoStream::SetupDataPathError::kStreamAlreadyExists);
262 }
263
TEST_F(IsoStreamTest,SetupOutputDataPathTwice)264 TEST_F(IsoStreamTest, SetupOutputDataPathTwice) {
265 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
266 SetupDataPath(
267 pw::bluetooth::emboss::DataPathDirection::OUTPUT,
268 /*codec_configuration=*/std::nullopt,
269 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
270 iso::IsoStream::SetupDataPathError::kSuccess);
271 SetupDataPath(pw::bluetooth::emboss::DataPathDirection::OUTPUT,
272 /*codec_configuration=*/std::nullopt,
273 /*cmd_complete_status=*/std::nullopt,
274 iso::IsoStream::SetupDataPathError::kStreamAlreadyExists);
275 }
276
TEST_F(IsoStreamTest,SetupBothInputAndOutputDataPaths)277 TEST_F(IsoStreamTest, SetupBothInputAndOutputDataPaths) {
278 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
279 SetupDataPath(
280 pw::bluetooth::emboss::DataPathDirection::OUTPUT,
281 /*codec_configuration=*/std::nullopt,
282 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
283 iso::IsoStream::SetupDataPathError::kSuccess);
284 SetupDataPath(
285 pw::bluetooth::emboss::DataPathDirection::INPUT,
286 /*codec_configuration=*/std::nullopt,
287 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
288 iso::IsoStream::SetupDataPathError::kSuccess);
289 }
290
TEST_F(IsoStreamTest,SetupDataPathInvalidArgs)291 TEST_F(IsoStreamTest, SetupDataPathInvalidArgs) {
292 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
293 SetupDataPath(static_cast<pw::bluetooth::emboss::DataPathDirection>(250),
294 /*codec_configuration=*/std::nullopt,
295 /*cmd_complete_status=*/std::nullopt,
296 iso::IsoStream::SetupDataPathError::kInvalidArgs);
297 }
298
TEST_F(IsoStreamTest,SetupDataPathWithCodecConfig)299 TEST_F(IsoStreamTest, SetupDataPathWithCodecConfig) {
300 std::vector<uint8_t> codec_config{5, 6, 7, 8};
301 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
302 SetupDataPath(
303 pw::bluetooth::emboss::DataPathDirection::OUTPUT,
304 codec_config,
305 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
306 iso::IsoStream::SetupDataPathError::kSuccess);
307 }
308
309 // If the connection ID doesn't match in the command complete packet, fail
TEST_F(IsoStreamTest,SetupDataPathHandleMismatch)310 TEST_F(IsoStreamTest, SetupDataPathHandleMismatch) {
311 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
312 SetupDataPath(
313 pw::bluetooth::emboss::DataPathDirection::INPUT,
314 /*codec_configuration=*/std::nullopt,
315 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
316 iso::IsoStream::SetupDataPathError::kStreamRejectedByController,
317 /*generate_mismatched_cid=*/true);
318 }
319
TEST_F(IsoStreamTest,SetupDataPathControllerError)320 TEST_F(IsoStreamTest, SetupDataPathControllerError) {
321 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
322 SetupDataPath(
323 pw::bluetooth::emboss::DataPathDirection::INPUT,
324 /*codec_configuration=*/std::nullopt,
325 /*cmd_complete_status=*/
326 pw::bluetooth::emboss::StatusCode::CONNECTION_ALREADY_EXISTS,
327 iso::IsoStream::SetupDataPathError::kStreamRejectedByController);
328 }
329
330 // If the client asks for frames before any are ready it will receive
331 // a notification when the next packet arrives.
TEST_F(IsoStreamTest,PendingRead)332 TEST_F(IsoStreamTest, PendingRead) {
333 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
334 SetupDataPath(
335 pw::bluetooth::emboss::DataPathDirection::OUTPUT,
336 /*codec_configuration=*/std::nullopt,
337 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
338 iso::IsoStream::SetupDataPathError::kSuccess);
339 const size_t kIsoSduLength = 212;
340 std::unique_ptr<std::vector<uint8_t>> sdu_data =
341 testing::GenDataBlob(kIsoSduLength, /*starting_value=*/14);
342 DynamicByteBuffer packet0 = testing::IsoDataPacket(
343 /*connection_handle=*/iso_stream()->cis_handle(),
344 pw::bluetooth::emboss::IsoDataPbFlag::COMPLETE_SDU,
345 /*time_stamp=*/std::nullopt,
346 /*packet_sequence_number=*/0,
347 kIsoSduLength,
348 pw::bluetooth::emboss::IsoDataPacketStatus::VALID_DATA,
349 *sdu_data);
350 pw::span<const std::byte> packet0_as_span = packet0.subspan();
351 ASSERT_EQ(iso_stream()->ReadNextQueuedIncomingPacket(), nullptr);
352 iso_stream()->ReceiveInboundPacket(packet0_as_span);
353 ASSERT_EQ(complete_incoming_sdus()->size(), 1u);
354 std::vector<std::byte>& received_frame = complete_incoming_sdus()->front();
355 ASSERT_EQ(packet0_as_span.size(), received_frame.size());
356 EXPECT_TRUE(std::equal(
357 packet0_as_span.begin(), packet0_as_span.end(), received_frame.begin()));
358 }
359
360 // If the client does not ask for frames it will not receive any notifications
361 // and the IsoStream will just queue them up.
TEST_F(IsoStreamTest,UnreadData)362 TEST_F(IsoStreamTest, UnreadData) {
363 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
364 SetupDataPath(
365 pw::bluetooth::emboss::DataPathDirection::OUTPUT,
366 /*codec_configuration=*/std::nullopt,
367 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
368 iso::IsoStream::SetupDataPathError::kSuccess);
369 const size_t kTotalFrameCount = 5;
370 DynamicByteBuffer packets[kTotalFrameCount];
371 pw::span<const std::byte> packets_as_span[kTotalFrameCount];
372 for (size_t i = 0; i < kTotalFrameCount; i++) {
373 std::unique_ptr<std::vector<uint8_t>> sdu_data = testing::GenDataBlob(
374 /*size=*/kMaxControllerSDUFragmentSize - i, /*starting_value=*/i);
375 packets[i] = testing::IsoDataPacket(
376 /*connection_handle=*/iso_stream()->cis_handle(),
377 pw::bluetooth::emboss::IsoDataPbFlag::COMPLETE_SDU,
378 /*time_stamp=*/std::nullopt,
379 /*packet_sequence_number=*/i,
380 /*iso_sdu_length=*/kMaxControllerSDUFragmentSize - i,
381 pw::bluetooth::emboss::IsoDataPacketStatus::VALID_DATA,
382 *sdu_data);
383 packets_as_span[i] = packets[i].subspan();
384 iso_stream()->ReceiveInboundPacket(packets_as_span[i]);
385 }
386 EXPECT_EQ(complete_incoming_sdus()->size(), 0u);
387 }
388
389 // This is the (somewhat unusual) case where the client asks for a frame but
390 // then rejects it when the frame is ready. The frame should stay in the queue
391 // and future frames should not receive notification, either.
TEST_F(IsoStreamTest,ReadRequestedAndThenRejected)392 TEST_F(IsoStreamTest, ReadRequestedAndThenRejected) {
393 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
394 SetupDataPath(
395 pw::bluetooth::emboss::DataPathDirection::OUTPUT,
396 /*codec_configuration=*/std::nullopt,
397 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
398 iso::IsoStream::SetupDataPathError::kSuccess);
399 size_t packet0_sdu_fragment_size = kMaxControllerSDUFragmentSize;
400 std::unique_ptr<std::vector<uint8_t>> packet0_sdu_data =
401 testing::GenDataBlob(packet0_sdu_fragment_size, /*starting_value=*/11);
402 DynamicByteBuffer packet0 = testing::IsoDataPacket(
403 /*connection_handle=*/iso_stream()->cis_handle(),
404 pw::bluetooth::emboss::IsoDataPbFlag::COMPLETE_SDU,
405 /*time_stamp=*/std::nullopt,
406 /*packet_sequence_number=*/0,
407 /*iso_sdu_length=*/packet0_sdu_fragment_size,
408 pw::bluetooth::emboss::IsoDataPacketStatus::VALID_DATA,
409 *packet0_sdu_data);
410 pw::span<const std::byte> packet0_as_span = packet0.subspan();
411 size_t packet1_sdu_fragment_size = packet0_sdu_fragment_size - 1;
412 std::unique_ptr<std::vector<uint8_t>> packet1_sdu_data =
413 testing::GenDataBlob(packet1_sdu_fragment_size, /*starting_value=*/13);
414 DynamicByteBuffer packet1 = testing::IsoDataPacket(
415 /*connection_handle=*/iso_stream()->cis_handle(),
416 pw::bluetooth::emboss::IsoDataPbFlag::COMPLETE_SDU,
417 /*time_stamp=*/std::nullopt,
418 /*packet_sequence_number=*/1,
419 /*iso_sdu_length=*/packet1_sdu_fragment_size,
420 pw::bluetooth::emboss::IsoDataPacketStatus::VALID_DATA,
421 *packet1_sdu_data);
422 pw::span<const std::byte> packet1_as_span = packet1.subspan();
423
424 // Request a frame but then reject it when proffered by the stream
425 ASSERT_EQ(iso_stream()->ReadNextQueuedIncomingPacket(), nullptr);
426 accept_incoming_sdus_ = false;
427 iso_stream()->ReceiveInboundPacket(packet0_as_span);
428 EXPECT_EQ(complete_incoming_sdus()->size(), 0u);
429
430 // Accept future frames, but because no read request has been made that we
431 // couldn't fulfill, the stream should just queue them up.
432 accept_incoming_sdus_ = true;
433 iso_stream()->ReceiveInboundPacket(packet1_as_span);
434 EXPECT_EQ(complete_incoming_sdus()->size(), 0u);
435
436 // And finally, we should be able to read out the packets in the right order
437 std::unique_ptr<IsoDataPacket> rx_packet_0 =
438 iso_stream()->ReadNextQueuedIncomingPacket();
439 ASSERT_NE(rx_packet_0, nullptr);
440 ASSERT_EQ(rx_packet_0->size(), packet0_as_span.size());
441 ASSERT_TRUE(std::equal(
442 rx_packet_0->begin(), rx_packet_0->end(), packet0_as_span.begin()));
443 std::unique_ptr<IsoDataPacket> rx_packet_1 =
444 iso_stream()->ReadNextQueuedIncomingPacket();
445 ASSERT_NE(rx_packet_1, nullptr);
446 ASSERT_EQ(rx_packet_1->size(), packet1_as_span.size());
447 ASSERT_TRUE(std::equal(
448 rx_packet_1->begin(), rx_packet_1->end(), packet1_as_span.begin()));
449
450 // Stream's packet queue should be empty now
451 ASSERT_EQ(iso_stream()->ReadNextQueuedIncomingPacket(), nullptr);
452 }
453
454 // Bad packets will not be passed on
TEST_F(IsoStreamTest,BadPacket)455 TEST_F(IsoStreamTest, BadPacket) {
456 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
457 SetupDataPath(
458 pw::bluetooth::emboss::DataPathDirection::OUTPUT,
459 /*codec_configuration=*/std::nullopt,
460 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
461 iso::IsoStream::SetupDataPathError::kSuccess);
462 const size_t kIsoSduLength = 212;
463 std::unique_ptr<std::vector<uint8_t>> sdu_data =
464 testing::GenDataBlob(kIsoSduLength, /*starting_value=*/91);
465 DynamicByteBuffer packet0 = testing::IsoDataPacket(
466 /*connection_handle=*/iso_stream()->cis_handle(),
467 pw::bluetooth::emboss::IsoDataPbFlag::COMPLETE_SDU,
468 /*time_stamp=*/std::nullopt,
469 /*packet_sequence_number=*/0,
470 kIsoSduLength,
471 pw::bluetooth::emboss::IsoDataPacketStatus::VALID_DATA,
472 *sdu_data);
473 // Improperly formatted packets are discarded. To test this, we'll remove the
474 // last byte of SDU data before we send it.
475 pw::span<const std::byte> packet0_as_span =
476 packet0.subspan(0, packet0.size() - 1);
477 ASSERT_EQ(iso_stream()->ReadNextQueuedIncomingPacket(), nullptr);
478 iso_stream()->ReceiveInboundPacket(packet0_as_span);
479 ASSERT_EQ(complete_incoming_sdus()->size(), 0u);
480 }
481
482 // Extra data at the end of the frame will be removed
TEST_F(IsoStreamTest,ExcessDataIsTruncated)483 TEST_F(IsoStreamTest, ExcessDataIsTruncated) {
484 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
485 SetupDataPath(
486 pw::bluetooth::emboss::DataPathDirection::OUTPUT,
487 /*codec_configuration=*/std::nullopt,
488 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
489 iso::IsoStream::SetupDataPathError::kSuccess);
490 const size_t kIsoSduLength = 212;
491 std::unique_ptr<std::vector<uint8_t>> sdu_data =
492 testing::GenDataBlob(kIsoSduLength, /*starting_value=*/91);
493 DynamicByteBuffer packet0 = testing::IsoDataPacket(
494 /*connection_handle=*/iso_stream()->cis_handle(),
495 pw::bluetooth::emboss::IsoDataPbFlag::COMPLETE_SDU,
496 /*time_stamp=*/std::nullopt,
497 /*packet_sequence_number=*/0,
498 kIsoSduLength,
499 pw::bluetooth::emboss::IsoDataPacketStatus::VALID_DATA,
500 *sdu_data);
501 size_t original_packet0_size = packet0.size();
502 packet0.expand(packet0.size() + 100);
503 pw::span<const std::byte> packet0_as_span = packet0.subspan();
504 ASSERT_EQ(iso_stream()->ReadNextQueuedIncomingPacket(), nullptr);
505 iso_stream()->ReceiveInboundPacket(packet0_as_span);
506 ASSERT_EQ(complete_incoming_sdus()->size(), 1u);
507 EXPECT_EQ(complete_incoming_sdus()->front().size(), original_packet0_size);
508 }
509
510 // Ensure sent packets are structured and sent correctly, including
511 // fragmentation.
TEST_F(IsoStreamTest,SendPacket)512 TEST_F(IsoStreamTest, SendPacket) {
513 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
514 SetupDataPath(
515 pw::bluetooth::emboss::DataPathDirection::OUTPUT,
516 /*codec_configuration=*/std::nullopt,
517 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
518 iso::IsoStream::SetupDataPathError::kSuccess);
519 RegisterStream();
520
521 constexpr size_t kMaxFirstPacketSize =
522 kMaxControllerSDUFragmentSize - kSduHeaderSize;
523
524 {
525 std::vector<uint8_t> blob =
526 *testing::GenDataBlob(kMaxFirstPacketSize / 2, /*starting_value=*/111);
527 EXPECT_ISO_PACKET_OUT(
528 test_device(),
529 testing::IsoDataPacket(
530 /*connection_handle = */ iso_stream()->cis_handle(),
531 /*pb_flag = */ IsoDataPbFlag::COMPLETE_SDU,
532 /*timestamp = */ std::nullopt,
533 /*sequence_number = */ 0,
534 /*sdu_length = */ blob.size(),
535 /*status_flag = */ IsoDataPacketStatus::VALID_DATA,
536 /*sdu_data = */ pw::span(blob.data(), blob.size())));
537
538 iso_stream()->Send(
539 pw::span(reinterpret_cast<const std::byte*>(blob.data()), blob.size()));
540 RunUntilIdle();
541 EXPECT_TRUE(test_device()->AllExpectedIsoPacketsSent());
542 }
543
544 {
545 // Send a packet that fits exactly within one ISO buffer easily.
546 std::vector<uint8_t> blob =
547 *testing::GenDataBlob(kMaxFirstPacketSize, /*starting_value=*/7);
548 EXPECT_ISO_PACKET_OUT(
549 test_device(),
550 testing::IsoDataPacket(
551 /*connection_handle = */ iso_stream()->cis_handle(),
552 /*pb_flag = */ IsoDataPbFlag::COMPLETE_SDU,
553 /*timestamp = */ std::nullopt,
554 /*sequence_number = */ 0,
555 /*sdu_length = */ blob.size(),
556 /*status_flag = */ IsoDataPacketStatus::VALID_DATA,
557 /*sdu_data = */ pw::span(blob.data(), blob.size())));
558
559 iso_stream()->Send(
560 pw::span(reinterpret_cast<const std::byte*>(blob.data()), blob.size()));
561 RunUntilIdle();
562 EXPECT_TRUE(test_device()->AllExpectedIsoPacketsSent());
563 }
564
565 {
566 // Send a packet that has to be split into three.
567 std::vector<uint8_t> blob = *testing::GenDataBlob(
568 kMaxFirstPacketSize + kMaxControllerSDUFragmentSize * 2,
569 /*starting_value=*/42);
570 EXPECT_ISO_PACKET_OUT(
571 test_device(),
572 testing::IsoDataPacket(
573 /*connection_handle = */ iso_stream()->cis_handle(),
574 /*pb_flag = */ IsoDataPbFlag::FIRST_FRAGMENT,
575 /*timestamp = */ std::nullopt,
576 /*sequence_number = */ 0,
577 /*sdu_length = */ blob.size(),
578 /*status_flag = */ IsoDataPacketStatus::VALID_DATA,
579 /*sdu_data = */ pw::span(blob.data(), kMaxFirstPacketSize)));
580 EXPECT_ISO_PACKET_OUT(
581 test_device(),
582 testing::IsoDataPacket(
583 /*connection_handle = */ iso_stream()->cis_handle(),
584 /*pb_flag = */ IsoDataPbFlag::INTERMEDIATE_FRAGMENT,
585 /*timestamp = */ std::nullopt,
586 /*sequence_number = */ std::nullopt,
587 /*sdu_length = */ std::nullopt,
588 /*status_flag = */ std::nullopt,
589 /*sdu_data = */
590 pw::span(blob.data(), blob.size())
591 .subspan(kMaxFirstPacketSize, kMaxControllerSDUFragmentSize)));
592 EXPECT_ISO_PACKET_OUT(
593 test_device(),
594 testing::IsoDataPacket(
595 /*connection_handle = */ iso_stream()->cis_handle(),
596 /*pb_flag = */ IsoDataPbFlag::LAST_FRAGMENT,
597 /*timestamp = */ std::nullopt,
598 /*sequence_number = */ std::nullopt,
599 /*sdu_length = */ std::nullopt,
600 /*status_flag = */ std::nullopt,
601 /*sdu_data = */
602 pw::span(blob.data(), blob.size())
603 .subspan(kMaxFirstPacketSize + kMaxControllerSDUFragmentSize)));
604
605 iso_stream()->Send(
606 pw::span(reinterpret_cast<const std::byte*>(blob.data()), blob.size()));
607 RunUntilIdle();
608 EXPECT_TRUE(test_device()->AllExpectedIsoPacketsSent());
609 }
610
611 {
612 // Send a packet that just barely needs to be split into three fragments.
613 std::vector<uint8_t> blob = *testing::GenDataBlob(
614 kMaxFirstPacketSize + kMaxControllerSDUFragmentSize + 1,
615 /*starting_value=*/77);
616 EXPECT_ISO_PACKET_OUT(
617 test_device(),
618 testing::IsoDataPacket(
619 /*connection_handle = */ iso_stream()->cis_handle(),
620 /*pb_flag = */ IsoDataPbFlag::FIRST_FRAGMENT,
621 /*timestamp = */ std::nullopt,
622 /*sequence_number = */ 0,
623 /*sdu_length = */ blob.size(),
624 /*status_flag = */ IsoDataPacketStatus::VALID_DATA,
625 /*sdu_data = */ pw::span(blob.data(), kMaxFirstPacketSize)));
626 EXPECT_ISO_PACKET_OUT(
627 test_device(),
628 testing::IsoDataPacket(
629 /*connection_handle = */ iso_stream()->cis_handle(),
630 /*pb_flag = */ IsoDataPbFlag::INTERMEDIATE_FRAGMENT,
631 /*timestamp = */ std::nullopt,
632 /*sequence_number = */ std::nullopt,
633 /*sdu_length = */ std::nullopt,
634 /*status_flag = */ std::nullopt,
635 /*sdu_data = */
636 pw::span(blob.data(), blob.size())
637 .subspan(kMaxFirstPacketSize, kMaxControllerSDUFragmentSize)));
638 EXPECT_ISO_PACKET_OUT(
639 test_device(),
640 testing::IsoDataPacket(
641 /*connection_handle = */ iso_stream()->cis_handle(),
642 /*pb_flag = */ IsoDataPbFlag::LAST_FRAGMENT,
643 /*timestamp = */ std::nullopt,
644 /*sequence_number = */ std::nullopt,
645 /*sdu_length = */ std::nullopt,
646 /*status_flag = */ std::nullopt,
647 /*sdu_data = */
648 pw::span(blob.data(), blob.size())
649 .subspan(kMaxFirstPacketSize + kMaxControllerSDUFragmentSize)));
650
651 iso_stream()->Send(
652 pw::span(reinterpret_cast<const std::byte*>(blob.data()), blob.size()));
653 RunUntilIdle();
654 EXPECT_TRUE(test_device()->AllExpectedIsoPacketsSent());
655 }
656 }
657
658 } // namespace
659 } // namespace bt::iso
660