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