1 #include "common/instance_id.hpp"
2 #include "common/types.hpp"
3 #include "common/utils.hpp"
4 #include "mock_request.hpp"
5 #include "requester/handler.hpp"
6 #include "test/test_instance_id.hpp"
7
8 #include <libpldm/base.h>
9 #include <libpldm/transport.h>
10
11 #include <sdbusplus/async.hpp>
12
13 #include <gmock/gmock.h>
14 #include <gtest/gtest.h>
15
16 using namespace pldm::requester;
17 using namespace std::chrono;
18
19 using ::testing::AtLeast;
20 using ::testing::Between;
21 using ::testing::Exactly;
22 using ::testing::NiceMock;
23 using ::testing::Return;
24
25 class HandlerTest : public testing::Test
26 {
27 protected:
HandlerTest()28 HandlerTest() : event(sdeventplus::Event::get_default()), instanceIdDb() {}
29
30 int fd = 0;
31 mctp_eid_t eid = 0;
32 PldmTransport* pldmTransport = nullptr;
33 sdeventplus::Event event;
34 TestInstanceIdDb instanceIdDb;
35
36 /** @brief This function runs the sd_event_run in a loop till all the events
37 * in the testcase are dispatched and exits when there are no events
38 * for the timeout time.
39 *
40 * @param[in] timeout - maximum time to wait for an event
41 */
waitEventExpiry(milliseconds timeout)42 void waitEventExpiry(milliseconds timeout)
43 {
44 while (1)
45 {
46 auto sleepTime = duration_cast<microseconds>(timeout);
47 // Returns 0 on timeout
48 if (!sd_event_run(event.get(), sleepTime.count()))
49 {
50 break;
51 }
52 }
53 }
54
55 public:
56 bool nullResponse = false;
57 bool validResponse = false;
58 int callbackCount = 0;
59 bool response2 = false;
60
pldmResponseCallBack(mctp_eid_t,const pldm_msg * response,size_t respMsgLen)61 void pldmResponseCallBack(mctp_eid_t /*eid*/, const pldm_msg* response,
62 size_t respMsgLen)
63 {
64 if (response == nullptr && respMsgLen == 0)
65 {
66 nullResponse = true;
67 }
68 else
69 {
70 validResponse = true;
71 }
72 callbackCount++;
73 }
74 };
75
TEST_F(HandlerTest,singleRequestResponseScenario)76 TEST_F(HandlerTest, singleRequestResponseScenario)
77 {
78 Handler<NiceMock<MockRequest>> reqHandler(
79 pldmTransport, event, instanceIdDb, false, seconds(1), 2,
80 milliseconds(100));
81 pldm::Request request{};
82 auto instanceId = instanceIdDb.next(eid);
83 EXPECT_EQ(instanceId, 0);
84 auto rc = reqHandler.registerRequest(
85 eid, instanceId, 0, 0, std::move(request),
86 std::bind_front(&HandlerTest::pldmResponseCallBack, this));
87 EXPECT_EQ(rc, PLDM_SUCCESS);
88
89 pldm::Response response(sizeof(pldm_msg_hdr) + sizeof(uint8_t));
90 auto responsePtr = reinterpret_cast<const pldm_msg*>(response.data());
91 reqHandler.handleResponse(eid, instanceId, 0, 0, responsePtr,
92 response.size());
93
94 EXPECT_EQ(validResponse, true);
95 }
96
TEST_F(HandlerTest,singleRequestInstanceIdTimerExpired)97 TEST_F(HandlerTest, singleRequestInstanceIdTimerExpired)
98 {
99 Handler<NiceMock<MockRequest>> reqHandler(
100 pldmTransport, event, instanceIdDb, false, seconds(1), 2,
101 milliseconds(100));
102 pldm::Request request{};
103 auto instanceId = instanceIdDb.next(eid);
104 EXPECT_EQ(instanceId, 0);
105 auto rc = reqHandler.registerRequest(
106 eid, instanceId, 0, 0, std::move(request),
107 std::bind_front(&HandlerTest::pldmResponseCallBack, this));
108 EXPECT_EQ(rc, PLDM_SUCCESS);
109
110 // Waiting for 500ms so that the instance ID expiry callback is invoked
111 waitEventExpiry(milliseconds(500));
112
113 EXPECT_EQ(nullResponse, true);
114 }
115
TEST_F(HandlerTest,multipleRequestResponseScenario)116 TEST_F(HandlerTest, multipleRequestResponseScenario)
117 {
118 Handler<NiceMock<MockRequest>> reqHandler(
119 pldmTransport, event, instanceIdDb, false, seconds(2), 2,
120 milliseconds(100));
121 pldm::Request request{};
122 auto instanceId = instanceIdDb.next(eid);
123 EXPECT_EQ(instanceId, 0);
124 auto rc = reqHandler.registerRequest(
125 eid, instanceId, 0, 0, std::move(request),
126 std::bind_front(&HandlerTest::pldmResponseCallBack, this));
127 EXPECT_EQ(rc, PLDM_SUCCESS);
128
129 pldm::Request requestNxt{};
130 auto instanceIdNxt = instanceIdDb.next(eid);
131 EXPECT_EQ(instanceIdNxt, 1);
132 rc = reqHandler.registerRequest(
133 eid, instanceIdNxt, 0, 0, std::move(requestNxt),
134 std::bind_front(&HandlerTest::pldmResponseCallBack, this));
135 EXPECT_EQ(rc, PLDM_SUCCESS);
136
137 pldm::Response response(sizeof(pldm_msg_hdr) + sizeof(uint8_t));
138 auto responsePtr = reinterpret_cast<const pldm_msg*>(response.data());
139 reqHandler.handleResponse(eid, instanceId, 0, 0, responsePtr,
140 response.size());
141 EXPECT_EQ(validResponse, true);
142 EXPECT_EQ(callbackCount, 1);
143 validResponse = false;
144
145 // Waiting for 500ms and handle the response for the first request, to
146 // simulate a delayed response for the first request
147 waitEventExpiry(milliseconds(500));
148
149 reqHandler.handleResponse(eid, instanceIdNxt, 0, 0, responsePtr,
150 response.size());
151
152 EXPECT_EQ(validResponse, true);
153 EXPECT_EQ(callbackCount, 2);
154 }
155
TEST_F(HandlerTest,singleRequestResponseScenarioUsingCoroutine)156 TEST_F(HandlerTest, singleRequestResponseScenarioUsingCoroutine)
157 {
158 exec::async_scope scope;
159 Handler<NiceMock<MockRequest>> reqHandler(
160 pldmTransport, event, instanceIdDb, false, seconds(1), 2,
161 milliseconds(100));
162
163 auto instanceId = instanceIdDb.next(eid);
164 EXPECT_EQ(instanceId, 0);
165
166 scope.spawn(
167 stdexec::just() | stdexec::let_value([&] -> exec::task<void> {
168 pldm::Request request(sizeof(pldm_msg_hdr) + sizeof(uint8_t), 0);
169 const pldm_msg* responseMsg;
170 size_t responseLen;
171 int rc = PLDM_SUCCESS;
172
173 auto requestPtr = reinterpret_cast<pldm_msg*>(request.data());
174 requestPtr->hdr.instance_id = instanceId;
175
176 try
177 {
178 std::tie(rc, responseMsg, responseLen) =
179 co_await reqHandler.sendRecvMsg(eid, std::move(request));
180 }
181 catch (...)
182 {
183 std::rethrow_exception(std::current_exception());
184 }
185
186 EXPECT_NE(responseLen, 0);
187
188 this->pldmResponseCallBack(eid, responseMsg, responseLen);
189
190 EXPECT_EQ(validResponse, true);
191 }),
192 exec::default_task_context<void>(exec::inline_scheduler{}));
193
194 pldm::Response mockResponse(sizeof(pldm_msg_hdr) + sizeof(uint8_t), 0);
195 auto mockResponsePtr =
196 reinterpret_cast<const pldm_msg*>(mockResponse.data());
197 reqHandler.handleResponse(eid, instanceId, 0, 0, mockResponsePtr,
198 mockResponse.size() - sizeof(pldm_msg_hdr));
199
200 stdexec::sync_wait(scope.on_empty());
201 }
202
TEST_F(HandlerTest,singleRequestCancellationScenarioUsingCoroutine)203 TEST_F(HandlerTest, singleRequestCancellationScenarioUsingCoroutine)
204 {
205 exec::async_scope scope;
206 Handler<NiceMock<MockRequest>> reqHandler(
207 pldmTransport, event, instanceIdDb, false, seconds(1), 2,
208 milliseconds(100));
209 auto instanceId = instanceIdDb.next(eid);
210 EXPECT_EQ(instanceId, 0);
211
212 bool stopped = false;
213
214 scope.spawn(
215 stdexec::just() | stdexec::let_value([&] -> exec::task<void> {
216 pldm::Request request(sizeof(pldm_msg_hdr) + sizeof(uint8_t), 0);
217 pldm::Response response;
218
219 auto requestPtr = reinterpret_cast<pldm_msg*>(request.data());
220 requestPtr->hdr.instance_id = instanceId;
221
222 co_await reqHandler.sendRecvMsg(eid, std::move(request));
223
224 EXPECT_TRUE(false); // unreachable
225 }) | stdexec::upon_stopped([&] { stopped = true; }),
226 exec::default_task_context<void>(exec::inline_scheduler{}));
227
228 scope.request_stop();
229
230 EXPECT_TRUE(stopped);
231
232 stdexec::sync_wait(scope.on_empty());
233 }
234
TEST_F(HandlerTest,asyncRequestResponseByCoroutine)235 TEST_F(HandlerTest, asyncRequestResponseByCoroutine)
236 {
237 struct _
238 {
239 static exec::task<uint8_t>
240 getTIDTask(Handler<MockRequest>& handler, mctp_eid_t eid,
241 uint8_t instanceId, uint8_t& tid)
242 {
243 pldm::Request request(sizeof(pldm_msg_hdr), 0);
244 auto requestMsg = reinterpret_cast<pldm_msg*>(request.data());
245 const pldm_msg* responseMsg;
246 size_t responseLen;
247
248 auto rc = encode_get_tid_req(instanceId, requestMsg);
249 EXPECT_EQ(rc, PLDM_SUCCESS);
250
251 std::tie(rc, responseMsg, responseLen) =
252 co_await handler.sendRecvMsg(eid, std::move(request));
253 EXPECT_NE(responseLen, 0);
254
255 uint8_t cc = 0;
256 rc = decode_get_tid_resp(responseMsg, responseLen, &cc, &tid);
257 EXPECT_EQ(rc, PLDM_SUCCESS);
258
259 co_return cc;
260 }
261 };
262
263 exec::async_scope scope;
264 Handler<MockRequest> reqHandler(pldmTransport, event, instanceIdDb, false,
265 seconds(1), 2, milliseconds(100));
266 auto instanceId = instanceIdDb.next(eid);
267
268 uint8_t expectedTid = 1;
269
270 // Execute a coroutine to send getTID command. The coroutine is suspended
271 // until reqHandler.handleResponse() is received.
272 scope.spawn(stdexec::just() | stdexec::let_value([&] -> exec::task<void> {
273 uint8_t respTid = 0;
274
275 co_await _::getTIDTask(reqHandler, eid, instanceId,
276 respTid);
277
278 EXPECT_EQ(expectedTid, respTid);
279 }),
280 exec::default_task_context<void>(exec::inline_scheduler{}));
281
282 pldm::Response mockResponse(sizeof(pldm_msg_hdr) + PLDM_GET_TID_RESP_BYTES,
283 0);
284 auto mockResponseMsg = reinterpret_cast<pldm_msg*>(mockResponse.data());
285
286 // Compose response message of getTID command
287 encode_get_tid_resp(instanceId, PLDM_SUCCESS, expectedTid, mockResponseMsg);
288
289 // Send response back to resume getTID coroutine to update respTid by
290 // calling reqHandler.handleResponse() manually
291 reqHandler.handleResponse(eid, instanceId, PLDM_BASE, PLDM_GET_TID,
292 mockResponseMsg,
293 mockResponse.size() - sizeof(pldm_msg_hdr));
294
295 stdexec::sync_wait(scope.on_empty());
296 }
297