Add GetPDRRepositoryInfo handler

Tested:
Unit tested and also verified that correct response is provided by
BMC

Google-Bug-Id: 423027264
Change-Id: Id5718b905fa72e12e1bbc91426780607ac9f2ee8
Signed-off-by: Kasun Athukorala <kasunath@google.com>
diff --git a/libpldmresponder/pdr_utils.cpp b/libpldmresponder/pdr_utils.cpp
index 5a1d08c..facd5b2 100644
--- a/libpldmresponder/pdr_utils.cpp
+++ b/libpldmresponder/pdr_utils.cpp
@@ -41,6 +41,11 @@
         // pldm_pdr_add() assert()ed on failure to add PDR
         throw std::runtime_error("Failed to add PDR");
     }
+
+    if (largestRecordSize < pdrEntry.size)
+    {
+        largestRecordSize = pdrEntry.size;
+    }
     return handle;
 }
 
@@ -89,6 +94,16 @@
     return !getRecordCount();
 }
 
+uint32_t Repo::getRepositorySize()
+{
+    return pldm_pdr_get_repo_size(getPdr());
+}
+
+uint32_t Repo::getLargestRecordSize()
+{
+    return largestRecordSize;
+}
+
 StatestoDbusVal populateMapping(const std::string& type, const Json& dBusValues,
                                 const PossibleValues& pv)
 {
diff --git a/libpldmresponder/pdr_utils.hpp b/libpldmresponder/pdr_utils.hpp
index 8167ea7..c12368d 100644
--- a/libpldmresponder/pdr_utils.hpp
+++ b/libpldmresponder/pdr_utils.hpp
@@ -126,7 +126,7 @@
 class RepoInterface
 {
   public:
-    RepoInterface(pldm_pdr* repo) : repo(repo) {}
+    RepoInterface(pldm_pdr* repo) : repo(repo), largestRecordSize(0) {}
 
     virtual ~RepoInterface() = default;
 
@@ -185,8 +185,21 @@
      */
     virtual bool empty() = 0;
 
+    /** @brief Get the size of the PDR repository
+     *
+     *  @return uint32_t - size
+     */
+    virtual uint32_t getRepositorySize() = 0;
+
+    /** @brief Get size of the largest PDR record
+     *
+     *  @return uint32_t - size of the largest PDR record
+     */
+    virtual uint32_t getLargestRecordSize() = 0;
+
   protected:
     pldm_pdr* repo;
+    uint32_t largestRecordSize;
 };
 
 /**
@@ -215,6 +228,10 @@
     uint32_t getRecordCount() override;
 
     bool empty() override;
+
+    uint32_t getRepositorySize() override;
+
+    uint32_t getLargestRecordSize() override;
 };
 
 /** @brief Parse the State Sensor PDR and return the parsed sensor info which
diff --git a/libpldmresponder/platform.cpp b/libpldmresponder/platform.cpp
index 7a3b63c..b034acb 100644
--- a/libpldmresponder/platform.cpp
+++ b/libpldmresponder/platform.cpp
@@ -1036,6 +1036,39 @@
     }
 }
 
+Response Handler::getPdrRepositoryInfo(const pldm_msg* request,
+                                       size_t payloadLength)
+{
+    // GetPDRRepositoryInfo request payload should be 0.
+    if (payloadLength > 0)
+    {
+        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR_INVALID_LENGTH);
+    }
+
+    Response response(
+        sizeof(pldm_msg_hdr) + sizeof(pldm_pdr_repository_info_resp));
+    auto responsePtr = new (response.data()) pldm_msg;
+
+    // TODO: Support update times. Currently set to all 0.
+    uint8_t updateTime[PLDM_TIMESTAMP104_SIZE];
+    memset(updateTime, 0, PLDM_TIMESTAMP104_SIZE);
+
+    uint8_t repository_state =
+        pdrCreated ? PLDM_AVAILABLE : PLDM_UPDATE_IN_PROGRESS;
+
+    int rc = encode_get_pdr_repository_info_resp(
+        request->hdr.instance_id, PLDM_SUCCESS, repository_state, updateTime,
+        updateTime, pdrRepo.getRecordCount(), pdrRepo.getRepositorySize(),
+        pdrRepo.getLargestRecordSize(), /*data_transfer_handle_timeout=*/0,
+        responsePtr);
+    if (rc != PLDM_SUCCESS)
+    {
+        return ccOnlyResponse(request, rc);
+    }
+
+    return response;
+}
+
 } // namespace platform
 } // namespace responder
 } // namespace pldm
