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_sapphire/internal/host/testing/controller_test.h"
18 #include "pw_bluetooth_sapphire/internal/host/testing/mock_controller.h"
19 #include "pw_bluetooth_sapphire/internal/host/testing/test_packets.h"
20
21 namespace bt::iso {
22
23 constexpr hci_spec::CigIdentifier kCigId = 0x22;
24 constexpr hci_spec::CisIdentifier kCisId = 0x42;
25
26 constexpr hci_spec::ConnectionHandle kCisHandleId = 0x59e;
27
28 constexpr size_t kMaxControllerPacketSize = 100;
29 constexpr size_t kMaxControllerPacketCount = 5;
30
31 using MockControllerTestBase =
32 bt::testing::FakeDispatcherControllerTest<bt::testing::MockController>;
33
34 class IsoStreamTest : public MockControllerTestBase {
35 public:
36 IsoStreamTest() = default;
37 ~IsoStreamTest() override = default;
38
SetUp()39 void SetUp() override {
40 MockControllerTestBase::SetUp(
41 pw::bluetooth::Controller::FeaturesBits::kHciIso);
42 hci::DataBufferInfo iso_buffer_info(kMaxControllerPacketSize,
43 kMaxControllerPacketCount);
44 transport()->InitializeIsoDataChannel(iso_buffer_info);
45 iso_stream_ = IsoStream::Create(
46 kCigId,
47 kCisId,
48 kCisHandleId,
49 /*on_established_cb=*/
50 [this](pw::bluetooth::emboss::StatusCode status,
51 std::optional<WeakSelf<IsoStream>::WeakPtr>,
52 const std::optional<CisEstablishedParameters>& parameters) {
53 ASSERT_FALSE(establishment_status_.has_value());
54 establishment_status_ = status;
55 established_parameters_ = parameters;
56 },
57 transport()->command_channel()->AsWeakPtr(),
58 /*on_closed_cb=*/
59 [this]() {
60 ASSERT_FALSE(closed_);
61 closed_ = true;
62 });
63 }
64
65 protected:
66 // Send an HCI_LE_CIS_Established event with the provided status
67 void EstablishCis(pw::bluetooth::emboss::StatusCode status);
68
69 // Call IsoStream::SetupDataPath().
70 // |cmd_complete_status| is nullopt if we do not expect an
71 // LE_Setup_ISO_Data_Path command to be generated, otherwise it should be set
72 // to the status code we want to generate in the response frame.
73 // |expected_cb_result| should be set to the expected result of the callback
74 // from IsoStream::SetupDataPath.
75 void SetupDataPath(pw::bluetooth::emboss::DataPathDirection direction,
76 const std::optional<std::vector<uint8_t>>& codec_config,
77 const std::optional<pw::bluetooth::emboss::StatusCode>&
78 cmd_complete_status,
79 iso::IsoStream::SetupDataPathError expected_cb_result,
80 bool generate_mismatched_cid = false);
81
82 bool HandleCompleteIncomingSDU(const pw::span<const std::byte>& complete_sdu);
83
iso_stream()84 IsoStream* iso_stream() { return iso_stream_.get(); }
85
complete_incoming_sdus()86 std::queue<std::vector<std::byte>>* complete_incoming_sdus() {
87 return &complete_incoming_sdus_;
88 }
89
establishment_status()90 std::optional<pw::bluetooth::emboss::StatusCode> establishment_status() {
91 return establishment_status_;
92 }
93
established_parameters()94 std::optional<CisEstablishedParameters> established_parameters() {
95 return established_parameters_;
96 }
97
closed()98 bool closed() { return closed_; }
99
100 protected:
101 bool accept_incoming_sdus_ = true;
102
103 private:
104 std::unique_ptr<IsoStream> iso_stream_;
105 std::optional<pw::bluetooth::emboss::StatusCode> establishment_status_;
106 std::optional<CisEstablishedParameters> established_parameters_;
107 std::queue<std::vector<std::byte>> complete_incoming_sdus_;
108 bool closed_ = false;
109 };
110
LECisEstablishedPacketWithDefaultValues(pw::bluetooth::emboss::StatusCode status)111 static DynamicByteBuffer LECisEstablishedPacketWithDefaultValues(
112 pw::bluetooth::emboss::StatusCode status) {
113 return testing::LECisEstablishedEventPacket(
114 status,
115 kCisHandleId,
116 /*cig_sync_delay_us=*/0x123456, // Must be in [0x0000ea, 0x7fffff]
117 /*cis_sync_delay_us=*/0x7890ab, // Must be in [0x0000ea, 0x7fffff]
118 /*transport_latency_c_to_p_us=*/0x654321, // Must be in [0x0000ea,
119 // 0x7fffff]
120 /*transport_latency_p_to_c_us=*/0x0fedcb, // Must be in [0x0000ea,
121 // 0x7fffff]
122 /*phy_c_to_p=*/pw::bluetooth::emboss::IsoPhyType::LE_2M,
123 /*phy_c_to_p=*/pw::bluetooth::emboss::IsoPhyType::LE_CODED,
124 /*nse=*/0x10, // Must be in [0x01, 0x1f]
125 /*bn_c_to_p=*/0x05, // Must be in [0x00, 0x0f]
126 /*bn_p_to_c=*/0x0f, // Must be in [0x00, 0x0f]
127 /*ft_c_to_p=*/0x01, // Must be in [0x01, 0xff]
128 /*ft_p_to_c=*/0xff, // Must be in [0x01, 0xff]
129 /*max_pdu_c_to_p=*/0x0042, // Must be in [0x0000, 0x00fb]
130 /*max_pdu_p_to_c=*/0x00fb, // Must be in [0x0000, 0x00fb]
131 /*iso_interval=*/0x0222 // Must be in [0x0004, 0x0c80]
132 );
133 }
134
EstablishCis(pw::bluetooth::emboss::StatusCode status)135 void IsoStreamTest::EstablishCis(pw::bluetooth::emboss::StatusCode status) {
136 DynamicByteBuffer le_cis_established_packet =
137 LECisEstablishedPacketWithDefaultValues(status);
138 test_device()->SendCommandChannelPacket(le_cis_established_packet);
139 RunUntilIdle();
140 ASSERT_TRUE(establishment_status().has_value());
141 EXPECT_EQ(*(establishment_status()), status);
142 if (status == pw::bluetooth::emboss::StatusCode::SUCCESS) {
143 EXPECT_TRUE(established_parameters().has_value());
144 } else {
145 EXPECT_FALSE(established_parameters().has_value());
146 }
147 }
148
GenerateCodecId()149 bt::StaticPacket<pw::bluetooth::emboss::CodecIdWriter> GenerateCodecId() {
150 const uint16_t kCompanyId = 0x1234;
151 bt::StaticPacket<pw::bluetooth::emboss::CodecIdWriter> codec_id;
152 auto codec_id_view = codec_id.view();
153 codec_id_view.coding_format().Write(pw::bluetooth::emboss::CodingFormat::LC3);
154 codec_id_view.company_id().Write(kCompanyId);
155 return codec_id;
156 }
157
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)158 void IsoStreamTest::SetupDataPath(
159 pw::bluetooth::emboss::DataPathDirection direction,
160 const std::optional<std::vector<uint8_t>>& codec_configuration,
161 const std::optional<pw::bluetooth::emboss::StatusCode>& cmd_complete_status,
162 iso::IsoStream::SetupDataPathError expected_cb_result,
163 bool generate_mismatched_cid) {
164 const uint32_t kControllerDelay = 1234; // Must be < 4000000
165 std::optional<iso::IsoStream::SetupDataPathError> actual_cb_result;
166
167 if (cmd_complete_status.has_value()) {
168 auto setup_data_path_packet =
169 testing::LESetupIsoDataPathPacket(kCisHandleId,
170 direction,
171 /*HCI*/ 0,
172 GenerateCodecId(),
173 kControllerDelay,
174 codec_configuration);
175 hci_spec::ConnectionHandle cis_handle =
176 kCisHandleId + (generate_mismatched_cid ? 1 : 0);
177 auto setup_data_path_complete_packet =
178 testing::LESetupIsoDataPathResponse(*cmd_complete_status, cis_handle);
179 EXPECT_CMD_PACKET_OUT(test_device(),
180 setup_data_path_packet,
181 &setup_data_path_complete_packet);
182 }
183
184 iso_stream()->SetupDataPath(
185 direction,
186 GenerateCodecId(),
187 codec_configuration,
188 kControllerDelay,
189 [&actual_cb_result](iso::IsoStream::SetupDataPathError result) {
190 actual_cb_result = result;
191 },
192 fit::bind_member<&IsoStreamTest::HandleCompleteIncomingSDU>(this));
193 RunUntilIdle();
194 ASSERT_TRUE(actual_cb_result.has_value());
195 EXPECT_EQ(expected_cb_result, *actual_cb_result);
196 }
197
HandleCompleteIncomingSDU(const pw::span<const std::byte> & sdu)198 bool IsoStreamTest::HandleCompleteIncomingSDU(
199 const pw::span<const std::byte>& sdu) {
200 if (!accept_incoming_sdus_) {
201 return false;
202 }
203 std::vector<std::byte> new_sdu(sdu.size());
204 std::copy(sdu.begin(), sdu.end(), new_sdu.begin());
205 complete_incoming_sdus_.emplace(std::move(new_sdu));
206 return true;
207 }
208
TEST_F(IsoStreamTest,CisEstablishedSuccessfully)209 TEST_F(IsoStreamTest, CisEstablishedSuccessfully) {
210 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
211 }
212
TEST_F(IsoStreamTest,CisEstablishmentFailed)213 TEST_F(IsoStreamTest, CisEstablishmentFailed) {
214 EstablishCis(pw::bluetooth::emboss::StatusCode::MEMORY_CAPACITY_EXCEEDED);
215 }
216
TEST_F(IsoStreamTest,ClosedCallsCloseCallback)217 TEST_F(IsoStreamTest, ClosedCallsCloseCallback) {
218 EXPECT_FALSE(closed());
219 iso_stream()->Close();
220 EXPECT_TRUE(closed());
221 }
222
TEST_F(IsoStreamTest,SetupDataPathSuccessfully)223 TEST_F(IsoStreamTest, SetupDataPathSuccessfully) {
224 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
225 SetupDataPath(pw::bluetooth::emboss::DataPathDirection::OUTPUT,
226 /*codec_configuration=*/std::nullopt,
227 pw::bluetooth::emboss::StatusCode::SUCCESS,
228 iso::IsoStream::SetupDataPathError::kSuccess);
229 }
230
TEST_F(IsoStreamTest,SetupDataPathBeforeCisEstablished)231 TEST_F(IsoStreamTest, SetupDataPathBeforeCisEstablished) {
232 SetupDataPath(pw::bluetooth::emboss::DataPathDirection::OUTPUT,
233 /*codec_configuration=*/std::nullopt,
234 /*cmd_complete_status=*/std::nullopt,
235 iso::IsoStream::SetupDataPathError::kCisNotEstablished);
236 }
237
TEST_F(IsoStreamTest,SetupInputDataPathTwice)238 TEST_F(IsoStreamTest, SetupInputDataPathTwice) {
239 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
240 SetupDataPath(
241 pw::bluetooth::emboss::DataPathDirection::INPUT,
242 /*codec_configuration=*/std::nullopt,
243 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
244 iso::IsoStream::SetupDataPathError::kSuccess);
245 SetupDataPath(pw::bluetooth::emboss::DataPathDirection::INPUT,
246 /*codec_configuration=*/std::nullopt,
247 /*cmd_complete_status=*/std::nullopt,
248 iso::IsoStream::SetupDataPathError::kStreamAlreadyExists);
249 }
250
TEST_F(IsoStreamTest,SetupOutputDataPathTwice)251 TEST_F(IsoStreamTest, SetupOutputDataPathTwice) {
252 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
253 SetupDataPath(
254 pw::bluetooth::emboss::DataPathDirection::OUTPUT,
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::OUTPUT,
259 /*codec_configuration=*/std::nullopt,
260 /*cmd_complete_status=*/std::nullopt,
261 iso::IsoStream::SetupDataPathError::kStreamAlreadyExists);
262 }
263
TEST_F(IsoStreamTest,SetupBothInputAndOutputDataPaths)264 TEST_F(IsoStreamTest, SetupBothInputAndOutputDataPaths) {
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(
272 pw::bluetooth::emboss::DataPathDirection::INPUT,
273 /*codec_configuration=*/std::nullopt,
274 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
275 iso::IsoStream::SetupDataPathError::kSuccess);
276 }
277
TEST_F(IsoStreamTest,SetupDataPathInvalidArgs)278 TEST_F(IsoStreamTest, SetupDataPathInvalidArgs) {
279 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
280 SetupDataPath(static_cast<pw::bluetooth::emboss::DataPathDirection>(250),
281 /*codec_configuration=*/std::nullopt,
282 /*cmd_complete_status=*/std::nullopt,
283 iso::IsoStream::SetupDataPathError::kInvalidArgs);
284 }
285
TEST_F(IsoStreamTest,SetupDataPathWithCodecConfig)286 TEST_F(IsoStreamTest, SetupDataPathWithCodecConfig) {
287 std::vector<uint8_t> codec_config{5, 6, 7, 8};
288 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
289 SetupDataPath(
290 pw::bluetooth::emboss::DataPathDirection::OUTPUT,
291 codec_config,
292 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
293 iso::IsoStream::SetupDataPathError::kSuccess);
294 }
295
296 // If the connection ID doesn't match in the command complete packet, fail
TEST_F(IsoStreamTest,SetupDataPathHandleMismatch)297 TEST_F(IsoStreamTest, SetupDataPathHandleMismatch) {
298 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
299 SetupDataPath(
300 pw::bluetooth::emboss::DataPathDirection::INPUT,
301 /*codec_configuration=*/std::nullopt,
302 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
303 iso::IsoStream::SetupDataPathError::kStreamRejectedByController,
304 /*generate_mismatched_cid=*/true);
305 }
306
TEST_F(IsoStreamTest,SetupDataPathControllerError)307 TEST_F(IsoStreamTest, SetupDataPathControllerError) {
308 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
309 SetupDataPath(
310 pw::bluetooth::emboss::DataPathDirection::INPUT,
311 /*codec_configuration=*/std::nullopt,
312 /*cmd_complete_status=*/
313 pw::bluetooth::emboss::StatusCode::CONNECTION_ALREADY_EXISTS,
314 iso::IsoStream::SetupDataPathError::kStreamRejectedByController);
315 }
316
317 // If the client asks for frames before any are ready it will receive
318 // a notification when the next packet arrives.
TEST_F(IsoStreamTest,PendingRead)319 TEST_F(IsoStreamTest, PendingRead) {
320 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
321 SetupDataPath(
322 pw::bluetooth::emboss::DataPathDirection::OUTPUT,
323 /*codec_configuration=*/std::nullopt,
324 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
325 iso::IsoStream::SetupDataPathError::kSuccess);
326 DynamicByteBuffer packet0 =
327 testing::IsoDataPacket(kMaxControllerPacketSize,
328 iso_stream()->cis_handle(),
329 /*packet_sequence_number=*/0);
330 pw::span<const std::byte> packet0_as_span = packet0.subspan();
331 ASSERT_EQ(iso_stream()->ReadNextQueuedIncomingPacket(), nullptr);
332 iso_stream()->ReceiveInboundPacket(packet0_as_span);
333 ASSERT_EQ(complete_incoming_sdus()->size(), 1u);
334 std::vector<std::byte>& received_frame = complete_incoming_sdus()->front();
335 ASSERT_EQ(packet0_as_span.size(), received_frame.size());
336 EXPECT_TRUE(std::equal(
337 packet0_as_span.begin(), packet0_as_span.end(), received_frame.begin()));
338 }
339
340 // If the client does not ask for frames it will not receive any notifications
341 // and the IsoStream will just queue them up.
TEST_F(IsoStreamTest,UnreadData)342 TEST_F(IsoStreamTest, UnreadData) {
343 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
344 SetupDataPath(
345 pw::bluetooth::emboss::DataPathDirection::OUTPUT,
346 /*codec_configuration=*/std::nullopt,
347 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
348 iso::IsoStream::SetupDataPathError::kSuccess);
349 const size_t kTotalFrameCount = 5;
350 DynamicByteBuffer packets[kTotalFrameCount];
351 pw::span<const std::byte> packets_as_span[kTotalFrameCount];
352 for (size_t i = 0; i < kTotalFrameCount; i++) {
353 packets[i] = testing::IsoDataPacket(kMaxControllerPacketSize - i,
354 iso_stream()->cis_handle(),
355 /*packet_sequence_number=*/i);
356 packets_as_span[i] = packets[i].subspan();
357 iso_stream()->ReceiveInboundPacket(packets_as_span[i]);
358 }
359 EXPECT_EQ(complete_incoming_sdus()->size(), 0u);
360 }
361
362 // This is the (somewhat unusual) case where the client asks for a frame but
363 // then rejects it when the frame is ready. The frame should stay in the queue
364 // and future frames should not receive notification, either.
TEST_F(IsoStreamTest,ReadRequestedAndThenRejected)365 TEST_F(IsoStreamTest, ReadRequestedAndThenRejected) {
366 EstablishCis(pw::bluetooth::emboss::StatusCode::SUCCESS);
367 SetupDataPath(
368 pw::bluetooth::emboss::DataPathDirection::OUTPUT,
369 /*codec_configuration=*/std::nullopt,
370 /*cmd_complete_status=*/pw::bluetooth::emboss::StatusCode::SUCCESS,
371 iso::IsoStream::SetupDataPathError::kSuccess);
372 DynamicByteBuffer packet0 =
373 testing::IsoDataPacket(kMaxControllerPacketSize,
374 iso_stream()->cis_handle(),
375 /*packet_sequence_number=*/0);
376 pw::span<const std::byte> packet0_as_span = packet0.subspan();
377 DynamicByteBuffer packet1 =
378 testing::IsoDataPacket(kMaxControllerPacketSize - 1,
379 iso_stream()->cis_handle(),
380 /*packet_sequence_number=*/1);
381 pw::span<const std::byte> packet1_as_span = packet1.subspan();
382
383 // Request a frame but then reject it when proffered by the stream
384 ASSERT_EQ(iso_stream()->ReadNextQueuedIncomingPacket(), nullptr);
385 accept_incoming_sdus_ = false;
386 iso_stream()->ReceiveInboundPacket(packet0_as_span);
387 EXPECT_EQ(complete_incoming_sdus()->size(), 0u);
388
389 // Accept future frames, but because no read request has been made that we
390 // couldn't fulfill, the stream should just queue them up.
391 accept_incoming_sdus_ = true;
392 iso_stream()->ReceiveInboundPacket(packet1_as_span);
393 EXPECT_EQ(complete_incoming_sdus()->size(), 0u);
394
395 // And finally, we should be able to read out the packets in the right order
396 std::unique_ptr<IsoDataPacket> rx_packet_0 =
397 iso_stream()->ReadNextQueuedIncomingPacket();
398 ASSERT_NE(rx_packet_0, nullptr);
399 ASSERT_EQ(rx_packet_0->size(), packet0_as_span.size());
400 ASSERT_TRUE(std::equal(
401 rx_packet_0->begin(), rx_packet_0->end(), packet0_as_span.begin()));
402 std::unique_ptr<IsoDataPacket> rx_packet_1 =
403 iso_stream()->ReadNextQueuedIncomingPacket();
404 ASSERT_NE(rx_packet_1, nullptr);
405 ASSERT_EQ(rx_packet_1->size(), packet1_as_span.size());
406 ASSERT_TRUE(std::equal(
407 rx_packet_1->begin(), rx_packet_1->end(), packet1_as_span.begin()));
408
409 // Stream's packet queue should be empty now
410 ASSERT_EQ(iso_stream()->ReadNextQueuedIncomingPacket(), nullptr);
411 }
412
413 } // namespace bt::iso
414