• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/testing/mock_controller.h"
16 
17 #include <pw_assert/check.h>
18 #include <pw_bytes/endian.h>
19 
20 #include <cstdint>
21 
22 #include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h"
23 #include "pw_bluetooth_sapphire/internal/host/testing/gtest_helpers.h"
24 #include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
25 #include "pw_unit_test/framework.h"
26 
27 namespace bt::testing {
28 
Transaction(const ByteBuffer & expected,const std::vector<const ByteBuffer * > & replies,ExpectationMetadata meta)29 Transaction::Transaction(const ByteBuffer& expected,
30                          const std::vector<const ByteBuffer*>& replies,
31                          ExpectationMetadata meta)
32     : expected_({DynamicByteBuffer(expected), meta}) {
33   for (const auto* buffer : replies) {
34     replies_.push(DynamicByteBuffer(*buffer));
35   }
36 }
37 
Match(const ByteBuffer & packet)38 bool Transaction::Match(const ByteBuffer& packet) {
39   return ContainersEqual(expected_.data, packet);
40 }
41 
CommandTransaction(hci_spec::OpCode expected_opcode,const std::vector<const ByteBuffer * > & replies,ExpectationMetadata meta)42 CommandTransaction::CommandTransaction(
43     hci_spec::OpCode expected_opcode,
44     const std::vector<const ByteBuffer*>& replies,
45     ExpectationMetadata meta)
46     : Transaction(DynamicByteBuffer(), replies, meta), prefix_(true) {
47   hci_spec::OpCode le_opcode =
48       pw::bytes::ConvertOrderTo(cpp20::endian::little, expected_opcode);
49   const BufferView expected(&le_opcode, sizeof(expected_opcode));
50   set_expected({DynamicByteBuffer(expected), meta});
51 }
52 
Match(const ByteBuffer & cmd)53 bool CommandTransaction::Match(const ByteBuffer& cmd) {
54   return ContainersEqual(
55       expected().data,
56       (prefix_ ? cmd.view(0, expected().data.size()) : cmd.view()));
57 }
58 
MockController(pw::async::Dispatcher & pw_dispatcher)59 MockController::MockController(pw::async::Dispatcher& pw_dispatcher)
60     : ControllerTestDoubleBase(pw_dispatcher), WeakSelf(this) {}
61 
~MockController()62 MockController::~MockController() {
63   while (!cmd_transactions_.empty()) {
64     auto& transaction = cmd_transactions_.front();
65     auto meta = transaction.expected().meta;
66     ADD_FAILURE_AT(meta.file, meta.line)
67         << "Didn't receive expected outbound command packet ("
68         << meta.expectation << ") {"
69         << ByteContainerToString(transaction.expected().data) << "}";
70     cmd_transactions_.pop();
71   }
72 
73   while (!data_transactions_.empty()) {
74     auto& transaction = data_transactions_.front();
75     auto meta = transaction.expected().meta;
76     ADD_FAILURE_AT(meta.file, meta.line)
77         << "Didn't receive expected outbound data packet (" << meta.expectation
78         << ") {" << ByteContainerToString(transaction.expected().data) << "}";
79     data_transactions_.pop();
80   }
81 
82   while (!sco_transactions_.empty()) {
83     auto& transaction = sco_transactions_.front();
84     auto meta = transaction.expected().meta;
85     ADD_FAILURE_AT(meta.file, meta.line)
86         << "Didn't receive expected outbound SCO packet (" << meta.expectation
87         << ") {" << ByteContainerToString(transaction.expected().data) << "}";
88     sco_transactions_.pop();
89   }
90 }
91 
QueueCommandTransaction(CommandTransaction transaction)92 void MockController::QueueCommandTransaction(CommandTransaction transaction) {
93   cmd_transactions_.push(std::move(transaction));
94 }
95 
QueueCommandTransaction(const ByteBuffer & expected,const std::vector<const ByteBuffer * > & replies,ExpectationMetadata meta)96 void MockController::QueueCommandTransaction(
97     const ByteBuffer& expected,
98     const std::vector<const ByteBuffer*>& replies,
99     ExpectationMetadata meta) {
100   QueueCommandTransaction(
101       CommandTransaction(DynamicByteBuffer(expected), replies, meta));
102 }
103 
QueueCommandTransaction(hci_spec::OpCode expected_opcode,const std::vector<const ByteBuffer * > & replies,ExpectationMetadata meta)104 void MockController::QueueCommandTransaction(
105     hci_spec::OpCode expected_opcode,
106     const std::vector<const ByteBuffer*>& replies,
107     ExpectationMetadata meta) {
108   QueueCommandTransaction(CommandTransaction(expected_opcode, replies, meta));
109 }
110 
QueueDataTransaction(DataTransaction transaction)111 void MockController::QueueDataTransaction(DataTransaction transaction) {
112   data_transactions_.push(std::move(transaction));
113 }
114 
QueueDataTransaction(const ByteBuffer & expected,const std::vector<const ByteBuffer * > & replies,ExpectationMetadata meta)115 void MockController::QueueDataTransaction(
116     const ByteBuffer& expected,
117     const std::vector<const ByteBuffer*>& replies,
118     ExpectationMetadata meta) {
119   QueueDataTransaction(
120       DataTransaction(DynamicByteBuffer(expected), replies, meta));
121 }
122 
QueueScoTransaction(const ByteBuffer & expected,ExpectationMetadata meta)123 void MockController::QueueScoTransaction(const ByteBuffer& expected,
124                                          ExpectationMetadata meta) {
125   sco_transactions_.push(ScoTransaction(DynamicByteBuffer(expected), meta));
126 }
127 
QueueIsoTransaction(const ByteBuffer & expected,ExpectationMetadata meta)128 void MockController::QueueIsoTransaction(const ByteBuffer& expected,
129                                          ExpectationMetadata meta) {
130   iso_transactions_.push(IsoTransaction(DynamicByteBuffer(expected), meta));
131 }
132 
AllExpectedScoPacketsSent() const133 bool MockController::AllExpectedScoPacketsSent() const {
134   return sco_transactions_.empty();
135 }
136 
AllExpectedDataPacketsSent() const137 bool MockController::AllExpectedDataPacketsSent() const {
138   return data_transactions_.empty();
139 }
140 
AllExpectedCommandPacketsSent() const141 bool MockController::AllExpectedCommandPacketsSent() const {
142   return cmd_transactions_.empty();
143 }
144 
AllExpectedIsoPacketsSent() const145 bool MockController::AllExpectedIsoPacketsSent() const {
146   return iso_transactions_.empty();
147 }
148 
SetDataCallback(DataCallback callback)149 void MockController::SetDataCallback(DataCallback callback) {
150   PW_DCHECK(callback);
151   PW_DCHECK(!data_callback_);
152 
153   data_callback_ = std::move(callback);
154 }
155 
ClearDataCallback()156 void MockController::ClearDataCallback() {
157   // Leave dispatcher set (if already set) to preserve its write-once-ness.
158   data_callback_ = nullptr;
159 }
160 
SetTransactionCallback(fit::closure callback)161 void MockController::SetTransactionCallback(fit::closure callback) {
162   SetTransactionCallback([f = std::move(callback)](const auto&) { f(); });
163 }
164 
SetTransactionCallback(TransactionCallback callback)165 void MockController::SetTransactionCallback(TransactionCallback callback) {
166   PW_DCHECK(callback);
167   PW_DCHECK(!transaction_callback_);
168   transaction_callback_ = std::move(callback);
169 }
170 
ClearTransactionCallback()171 void MockController::ClearTransactionCallback() {
172   // Leave dispatcher set (if already set) to preserve its write-once-ness.
173   transaction_callback_ = nullptr;
174 }
175 
OnCommandReceived(const ByteBuffer & data)176 void MockController::OnCommandReceived(const ByteBuffer& data) {
177   ASSERT_GE(data.size(), sizeof(hci_spec::OpCode));
178   const hci_spec::OpCode opcode = pw::bytes::ConvertOrderFrom(
179       cpp20::endian::little, data.To<hci_spec::OpCode>());
180   const uint8_t ogf = hci_spec::GetOGF(opcode);
181   const uint16_t ocf = hci_spec::GetOCF(opcode);
182 
183   // Note: we upcast ogf to uint16_t so that it does not get interpreted as a
184   // char for printing
185   ASSERT_FALSE(cmd_transactions_.empty())
186       << "Received unexpected command packet with OGF: 0x" << std::hex
187       << static_cast<uint16_t>(ogf) << ", OCF: 0x" << ocf;
188 
189   auto& transaction = cmd_transactions_.front();
190   const hci_spec::OpCode expected_opcode = pw::bytes::ConvertOrderFrom(
191       cpp20::endian::little,
192       transaction.expected().data.To<hci_spec::OpCode>());
193   const uint8_t expected_ogf = hci_spec::GetOGF(expected_opcode);
194   const uint16_t expected_ocf = hci_spec::GetOCF(expected_opcode);
195 
196   if (!transaction.Match(data)) {
197     auto meta = transaction.expected().meta;
198     GTEST_FAIL_AT(meta.file, meta.line)
199         << " Expected command packet (" << meta.expectation << ") with OGF: 0x"
200         << std::hex << static_cast<uint16_t>(expected_ogf) << ", OCF: 0x"
201         << expected_ocf << ". Received command packet with OGF: 0x"
202         << static_cast<uint16_t>(ogf) << ", OCF: 0x" << ocf;
203   }
204 
205   while (!transaction.replies().empty()) {
206     DynamicByteBuffer& reply = transaction.replies().front();
207     ASSERT_TRUE(SendCommandChannelPacket(reply)) << "Failed to send reply";
208     transaction.replies().pop();
209   }
210   cmd_transactions_.pop();
211 
212   if (transaction_callback_) {
213     DynamicByteBuffer rx(data);
214     (void)heap_dispatcher().Post(
215         [rx = std::move(rx), f = transaction_callback_.share()](
216             auto, pw::Status status) {
217           if (status.ok()) {
218             f(rx);
219           }
220         });
221   }
222 }
223 
OnACLDataPacketReceived(const ByteBuffer & acl_data_packet)224 void MockController::OnACLDataPacketReceived(
225     const ByteBuffer& acl_data_packet) {
226   ASSERT_FALSE(data_transactions_.empty())
227       << "Received unexpected acl data packet: { "
228       << ByteContainerToString(acl_data_packet) << "}";
229 
230   auto& expected = data_transactions_.front();
231   if (!expected.Match(acl_data_packet.view())) {
232     auto meta = expected.expected().meta;
233     GTEST_FAIL_AT(meta.file, meta.line)
234         << "Expected data packet (" << meta.expectation << ")";
235   }
236 
237   while (!expected.replies().empty()) {
238     auto& reply = expected.replies().front();
239     ASSERT_TRUE(SendACLDataChannelPacket(reply)) << "Failed to send reply";
240     expected.replies().pop();
241   }
242   data_transactions_.pop();
243 
244   if (data_callback_) {
245     DynamicByteBuffer packet_copy(acl_data_packet);
246     (void)heap_dispatcher().Post(
247         [packet_copy = std::move(packet_copy), cb = data_callback_.share()](
248             auto, pw::Status status) mutable {
249           if (status.ok()) {
250             cb(packet_copy);
251           }
252         });
253   }
254 }
255 
OnScoDataPacketReceived(const ByteBuffer & sco_data_packet)256 void MockController::OnScoDataPacketReceived(
257     const ByteBuffer& sco_data_packet) {
258   ASSERT_FALSE(sco_transactions_.empty())
259       << "Received unexpected SCO data packet: { "
260       << ByteContainerToString(sco_data_packet) << "}";
261 
262   auto& expected = sco_transactions_.front();
263   if (!expected.Match(sco_data_packet.view())) {
264     auto meta = expected.expected().meta;
265     GTEST_FAIL_AT(meta.file, meta.line)
266         << "Expected SCO packet (" << meta.expectation << ")";
267   }
268 
269   sco_transactions_.pop();
270 }
271 
OnIsoDataPacketReceived(const ByteBuffer & iso_data_packet)272 void MockController::OnIsoDataPacketReceived(
273     const ByteBuffer& iso_data_packet) {
274   ASSERT_FALSE(iso_transactions_.empty())
275       << "Received unexpected ISO data packet: { "
276       << ByteContainerToString(iso_data_packet) << "}";
277 
278   auto& expected = iso_transactions_.front();
279   if (!expected.Match(iso_data_packet.view())) {
280     auto meta = expected.expected().meta;
281     GTEST_FAIL_AT(meta.file, meta.line)
282         << "Expected ISO packet (" << meta.expectation << ")";
283   }
284 
285   iso_transactions_.pop();
286 }
287 
SendCommand(pw::span<const std::byte> data)288 void MockController::SendCommand(pw::span<const std::byte> data) {
289   // Post task to simulate async
290   DynamicByteBuffer buffer(BufferView(data.data(), data.size()));
291   (void)heap_dispatcher().Post(
292       [this, buffer = std::move(buffer)](pw::async::Context /*ctx*/,
293                                          pw::Status status) {
294         if (status.ok()) {
295           OnCommandReceived(buffer);
296         }
297       });
298 }
SendAclData(pw::span<const std::byte> data)299 void MockController::SendAclData(pw::span<const std::byte> data) {
300   // Post task to simulate async
301   DynamicByteBuffer buffer(BufferView(data.data(), data.size()));
302   (void)heap_dispatcher().Post(
303       [this, buffer = std::move(buffer)](pw::async::Context /*ctx*/,
304                                          pw::Status status) {
305         if (status.ok()) {
306           OnACLDataPacketReceived(buffer);
307         }
308       });
309 }
SendScoData(pw::span<const std::byte> data)310 void MockController::SendScoData(pw::span<const std::byte> data) {
311   // Post task to simulate async
312   DynamicByteBuffer buffer(BufferView(data.data(), data.size()));
313   (void)heap_dispatcher().Post(
314       [this, buffer = std::move(buffer)](pw::async::Context /*ctx*/,
315                                          pw::Status status) {
316         if (status.ok()) {
317           OnScoDataPacketReceived(buffer);
318         }
319       });
320 }
SendIsoData(pw::span<const std::byte> data)321 void MockController::SendIsoData(pw::span<const std::byte> data) {
322   // Post task to simulate async
323   DynamicByteBuffer buffer(BufferView(data.data(), data.size()));
324   (void)heap_dispatcher().Post(
325       [this, buffer = std::move(buffer)](pw::async::Context /*ctx*/,
326                                          pw::Status status) {
327         if (status.ok()) {
328           OnIsoDataPacketReceived(buffer);
329         }
330       });
331 }
332 
333 }  // namespace bt::testing
334