xref: /openbmc/pldm/requester/test/handler_test.cpp (revision f9090f3727a1a7d25c0a29b2c612fe5e3f16689a)
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:
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      */
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 
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 
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 instanceIdResult = instanceIdDb.next(eid);
83     ASSERT_TRUE(instanceIdResult);
84     auto instanceId = instanceIdResult.value();
85     EXPECT_EQ(instanceId, 0);
86     auto rc = reqHandler.registerRequest(
87         eid, instanceId, 0, 0, std::move(request),
88         [this](mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen) {
89             this->pldmResponseCallBack(eid, response, respMsgLen);
90         });
91     EXPECT_EQ(rc, PLDM_SUCCESS);
92 
93     pldm::Response response(sizeof(pldm_msg_hdr) + sizeof(uint8_t));
94     auto responsePtr = reinterpret_cast<const pldm_msg*>(response.data());
95     reqHandler.handleResponse(eid, instanceId, 0, 0, responsePtr,
96                               response.size());
97 
98     EXPECT_EQ(validResponse, true);
99 }
100 
101 TEST_F(HandlerTest, singleRequestInstanceIdTimerExpired)
102 {
103     Handler<NiceMock<MockRequest>> reqHandler(
104         pldmTransport, event, instanceIdDb, false, seconds(1), 2,
105         milliseconds(100));
106     pldm::Request request{};
107     auto instanceIdResult = instanceIdDb.next(eid);
108     ASSERT_TRUE(instanceIdResult);
109     auto instanceId = instanceIdResult.value();
110     EXPECT_EQ(instanceId, 0);
111     auto rc = reqHandler.registerRequest(
112         eid, instanceId, 0, 0, std::move(request),
113         [this](mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen) {
114             this->pldmResponseCallBack(eid, response, respMsgLen);
115         });
116     EXPECT_EQ(rc, PLDM_SUCCESS);
117 
118     // Waiting for 500ms so that the instance ID expiry callback is invoked
119     waitEventExpiry(milliseconds(500));
120 
121     EXPECT_EQ(nullResponse, true);
122 }
123 
124 TEST_F(HandlerTest, multipleRequestResponseScenario)
125 {
126     Handler<NiceMock<MockRequest>> reqHandler(
127         pldmTransport, event, instanceIdDb, false, seconds(2), 2,
128         milliseconds(100));
129     pldm::Request request{};
130     auto instanceIdResult = instanceIdDb.next(eid);
131     ASSERT_TRUE(instanceIdResult);
132     auto instanceId = instanceIdResult.value();
133     EXPECT_EQ(instanceId, 0);
134     auto rc = reqHandler.registerRequest(
135         eid, instanceId, 0, 0, std::move(request),
136         [this](mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen) {
137             this->pldmResponseCallBack(eid, response, respMsgLen);
138         });
139     EXPECT_EQ(rc, PLDM_SUCCESS);
140 
141     pldm::Request requestNxt{};
142     auto instanceIdNxtResult = instanceIdDb.next(eid);
143     ASSERT_TRUE(instanceIdNxtResult);
144     auto instanceIdNxt = instanceIdNxtResult.value();
145     EXPECT_EQ(instanceIdNxt, 1);
146     rc = reqHandler.registerRequest(
147         eid, instanceIdNxt, 0, 0, std::move(requestNxt),
148         [this](mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen) {
149             this->pldmResponseCallBack(eid, response, respMsgLen);
150         });
151     EXPECT_EQ(rc, PLDM_SUCCESS);
152 
153     pldm::Response response(sizeof(pldm_msg_hdr) + sizeof(uint8_t));
154     auto responsePtr = reinterpret_cast<const pldm_msg*>(response.data());
155     reqHandler.handleResponse(eid, instanceId, 0, 0, responsePtr,
156                               response.size());
157     EXPECT_EQ(validResponse, true);
158     EXPECT_EQ(callbackCount, 1);
159     validResponse = false;
160 
161     // Waiting for 500ms and handle the response for the first request, to
162     // simulate a delayed response for the first request
163     waitEventExpiry(milliseconds(500));
164 
165     reqHandler.handleResponse(eid, instanceIdNxt, 0, 0, responsePtr,
166                               response.size());
167 
168     EXPECT_EQ(validResponse, true);
169     EXPECT_EQ(callbackCount, 2);
170 }
171 
172 TEST_F(HandlerTest, singleRequestResponseScenarioUsingCoroutine)
173 {
174     exec::async_scope scope;
175     Handler<NiceMock<MockRequest>> reqHandler(
176         pldmTransport, event, instanceIdDb, false, seconds(1), 2,
177         milliseconds(100));
178 
179     auto instanceIdResult = instanceIdDb.next(eid);
180     ASSERT_TRUE(instanceIdResult);
181     auto instanceId = instanceIdResult.value();
182     EXPECT_EQ(instanceId, 0);
183 
184     scope.spawn(
185         stdexec::just() | stdexec::let_value([&] -> exec::task<void> {
186             pldm::Request request(sizeof(pldm_msg_hdr) + sizeof(uint8_t), 0);
187             const pldm_msg* responseMsg;
188             size_t responseLen;
189             int rc = PLDM_SUCCESS;
190 
191             auto requestPtr = new (request.data()) pldm_msg;
192             requestPtr->hdr.instance_id = instanceId;
193 
194             try
195             {
196                 std::tie(rc, responseMsg, responseLen) =
197                     co_await reqHandler.sendRecvMsg(eid, std::move(request));
198             }
199             catch (...)
200             {
201                 std::rethrow_exception(std::current_exception());
202             }
203 
204             EXPECT_NE(responseLen, 0);
205 
206             this->pldmResponseCallBack(eid, responseMsg, responseLen);
207 
208             EXPECT_EQ(validResponse, true);
209         }),
210         exec::default_task_context<void>(stdexec::inline_scheduler{}));
211 
212     pldm::Response mockResponse(sizeof(pldm_msg_hdr) + sizeof(uint8_t), 0);
213     auto mockResponsePtr =
214         reinterpret_cast<const pldm_msg*>(mockResponse.data());
215     reqHandler.handleResponse(eid, instanceId, 0, 0, mockResponsePtr,
216                               mockResponse.size() - sizeof(pldm_msg_hdr));
217 
218     stdexec::sync_wait(scope.on_empty());
219 }
220 
221 TEST_F(HandlerTest, singleRequestCancellationScenarioUsingCoroutine)
222 {
223     exec::async_scope scope;
224     Handler<NiceMock<MockRequest>> reqHandler(
225         pldmTransport, event, instanceIdDb, false, seconds(1), 2,
226         milliseconds(100));
227     auto instanceIdResult = instanceIdDb.next(eid);
228     ASSERT_TRUE(instanceIdResult);
229     auto instanceId = instanceIdResult.value();
230     EXPECT_EQ(instanceId, 0);
231 
232     bool stopped = false;
233 
234     scope.spawn(
235         stdexec::just() | stdexec::let_value([&] -> exec::task<void> {
236             pldm::Request request(sizeof(pldm_msg_hdr) + sizeof(uint8_t), 0);
237             pldm::Response response;
238 
239             auto requestPtr = new (request.data()) pldm_msg;
240             requestPtr->hdr.instance_id = instanceId;
241 
242             co_await reqHandler.sendRecvMsg(eid, std::move(request));
243 
244             EXPECT_TRUE(false); // unreachable
245         }) | stdexec::upon_stopped([&] { stopped = true; }),
246         exec::default_task_context<void>(stdexec::inline_scheduler{}));
247 
248     scope.request_stop();
249 
250     EXPECT_TRUE(stopped);
251 
252     stdexec::sync_wait(scope.on_empty());
253 }
254 
255 TEST_F(HandlerTest, asyncRequestResponseByCoroutine)
256 {
257     struct _
258     {
259         static exec::task<uint8_t> getTIDTask(Handler<MockRequest>& handler,
260                                               mctp_eid_t eid,
261                                               uint8_t instanceId, uint8_t& tid)
262         {
263             pldm::Request request(sizeof(pldm_msg_hdr), 0);
264             auto requestMsg = new (request.data()) pldm_msg;
265             const pldm_msg* responseMsg;
266             size_t responseLen;
267 
268             auto rc = encode_get_tid_req(instanceId, requestMsg);
269             EXPECT_EQ(rc, PLDM_SUCCESS);
270 
271             std::tie(rc, responseMsg, responseLen) =
272                 co_await handler.sendRecvMsg(eid, std::move(request));
273             EXPECT_NE(responseLen, 0);
274 
275             uint8_t cc = 0;
276             rc = decode_get_tid_resp(responseMsg, responseLen, &cc, &tid);
277             EXPECT_EQ(rc, PLDM_SUCCESS);
278 
279             co_return cc;
280         }
281     };
282 
283     exec::async_scope scope;
284     Handler<MockRequest> reqHandler(pldmTransport, event, instanceIdDb, false,
285                                     seconds(1), 2, milliseconds(100));
286     auto instanceIdResult = instanceIdDb.next(eid);
287     ASSERT_TRUE(instanceIdResult);
288     auto instanceId = instanceIdResult.value();
289 
290     uint8_t expectedTid = 1;
291 
292     // Execute a coroutine to send getTID command. The coroutine is suspended
293     // until reqHandler.handleResponse() is received.
294     scope.spawn(stdexec::just() | stdexec::let_value([&] -> exec::task<void> {
295                     uint8_t respTid = 0;
296 
297                     co_await _::getTIDTask(reqHandler, eid, instanceId,
298                                            respTid);
299 
300                     EXPECT_EQ(expectedTid, respTid);
301                 }),
302                 exec::default_task_context<void>(stdexec::inline_scheduler{}));
303 
304     pldm::Response mockResponse(sizeof(pldm_msg_hdr) + PLDM_GET_TID_RESP_BYTES,
305                                 0);
306     auto mockResponseMsg = new (mockResponse.data()) pldm_msg;
307 
308     // Compose response message of getTID command
309     encode_get_tid_resp(instanceId, PLDM_SUCCESS, expectedTid, mockResponseMsg);
310 
311     // Send response back to resume getTID coroutine to update respTid by
312     // calling  reqHandler.handleResponse() manually
313     reqHandler.handleResponse(eid, instanceId, PLDM_BASE, PLDM_GET_TID,
314                               mockResponseMsg,
315                               mockResponse.size() - sizeof(pldm_msg_hdr));
316 
317     stdexec::sync_wait(scope.on_empty());
318 }
319