• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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