• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #pragma once
16 
17 #include <cstdint>
18 #include <numeric>
19 #include <variant>
20 #include <vector>
21 
22 #include "pw_assert/check.h"
23 #include "pw_bluetooth/emboss_util.h"
24 #include "pw_bluetooth/hci_common.emb.h"
25 #include "pw_bluetooth/hci_data.emb.h"
26 #include "pw_bluetooth/hci_events.emb.h"
27 #include "pw_bluetooth/hci_h4.emb.h"
28 #include "pw_bluetooth/l2cap_frames.emb.h"
29 #include "pw_bluetooth_proxy/basic_l2cap_channel.h"
30 #include "pw_bluetooth_proxy/gatt_notify_channel.h"
31 #include "pw_bluetooth_proxy/h4_packet.h"
32 #include "pw_bluetooth_proxy/internal/l2cap_channel.h"
33 #include "pw_bluetooth_proxy/internal/logical_transport.h"
34 #include "pw_bluetooth_proxy/l2cap_channel_common.h"
35 #include "pw_bluetooth_proxy/l2cap_coc.h"
36 #include "pw_bluetooth_proxy/l2cap_status_delegate.h"
37 #include "pw_bluetooth_proxy/proxy_host.h"
38 #include "pw_bluetooth_proxy/rfcomm_channel.h"
39 #include "pw_containers/flat_map.h"
40 #include "pw_function/function.h"
41 #include "pw_multibuf/simple_allocator_for_test.h"
42 #include "pw_status/status.h"
43 #include "pw_status/try.h"
44 #include "pw_unit_test/framework.h"
45 
46 namespace pw::bluetooth::proxy {
47 
48 // ########## Util functions
49 
50 struct AclFrameWithStorage {
51   std::vector<uint8_t> storage;
52   emboss::AclDataFrameWriter writer;
53 
54   static constexpr size_t kH4HeaderSize = 1;
h4_spanAclFrameWithStorage55   pw::span<uint8_t> h4_span() { return storage; }
hci_spanAclFrameWithStorage56   pw::span<uint8_t> hci_span() {
57     return pw::span(storage).subspan(kH4HeaderSize);
58   }
59 };
60 
61 // Allocate storage and populate an ACL packet header with the given length.
62 Result<AclFrameWithStorage> SetupAcl(uint16_t handle, uint16_t l2cap_length);
63 
64 struct BFrameWithStorage {
65   AclFrameWithStorage acl;
66   emboss::BFrameWriter writer;
67 };
68 
69 Result<BFrameWithStorage> SetupBFrame(uint16_t handle,
70                                       uint16_t channel_id,
71                                       uint16_t bframe_len);
72 
73 struct CFrameWithStorage {
74   AclFrameWithStorage acl;
75   emboss::CFrameWriter writer;
76 };
77 
78 Result<CFrameWithStorage> SetupCFrame(uint16_t handle,
79                                       uint16_t channel_id,
80                                       uint16_t cframe_len);
81 
82 struct KFrameWithStorage {
83   AclFrameWithStorage acl;
84   std::variant<emboss::FirstKFrameWriter, emboss::SubsequentKFrameWriter>
85       writer;
86 };
87 
88 // Size of sdu_length field in first K-frames.
89 inline constexpr uint8_t kSduLengthFieldSize = 2;
90 
91 // Populate a KFrame that encodes a particular segment of `payload` based on the
92 // `mps`, or maximum PDU payload size of a segment. `segment_no` is the nth
93 // segment that would be generated based on the `mps`. The first segment is
94 // `segment_no == 0` and returns the `FirstKFrameWriter` variant in
95 // `KFrameWithStorage`.
96 //
97 // Returns PW_STATUS_OUT_OF_RANGE if a segment is requested beyond the last
98 // segment that would be generated based on `mps`.
99 Result<KFrameWithStorage> SetupKFrame(uint16_t handle,
100                                       uint16_t channel_id,
101                                       uint16_t mps,
102                                       uint16_t segment_no,
103                                       span<const uint8_t> payload);
104 
105 // Populate passed H4 command buffer and return Emboss view on it.
106 template <typename EmbossT>
CreateAndPopulateToControllerView(H4PacketWithH4 & h4_packet,emboss::OpCode opcode,size_t parameter_total_size)107 Result<EmbossT> CreateAndPopulateToControllerView(H4PacketWithH4& h4_packet,
108                                                   emboss::OpCode opcode,
109                                                   size_t parameter_total_size) {
110   h4_packet.SetH4Type(emboss::H4PacketType::COMMAND);
111   PW_TRY_ASSIGN(auto view, MakeEmbossWriter<EmbossT>(h4_packet.GetHciSpan()));
112   view.header().opcode().Write(opcode);
113   view.header().parameter_total_size().Write(parameter_total_size);
114   return view;
115 }
116 
117 // Populate passed H4 event buffer and return Emboss writer on it. Suitable for
118 // use with EmbossT types whose `SizeInBytes()` accurately represents
119 // the `parameter_total_size` that should be written (minus `EventHeader` size).
120 template <typename EmbossT>
121 Result<EmbossT> CreateAndPopulateToHostEventWriter(
122     H4PacketWithHci& h4_packet,
123     emboss::EventCode event_code,
124     size_t parameter_total_size = EmbossT::SizeInBytes() -
125                                   emboss::EventHeader::IntrinsicSizeInBytes()) {
126   h4_packet.SetH4Type(emboss::H4PacketType::EVENT);
127   PW_TRY_ASSIGN(auto view, MakeEmbossWriter<EmbossT>(h4_packet.GetHciSpan()));
128   view.header().event_code().Write(event_code);
129   view.header().parameter_total_size().Write(parameter_total_size);
130   view.status().Write(emboss::StatusCode::SUCCESS);
131   EXPECT_TRUE(view.IsComplete());
132   return view;
133 }
134 
135 // Send an LE_Read_Buffer_Size (V2) CommandComplete event to `proxy` to request
136 // the reservation of a number of LE ACL send credits.
137 Status SendLeReadBufferResponseFromController(
138     ProxyHost& proxy,
139     uint8_t num_credits_to_reserve,
140     uint16_t le_acl_data_packet_length = 251);
141 
142 Status SendReadBufferResponseFromController(ProxyHost& proxy,
143                                             uint8_t num_credits_to_reserve);
144 
145 // Send a Number_of_Completed_Packets event to `proxy` that reports each
146 // {connection handle, number of completed packets} entry provided.
147 template <size_t kNumConnections>
SendNumberOfCompletedPackets(ProxyHost & proxy,containers::FlatMap<uint16_t,uint16_t,kNumConnections> packets_per_connection)148 Status SendNumberOfCompletedPackets(
149     ProxyHost& proxy,
150     containers::FlatMap<uint16_t, uint16_t, kNumConnections>
151         packets_per_connection) {
152   std::array<
153       uint8_t,
154       emboss::NumberOfCompletedPacketsEvent::MinSizeInBytes() +
155           kNumConnections *
156               emboss::NumberOfCompletedPacketsEventData::IntrinsicSizeInBytes()>
157       hci_arr;
158   hci_arr.fill(0);
159   H4PacketWithHci nocp_event{emboss::H4PacketType::EVENT, hci_arr};
160   PW_TRY_ASSIGN(auto view,
161                 MakeEmbossWriter<emboss::NumberOfCompletedPacketsEventWriter>(
162                     nocp_event.GetHciSpan()));
163   view.header().event_code().Write(
164       emboss::EventCode::NUMBER_OF_COMPLETED_PACKETS);
165   view.header().parameter_total_size().Write(
166       nocp_event.GetHciSpan().size() -
167       emboss::EventHeader::IntrinsicSizeInBytes());
168   view.num_handles().Write(kNumConnections);
169 
170   size_t i = 0;
171   for (const auto& [handle, num_packets] : packets_per_connection) {
172     view.nocp_data()[i].connection_handle().Write(handle);
173     view.nocp_data()[i].num_completed_packets().Write(num_packets);
174     ++i;
175   }
176 
177   proxy.HandleH4HciFromController(std::move(nocp_event));
178   return OkStatus();
179 }
180 
181 // Send a Connection_Complete event to `proxy` indicating the provided
182 // `handle` has disconnected.
183 Status SendConnectionCompleteEvent(ProxyHost& proxy,
184                                    uint16_t handle,
185                                    emboss::StatusCode status);
186 
187 // Send a LE_Connection_Complete event to `proxy` indicating the provided
188 // `handle` has disconnected.
189 Status SendLeConnectionCompleteEvent(ProxyHost& proxy,
190                                      uint16_t handle,
191                                      emboss::StatusCode status);
192 
193 // Send a Disconnection_Complete event to `proxy` indicating the provided
194 // `handle` has disconnected.
195 Status SendDisconnectionCompleteEvent(
196     ProxyHost& proxy,
197     uint16_t handle,
198     Direction direction = Direction::kFromController,
199     bool successful = true);
200 
201 Status SendL2capConnectionReq(ProxyHost& proxy,
202                               uint16_t handle,
203                               uint16_t source_cid,
204                               uint16_t psm);
205 
206 Status SendL2capConnectionRsp(ProxyHost& proxy,
207                               uint16_t handle,
208                               uint16_t source_cid,
209                               uint16_t destination_cid,
210                               emboss::L2capConnectionRspResultCode result_code);
211 
212 Status SendL2capDisconnectRsp(ProxyHost& proxy,
213                               AclTransportType transport,
214                               uint16_t handle,
215                               uint16_t source_cid,
216                               uint16_t destination_cid,
217                               Direction direction = Direction::kFromHost);
218 
219 /// Sends an L2CAP B-Frame.
220 ///
221 /// This can be either a complete PDU (pdu_length == payload.size()) or an
222 /// initial fragment (pdu_length > payload.size()).
223 void SendL2capBFrame(ProxyHost& proxy,
224                      uint16_t handle,
225                      pw::span<const uint8_t> payload,
226                      size_t pdu_length,
227                      uint16_t channel_id);
228 
229 /// Sends an ACL frame with CONTINUING_FRAGMENT boundary flag.
230 ///
231 /// No L2CAP header is included.
232 void SendAclContinuingFrag(ProxyHost& proxy,
233                            uint16_t handle,
234                            pw::span<const uint8_t> payload);
235 
236 // TODO: https://pwbug.dev/382783733 - Migrate to L2capChannelEvent callback.
237 struct CocParameters {
238   uint16_t handle = 123;
239   uint16_t local_cid = 234;
240   uint16_t remote_cid = 456;
241   uint16_t rx_mtu = 100;
242   uint16_t rx_mps = 100;
243   uint16_t rx_credits = 1;
244   uint16_t tx_mtu = 100;
245   uint16_t tx_mps = 100;
246   uint16_t tx_credits = 1;
247   Function<void(multibuf::MultiBuf&& payload)>&& receive_fn = nullptr;
248   ChannelEventCallback&& event_fn = nullptr;
249 };
250 
251 struct BasicL2capParameters {
252   uint16_t handle = 123;
253   uint16_t local_cid = 234;
254   uint16_t remote_cid = 456;
255   AclTransportType transport = AclTransportType::kLe;
256   OptionalPayloadReceiveCallback&& payload_from_controller_fn = nullptr;
257   OptionalPayloadReceiveCallback&& payload_from_host_fn = nullptr;
258   ChannelEventCallback&& event_fn = nullptr;
259 };
260 
261 struct GattNotifyChannelParameters {
262   uint16_t handle = 0xAB;
263   uint16_t attribute_handle = 0xBC;
264   ChannelEventCallback&& event_fn = nullptr;
265 };
266 
267 struct RfcommConfigParameters {
268   uint16_t cid = 123;
269   uint16_t max_information_length = 900;
270   uint16_t credits = 10;
271 };
272 
273 struct RfcommParameters {
274   uint16_t handle = 123;
275   RfcommConfigParameters rx_config = {
276       .cid = 234, .max_information_length = 900, .credits = 10};
277   RfcommConfigParameters tx_config = {
278       .cid = 456, .max_information_length = 900, .credits = 10};
279   uint8_t rfcomm_channel = 3;
280 };
281 
282 // See BuildOneOfEachChannel
283 struct OneOfEachChannelParameters {
284   Function<void(multibuf::MultiBuf&& payload)>&& receive_fn = nullptr;
285   ChannelEventCallback&& event_fn = nullptr;
286 };
287 
288 // See BuildOneOfEachChannel
289 struct OneOfEachChannel {
OneOfEachChannelOneOfEachChannel290   OneOfEachChannel(BasicL2capChannel&& basic,
291                    L2capCoc&& coc,
292                    RfcommChannel&& rfcomm,
293                    GattNotifyChannel&& gatt)
294       : basic_{std::move(basic)},
295         coc_{std::move(coc)},
296         rfcomm_{std::move(rfcomm)},
297         gatt_{std::move(gatt)} {}
298 
AllChannelsOneOfEachChannel299   std::vector<L2capChannel*> AllChannels() {
300     return std::vector<L2capChannel*>{&basic_, &coc_, &rfcomm_, &gatt_};
301   }
302 
303   BasicL2capChannel basic_;
304   L2capCoc coc_;
305   RfcommChannel rfcomm_;
306   GattNotifyChannel gatt_;
307 };
308 
309 // ########## Test Suites
310 
311 class ProxyHostTest : public testing::Test {
312  protected:
313   pw::Result<L2capCoc> BuildCocWithResult(ProxyHost& proxy,
314                                           CocParameters params);
315 
316   L2capCoc BuildCoc(ProxyHost& proxy, CocParameters params);
317 
318   Result<BasicL2capChannel> BuildBasicL2capChannelWithResult(
319       ProxyHost& proxy, BasicL2capParameters params);
320 
321   BasicL2capChannel BuildBasicL2capChannel(ProxyHost& proxy,
322                                            BasicL2capParameters params);
323 
324   Result<GattNotifyChannel> BuildGattNotifyChannelWithResult(
325       ProxyHost& proxy, GattNotifyChannelParameters params);
326 
327   GattNotifyChannel BuildGattNotifyChannel(ProxyHost& proxy,
328                                            GattNotifyChannelParameters params);
329 
330   RfcommChannel BuildRfcomm(
331       ProxyHost& proxy,
332       RfcommParameters params = {},
333       Function<void(multibuf::MultiBuf&& payload)>&& receive_fn = nullptr,
334       ChannelEventCallback&& event_fn = nullptr);
335 
336   template <typename T, size_t N>
MultiBufFromSpan(span<T,N> buf)337   pw::multibuf::MultiBuf MultiBufFromSpan(span<T, N> buf) {
338     std::optional<pw::multibuf::MultiBuf> multibuf =
339         test_multibuf_allocator_.AllocateContiguous(buf.size());
340     PW_ASSERT(multibuf.has_value());
341     std::optional<ConstByteSpan> multibuf_span = multibuf->ContiguousSpan();
342     PW_ASSERT(multibuf_span);
343     PW_TEST_EXPECT_OK(multibuf->CopyFrom(as_bytes(buf)));
344     return std::move(*multibuf);
345   }
346 
347   // Builds a struct with one of each channel to support tests across all
348   // of them.
349   //
350   // Note, shared_event_fn is a reference (rather than a rvalue) so it can
351   // be shared across each channel.
352   OneOfEachChannel BuildOneOfEachChannel(ProxyHost& proxy,
353                                          ChannelEventCallback& shared_event_fn);
354 
355   template <typename T, size_t N>
MultiBufFromArray(const std::array<T,N> & arr)356   pw::multibuf::MultiBuf MultiBufFromArray(const std::array<T, N>& arr) {
357     return MultiBufFromSpan(pw::span{arr});
358   }
359 
360  private:
361   // MultiBuf allocator for creating objects to pass to the system under
362   // test (e.g. creating test packets to send to proxy host).
363   pw::multibuf::test::SimpleAllocatorForTest</*kDataSizeBytes=*/2 * 1024,
364                                              /*kMetaSizeBytes=*/2 * 1024>
365       test_multibuf_allocator_{};
366 
367   // Default MultiBuf allocator to be passed to system under test (e.g.
368   // to pass to AcquireL2capCoc).
369   pw::multibuf::test::SimpleAllocatorForTest</*kDataSizeBytes=*/1024,
370                                              /*kMetaSizeBytes=*/2 * 1024>
371       sut_multibuf_allocator_{};
372 };
373 
374 }  // namespace pw::bluetooth::proxy
375