oem-ibm responder : Implement ReadFile and WriteFile

ReadFile and WriteFile command is required for in-band
file transfers via LPC channel. These commands will be
mostly used for files that are small size.

This commit covers responder for ReadFile and WriteFile.

Change-Id: Id2cbf7afb9aa3ed193376bc93eaae5b8a334d5f7
Signed-off-by: vkaverap <vkaverap@in.ibm.com>
diff --git a/oem/ibm/libpldmresponder/file_io.cpp b/oem/ibm/libpldmresponder/file_io.cpp
index a3a81b4..24997fb 100644
--- a/oem/ibm/libpldmresponder/file_io.cpp
+++ b/oem/ibm/libpldmresponder/file_io.cpp
@@ -34,6 +34,8 @@
                     std::move(readFileIntoMemory));
     registerHandler(PLDM_OEM, PLDM_WRITE_FILE_FROM_MEMORY,
                     std::move(writeFileFromMemory));
+    registerHandler(PLDM_OEM, PLDM_READ_FILE, std::move(readFile));
+    registerHandler(PLDM_OEM, PLDM_WRITE_FILE, std::move(writeFile));
 }
 
 } // namespace oem_ibm
@@ -366,5 +368,160 @@
     return response;
 }
 
+Response readFile(const pldm_msg* request, size_t payloadLength)
+{
+    uint32_t fileHandle = 0;
+    uint32_t offset = 0;
+    uint32_t length = 0;
+
+    Response response(sizeof(pldm_msg_hdr) + PLDM_READ_FILE_RESP_BYTES);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+    if (payloadLength != PLDM_READ_FILE_REQ_BYTES)
+    {
+        encode_read_file_resp(request->hdr.instance_id,
+                              PLDM_ERROR_INVALID_LENGTH, length, responsePtr);
+        return response;
+    }
+
+    auto rc = decode_read_file_req(request, payloadLength, &fileHandle, &offset,
+                                   &length);
+
+    if (rc)
+    {
+        encode_read_file_resp(request->hdr.instance_id, rc, 0, responsePtr);
+        return response;
+    }
+
+    using namespace pldm::filetable;
+    auto& table = buildFileTable(FILE_TABLE_JSON);
+    FileEntry value{};
+
+    try
+    {
+        value = table.at(fileHandle);
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>("File handle does not exist in the file table",
+                        entry("HANDLE=%d", fileHandle));
+        encode_read_file_resp(request->hdr.instance_id,
+                              PLDM_INVALID_FILE_HANDLE, length, responsePtr);
+        return response;
+    }
+
+    if (!fs::exists(value.fsPath))
+    {
+        log<level::ERR>("File does not exist", entry("HANDLE=%d", fileHandle));
+        encode_read_file_resp(request->hdr.instance_id,
+                              PLDM_INVALID_FILE_HANDLE, length, responsePtr);
+        return response;
+    }
+
+    auto fileSize = fs::file_size(value.fsPath);
+    if (offset >= fileSize)
+    {
+        log<level::ERR>("Offset exceeds file size", entry("OFFSET=%d", offset),
+                        entry("FILE_SIZE=%d", fileSize));
+        encode_read_file_resp(request->hdr.instance_id, PLDM_DATA_OUT_OF_RANGE,
+                              length, responsePtr);
+        return response;
+    }
+
+    if (offset + length > fileSize)
+    {
+        length = fileSize - offset;
+    }
+
+    response.resize(response.size() + length);
+    responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    auto fileDataPos = reinterpret_cast<char*>(responsePtr);
+    fileDataPos += sizeof(pldm_msg_hdr) + sizeof(uint8_t) + sizeof(length);
+
+    std::ifstream stream(value.fsPath, std::ios::in | std::ios::binary);
+    stream.seekg(offset);
+    stream.read(fileDataPos, length);
+
+    encode_read_file_resp(request->hdr.instance_id, PLDM_SUCCESS, length,
+                          responsePtr);
+
+    return response;
+}
+
+Response writeFile(const pldm_msg* request, size_t payloadLength)
+{
+    uint32_t fileHandle = 0;
+    uint32_t offset = 0;
+    uint32_t length = 0;
+    size_t fileDataOffset = 0;
+
+    Response response(sizeof(pldm_msg_hdr) + PLDM_WRITE_FILE_RESP_BYTES);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+    if (payloadLength < PLDM_WRITE_FILE_REQ_BYTES)
+    {
+        encode_write_file_resp(request->hdr.instance_id,
+                               PLDM_ERROR_INVALID_LENGTH, 0, responsePtr);
+        return response;
+    }
+
+    auto rc = decode_write_file_req(request, payloadLength, &fileHandle,
+                                    &offset, &length, &fileDataOffset);
+
+    if (rc)
+    {
+        encode_write_file_resp(request->hdr.instance_id, rc, 0, responsePtr);
+        return response;
+    }
+
+    using namespace pldm::filetable;
+    auto& table = buildFileTable(FILE_TABLE_JSON);
+    FileEntry value{};
+
+    try
+    {
+        value = table.at(fileHandle);
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>("File handle does not exist in the file table",
+                        entry("HANDLE=%d", fileHandle));
+        encode_write_file_resp(request->hdr.instance_id,
+                               PLDM_INVALID_FILE_HANDLE, 0, responsePtr);
+        return response;
+    }
+
+    if (!fs::exists(value.fsPath))
+    {
+        log<level::ERR>("File does not exist", entry("HANDLE=%d", fileHandle));
+        encode_write_file_resp(request->hdr.instance_id,
+                               PLDM_INVALID_FILE_HANDLE, 0, responsePtr);
+        return response;
+    }
+
+    auto fileSize = fs::file_size(value.fsPath);
+    if (offset >= fileSize)
+    {
+        log<level::ERR>("Offset exceeds file size", entry("OFFSET=%d", offset),
+                        entry("FILE_SIZE=%d", fileSize));
+        encode_write_file_resp(request->hdr.instance_id, PLDM_DATA_OUT_OF_RANGE,
+                               0, responsePtr);
+        return response;
+    }
+
+    auto fileDataPos =
+        reinterpret_cast<const char*>(request->payload) + fileDataOffset;
+
+    std::ofstream stream(value.fsPath,
+                         std::ios::in | std::ios::out | std::ios::binary);
+    stream.seekp(offset);
+    stream.write(fileDataPos, length);
+
+    encode_write_file_resp(request->hdr.instance_id, PLDM_SUCCESS, length,
+                           responsePtr);
+
+    return response;
+}
+
 } // namespace responder
 } // namespace pldm
