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 <iostream>
22 #include <thread>
23 #include <type_traits>
24
25 #include "chpp/app.h"
26 #include "chpp/crc.h"
27 #include "chpp/link.h"
28 #include "chpp/log.h"
29 #include "chpp/platform/platform_link.h"
30 #include "chpp/transport.h"
31 #include "fake_link.h"
32 #include "packet_util.h"
33
34 using chpp::test::FakeLink;
35
36 namespace {
37
init(void * linkContext,struct ChppTransportState * transportContext)38 static void init(void *linkContext,
39 struct ChppTransportState *transportContext) {
40 auto context = static_cast<struct ChppTestLinkState *>(linkContext);
41 context->fake = new FakeLink();
42 context->transportContext = transportContext;
43 }
44
deinit(void * linkContext)45 static void deinit(void *linkContext) {
46 auto context = static_cast<struct ChppTestLinkState *>(linkContext);
47 auto *fake = reinterpret_cast<FakeLink *>(context->fake);
48 delete fake;
49 }
50
send(void * linkContext,size_t len)51 static enum ChppLinkErrorCode send(void *linkContext, size_t len) {
52 auto context = static_cast<struct ChppTestLinkState *>(linkContext);
53 auto *fake = reinterpret_cast<FakeLink *>(context->fake);
54 fake->appendTxPacket(&context->txBuffer[0], len);
55 return CHPP_LINK_ERROR_NONE_SENT;
56 }
57
doWork(void *,uint32_t)58 static void doWork(void * /*linkContext*/, uint32_t /*signal*/) {}
59
reset(void *)60 static void reset(void * /*linkContext*/) {}
61
getConfig(void *)62 struct ChppLinkConfiguration getConfig(void * /*linkContext*/) {
63 return ChppLinkConfiguration{
64 .txBufferLen = CHPP_TEST_LINK_TX_MTU_BYTES,
65 .rxBufferLen = CHPP_TEST_LINK_RX_MTU_BYTES,
66 };
67 }
68
getTxBuffer(void * linkContext)69 uint8_t *getTxBuffer(void *linkContext) {
70 auto context = static_cast<struct ChppTestLinkState *>(linkContext);
71 return &context->txBuffer[0];
72 }
73
74 } // namespace
75
76 const struct ChppLinkApi gLinkApi = {
77 .init = &init,
78 .deinit = &deinit,
79 .send = &send,
80 .doWork = &doWork,
81 .reset = &reset,
82 .getConfig = &getConfig,
83 .getTxBuffer = &getTxBuffer,
84 };
85
86 namespace chpp::test {
87
88 class FakeLinkSyncTests : public testing::Test {
89 protected:
SetUp()90 void SetUp() override {
91 memset(&mLinkContext, 0, sizeof(mLinkContext));
92 chppTransportInit(&mTransportContext, &mAppContext, &mLinkContext,
93 &gLinkApi);
94 chppAppInitWithClientServiceSet(&mAppContext, &mTransportContext,
95 /*clientServiceSet=*/{});
96 mFakeLink = reinterpret_cast<FakeLink *>(mLinkContext.fake);
97
98 mWorkThread = std::thread(chppWorkThreadStart, &mTransportContext);
99
100 // Proceed to the initialized state by performing the CHPP 3-way handshake
101 CHPP_LOGI("Send a RESET packet");
102 ASSERT_TRUE(mFakeLink->waitForTxPacket());
103 std::vector<uint8_t> resetPkt = mFakeLink->popTxPacket();
104 ASSERT_TRUE(comparePacket(resetPkt, generateResetPacket()))
105 << "Full packet: " << asResetPacket(resetPkt);
106
107 CHPP_LOGI("Receive a RESET ACK packet");
108 ChppResetPacket resetAck = generateResetAckPacket();
109 chppRxDataCb(&mTransportContext, reinterpret_cast<uint8_t *>(&resetAck),
110 sizeof(resetAck));
111
112 // chppProcessResetAck() results in sending a no error packet.
113 CHPP_LOGI("Send CHPP_TRANSPORT_ERROR_NONE packet");
114 ASSERT_TRUE(mFakeLink->waitForTxPacket());
115 std::vector<uint8_t> ackPkt = mFakeLink->popTxPacket();
116 ASSERT_TRUE(comparePacket(ackPkt, generateEmptyPacket()))
117 << "Full packet: " << asChpp(ackPkt);
118 CHPP_LOGI("CHPP handshake complete");
119 }
120
TearDown()121 void TearDown() override {
122 chppWorkThreadStop(&mTransportContext);
123 mWorkThread.join();
124 EXPECT_EQ(mFakeLink->getTxPacketCount(), 0);
125 }
126
txPacket()127 void txPacket() {
128 uint32_t *payload = static_cast<uint32_t *>(chppMalloc(sizeof(uint32_t)));
129 *payload = 0xdeadbeef;
130 bool enqueued = chppEnqueueTxDatagramOrFail(&mTransportContext, payload,
131 sizeof(uint32_t));
132 EXPECT_TRUE(enqueued);
133 }
134
135 ChppTransportState mTransportContext = {};
136 ChppAppState mAppContext = {};
137 ChppTestLinkState mLinkContext;
138 FakeLink *mFakeLink;
139 std::thread mWorkThread;
140 };
141
TEST_F(FakeLinkSyncTests,CheckRetryOnTimeout)142 TEST_F(FakeLinkSyncTests, CheckRetryOnTimeout) {
143 txPacket();
144 ASSERT_TRUE(mFakeLink->waitForTxPacket());
145 EXPECT_EQ(mFakeLink->getTxPacketCount(), 1);
146
147 std::vector<uint8_t> pkt1 = mFakeLink->popTxPacket();
148
149 // Not calling chppRxDataCb() will result in a timeout.
150 // Ideally, to speed up the test, we'd have a mechanism to trigger
151 // chppNotifierWait() to return immediately, to simulate timeout
152 ASSERT_TRUE(mFakeLink->waitForTxPacket());
153 EXPECT_EQ(mFakeLink->getTxPacketCount(), 1);
154 std::vector<uint8_t> pkt2 = mFakeLink->popTxPacket();
155
156 // The retry packet should be an exact match of the first one
157 EXPECT_EQ(pkt1, pkt2);
158 }
159
TEST_F(FakeLinkSyncTests,NoRetryAfterAck)160 TEST_F(FakeLinkSyncTests, NoRetryAfterAck) {
161 txPacket();
162 ASSERT_TRUE(mFakeLink->waitForTxPacket());
163 EXPECT_EQ(mFakeLink->getTxPacketCount(), 1);
164
165 // Generate and reply back with an ACK
166 std::vector<uint8_t> pkt = mFakeLink->popTxPacket();
167 ChppEmptyPacket ack = generateAck(pkt);
168 chppRxDataCb(&mTransportContext, reinterpret_cast<uint8_t *>(&ack),
169 sizeof(ack));
170
171 // We shouldn't get that packet again
172 EXPECT_FALSE(mFakeLink->waitForTxPacket());
173 }
174
TEST_F(FakeLinkSyncTests,MultipleNotifications)175 TEST_F(FakeLinkSyncTests, MultipleNotifications) {
176 constexpr int kNumPackets = 5;
177 for (int i = 0; i < kNumPackets; i++) {
178 txPacket();
179 }
180
181 for (int i = 0; i < kNumPackets; i++) {
182 ASSERT_TRUE(mFakeLink->waitForTxPacket());
183
184 // Generate and reply back with an ACK
185 std::vector<uint8_t> pkt = mFakeLink->popTxPacket();
186 ChppEmptyPacket ack = generateAck(pkt);
187 chppRxDataCb(&mTransportContext, reinterpret_cast<uint8_t *>(&ack),
188 sizeof(ack));
189 }
190
191 EXPECT_FALSE(mFakeLink->waitForTxPacket());
192 }
193
194 // This test is essentially CheckRetryOnTimeout but with a twist: we send a
195 // packet, then don't send an ACK in the expected time so it gets retried, then
196 // after the retry, we send two equivalent ACKs back-to-back
TEST_F(FakeLinkSyncTests,DelayedThenDupeAck)197 TEST_F(FakeLinkSyncTests, DelayedThenDupeAck) {
198 // Post the TX packet
199 txPacket();
200 ASSERT_TRUE(mFakeLink->waitForTxPacket());
201 ASSERT_EQ(mFakeLink->getTxPacketCount(), 1);
202 (void)mFakeLink->popTxPacket(); // discard the first packet
203
204 // Second wait should yield timeout + retry
205 ASSERT_TRUE(mFakeLink->waitForTxPacket());
206 ASSERT_EQ(mFakeLink->getTxPacketCount(), 1);
207
208 // Now deliver duplicate ACKs
209 ChppEmptyPacket ack = generateAck(mFakeLink->popTxPacket());
210 chppRxDataCb(&mTransportContext, reinterpret_cast<uint8_t *>(&ack),
211 sizeof(ack));
212 chppRxDataCb(&mTransportContext, reinterpret_cast<uint8_t *>(&ack),
213 sizeof(ack));
214
215 // We shouldn't get another packet (e.g. NAK)
216 EXPECT_FALSE(mFakeLink->waitForTxPacket())
217 << "Got unexpected packet: " << asChpp(mFakeLink->popTxPacket());
218
219 // The next outbound packet should carry the next sequence number
220 txPacket();
221 ASSERT_TRUE(mFakeLink->waitForTxPacket());
222 EXPECT_EQ(asChpp(mFakeLink->popTxPacket()).header.seq, ack.header.ackSeq);
223 }
224
225 // This tests the opposite side of DelayedThenDuplicateAck: confirms that if we
226 // receive a packet, then send an ACK, then we receive a duplicate, we send the
227 // ACK again
TEST_F(FakeLinkSyncTests,ResendAckOnDupe)228 TEST_F(FakeLinkSyncTests, ResendAckOnDupe) {
229 // Note that seq and ackSeq should both be 1, since RESET/RESET_ACK will use 0
230 constexpr uint8_t kSeq = 1;
231 constexpr uint8_t kAckSeq = 1;
232 auto rxPkt = generatePacketWithPayload<1>(kAckSeq, kSeq);
233 EXPECT_TRUE(chppRxDataCb(&mTransportContext,
234 reinterpret_cast<const uint8_t *>(&rxPkt),
235 sizeof(rxPkt)));
236
237 ASSERT_TRUE(mFakeLink->waitForTxPacket());
238 ASSERT_EQ(mFakeLink->getTxPacketCount(), 1);
239 std::vector<uint8_t> pkt = mFakeLink->popTxPacket();
240 // We should get an ACK in response
241 EXPECT_TRUE(comparePacket(pkt, generateEmptyPacket(kSeq + 1)))
242 << "Expected first ACK for seq 1 but got: " << asEmptyPacket(pkt);
243
244 // Pretend that we lost that ACK, so resend the same packet
245 EXPECT_TRUE(chppRxDataCb(&mTransportContext,
246 reinterpret_cast<const uint8_t *>(&rxPkt),
247 sizeof(rxPkt)));
248
249 // We should get another ACK that matches the first
250 ASSERT_TRUE(mFakeLink->waitForTxPacket());
251 ASSERT_EQ(mFakeLink->getTxPacketCount(), 1);
252 pkt = mFakeLink->popTxPacket();
253 EXPECT_TRUE(comparePacket(pkt, generateEmptyPacket(kSeq + 1)))
254 << "Expected second ACK for seq 1 but got: " << asEmptyPacket(pkt);
255
256 // Sending another packet should succeed
257 auto secondRxPkt = generatePacketWithPayload<2>(kAckSeq, kSeq + 1);
258 EXPECT_TRUE(chppRxDataCb(&mTransportContext,
259 reinterpret_cast<const uint8_t *>(&secondRxPkt),
260 sizeof(secondRxPkt)));
261
262 ASSERT_TRUE(mFakeLink->waitForTxPacket());
263 ASSERT_EQ(mFakeLink->getTxPacketCount(), 1);
264 pkt = mFakeLink->popTxPacket();
265 EXPECT_TRUE(comparePacket(pkt, generateEmptyPacket(kSeq + 2)))
266 << "Expected ACK for seq 2 but got: " << asEmptyPacket(pkt);
267 }
268
269 } // namespace chpp::test
270