1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <gtest/gtest.h>
18
19 #include <string.h>
20 #include <cstdint>
21 #include <future>
22 #include <iostream>
23 #include <thread>
24 #include <type_traits>
25 #include <vector>
26
27 #include "chpp/app.h"
28 #include "chpp/clients/wifi.h"
29 #include "chpp/common/wifi.h"
30 #include "chpp/link.h"
31 #include "chpp/log.h"
32 #include "chpp/macros.h"
33 #include "chpp/platform/platform_link.h"
34 #include "chpp/transport.h"
35 #include "chre/pal/wifi.h"
36 #include "chre/platform/shared/pal_system_api.h"
37 #include "fake_link.h"
38 #include "fake_link_client.h"
39 #include "packet_util.h"
40
41 using chpp::test::FakeLink;
42
43 namespace {
44
init(void * linkContext,struct ChppTransportState * transportContext)45 static void init(void *linkContext,
46 struct ChppTransportState *transportContext) {
47 auto context = static_cast<struct ChppTestLinkState *>(linkContext);
48 context->fake = new FakeLink();
49 context->transportContext = transportContext;
50 }
51
deinit(void * linkContext)52 static void deinit(void *linkContext) {
53 auto context = static_cast<struct ChppTestLinkState *>(linkContext);
54 auto *fake = reinterpret_cast<FakeLink *>(context->fake);
55 delete fake;
56 }
57
send(void * linkContext,size_t len)58 static enum ChppLinkErrorCode send(void *linkContext, size_t len) {
59 auto context = static_cast<struct ChppTestLinkState *>(linkContext);
60 auto *fake = reinterpret_cast<FakeLink *>(context->fake);
61 // At the test layer, we expect things to be serialized such that
62 // packets are fetched before the next one can be sent.
63 if (!fake->waitForEmpty()) {
64 CHPP_LOGW("Timed out waiting for TX queue to become empty");
65 }
66 fake->appendTxPacket(&context->txBuffer[0], len);
67
68 return fake->isEnabled() ? CHPP_LINK_ERROR_NONE_SENT
69 : CHPP_LINK_ERROR_UNSPECIFIED;
70 }
71
doWork(void *,uint32_t)72 static void doWork(void * /*linkContext*/, uint32_t /*signal*/) {}
73
reset(void *)74 static void reset(void * /*linkContext*/) {}
75
getConfig(void *)76 struct ChppLinkConfiguration getConfig(void * /*linkContext*/) {
77 return ChppLinkConfiguration{
78 .txBufferLen = CHPP_TEST_LINK_TX_MTU_BYTES,
79 .rxBufferLen = CHPP_TEST_LINK_RX_MTU_BYTES,
80 };
81 }
82
getTxBuffer(void * linkContext)83 uint8_t *getTxBuffer(void *linkContext) {
84 auto context = static_cast<struct ChppTestLinkState *>(linkContext);
85 return &context->txBuffer[0];
86 }
87
88 } // namespace
89
90 const struct ChppLinkApi gLinkApi = {
91 .init = &init,
92 .deinit = &deinit,
93 .send = &send,
94 .doWork = &doWork,
95 .reset = &reset,
96 .getConfig = &getConfig,
97 .getTxBuffer = &getTxBuffer,
98 };
99
100 namespace chpp::test {
101
102 class FakeLinkSyncTests : public testing::Test {
103 protected:
SetUp()104 void SetUp() override {
105 pthread_setname_np(pthread_self(), "test");
106 memset(&mLinkContext, 0, sizeof(mLinkContext));
107 chppTransportInit(&mTransportContext, &mAppContext, &mLinkContext,
108 &gLinkApi);
109 initChppAppLayer();
110 mFakeLink = reinterpret_cast<FakeLink *>(mLinkContext.fake);
111
112 // Note that while the tests tend to primarily execute in the main thread,
113 // some behaviors rely on the work thread, which can create some flakiness,
114 // e.g. if the thread doesn't get scheduled within the timeout. It would be
115 // possible to "pause" the work thread by sending a link signal that blocks
116 // indefinitely, so we can execute any pending operations synchronously in
117 // waitForTxPacket(), but it would be best to combine this approach with
118 // simulated timestamps/delays so we can guarantee no unexpected timeouts
119 // and so we can force timeout behavior without having to delay test
120 // execution (as seen in CHRE's TransactionManagerTest).
121 mWorkThread = std::thread([this] {
122 pthread_setname_np(pthread_self(), "worker");
123 chppWorkThreadStart(&mTransportContext);
124 });
125 performHandshake();
126 }
127
initChppAppLayer()128 virtual void initChppAppLayer() {
129 chppAppInitWithClientServiceSet(&mAppContext, &mTransportContext,
130 /*clientServiceSet=*/{});
131 mAppContext.isDiscoveryComplete = true; // Skip discovery
132 }
133
performHandshake()134 void performHandshake() {
135 // Proceed to the initialized state by performing the CHPP 3-way handshake
136 CHPP_LOGI("Send a RESET packet");
137 ASSERT_TRUE(mFakeLink->waitForTxPacket());
138 std::vector<uint8_t> resetPkt = mFakeLink->popTxPacket();
139 ASSERT_TRUE(comparePacket(resetPkt, generateResetPacket()))
140 << "Full packet: " << asResetPacket(resetPkt);
141
142 CHPP_LOGI("Receive a RESET ACK packet");
143 ChppResetPacket resetAck = generateResetAckPacket();
144 chppRxDataCb(&mTransportContext, reinterpret_cast<uint8_t *>(&resetAck),
145 sizeof(resetAck));
146
147 // Handling of the ACK to RESET-ACK depends on configuration
148 handleFirstPacket();
149 }
150
handleFirstPacket()151 virtual void handleFirstPacket() {
152 // chppProcessResetAck() results in sending a no error packet, with no
153 // payload when discovery is disabled
154 CHPP_LOGI("Send CHPP_TRANSPORT_ERROR_NONE packet");
155 ASSERT_TRUE(mFakeLink->waitForTxPacket());
156 std::vector<uint8_t> ackPkt = mFakeLink->popTxPacket();
157 ASSERT_TRUE(comparePacket(ackPkt, generateEmptyPacket()))
158 << "Full packet: " << asChpp(ackPkt);
159 CHPP_LOGI("CHPP handshake complete");
160 }
161
discardTxPacket()162 void discardTxPacket() {
163 ASSERT_TRUE(mFakeLink->waitForTxPacket());
164 EXPECT_EQ(mFakeLink->getTxPacketCount(), 1);
165 (void)mFakeLink->popTxPacket();
166 }
167
getNextPacket()168 std::vector<uint8_t> getNextPacket() {
169 if (!mFakeLink->waitForTxPacket()) {
170 CHPP_LOGE("Didn't get expected packet");
171 return std::vector<uint8_t>();
172 }
173 EXPECT_EQ(mFakeLink->getTxPacketCount(), 1);
174 return mFakeLink->popTxPacket();
175 }
176
compareNextPacket(const ChppEmptyPacket & expected)177 bool compareNextPacket(const ChppEmptyPacket &expected) {
178 return comparePacket(getNextPacket(), expected);
179 }
180
compareNextPacket(const ChppResetPacket & expected)181 bool compareNextPacket(const ChppResetPacket &expected) {
182 return comparePacket(getNextPacket(), expected);
183 }
184
185 template <typename PacketType>
deliverRxPacket(const PacketType & packet)186 bool deliverRxPacket(const PacketType &packet) {
187 CHPP_LOGW("Debug dump of RX packet:");
188 std::vector<uint8_t> vec;
189 vec.resize(sizeof(packet));
190 memcpy(vec.data(), &packet, sizeof(packet));
191 std::cout << asChpp(vec);
192 return chppRxDataCb(&mTransportContext,
193 reinterpret_cast<const uint8_t *>(&packet),
194 sizeof(packet));
195 }
196
TearDown()197 void TearDown() override {
198 chppWorkThreadStop(&mTransportContext);
199 mWorkThread.join();
200 EXPECT_EQ(mFakeLink->getTxPacketCount(), 0);
201 }
202
txPacket()203 void txPacket() {
204 uint32_t *payload = static_cast<uint32_t *>(chppMalloc(sizeof(uint32_t)));
205 *payload = 0xdeadbeef;
206 bool enqueued = chppEnqueueTxDatagramOrFail(&mTransportContext, payload,
207 sizeof(uint32_t));
208 EXPECT_TRUE(enqueued);
209 }
210
211 ChppTransportState mTransportContext = {};
212 ChppAppState mAppContext = {};
213 ChppTestLinkState mLinkContext;
214 FakeLink *mFakeLink;
215 std::thread mWorkThread;
216 };
217
218 class FakeLinkWithClientSyncTests : public FakeLinkSyncTests {
219 public:
initChppAppLayer()220 void initChppAppLayer() override {
221 // We use the WiFi client to simulate real-world integrations, but any
222 // service (including a dedicated test client/service) would work
223 ChppClientServiceSet set = {
224 .wifiClient = 1,
225 };
226 chppAppInitWithClientServiceSet(&mAppContext, &mTransportContext, set);
227 mAppContext.isDiscoveryComplete = true; // Bypass initial discovery
228 }
229
handleFirstPacket()230 virtual void handleFirstPacket() override {
231 ASSERT_TRUE(mFakeLink->waitForTxPacket());
232 std::vector<uint8_t> ackPkt = mFakeLink->popTxPacket();
233 ASSERT_TRUE(comparePacket(ackPkt, generateEmptyPacket()))
234 << "Full packet: " << asChpp(ackPkt);
235 CHPP_LOGI("CHPP handshake complete");
236
237 mAppContext.matchedClientCount = mAppContext.discoveredServiceCount = 1;
238 // Initialize the client similar to how discovery would
239 EXPECT_TRUE(mAppContext.registeredClients[0]->initFunctionPtr(
240 mAppContext.registeredClientStates[0]->context,
241 CHPP_SERVICE_HANDLE_OF_INDEX(0), /*version=*/{1, 0, 0}));
242 }
243
sendOpenResp(const ChppPacketWithAppHeader & openReq)244 void sendOpenResp(const ChppPacketWithAppHeader &openReq) {
245 ChppAppHeader appHdr = {
246 .handle = openReq.appHeader.handle,
247 .type = CHPP_MESSAGE_TYPE_SERVICE_RESPONSE,
248 .transaction = openReq.appHeader.transaction,
249 .error = CHPP_APP_ERROR_NONE,
250 .command = openReq.appHeader.command,
251 };
252 std::span<uint8_t, sizeof(appHdr)> payload(
253 reinterpret_cast<uint8_t *>(&appHdr), sizeof(appHdr));
254 auto rsp = generatePacketWithPayload<sizeof(appHdr)>(
255 openReq.transportHeader.seq + 1, openReq.transportHeader.ackSeq,
256 &payload);
257 deliverRxPacket(rsp);
258 }
259
openWifiPal(const struct chrePalWifiApi * api,const struct chrePalWifiCallbacks * callbacks=nullptr)260 void openWifiPal(const struct chrePalWifiApi *api,
261 const struct chrePalWifiCallbacks *callbacks = nullptr) {
262 ASSERT_NE(api, nullptr);
263
264 // Calling open() blocks until the open response is received, so spin off
265 // another thread to wait on the open request and post the response.
266 // This puts us in the opened state - we are mainly interested in testing
267 auto result = std::async(std::launch::async, [this] {
268 if (mFakeLink->waitForTxPacket()) {
269 std::vector<uint8_t> rawPkt = mFakeLink->popTxPacket();
270 const ChppPacketWithAppHeader &pkt = asApp(rawPkt);
271 ASSERT_EQ(pkt.appHeader.command, CHPP_WIFI_OPEN);
272 sendOpenResp(pkt);
273 }
274 });
275 ASSERT_TRUE(api->open(&chre::gChrePalSystemApi, callbacks));
276
277 // Confirm our open response was ACKed
278 ASSERT_TRUE(comparePacket(getNextPacket(), generateEmptyPacket(2)));
279 }
280
waitForReopenRequest()281 void waitForReopenRequest() {
282 // Confirm we get OPEN request, send OPEN response
283 ASSERT_TRUE(mFakeLink->waitForTxPacket());
284 auto rawPkt = getNextPacket();
285 const ChppPacketWithAppHeader &pkt = asApp(rawPkt);
286 ASSERT_EQ(pkt.appHeader.command, CHPP_WIFI_OPEN);
287 sendOpenResp(pkt);
288
289 // Confirm we got an ACK to our OPEN_RESP
290 ASSERT_TRUE(mFakeLink->waitForTxPacket());
291 rawPkt = getNextPacket();
292 EXPECT_TRUE(comparePacket(rawPkt, generateEmptyPacket(2)))
293 << "Full packet: " << asChpp(rawPkt);
294 }
295
waitForWifiClientOpenState(uint8_t openState)296 void waitForWifiClientOpenState(uint8_t openState) {
297 ChppEndpointState *wifiClientState = mAppContext.registeredClientStates[0];
298 ASSERT_NE(wifiClientState, nullptr);
299 chppMutexLock(&wifiClientState->syncResponse.mutex);
300 while (!wifiClientState->syncResponse.ready) {
301 chppConditionVariableTimedWait(&wifiClientState->syncResponse.condVar,
302 &wifiClientState->syncResponse.mutex,
303 CHPP_REQUEST_TIMEOUT_DEFAULT);
304 }
305 chppMutexUnlock(&wifiClientState->syncResponse.mutex);
306 ASSERT_EQ(wifiClientState->openState, openState);
307 }
308 };
309
TEST_F(FakeLinkSyncTests,CheckRetryOnTimeout)310 TEST_F(FakeLinkSyncTests, CheckRetryOnTimeout) {
311 txPacket();
312 ASSERT_TRUE(mFakeLink->waitForTxPacket());
313 EXPECT_EQ(mFakeLink->getTxPacketCount(), 1);
314
315 std::vector<uint8_t> pkt1 = mFakeLink->popTxPacket();
316
317 // Not calling chppRxDataCb() will result in a timeout.
318 // Ideally, to speed up the test, we'd have a mechanism to trigger
319 // chppNotifierWait() to return immediately, to simulate timeout
320 ASSERT_TRUE(mFakeLink->waitForTxPacket());
321 EXPECT_EQ(mFakeLink->getTxPacketCount(), 1);
322 std::vector<uint8_t> pkt2 = mFakeLink->popTxPacket();
323
324 // The retry packet should be an exact match of the first one
325 EXPECT_EQ(pkt1, pkt2);
326 }
327
TEST_F(FakeLinkSyncTests,NoRetryAfterAck)328 TEST_F(FakeLinkSyncTests, NoRetryAfterAck) {
329 txPacket();
330 ASSERT_TRUE(mFakeLink->waitForTxPacket());
331 EXPECT_EQ(mFakeLink->getTxPacketCount(), 1);
332
333 // Generate and reply back with an ACK
334 std::vector<uint8_t> pkt = mFakeLink->popTxPacket();
335 ChppEmptyPacket ack = generateAck(pkt);
336 chppRxDataCb(&mTransportContext, reinterpret_cast<uint8_t *>(&ack),
337 sizeof(ack));
338
339 // We shouldn't get that packet again
340 EXPECT_FALSE(mFakeLink->waitForTxPacket());
341 }
342
TEST_F(FakeLinkSyncTests,MultipleNotifications)343 TEST_F(FakeLinkSyncTests, MultipleNotifications) {
344 constexpr int kNumPackets = 5;
345 for (int i = 0; i < kNumPackets; i++) {
346 txPacket();
347 }
348
349 for (int i = 0; i < kNumPackets; i++) {
350 ASSERT_TRUE(mFakeLink->waitForTxPacket());
351
352 // Generate and reply back with an ACK
353 std::vector<uint8_t> pkt = mFakeLink->popTxPacket();
354 ChppEmptyPacket ack = generateAck(pkt);
355 chppRxDataCb(&mTransportContext, reinterpret_cast<uint8_t *>(&ack),
356 sizeof(ack));
357 }
358
359 EXPECT_FALSE(mFakeLink->waitForTxPacket());
360 }
361
362 // This test validates that the CHPP transport maintains 1 un-ACKed packet when
363 // multiple packets are pending in the queue
TEST_F(FakeLinkSyncTests,OutboundThrottling)364 TEST_F(FakeLinkSyncTests, OutboundThrottling) {
365 txPacket();
366 ASSERT_TRUE(mFakeLink->waitForTxPacket());
367 EXPECT_EQ(mFakeLink->getTxPacketCount(), 1);
368
369 // Enqueuing more packets should not trigger sending again
370 txPacket();
371 txPacket();
372 EXPECT_EQ(mFakeLink->getTxPacketCount(), 1);
373
374 // Delivering an ACK should unblock the second packet
375 ChppEmptyPacket ack = generateAck(mFakeLink->popTxPacket());
376 deliverRxPacket(ack);
377 ASSERT_TRUE(mFakeLink->waitForTxPacket());
378 EXPECT_EQ(mFakeLink->getTxPacketCount(), 1);
379 std::vector<uint8_t> pkt2 = mFakeLink->popTxPacket();
380 EXPECT_EQ(asChpp(pkt2).header.seq, 2);
381
382 // Receiving a duplicate ACK should not result in sending again
383 deliverRxPacket(ack);
384 EXPECT_EQ(mFakeLink->getTxPacketCount(), 0);
385
386 // Now send the final ACKs
387 deliverRxPacket(generateAck(pkt2));
388 ASSERT_TRUE(mFakeLink->waitForTxPacket());
389 EXPECT_EQ(mFakeLink->getTxPacketCount(), 1);
390 std::vector<uint8_t> pkt3 = mFakeLink->popTxPacket();
391 deliverRxPacket(generateAck(pkt3));
392
393 EXPECT_EQ(asChpp(pkt3).header.seq, 3);
394 EXPECT_FALSE(mFakeLink->waitForTxPacket());
395 }
396
397 // This test is essentially CheckRetryOnTimeout but with a twist: we send a
398 // packet, then don't send an ACK in the expected time so it gets retried, then
399 // after the retry, we send two equivalent ACKs back-to-back
TEST_F(FakeLinkSyncTests,DelayedThenDupeAck)400 TEST_F(FakeLinkSyncTests, DelayedThenDupeAck) {
401 // Post the TX packet, discard the first ACK
402 txPacket();
403 discardTxPacket();
404
405 // Second wait should yield timeout + retry
406 ASSERT_TRUE(mFakeLink->waitForTxPacket());
407 ASSERT_EQ(mFakeLink->getTxPacketCount(), 1);
408
409 // Now deliver duplicate ACKs
410 ChppEmptyPacket ack = generateAck(mFakeLink->popTxPacket());
411 chppRxDataCb(&mTransportContext, reinterpret_cast<uint8_t *>(&ack),
412 sizeof(ack));
413 chppRxDataCb(&mTransportContext, reinterpret_cast<uint8_t *>(&ack),
414 sizeof(ack));
415
416 // We shouldn't get another packet (e.g. NAK)
417 EXPECT_FALSE(mFakeLink->waitForTxPacket())
418 << "Got unexpected packet: " << asChpp(mFakeLink->popTxPacket());
419
420 // The next outbound packet should carry the next sequence number
421 txPacket();
422 ASSERT_TRUE(mFakeLink->waitForTxPacket());
423 EXPECT_EQ(asChpp(mFakeLink->popTxPacket()).header.seq, ack.header.ackSeq);
424 }
425
426 // This tests the opposite side of DelayedThenDuplicateAck: confirms that if we
427 // receive a packet, then send an ACK, then we receive a duplicate, we send the
428 // ACK again
TEST_F(FakeLinkSyncTests,ResendAckOnDupe)429 TEST_F(FakeLinkSyncTests, ResendAckOnDupe) {
430 // Note that seq and ackSeq should both be 1, since RESET/RESET_ACK will use 0
431 constexpr uint8_t kSeq = 1;
432 constexpr uint8_t kAckSeq = 1;
433 auto rxPkt = generatePacketWithPayload<1>(kAckSeq, kSeq);
434 EXPECT_TRUE(chppRxDataCb(&mTransportContext,
435 reinterpret_cast<const uint8_t *>(&rxPkt),
436 sizeof(rxPkt)));
437
438 ASSERT_TRUE(mFakeLink->waitForTxPacket());
439 ASSERT_EQ(mFakeLink->getTxPacketCount(), 1);
440 std::vector<uint8_t> pkt = mFakeLink->popTxPacket();
441 // We should get an ACK in response
442 EXPECT_TRUE(comparePacket(pkt, generateEmptyPacket(kSeq + 1)))
443 << "Expected first ACK for seq 1 but got: " << asEmptyPacket(pkt);
444
445 // Pretend that we lost that ACK, so resend the same packet
446 EXPECT_TRUE(chppRxDataCb(&mTransportContext,
447 reinterpret_cast<const uint8_t *>(&rxPkt),
448 sizeof(rxPkt)));
449
450 // We should get another ACK that matches the first
451 ASSERT_TRUE(mFakeLink->waitForTxPacket());
452 ASSERT_EQ(mFakeLink->getTxPacketCount(), 1);
453 pkt = mFakeLink->popTxPacket();
454 EXPECT_TRUE(comparePacket(pkt, generateEmptyPacket(kSeq + 1)))
455 << "Expected second ACK for seq 1 but got: " << asEmptyPacket(pkt);
456
457 // Sending another packet should succeed
458 auto secondRxPkt = generatePacketWithPayload<2>(kAckSeq, kSeq + 1);
459 EXPECT_TRUE(chppRxDataCb(&mTransportContext,
460 reinterpret_cast<const uint8_t *>(&secondRxPkt),
461 sizeof(secondRxPkt)));
462
463 ASSERT_TRUE(mFakeLink->waitForTxPacket());
464 ASSERT_EQ(mFakeLink->getTxPacketCount(), 1);
465 pkt = mFakeLink->popTxPacket();
466 EXPECT_TRUE(comparePacket(pkt, generateEmptyPacket(kSeq + 2)))
467 << "Expected ACK for seq 2 but got: " << asEmptyPacket(pkt);
468 }
469
TEST_F(FakeLinkWithClientSyncTests,RecoverFromAbortedOpen)470 TEST_F(FakeLinkWithClientSyncTests, RecoverFromAbortedOpen) {
471 // Setting all callbacks as null here since none should be invoked
472 const struct chrePalWifiCallbacks kCallbacks = {};
473 const struct chrePalWifiApi *api =
474 chppPalWifiGetApi(CHPP_PAL_WIFI_API_VERSION);
475 ASSERT_NO_FATAL_FAILURE(openWifiPal(api, &kCallbacks));
476
477 // Now we're in the opened state and can trigger the test condition: feed in a
478 // RESET, discard the RESET_ACK, confirm we got OPEN_REQ, but instead of
479 // OPEN_RESP, send another RESET, then confirm we can open successfully
480 CHPP_LOGI("Triggering RESET after successful open");
481 auto resetPkt = generateResetPacket();
482 deliverRxPacket(resetPkt);
483 auto rawPkt = getNextPacket();
484 ASSERT_TRUE(comparePacket(rawPkt, generateResetAckPacket()));
485 // It shouldn't send anything until we ack RESET-ACK, which we aren't going to
486 // do here
487 ASSERT_EQ(mFakeLink->getTxPacketCount(), 0);
488
489 CHPP_LOGI("Triggering abort of open request via another RESET");
490 deliverRxPacket(resetPkt);
491 rawPkt = getNextPacket();
492 ASSERT_TRUE(comparePacket(rawPkt, generateResetAckPacket()));
493 auto ackForResetAck = generateEmptyPacket();
494 deliverRxPacket(ackForResetAck);
495
496 ASSERT_NO_FATAL_FAILURE(waitForReopenRequest());
497 }
498
499 // This test is similar to RecoverFromAbortedOpen, but the link is disabled
500 // while a RESET is triggered from the remote endpoint.
TEST_F(FakeLinkWithClientSyncTests,ReopenFromBrokenLink)501 TEST_F(FakeLinkWithClientSyncTests, ReopenFromBrokenLink) {
502 // Setting all callbacks as null here since none should be invoked
503 const struct chrePalWifiCallbacks kCallbacks = {};
504 const struct chrePalWifiApi *api =
505 chppPalWifiGetApi(CHPP_PAL_WIFI_API_VERSION);
506 ASSERT_NO_FATAL_FAILURE(openWifiPal(api, &kCallbacks));
507 ASSERT_NO_FATAL_FAILURE(waitForWifiClientOpenState(CHPP_OPEN_STATE_OPENED));
508
509 // Disable the link and trigger a RESET from the remote endpoint. This will
510 // cause the local client to attempt a re-open of the WiFi API. But since the
511 // local link is disabled, the transport will enter a "PERMANENT_FAILURE"
512 // state, and the re-open will time out.
513 mFakeLink->disable();
514
515 CHPP_LOGI("Triggering RESET after successful open");
516 auto resetPkt = generateResetPacket();
517 deliverRxPacket(resetPkt);
518 for (uint32_t i = 0; i < CHPP_TRANSPORT_MAX_RETX + 1; i++) {
519 ASSERT_TRUE(compareNextPacket(generateResetAckPacket()));
520 }
521
522 CHPP_LOGI("Expecting RESET from local transport");
523 for (uint32_t i = 0; i < CHPP_TRANSPORT_MAX_RESET; i++) {
524 uint8_t error = (i == 0) ? CHPP_TRANSPORT_ERROR_MAX_RETRIES
525 : CHPP_TRANSPORT_ERROR_TIMEOUT;
526 ASSERT_TRUE(compareNextPacket(generateResetPacket(1, 0, error)));
527
528 // TODO(b/392728565): Fix inconsistent counting of retx in transport code
529 for (uint32_t j = 0; j < CHPP_TRANSPORT_MAX_RETX + 1; j++) {
530 ASSERT_TRUE(compareNextPacket(
531 generateResetPacket(1, 0, CHPP_TRANSPORT_ERROR_NONE)));
532 }
533 }
534
535 ASSERT_NO_FATAL_FAILURE(waitForWifiClientOpenState(CHPP_OPEN_STATE_CLOSED));
536
537 // We then re-enable the link and attempt a new request from the Wifi API.
538 // This request will fail, but triggers a re-open that now should succeed.
539 mFakeLink->enable();
540
541 CHPP_LOGI("Triggering a new request after re-open failure");
542 ASSERT_FALSE(api->configureScanMonitor(true));
543 ASSERT_TRUE(compareNextPacket(
544 generateResetPacket(1, 0, CHPP_TRANSPORT_SIGNAL_FORCE_RESET)));
545 auto ackForReset = generateResetAckPacket();
546 deliverRxPacket(ackForReset);
547
548 ASSERT_NO_FATAL_FAILURE(waitForReopenRequest());
549 }
550
551 class FakeLinkWithTestClientSyncTests : public FakeLinkSyncTests {
552 public:
initChppAppLayer()553 void initChppAppLayer() override {
554 // We use a vendor client which triggers a client-layer timeout during init.
555 // This is used to test the timeout mechanism.
556 ChppClientServiceSet set = {
557 .vendorClients = 1,
558 };
559 chppAppInitWithClientServiceSet(&mAppContext, &mTransportContext, set);
560 mAppContext.isDiscoveryComplete = true; // Bypass initial discovery
561 }
562
handleFirstPacket()563 virtual void handleFirstPacket() override {
564 ASSERT_TRUE(mFakeLink->waitForTxPacket());
565 std::vector<uint8_t> ackPkt = mFakeLink->popTxPacket();
566 ASSERT_TRUE(comparePacket(ackPkt, generateEmptyPacket()))
567 << "Full packet: " << asChpp(ackPkt);
568 CHPP_LOGI("CHPP handshake complete");
569
570 mAppContext.matchedClientCount = mAppContext.discoveredServiceCount = 1;
571 // Initialize the client similar to how discovery would
572 EXPECT_TRUE(mAppContext.registeredClients[0]->initFunctionPtr(
573 mAppContext.registeredClientStates[0]->context,
574 CHPP_SERVICE_HANDLE_OF_INDEX(0), /*version=*/{1, 0, 0}));
575 }
576 };
577
TEST_F(FakeLinkWithTestClientSyncTests,SampleTimeoutTest)578 TEST_F(FakeLinkWithTestClientSyncTests, SampleTimeoutTest) {
579 EXPECT_TRUE(chppTestClientWaitForTimeout(
580 /* timeoutMs=*/CHPP_MSEC_PER_SEC));
581 }
582
583 } // namespace chpp::test
584