diff --git a/oem/ibm/libpldmresponder/file_io.hpp b/oem/ibm/libpldmresponder/file_io.hpp
index 30236ce..0ee4ed6 100644
--- a/oem/ibm/libpldmresponder/file_io.hpp
+++ b/oem/ibm/libpldmresponder/file_io.hpp
@@ -147,5 +147,23 @@
  *  @return PLDM response message
  */
 Response getFileTable(const pldm_msg* request, size_t payloadLength);
+
+/** @brief Handler for readFile command
+ *
+ *  @param[in] request - PLDM request msg
+ *  @param[in] payloadLength - length of the message payload
+ *
+ *  @return PLDM response message
+ */
+Response readFile(const pldm_msg* request, size_t payloadLength);
+
+/** @brief Handler for writeFile command
+ *
+ *  @param[in] request - PLDM request msg
+ *  @param[in] payloadLength - length of the message payload
+ *
+ *  @return PLDM response message
+ */
+Response writeFile(const pldm_msg* request, size_t payloadLength);
 } // namespace responder
 } // namespace pldm
diff --git a/oem/ibm/test/libpldmresponder_fileio_test.cpp b/oem/ibm/test/libpldmresponder_fileio_test.cpp
index d44854f..c132895 100644
--- a/oem/ibm/test/libpldmresponder_fileio_test.cpp
+++ b/oem/ibm/test/libpldmresponder_fileio_test.cpp
@@ -560,3 +560,185 @@
     auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
     ASSERT_EQ(responsePtr->payload[0], PLDM_INVALID_FILE_TABLE_TYPE);
 }
