xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/iso/iso_stream_test.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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