diff --git a/libpldmresponder/platform.hpp b/libpldmresponder/platform.hpp
index 8d7a55e..7c835d4 100644
--- a/libpldmresponder/platform.hpp
+++ b/libpldmresponder/platform.hpp
@@ -73,6 +73,11 @@
         }
 
         handlers.emplace(
+            PLDM_GET_PDR_REPOSITORY_INFO,
+            [this](pldm_tid_t, const pldm_msg* request, size_t payloadLength) {
+                return this->getPdrRepositoryInfo(request, payloadLength);
+            });
+        handlers.emplace(
             PLDM_GET_PDR,
             [this](pldm_tid_t, const pldm_msg* request, size_t payloadLength) {
                 return this->getPDR(request, payloadLength);
@@ -512,6 +517,15 @@
     /** @brief Method for setEventreceiver */
     void setEventReceiver();
 
+    /** @brief Handler for GetPDRRepositoryInfo
+     *
+     *  @param[in] request - Request message
+     *  @param[in] payloadLength - Request payload length
+     *  @return Response - PLDM Response message
+     */
+    Response getPdrRepositoryInfo(const pldm_msg* request,
+                                  size_t payloadLength);
+
   private:
     uint8_t eid;
     InstanceIdDb* instanceIdDb;
diff --git a/libpldmresponder/test/libpldmresponder_platform_test.cpp b/libpldmresponder/test/libpldmresponder_platform_test.cpp
index 28859e0..474aec9 100644
--- a/libpldmresponder/test/libpldmresponder_platform_test.cpp
+++ b/libpldmresponder/test/libpldmresponder_platform_test.cpp
@@ -265,6 +265,117 @@
     pldm_pdr_destroy(outPDRRepo);
 }
 
+TEST(GetPdrRepositoryInfo, SuccessRepoAvailable)
+{
+    // Test when PDRs are created and repository is available.
+    std::array<uint8_t, sizeof(pldm_msg_hdr)> requestPayload{};
+    auto req = new (requestPayload.data()) pldm_msg;
+    // GetPDRRepositoryInfo has no payload.
+    size_t requestPayloadLength = 0;
+    req->hdr.instance_id = 4;
+
+    MockdBusHandler mockedUtils;
+    EXPECT_CALL(mockedUtils, getService(StrEq("/foo/bar"), _))
+        .WillRepeatedly(Return("foo.bar"));
+
+    auto pdrRepoPtr = pldm_pdr_init();
+    ASSERT_NE(pdrRepoPtr, nullptr);
+    auto event = sdeventplus::Event::get_default();
+    // buildPDRLazily is false by default, so PDRs are generated.
+    Handler handler(&mockedUtils, 0, nullptr, "./pdr_jsons/state_effecter/good",
+                    pdrRepoPtr, nullptr, nullptr, nullptr, nullptr, nullptr,
+                    event);
+
+    // Get expected values directly from libpldm. The Repo class wraps these.
+    uint32_t expectedRecordCount = pldm_pdr_get_record_count(pdrRepoPtr);
+    uint32_t expectedRepoSize = pldm_pdr_get_repo_size(pdrRepoPtr);
+    uint32_t expectedLargestRecordSize = 84;
+
+    auto response = handler.getPdrRepositoryInfo(req, requestPayloadLength);
+
+    ASSERT_EQ(response.size(),
+              sizeof(pldm_msg_hdr) + sizeof(pldm_pdr_repository_info_resp));
+
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    auto resp =
+        reinterpret_cast<pldm_pdr_repository_info_resp*>(responsePtr->payload);
+
+    EXPECT_EQ(responsePtr->hdr.instance_id, req->hdr.instance_id);
+    EXPECT_EQ(resp->completion_code, PLDM_SUCCESS);
+    EXPECT_EQ(resp->repository_state, PLDM_AVAILABLE);
+    EXPECT_EQ(le32toh(resp->record_count), expectedRecordCount);
+    EXPECT_EQ(le32toh(resp->repository_size), expectedRepoSize);
+    EXPECT_EQ(le32toh(resp->largest_record_size), expectedLargestRecordSize);
+
+    // The number of records should be greater than 1 because of
+    // TerminusLocatorPDR and PDRs from JSON.
+    EXPECT_GT(expectedRecordCount, 1);
+
+    pldm_pdr_destroy(pdrRepoPtr);
+}
+
+TEST(GetPdrRepositoryInfo, SuccessRepoNotAvailable)
+{
+    // Test when PDRs are not yet created (lazy build).
+    std::array<uint8_t, sizeof(pldm_msg_hdr)> requestPayload{};
+    auto req = new (requestPayload.data()) pldm_msg;
+    // GetPDRRepositoryInfo has no payload.
+    size_t requestPayloadLength = 0;
+    req->hdr.instance_id = 4;
+
+    auto pdrRepoPtr = pldm_pdr_init();
+    ASSERT_NE(pdrRepoPtr, nullptr);
+    auto event = sdeventplus::Event::get_default();
+    MockdBusHandler mockedUtils;
+    // Last parameter `buildPDRLazily` is true.
+    Handler handler(&mockedUtils, 0, nullptr, "", pdrRepoPtr, nullptr, nullptr,
+                    nullptr, nullptr, nullptr, event, true);
+
+    auto response = handler.getPdrRepositoryInfo(req, requestPayloadLength);
+
+    ASSERT_EQ(response.size(),
+              sizeof(pldm_msg_hdr) + sizeof(pldm_pdr_repository_info_resp));
+
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    auto resp =
+        reinterpret_cast<pldm_pdr_repository_info_resp*>(responsePtr->payload);
+
+    EXPECT_EQ(responsePtr->hdr.instance_id, req->hdr.instance_id);
+    EXPECT_EQ(resp->completion_code, PLDM_SUCCESS);
+    EXPECT_EQ(resp->repository_state, PLDM_UPDATE_IN_PROGRESS);
+    EXPECT_EQ(le32toh(resp->record_count), 0);
+    EXPECT_EQ(le32toh(resp->repository_size), 0);
+    EXPECT_EQ(le32toh(resp->largest_record_size), 0);
+
+    pldm_pdr_destroy(pdrRepoPtr);
+}
+
+TEST(GetPdrRepositoryInfo, HandlesInvalidRequestLength)
+{
+    // Test with a non-zero payload length. The command should have zero
+    // payload length. A robust implementation would return
+    // PLDM_ERROR_INVALID_LENGTH. This test verifies the current behavior, which
+    // is to succeed.
+    std::array<uint8_t, sizeof(pldm_msg_hdr) + 1> requestPayload{};
+    auto req = new (requestPayload.data()) pldm_msg;
+    size_t requestPayloadLength = 1;
+
+    auto pdrRepoPtr = pldm_pdr_init();
+    ASSERT_NE(pdrRepoPtr, nullptr);
+    auto event = sdeventplus::Event::get_default();
+    MockdBusHandler mockedUtils;
+    Handler handler(&mockedUtils, 0, nullptr, "", pdrRepoPtr, nullptr, nullptr,
+                    nullptr, nullptr, nullptr, event, true);
+
+    auto response = handler.getPdrRepositoryInfo(req, requestPayloadLength);
+    auto resp = reinterpret_cast<pldm_pdr_repository_info_resp*>(
+        reinterpret_cast<pldm_msg*>(response.data())->payload);
+
+    EXPECT_EQ(resp->completion_code, PLDM_ERROR_INVALID_LENGTH);
+
+    pldm_pdr_destroy(pdrRepoPtr);
+}
+
 TEST(setStateEffecterStatesHandler, testBadRequest)
 {
     std::array<uint8_t, sizeof(pldm_msg_hdr) + PLDM_GET_PDR_REQ_BYTES>