+
+TEST_F(TestFileTable, ReadFileBadPath)
+{
+    uint32_t fileHandle = 1;
+    uint32_t offset = 0;
+    uint32_t length = 0x4;
+
+    std::array<uint8_t, sizeof(pldm_msg_hdr) + PLDM_READ_FILE_REQ_BYTES>
+        requestMsg{};
+    auto requestMsgPtr = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    auto payload_length = requestMsg.size() - sizeof(pldm_msg_hdr);
+    auto request = reinterpret_cast<pldm_read_file_req*>(requestMsg.data() +
+                                                         sizeof(pldm_msg_hdr));
+
+    request->file_handle = fileHandle;
+    request->offset = offset;
+    request->length = length;
+
+    using namespace pldm::filetable;
+    // Initialise the file table with 2 valid file handles 0 & 1.
+    auto& table = buildFileTable(fileTableConfig.c_str());
+
+    // Invalid payload length
+    auto response = readFile(requestMsgPtr, 0);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_ERROR_INVALID_LENGTH);
+
+    // Data out of range. File size is 1024, offset = 1024 is invalid.
+    request->offset = 1024;
+
+    response = readFile(requestMsgPtr, payload_length);
+    responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_DATA_OUT_OF_RANGE);
+
+    // Invalid file handle
+    request->file_handle = 2;
+
+    response = readFile(requestMsgPtr, payload_length);
+    responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_INVALID_FILE_HANDLE);
+
+    table.clear();
+}
+
+TEST_F(TestFileTable, ReadFileGoodPath)
+{
+    uint32_t fileHandle = 0;
+    uint32_t offset = 0;
+    uint32_t length = 0x4;
+
+    std::array<uint8_t, sizeof(pldm_msg_hdr) + PLDM_READ_FILE_REQ_BYTES>
+        requestMsg{};
+    auto requestMsgPtr = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    auto payload_length = requestMsg.size() - sizeof(pldm_msg_hdr);
+    auto request = reinterpret_cast<pldm_read_file_req*>(requestMsg.data() +
+                                                         sizeof(pldm_msg_hdr));
+
+    request->file_handle = fileHandle;
+    request->offset = offset;
+    request->length = length;
+
+    using namespace pldm::filetable;
+    // Initialise the file table with 2 valid file handles 0 & 1.
+    auto& table = buildFileTable(fileTableConfig.c_str());
+    FileEntry value{};
+    value = table.at(fileHandle);
+
+    std::ifstream stream(value.fsPath, std::ios::in | std::ios::binary);
+    stream.seekg(offset);
+    std::vector<char> buffer(length);
+    stream.read(buffer.data(), length);
+
+    auto responseMsg = readFile(requestMsgPtr, payload_length);
+    auto response = reinterpret_cast<pldm_read_file_resp*>(
+        responseMsg.data() + sizeof(pldm_msg_hdr));
+    ASSERT_EQ(response->completion_code, PLDM_SUCCESS);
+    ASSERT_EQ(response->length, length);
+    ASSERT_EQ(0, memcmp(response->file_data, buffer.data(), length));
+
+    // Test condition offset + length > fileSize;
+    size_t fileSize = 1024;
+    request->offset = 1023;
+    request->length = 10;
+
+    stream.seekg(request->offset);
+    buffer.resize(fileSize - request->offset);
+    stream.read(buffer.data(), (fileSize - request->offset));
+
+    responseMsg = readFile(requestMsgPtr, payload_length);
+    response = reinterpret_cast<pldm_read_file_resp*>(responseMsg.data() +
+                                                      sizeof(pldm_msg_hdr));
+    ASSERT_EQ(response->completion_code, PLDM_SUCCESS);
+    ASSERT_EQ(response->length, (fileSize - request->offset));
+    ASSERT_EQ(0, memcmp(response->file_data, buffer.data(),
+                        (fileSize - request->offset)));
+
+    table.clear();
+}
+
+TEST_F(TestFileTable, WriteFileBadPath)
+{
+    uint32_t fileHandle = 0;
+    uint32_t offset = 0;
+    uint32_t length = 0x10;
+
+    std::vector<uint8_t> requestMsg(sizeof(pldm_msg_hdr) +
+                                    PLDM_WRITE_FILE_REQ_BYTES + length);
+    auto requestMsgPtr = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    auto payload_length = requestMsg.size() - sizeof(pldm_msg_hdr);
+    auto request = reinterpret_cast<pldm_write_file_req*>(requestMsg.data() +
+                                                          sizeof(pldm_msg_hdr));
+
+    using namespace pldm::filetable;
+    // Initialise the file table with 2 valid file handles 0 & 1.
+    auto& table = buildFileTable(fileTableConfig.c_str());
+
+    request->file_handle = fileHandle;
+    request->offset = offset;
+    request->length = length;
+
+    // Invalid payload length
+    auto response = writeFile(requestMsgPtr, 0);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_ERROR_INVALID_LENGTH);
+
+    // Data out of range. File size is 1024, offset = 1024 is invalid.
+    request->offset = 1024;
+
+    response = writeFile(requestMsgPtr, payload_length);
+    responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_DATA_OUT_OF_RANGE);
+
+    // Invalid file handle
+    request->file_handle = 2;
+
+    response = writeFile(requestMsgPtr, payload_length);
+    responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_EQ(responsePtr->payload[0], PLDM_INVALID_FILE_HANDLE);
+
+    table.clear();
+}
+
+TEST_F(TestFileTable, WriteFileGoodPath)
+{
+    uint32_t fileHandle = 1;
+    uint32_t offset = 0;
+    std::array<uint8_t, 4> fileData = {0x41, 0x42, 0x43, 0x44};
+    uint32_t length = fileData.size();
+
+    std::vector<uint8_t> requestMsg(sizeof(pldm_msg_hdr) +
+                                    PLDM_WRITE_FILE_REQ_BYTES + length);
+    auto requestMsgPtr = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    auto payload_length = requestMsg.size() - sizeof(pldm_msg_hdr);
+    auto request = reinterpret_cast<pldm_write_file_req*>(requestMsg.data() +
+                                                          sizeof(pldm_msg_hdr));
+
+    using namespace pldm::filetable;
+    // Initialise the file table with 2 valid file handles 0 & 1.
+    auto& table = buildFileTable(fileTableConfig.c_str());
+    FileEntry value{};
+    value = table.at(fileHandle);
+
+    request->file_handle = fileHandle;
+    request->offset = offset;
+    request->length = length;
+    memcpy(request->file_data, fileData.data(), fileData.size());
+
+    auto responseMsg = writeFile(requestMsgPtr, payload_length);
+    auto response = reinterpret_cast<pldm_read_file_resp*>(
+        responseMsg.data() + sizeof(pldm_msg_hdr));
+
+    std::ifstream stream(value.fsPath, std::ios::in | std::ios::binary);
+    stream.seekg(offset);
+    std::vector<char> buffer(length);
+    stream.read(buffer.data(), length);
+
+    ASSERT_EQ(response->completion_code, PLDM_SUCCESS);
+    ASSERT_EQ(response->length, length);
+    ASSERT_EQ(0, memcmp(fileData.data(), buffer.data(), length));
+
+    table.clear();
+}