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