Add file transfer responder handlers

Tested:
Tested with unit tests

Google-Bug-Id: 423027264
Change-Id: I6c9be31c35c3704ce672de9ef31052d20ff07ed8
Signed-off-by: Kasun Athukorala <kasunath@google.com>
diff --git a/libpldmresponder/base.hpp b/libpldmresponder/base.hpp
index c9ac56c..3ed5c8f 100644
--- a/libpldmresponder/base.hpp
+++ b/libpldmresponder/base.hpp
@@ -17,6 +17,9 @@
 {
 namespace base
 {
+
+#define PLDM_MIN_PART_SIZE 256
+
 class Handler : public CmdHandler
 {
   public:
diff --git a/libpldmresponder/file_transfer.cpp b/libpldmresponder/file_transfer.cpp
new file mode 100644
index 0000000..1dab5ae
--- /dev/null
+++ b/libpldmresponder/file_transfer.cpp
@@ -0,0 +1,578 @@
+#include "file_transfer.hpp"
+
+#include <libpldm/base.h>
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <fstream>
+#include <optional>
+#include <vector>
+
+PHOSPHOR_LOG2_USING;
+
+namespace pldm
+{
+namespace responder
+{
+namespace file_transfer
+{
+
+int fileRead(FILE* fp, uint32_t fileSize, uint32_t offset, uint32_t readSize,
+             uint8_t* dataPtr)
+{
+    if (!fp)
+    {
+        error("Error: fileRead received a NULL FILE pointer.");
+        return -1;
+    }
+
+    if (!dataPtr)
+    {
+        error("Error: fileRead received a NULL dataPtr buffer.");
+        return -1;
+    }
+
+    if (readSize == 0)
+    {
+        return 0;
+    }
+
+    if (offset >= fileSize)
+    {
+        error("Error: Start offset '{OFFSET}' is beyond file size '{FSIZE}'.",
+              "OFFSET", offset, "FSIZE", fileSize);
+        return -1;
+    }
+
+    if (readSize > fileSize)
+    {
+        error("Error: readSize '{READ_SIZE}' is beyond file size '{FSIZE}'.",
+              "READ_SIZE", readSize, "FSIZE", fileSize);
+        return -1;
+    }
+
+    if (offset > fileSize - readSize)
+    {
+        error(
+            "Error: Read would go beyond file size. Offset '{OFFSET}', readSize '{READ_SIZE}', fileSize '{FSIZE}'.",
+            "OFFSET", offset, "READ_SIZE", readSize, "FSIZE", fileSize);
+        return -1;
+    }
+
+    if (fseek(fp, offset, SEEK_SET) != 0)
+    {
+        error("Error: fseek failed");
+        return -1;
+    }
+
+    size_t bytes_read = fread(dataPtr, 1, readSize, fp);
+    if (bytes_read != readSize)
+    {
+        if (feof(fp))
+        {
+            error("Error: Unexpected end of file reached.");
+        }
+        else if (ferror(fp))
+        {
+            error("Error: fread failed");
+        }
+        else
+        {
+            error("Error: Unknown fread error.");
+        }
+        return -1;
+    }
+
+    return 0;
+}
+
+Handler::Handler(responder::platform::Handler* platformHandler) :
+    platformHandler(platformHandler), nextFileDescriptor(0),
+    multipartSize(PLDM_MIN_PART_SIZE)
+{
+    // Register command handlers. The "handlers" come from CmdHandler class.
+    handlers.emplace(
+        PLDM_FILE_CMD_DF_OPEN,
+        [this](pldm_tid_t, const pldm_msg* request, size_t payloadLength) {
+            return this->dfOpen(request, payloadLength);
+        });
+
+    handlers.emplace(
+        PLDM_FILE_CMD_DF_CLOSE,
+        [this](pldm_tid_t, const pldm_msg* request, size_t payloadLength) {
+            return this->dfClose(request, payloadLength);
+        });
+
+    handlers.emplace(
+        PLDM_FILE_CMD_DF_READ,
+        [this](pldm_tid_t, const pldm_msg* request, size_t payloadLength) {
+            return this->dfRead(request, payloadLength);
+        });
+}
+
+void Handler::setMultipartSize(uint16_t size)
+{
+    multipartSize = size;
+}
+
+std::optional<uint16_t> Handler::getFreeFileDescriptor(uint16_t fileIdentifier)
+{
+    uint16_t startValue = this->nextFileDescriptor;
+
+    // Find a free descriptor value.
+    // TODO: In case a device do not release file descriptors by calling
+    // dfClose, we will need a mechanism to free unused file descriptors
+    while (this->fileDescriptors.contains(this->nextFileDescriptor))
+    {
+        ++(this->nextFileDescriptor);
+        if (this->nextFileDescriptor == startValue)
+        {
+            return std::nullopt;
+        }
+    }
+
+    // TODO: Also track client TID with the file descriptor for better request
+    // filtering.
+    this->fileDescriptors.emplace(
+        this->nextFileDescriptor,
+        FileDescriptor{.fp = nullptr,
+                       .fileSize = 0,
+                       .startingOffset = 0,
+                       .sectionLength = 0,
+                       .prevChecksum = 0,
+                       .fileIdentifier = fileIdentifier});
+    return this->nextFileDescriptor;
+}
+
+void Handler::removeFileDescriptor(uint16_t fileDescriptor)
+{
+    fileDescriptors.erase(fileDescriptor);
+}
+
+Response Handler::dfOpen(const pldm_msg* request, size_t payloadLength)
+{
+    pldm_file_df_open_req decodedReq;
+    int rc = decode_pldm_file_df_open_req(request, payloadLength, &decodedReq);
+    if (rc != 0)
+    {
+        error("Failed to decode DfOpen request. Err: '{ERROR}'", "ERROR", rc);
+        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR);
+    }
+
+    uint16_t file_identifier = decodedReq.file_identifier;
+    std::optional<uint16_t> fdOrRet = getFreeFileDescriptor(file_identifier);
+    if (!fdOrRet.has_value())
+    {
+        error(
+            "No free file descriptors available for FileIdentifier: '{FILE_ID}'",
+            "FILE_ID", file_identifier);
+        return CmdHandler::ccOnlyResponse(request,
+                                          PLDM_FILE_CC_UNABLE_TO_OPEN_FILE);
+    }
+
+    FILE* fp = nullptr;
+    uint32_t fileSize;
+
+    // TODO: We might need a mechanism to cleanup the unsued file pointers
+    // returned here if dfClose is not called on this. One way to do that would
+    // be to update a time value in struct FileDescriptor based on the last time
+    // a file transfer operation was performed on it.
+    int ret = platformHandler->getFilePointer(file_identifier, &fp, &fileSize);
+    if (ret != 0)
+    {
+        error("Requested FileIdentifier: '{FILE_ID}' not found in PDR",
+              "FILE_ID", file_identifier);
+        return CmdHandler::ccOnlyResponse(request,
+                                          PLDM_FILE_CC_INVALID_FILE_IDENTIFIER);
+    }
+
+    // Save the open file information
+    FileDescriptor& fileDescriptor = fileDescriptors[fdOrRet.value()];
+    fileDescriptor.fp = fp;
+    fileDescriptor.fileSize = fileSize;
+
+    size_t respPayloadLength = PLDM_DF_OPEN_RESP_BYTES;
+    Response response(sizeof(pldm_msg_hdr) + respPayloadLength, 0);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+    pldm_file_df_open_resp resp;
+    resp.completion_code = PLDM_SUCCESS;
+    resp.file_descriptor = fdOrRet.value();
+
+    rc = encode_pldm_file_df_open_resp(request->hdr.instance_id, &resp,
+                                       responsePtr, &respPayloadLength);
+    if (rc != 0)
+    {
+        removeFileDescriptor(fdOrRet.value());
+        fclose(fp);
+        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR);
+    }
+
+    return response;
+}
+
+Response Handler::dfClose(const pldm_msg* request, size_t payloadLength)
+{
+    pldm_file_df_close_req decodedReq;
+    int rc = decode_pldm_file_df_close_req(request, payloadLength, &decodedReq);
+    if (rc != 0)
+    {
+        error("Failed to decode DfClose request. Err: '{ERROR}'", "ERROR", rc);
+        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR);
+    }
+
+    uint16_t file_descriptor = decodedReq.file_descriptor;
+
+    auto it = fileDescriptors.find(file_descriptor);
+    if (it == fileDescriptors.end())
+    {
+        error("Requested FileDescriptor: '{FILE_DES}' is not valid", "FILE_DES",
+              file_descriptor);
+        return CmdHandler::ccOnlyResponse(request,
+                                          PLDM_FILE_CC_INVALID_FILE_DESCRIPTOR);
+    }
+
+    size_t respPayloadLength = PLDM_DF_CLOSE_RESP_BYTES;
+    Response response(sizeof(pldm_msg_hdr) + respPayloadLength, 0);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+    pldm_file_df_close_resp resp;
+    resp.completion_code = PLDM_SUCCESS;
+
+    rc = encode_pldm_file_df_close_resp(request->hdr.instance_id, &resp,
+                                        responsePtr, &respPayloadLength);
+    if (rc != 0)
+    {
+        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR);
+    }
+
+    // If there is any other failure before this, we should not clean the
+    // resources and wait for another dfClose.
+    fclose(it->second.fp);
+    removeFileDescriptor(file_descriptor);
+
+    return response;
+}
+
+uint32_t Handler::getDataLengthBytesAndTransferFlag(
+    const FileDescriptor& fileDescriptor, uint32_t currOffset,
+    uint8_t* transferFlag)
+{
+    // Verify that the current offset is greater than the starting offset of the
+    // data sequence.
+    if (currOffset < fileDescriptor.startingOffset)
+    {
+        error(
+            "currOffset: '{CUR_OFFSET}' cannot be smaller than starting_offset: '{ST_OFFSET}'",
+            "CUR_OFFSET", currOffset, "ST_OFFSET",
+            fileDescriptor.startingOffset);
+        return 0;
+    }
+
+    // Calculate already sent data
+    uint32_t sentData = currOffset - fileDescriptor.startingOffset;
+
+    if (sentData > fileDescriptor.sectionLength)
+    {
+        error(
+            "sentData: '{SENT_DATA}' cannot be larger than section_length: '{SECT_LEN}'",
+            "SENT_DATA", sentData, "SECT_LEN", fileDescriptor.sectionLength);
+        return 0;
+    }
+
+    uint32_t remaingData = fileDescriptor.sectionLength - sentData;
+    if (remaingData == 0)
+    {
+        error(
+            "Ramaining data cannot be 0. Client might be requesting data beyond initially negotiated section size");
+        return 0;
+    }
+
+    // Includes the size of the 32bit checksum
+    uint32_t nonDataLength =
+        sizeof(pldm_msg_hdr) + PLDM_BASE_MULTIPART_RECEIVE_RESP_MIN_BYTES +
+        sizeof(uint32_t);
+    uint32_t lenForData = multipartSize - nonDataLength;
+
+    // Derive the response transfer flag
+    if (sentData == 0)
+    {
+        *transferFlag =
+            (lenForData < remaingData) ? PLDM_START : PLDM_START_AND_END;
+    }
+    else
+    {
+        *transferFlag = (lenForData < remaingData) ? PLDM_MIDDLE : PLDM_END;
+    }
+
+    return std::min(lenForData, remaingData);
+}
+
+uint32_t Handler::getNextOffset(uint32_t currOffset, uint32_t dataLengthBytes)
+{
+    return currOffset + dataLengthBytes;
+}
+
+Response Handler::createReadResponse(
+    const pldm_msg* request, const pldm_base_multipart_receive_req& decodedReq,
+    FileDescriptor& fileDescriptor, uint32_t currentTransferHandle,
+    uint8_t transferFlag, uint32_t nextTransferHandle, uint32_t dataLengthBytes)
+{
+    // data_length_bytes shouldn't be 0 unless this is ACK response
+    if ((transferFlag != PLDM_ACKNOWLEDGE_COMPLETION) && (dataLengthBytes == 0))
+    {
+        error("data_length_bytes cannot be 0 unless this is ACK response");
+        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR);
+    }
+
+    // Response payload length includes the checksum if there is data
+    size_t respPayloadLength =
+        PLDM_BASE_MULTIPART_RECEIVE_RESP_MIN_BYTES + dataLengthBytes;
+    if (dataLengthBytes > 0)
+    {
+        respPayloadLength += sizeof(uint32_t);
+    }
+
+    Response response(sizeof(pldm_msg_hdr) + respPayloadLength, 0);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+    struct pldm_base_multipart_receive_resp resp;
+    resp.completion_code = PLDM_SUCCESS;
+    resp.transfer_flag = transferFlag;
+    resp.next_transfer_handle = nextTransferHandle;
+    resp.data.length = dataLengthBytes;
+
+    uint8_t* dataPtr = nullptr;
+    uint32_t checksum = 0;
+
+    // Read the file data and calculate the checksum if dataLengthBytes is non
+    // zero
+    if (dataLengthBytes > 0)
+    {
+        // Point data.ptr to the start of the data section
+        dataPtr = responsePtr->payload +
+                  PLDM_BASE_MULTIPART_RECEIVE_RESP_MIN_BYTES;
+        int ret = fileRead(fileDescriptor.fp, fileDescriptor.fileSize,
+                           currentTransferHandle, dataLengthBytes, dataPtr);
+        if (ret != 0)
+        {
+            return CmdHandler::ccOnlyResponse(request, PLDM_ERROR);
+        }
+
+        // Calculate checksum
+        if ((transferFlag == PLDM_START) ||
+            (transferFlag == PLDM_START_AND_END))
+        {
+            checksum = pldm_edac_crc32(dataPtr, dataLengthBytes);
+        }
+        else
+        {
+            checksum = pldm_edac_crc32_extend(dataPtr, dataLengthBytes,
+                                              fileDescriptor.prevChecksum);
+        }
+    }
+
+    resp.data.ptr = dataPtr;
+
+    // If this is a response for a PLDM_XFER_CURRENT_PART, checksum is the same
+    // as the previous one.
+    if (decodedReq.transfer_opflag == PLDM_XFER_CURRENT_PART)
+    {
+        checksum = fileDescriptor.prevChecksum;
+    }
+
+    int ret = encode_base_multipart_receive_resp(
+        request->hdr.instance_id, &resp, checksum, responsePtr,
+        &respPayloadLength);
+    if (ret != 0)
+    {
+        error("Multipart receive resp encoding failed for dfRead: {ERROR}",
+              "ERROR", ret);
+        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR);
+    }
+
+    fileDescriptor.prevChecksum = checksum;
+    return response;
+}
+
+Response Handler::dfReadTransferFirstPart(
+    const pldm_msg* request, const pldm_base_multipart_receive_req& decodedReq,
+    FileDescriptor& fileDescriptor)
+{
+    // For the first part, transfer handle should be 0
+    if (decodedReq.transfer_handle != 0)
+    {
+        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR_INVALID_DATA);
+    }
+
+    // Verify that the offset is within file size
+    if (decodedReq.section_offset >= fileDescriptor.fileSize)
+    {
+        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR_INVALID_DATA);
+    }
+
+    // Verify that the offset + read length is within file size
+    if (decodedReq.section_length >
+        fileDescriptor.fileSize - decodedReq.section_offset)
+    {
+        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR_INVALID_DATA);
+    }
+
+    fileDescriptor.startingOffset = decodedReq.section_offset;
+    fileDescriptor.sectionLength = decodedReq.section_length;
+
+    // If the requested section length is 0, the requester doesn't know the
+    // size. So the remainder of the complete file will be transmitted.
+    if (decodedReq.section_length == 0)
+    {
+        fileDescriptor.sectionLength =
+            fileDescriptor.fileSize - decodedReq.section_offset;
+    }
+
+    // Get the length of the data bytes we need to transfer in this packet and
+    // the corresponding transfer flag.
+    uint8_t transfer_flag;
+    uint32_t data_length_bytes = getDataLengthBytesAndTransferFlag(
+        fileDescriptor, fileDescriptor.startingOffset, &transfer_flag);
+    if (data_length_bytes == 0)
+    {
+        // This can happen if the client is requesting data beyond initially
+        // negotiated values.
+        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR_INVALID_DATA);
+    }
+
+    // Transfer handle is used as the offset to the file. Get the next transfer
+    // handle if are more future packets in this sequence.
+    uint32_t next_transfer_handle = 0;
+    if (transfer_flag == PLDM_START)
+    {
+        next_transfer_handle =
+            getNextOffset(fileDescriptor.startingOffset, data_length_bytes);
+    }
+
+    return createReadResponse(request, decodedReq, fileDescriptor,
+                              fileDescriptor.startingOffset, transfer_flag,
+                              next_transfer_handle, data_length_bytes);
+}
+
+Response Handler::dfReadTransferCurrentOrNextPart(
+    const pldm_msg* request, const pldm_base_multipart_receive_req& decodedReq,
+    FileDescriptor& fileDescriptor)
+{
+    // Validate transfer flag. This doesn't strictly check the transfer flag
+    // with an expected value. Instead veryfying that it is within valid range.
+    if (decodedReq.transfer_handle < fileDescriptor.startingOffset)
+    {
+        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR_INVALID_DATA);
+    }
+
+    // Validate that the transfer_handle is within requested data range.
+    if (decodedReq.transfer_handle - fileDescriptor.startingOffset >=
+        fileDescriptor.sectionLength)
+    {
+        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR_INVALID_DATA);
+    }
+
+    // Get the length of the data bytes we need to transfer in this packet and
+    // the corresponding transfer flag.
+    uint8_t transfer_flag;
+    uint32_t data_length_bytes = getDataLengthBytesAndTransferFlag(
+        fileDescriptor, decodedReq.transfer_handle, &transfer_flag);
+    if (data_length_bytes == 0)
+    {
+        // This can happen if the client is requesting data beyond initially
+        // negotiated values.
+        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR_INVALID_DATA);
+    }
+
+    // Transfer handle is used as the offset to the file. Get the next transfer
+    // handle if are more future packets in this sequence.
+    uint32_t next_transfer_handle = 0;
+    if ((transfer_flag == PLDM_START) || (transfer_flag == PLDM_MIDDLE))
+    {
+        next_transfer_handle =
+            getNextOffset(decodedReq.transfer_handle, data_length_bytes);
+    }
+
+    return createReadResponse(request, decodedReq, fileDescriptor,
+                              decodedReq.transfer_handle, transfer_flag,
+                              next_transfer_handle, data_length_bytes);
+}
+
+Response Handler::dfReadTransferCompleteOrAbort(
+    const pldm_msg* request, const pldm_base_multipart_receive_req& decodedReq,
+    FileDescriptor& fileDescriptor)
+{
+    // Check whether the requester wants to start a new connection
+    if (decodedReq.section_length > 0 || decodedReq.section_offset > 0)
+    {
+        return dfReadTransferFirstPart(request, decodedReq, fileDescriptor);
+    }
+    return createReadResponse(
+        request, decodedReq, fileDescriptor, /*current_transfer_handle=*/0,
+        /*transfer_flag*/ PLDM_ACKNOWLEDGE_COMPLETION,
+        /*next_transfer_handle*/ 0, /*data_length_bytes*/ 0);
+}
+
+Response Handler::dfRead(const pldm_msg* request, size_t payloadLength)
+{
+    uint8_t pldmType;
+    uint8_t transferOpflag;
+    uint32_t transferCtx;
+    uint32_t transferHandle;
+    uint32_t sectionOffset;
+    uint32_t sectionLength;
+
+    int rc = decode_multipart_receive_req(
+        request, payloadLength, &pldmType, &transferOpflag, &transferCtx,
+        &transferHandle, &sectionOffset, &sectionLength);
+    if (rc != 0)
+    {
+        error("Failed to decode DfRead request. Err: '{ERROR}'", "ERROR", rc);
+        return CmdHandler::ccOnlyResponse(request, PLDM_ERROR);
+    }
+
+    pldm_base_multipart_receive_req decodedReq;
+    decodedReq.pldm_type = pldmType;
+    decodedReq.transfer_opflag = transferOpflag;
+    decodedReq.transfer_ctx = transferCtx;
+    decodedReq.transfer_handle = transferHandle;
+    decodedReq.section_offset = sectionOffset;
+    decodedReq.section_length = sectionLength;
+
+    uint32_t file_descriptor = transferCtx;
+    auto it = fileDescriptors.find(file_descriptor);
+    if (it == fileDescriptors.end())
+    {
+        error("Requested FileDescriptor: '{FILE_DES}' is not valid", "FILE_DES",
+              file_descriptor);
+        return CmdHandler::ccOnlyResponse(request,
+                                          PLDM_FILE_CC_INVALID_FILE_DESCRIPTOR);
+    }
+
+    switch (transferOpflag)
+    {
+        case PLDM_XFER_FIRST_PART:
+            return dfReadTransferFirstPart(request, decodedReq, it->second);
+        case PLDM_XFER_NEXT_PART:
+            return dfReadTransferCurrentOrNextPart(request, decodedReq,
+                                                   it->second);
+        case PLDM_XFER_ABORT:
+            return dfReadTransferCompleteOrAbort(request, decodedReq,
+                                                 it->second);
+        case PLDM_XFER_COMPLETE:
+            return dfReadTransferCompleteOrAbort(request, decodedReq,
+                                                 it->second);
+        case PLDM_XFER_CURRENT_PART:
+            return dfReadTransferCurrentOrNextPart(request, decodedReq,
+                                                   it->second);
+        default:
+            break;
+    }
+
+    return CmdHandler::ccOnlyResponse(request, PLDM_ERROR);
+}
+
+} // namespace file_transfer
+} // namespace responder
+} // namespace pldm
diff --git a/libpldmresponder/file_transfer.hpp b/libpldmresponder/file_transfer.hpp
new file mode 100644
index 0000000..92af1be
--- /dev/null
+++ b/libpldmresponder/file_transfer.hpp
@@ -0,0 +1,148 @@
+#pragma once
+
+#include "base.hpp"
+#include "platform.hpp"
+#include "pldmd/handler.hpp"
+
+#include <libpldm/file.h>
+
+#include <fstream>
+#include <functional>
+#include <map>
+#include <string>
+#include <string_view>
+
+namespace pldm
+{
+namespace responder
+{
+namespace file_transfer
+{
+
+struct FileDescriptor
+{
+    FILE* fp;
+    uint32_t fileSize;
+    uint32_t startingOffset;
+    uint32_t sectionLength;
+    uint32_t prevChecksum;
+    uint16_t fileIdentifier;
+};
+
+/**
+ * @brief Reads a specified portion of a file
+ *
+ * @param fp An open file pointer.
+ * @param fileSize The total size of the file in bytes.
+ * @param offset The starting position from where to read in the file stream.
+ * @param readSize The number of bytes to read from the offset.
+ * @param data_ptr Pointer to the buffer to store data. At minimum buffer size
+ * should be readSize.
+ * @return 0 if successful else -1.
+ */
+int fileRead(FILE* fp, uint32_t fileSize, uint32_t offset, uint32_t readSize,
+             uint8_t* dataPtr);
+
+class Handler : public CmdHandler
+{
+  public:
+    Handler(responder::platform::Handler* platformHandler);
+
+    /**
+     * Sets the multipart size used for DfRead commands
+     */
+    void setMultipartSize(uint16_t size);
+
+  private:
+    /**
+     * @brief Returns a free file descriptor
+     */
+    std::optional<uint16_t> getFreeFileDescriptor(uint16_t fileIdentifier);
+
+    /**
+     * @brief Removes a file descriptor from fileDescriptors
+     */
+    void removeFileDescriptor(uint16_t fileDescriptor);
+
+    /**
+     * @brief Handler for the DfOpen command
+     */
+    Response dfOpen(const pldm_msg* request, size_t payloadLength);
+
+    /**
+     * @brief Handler for the DfClose command
+     */
+    Response dfClose(const pldm_msg* request, size_t payloadLength);
+
+    /**
+     * @brief Calculates the data length and transfer flag for a DfRead response
+     */
+    uint32_t getDataLengthBytesAndTransferFlag(
+        const FileDescriptor& fileDescriptor, uint32_t currOffset,
+        uint8_t* transferFlag);
+
+    /**
+     * @brief Calculates the next offset for a DfRead response
+     */
+    uint32_t getNextOffset(uint32_t currOffset, uint32_t dataLengthBytes);
+
+    /**
+     * @brief Creates a DfRead response message
+     */
+    Response createReadResponse(
+        const pldm_msg* request,
+        const pldm_base_multipart_receive_req& decodedReq,
+        FileDescriptor& fileDescriptor, uint32_t currentTransferHandle,
+        uint8_t transferFlag, uint32_t nextTransferHandle,
+        uint32_t dataLengthBytes);
+
+    /**
+     * @brief Handles the first part of a DfRead transfer
+     */
+    Response dfReadTransferFirstPart(
+        const pldm_msg* request,
+        const pldm_base_multipart_receive_req& decodedReq,
+        FileDescriptor& fileDescriptor);
+
+    /**
+     * @brief Handles the current or next part of a DfRead transfer
+     */
+    Response dfReadTransferCurrentOrNextPart(
+        const pldm_msg* request,
+        const pldm_base_multipart_receive_req& decodedReq,
+        FileDescriptor& fileDescriptor);
+
+    /**
+     * @brief Handles the completion or abort of a DfRead transfer
+     */
+    Response dfReadTransferCompleteOrAbort(
+        const pldm_msg* request,
+        const pldm_base_multipart_receive_req& decodedReq,
+        FileDescriptor& fileDescriptor);
+
+    /**
+     * @brief Handler for the DfRead command
+     */
+    Response dfRead(const pldm_msg* request, size_t payloadLength);
+
+    // PLDM Platform type handler. File pointers will be initialized during file
+    // PDR descriptor handling. So This will allow the file transfer handler to
+    // get the FILE pointers from platform handler.
+    responder::platform::Handler* platformHandler;
+
+    // Store active file descriptors
+    std::map<uint16_t, FileDescriptor> fileDescriptors;
+
+    // This value will be incremented each time a new file descriptor is
+    // created. This is the simplest approach. We could change this later if we
+    // need a better approach.
+    uint16_t nextFileDescriptor;
+
+    // Transfer size for DfRead command. Should be set by the base type
+    // handlers. If not set, min part size will be used.
+    uint16_t multipartSize;
+};
+
+} // namespace file_transfer
+} // namespace responder
+} // namespace pldm
diff --git a/libpldmresponder/meson.build b/libpldmresponder/meson.build
index 25cb1f6..2d76625 100644
--- a/libpldmresponder/meson.build
+++ b/libpldmresponder/meson.build
@@ -22,6 +22,7 @@
     'pdr_file.cpp',
     'platform.cpp',
     'platform_config.cpp',
+    'file_transfer.cpp',
     'fru_parser.cpp',
     'fru.cpp',
     '../host-bmc/host_pdr_handler.cpp',
diff --git a/libpldmresponder/pdr_file.cpp b/libpldmresponder/pdr_file.cpp
index dc22805..9661e4e 100644
--- a/libpldmresponder/pdr_file.cpp
+++ b/libpldmresponder/pdr_file.cpp
@@ -150,7 +150,18 @@
             pdr_utils::PdrEntry pdrEntry{};
             pdrEntry.data = entry.data();
             pdrEntry.size = pdrSize;
-            pdr_utils::RecordHandle recordHandle = repo.addRecord(pdrEntry);
+            pdr_utils::RecordHandle recordHandle;
+
+            try
+            {
+                recordHandle = repo.addRecord(pdrEntry);
+            }
+            catch (const std::runtime_error& e)
+            {
+                error("Error adding record for file '{FILE_PATH}'", "FILE_PATH",
+                      fileInfo.filePath);
+                continue;
+            }
 
             fileInfo.recordHandle = static_cast<uint32_t>(recordHandle);
 
diff --git a/libpldmresponder/platform.cpp b/libpldmresponder/platform.cpp
index 66f205d..d6082af 100644
--- a/libpldmresponder/platform.cpp
+++ b/libpldmresponder/platform.cpp
@@ -1077,6 +1077,61 @@
     return response;
 }
 
+int Handler::getFilePointer(uint16_t fileIdentifier, FILE** fp,
+                            uint32_t* fileSize)
+{
+    auto it = fileIdentifierToFileInfo.find(fileIdentifier);
+    if (it == fileIdentifierToFileInfo.end())
+    {
+        error(
+            "File identifier '{FILE_ID}' not found in fileIdentifierToFileInfo map",
+            "FILE_ID", fileIdentifier);
+        return -1;
+    }
+
+    FILE* original_fp = it->second.fp;
+    if (original_fp == nullptr)
+    {
+        error("Invalid file pointer for file identifier '{FILE_ID}'", "FILE_ID",
+              fileIdentifier);
+        return -1;
+    }
+
+    int original_fd = fileno(original_fp);
+    if (original_fd == -1)
+    {
+        error(
+            "Failed to get file descriptor for file pointer for file identifier '{FILE_ID}'",
+            "FILE_ID", fileIdentifier);
+        return -1;
+    }
+
+    int duplicated_fd = dup(original_fd);
+    if (duplicated_fd == -1)
+    {
+        error(
+            "Failed to duplicate file descriptor for file identifier '{FILE_ID}'",
+            "FILE_ID", fileIdentifier);
+        return -1;
+    }
+
+    // TODO: the mode should match the original mode. For now read only is
+    // supported.
+    FILE* duplicated_fp = fdopen(duplicated_fd, "rb");
+    if (!duplicated_fp)
+    {
+        close(duplicated_fd);
+        error("Failed to open file descriptor for file identifier '{FILE_ID}'",
+              "FILE_ID", fileIdentifier);
+        return -1;
+    }
+
+    *fp = duplicated_fp;
+    *fileSize = it->second.fileSize;
+
+    return 0;
+}
+
 } // namespace platform
 } // namespace responder
 } // namespace pldm
diff --git a/libpldmresponder/platform.hpp b/libpldmresponder/platform.hpp
index 0defc8a..6f85f66 100644
--- a/libpldmresponder/platform.hpp
+++ b/libpldmresponder/platform.hpp
@@ -527,6 +527,16 @@
     Response getPdrRepositoryInfo(const pldm_msg* request,
                                   size_t payloadLength);
 
+    /** @brief Get file pointer and file size for a given file identifier
+     *
+     *  @param[in] fileIdentifier - file identifier
+     *  @param[out] fp - file pointer
+     *  @param[out] fileSize - file size
+     *
+     *  @return 0 on success, -1 on failure
+     */
+    int getFilePointer(uint16_t fileIdentifier, FILE** fp, uint32_t* fileSize);
+
   private:
     uint8_t eid;
     InstanceIdDb* instanceIdDb;
diff --git a/libpldmresponder/test/libpldmresponder_file_transfer_test.cpp b/libpldmresponder/test/libpldmresponder_file_transfer_test.cpp
new file mode 100644
index 0000000..c3fc8f0
--- /dev/null
+++ b/libpldmresponder/test/libpldmresponder_file_transfer_test.cpp
@@ -0,0 +1,1357 @@
+#include "libpldmresponder/file_transfer.hpp"
+#include "libpldmresponder/platform.hpp"
+
+#include <libpldm/base.h>
+#include <libpldm/file.h>
+
+#include <algorithm>
+#include <filesystem>
+#include <fstream>
+#include <numeric>
+#include <random>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace fs = std::filesystem;
+using namespace pldm::responder;
+using namespace pldm::responder::file_transfer;
+
+using ::testing::_;
+using ::testing::NiceMock;
+using ::testing::Return;
+
+// Test fixture for the standalone fileRead function
+class FileReadTest : public ::testing::Test
+{
+  protected:
+    void SetUp() override
+    {
+        // Create a temporary file with known content
+        char tmppath[] = "/tmp/pldm_fileread_test.XXXXXX";
+        dir = fs::path(mkdtemp(tmppath));
+        testFilePath = dir / "testfile.bin";
+        std::ofstream testFile(testFilePath, std::ios::binary);
+        fileContent.resize(256);
+        std::iota(fileContent.begin(), fileContent.end(), 0);
+        testFile.write(reinterpret_cast<const char*>(fileContent.data()),
+                       fileContent.size());
+        testFile.close();
+        fileSize = fileContent.size();
+    }
+
+    void TearDown() override
+    {
+        fs::remove_all(dir);
+    }
+
+    fs::path dir;
+    fs::path testFilePath;
+    std::vector<uint8_t> fileContent;
+    uint32_t fileSize;
+};
+
+TEST_F(FileReadTest, SuccessCases)
+{
+    FILE* fp = fopen(testFilePath.c_str(), "rb");
+    ASSERT_NE(fp, nullptr);
+
+    // Read from start
+    uint32_t readSize = 64;
+
+    std::vector<uint8_t> buffer(readSize);
+    EXPECT_EQ(fileRead(fp, fileSize, 0, readSize, buffer.data()), 0);
+    EXPECT_EQ(0, memcmp(buffer.data(), fileContent.data(), readSize));
+
+    // Read from middle
+    uint32_t offset = 100;
+    readSize = 50;
+    buffer.resize(readSize);
+    EXPECT_EQ(fileRead(fp, fileSize, offset, readSize, buffer.data()), 0);
+    EXPECT_EQ(0, memcmp(buffer.data(), fileContent.data() + offset, readSize));
+
+    // Test case where offset + readSize == fileSize
+    offset = 128;
+    readSize = fileSize - offset;
+    buffer.resize(readSize);
+    EXPECT_EQ(fileRead(fp, fileSize, offset, readSize, buffer.data()), 0);
+    EXPECT_EQ(0, memcmp(buffer.data(), fileContent.data() + offset, readSize));
+
+    // Read the full file
+    buffer.resize(fileSize);
+    EXPECT_EQ(fileRead(fp, fileSize, 0, fileSize, buffer.data()), 0);
+    EXPECT_EQ(buffer, fileContent);
+
+    fclose(fp);
+}
+
+TEST_F(FileReadTest, FailureCases)
+{
+    std::vector<uint8_t> buffer(10);
+
+    // Null file pointer
+    EXPECT_EQ(fileRead(nullptr, fileSize, 0, 10, buffer.data()), -1);
+
+    FILE* fp = fopen(testFilePath.c_str(), "rb");
+    ASSERT_NE(fp, nullptr);
+
+    // Null data buffer
+    EXPECT_EQ(fileRead(fp, fileSize, 0, 10, nullptr), -1);
+
+    // Offset >= fileSize
+    EXPECT_EQ(fileRead(fp, fileSize, fileSize, 1, buffer.data()), -1);
+    EXPECT_EQ(fileRead(fp, fileSize, fileSize + 1, 1, buffer.data()), -1);
+
+    // Read size > fileSize
+    std::vector<uint8_t> largeBuffer(fileSize + 1);
+    EXPECT_EQ(fileRead(fp, fileSize, 0, fileSize + 1, largeBuffer.data()), -1);
+
+    // Read would go beyond file
+    EXPECT_EQ(fileRead(fp, fileSize, fileSize - 5, 10, buffer.data()), -1);
+
+    fclose(fp);
+}
+
+class FileTransferTests : public ::testing::Test
+{
+  protected:
+    FileTransferTests() :
+        event(sdeventplus::Event::get_default()),
+        // platform::Handler needs a valid directory for PDRs
+        pdrDir(fs::temp_directory_path() / "pldm_pdr"),
+        pdrRepo(pldm_pdr_init(), pldm_pdr_destroy),
+        platformHandler(nullptr, 0, nullptr, pdrDir, pdrRepo.get(), nullptr,
+                        nullptr, nullptr, nullptr, nullptr, event, true),
+        handler(&platformHandler)
+    {}
+
+    void SetUp() override
+    {
+        // Create a directory to hold the PDR JSON files
+        fs::create_directories(pdrDir);
+
+        // Create a target file that the PDR will point to
+        char tmppath[] = "/tmp/pldm_target_file.XXXXXX";
+        targetFileDir = fs::path(mkdtemp(tmppath));
+        targetFilePath = targetFileDir / "testfile.bin";
+        fileContent.resize(400);
+        // Assign random values to the file
+        std::random_device rd;
+        std::mt19937 gen(rd());
+        std::uniform_int_distribution<> distrib(0, 255);
+        std::generate(fileContent.begin(), fileContent.end(),
+                      [&]() { return distrib(gen); });
+        std::ofstream testFile(targetFilePath, std::ios::binary);
+        testFile.write(reinterpret_cast<const char*>(fileContent.data()),
+                       fileContent.size());
+    }
+
+    void TearDown() override
+    {
+        // Clean up the created directories and files
+        fs::remove_all(targetFileDir);
+        fs::remove_all(pdrDir);
+    }
+
+    /**
+     * @brief Creates a PDR JSON file to set up the platform handler's state
+     *
+     * @param fileIdentifier The identifier for the file PDR
+     * @param filePath The path to the file on disk
+     */
+    void setupPDR(uint16_t fileIdentifier, const fs::path& filePath)
+    {
+        uint32_t fileSize = fs::file_size(filePath);
+
+        // Construct the JSON content
+        nlohmann::json pdrJson = {
+            {"fileDescriptorPDRs",
+             {{{"entries",
+                {{{"EntityType", 9},
+                  {"Path", targetFileDir.string()},
+                  {"Files",
+                   {{{"FileIdentifier", fileIdentifier},
+                     {"FileName", filePath.filename().string()},
+                     {"FileSize", fileSize}}}}}}}}}}};
+
+        // Write the JSON to a file in the PDR directory
+        std::ofstream jsonFile(pdrDir / "test_pdr.json");
+        jsonFile << pdrJson;
+        jsonFile.close();
+
+        // Use the public API to parse the PDRs and populate internal state
+        const pldm::utils::DBusHandler dBusIntf;
+        platformHandler.generate(dBusIntf, {pdrDir}, platformHandler.getRepo());
+    }
+
+    sdeventplus::Event event;
+    fs::path pdrDir;
+    std::unique_ptr<pldm_pdr, decltype(&pldm_pdr_destroy)> pdrRepo;
+    pldm::responder::platform::Handler platformHandler;
+    pldm::responder::file_transfer::Handler handler;
+    fs::path targetFileDir;
+    fs::path targetFilePath;
+    std::vector<uint8_t> fileContent;
+};
+
+// Verify successfully opening a file
+TEST_F(FileTransferTests, DfOpenSuccess)
+{
+    uint16_t fileIdentifier = 1;
+    setupPDR(fileIdentifier, targetFilePath);
+
+    std::vector<uint8_t> request_msg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_OPEN_REQ_BYTES);
+    struct pldm_msg* openRequest =
+        reinterpret_cast<struct pldm_msg*>(request_msg.data());
+
+    struct pldm_file_df_open_req req{};
+    req.file_identifier = fileIdentifier;
+    size_t requestPayloadLength = PLDM_DF_OPEN_REQ_BYTES;
+
+    int encode_rc = encode_pldm_file_df_open_req(0, &req, openRequest,
+                                                 &requestPayloadLength);
+    ASSERT_EQ(encode_rc, 0);
+
+    auto response = handler.handle(0, PLDM_FILE_CMD_DF_OPEN, openRequest,
+                                   PLDM_DF_OPEN_REQ_BYTES);
+    ASSERT_NE(response.data(), nullptr);
+
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    ASSERT_GE(response.size(), sizeof(pldm_msg_hdr) + PLDM_DF_OPEN_RESP_BYTES);
+
+    struct pldm_file_df_open_resp decoded_resp{};
+    decode_pldm_file_df_open_resp(
+        responsePtr, response.size() - sizeof(pldm_msg_hdr), &decoded_resp);
+    ASSERT_EQ(decoded_resp.completion_code, PLDM_SUCCESS);
+}
+
+// Invalid DfOpen request length
+TEST_F(FileTransferTests, InvalidDfOpenRequestLength)
+{
+    pldm_msg request{};
+    // Use an incorrect payload length
+    size_t invalidPayloadLength = PLDM_DF_OPEN_REQ_BYTES - 1;
+
+    // FIX: Use the correct 'handle' method name
+    auto response = handler.handle(0, PLDM_FILE_CMD_DF_OPEN, &request,
+                                   invalidPayloadLength);
+
+    ASSERT_NE(response.data(), nullptr);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    EXPECT_EQ(responsePtr->payload[0], PLDM_ERROR);
+}
+
+// Successfully open a file and read a section
+TEST_F(FileTransferTests, DfOpenAndRead)
+{
+    // Setup PDR for the test file
+    uint16_t fileIdentifier = 1;
+    setupPDR(fileIdentifier, targetFilePath);
+
+    // Open the file via dfOpen
+    std::vector<uint8_t> openRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_OPEN_REQ_BYTES);
+    auto openRequest = reinterpret_cast<pldm_msg*>(openRequestMsg.data());
+    struct pldm_file_df_open_req req{};
+    req.file_identifier = fileIdentifier;
+    size_t openRequestPayloadLength = PLDM_DF_OPEN_REQ_BYTES;
+
+    int rc = encode_pldm_file_df_open_req(0, &req, openRequest,
+                                          &openRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    auto openResponse = handler.handle(0, PLDM_FILE_CMD_DF_OPEN, openRequest,
+                                       PLDM_DF_OPEN_REQ_BYTES);
+    ASSERT_NE(openResponse.data(), nullptr);
+
+    auto openResponsePtr = reinterpret_cast<pldm_msg*>(openResponse.data());
+    struct pldm_file_df_open_resp decodedOpenResp{};
+    decode_pldm_file_df_open_resp(openResponsePtr,
+                                  openResponse.size() - sizeof(pldm_msg_hdr),
+                                  &decodedOpenResp);
+    ASSERT_EQ(decodedOpenResp.completion_code, PLDM_SUCCESS);
+
+    uint16_t fileDescriptor = decodedOpenResp.file_descriptor;
+
+    // Read a portion of the file via dfRead
+    uint32_t offset = 10;
+    uint32_t lengthToRead = 64;
+    std::vector<uint8_t> readRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    auto readRequest = reinterpret_cast<pldm_msg*>(readRequestMsg.data());
+    size_t readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    struct pldm_base_multipart_receive_req read_req{};
+    read_req.pldm_type = PLDM_FILE;
+    read_req.transfer_opflag = PLDM_XFER_FIRST_PART;
+    read_req.transfer_ctx = fileDescriptor;
+    read_req.transfer_handle = 0;
+    read_req.section_offset = offset;
+    read_req.section_length = lengthToRead;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req, readRequest,
+                                                &readRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handlers
+    auto readResponse = handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                                       PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    ASSERT_NE(readResponse.data(), nullptr);
+
+    // Verify the response
+    auto readResponsePtr = reinterpret_cast<pldm_msg*>(readResponse.data());
+
+    uint32_t data_integrity_checksum;
+    pldm_base_multipart_receive_resp resp;
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  readResponsePtr, readResponse.size() - sizeof(pldm_msg_hdr),
+                  &resp, &data_integrity_checksum),
+              0);
+
+    EXPECT_EQ(resp.completion_code, PLDM_SUCCESS);
+    EXPECT_EQ(resp.transfer_flag, PLDM_START_AND_END);
+    EXPECT_EQ(resp.data.length, lengthToRead);
+
+    const uint8_t* responseData = resp.data.ptr;
+    EXPECT_EQ(0,
+              memcmp(responseData, fileContent.data() + offset, lengthToRead));
+
+    EXPECT_EQ(data_integrity_checksum,
+              pldm_edac_crc32(responseData, lengthToRead));
+}
+
+// Opening a file with an invalid file identifier
+TEST_F(FileTransferTests, InvalidFileIdentifier)
+{
+    // Setup: We set up a PDR for a specific ID.
+    uint16_t validFileIdentifier = 1;
+    setupPDR(validFileIdentifier, targetFilePath);
+
+    // Create request for a DIFFERENT, invalid ID.
+    uint16_t invalidFileIdentifier = 99;
+    pldm_msg request{};
+    struct pldm_file_df_open_req req{};
+    req.file_identifier = invalidFileIdentifier;
+    size_t requestPayloadLength = PLDM_DF_OPEN_REQ_BYTES;
+
+    int encode_rc =
+        encode_pldm_file_df_open_req(0, &req, &request, &requestPayloadLength);
+    ASSERT_EQ(encode_rc, 0);
+
+    // Process request and check for the expected error.
+    auto response = handler.handle(0, PLDM_FILE_CMD_DF_OPEN, &request,
+                                   PLDM_DF_OPEN_REQ_BYTES);
+    ASSERT_NE(response.data(), nullptr);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+
+    struct pldm_file_df_open_resp decodedOpenResp{};
+    ASSERT_EQ(decode_pldm_file_df_open_resp(
+                  responsePtr, response.size() - sizeof(pldm_msg_hdr),
+                  &decodedOpenResp),
+              0);
+    ASSERT_EQ(decodedOpenResp.completion_code,
+              PLDM_FILE_CC_INVALID_FILE_IDENTIFIER);
+}
+
+// Successfully open a file and close it
+TEST_F(FileTransferTests, OpenAndClose)
+{
+    uint16_t fileIdentifier = 1;
+    setupPDR(fileIdentifier, targetFilePath);
+
+    std::vector<uint8_t> openeRquestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_OPEN_REQ_BYTES);
+    struct pldm_msg* openRequest =
+        reinterpret_cast<struct pldm_msg*>(openeRquestMsg.data());
+
+    struct pldm_file_df_open_req req{};
+    req.file_identifier = fileIdentifier;
+    size_t requestPayloadLength = PLDM_DF_OPEN_REQ_BYTES;
+
+    int encode_rc = encode_pldm_file_df_open_req(0, &req, openRequest,
+                                                 &requestPayloadLength);
+    ASSERT_EQ(encode_rc, 0);
+
+    // Send the request to the handler
+    auto response = handler.handle(0, PLDM_FILE_CMD_DF_OPEN, openRequest,
+                                   PLDM_DF_OPEN_REQ_BYTES);
+    ASSERT_NE(response.data(), nullptr);
+
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    struct pldm_file_df_open_resp decoded_resp{};
+    decode_pldm_file_df_open_resp(
+        responsePtr, response.size() - sizeof(pldm_msg_hdr), &decoded_resp);
+    uint16_t new_fd = decoded_resp.file_descriptor;
+
+    std::vector<uint8_t> closeRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_CLOSE_REQ_BYTES);
+    struct pldm_msg* closeRequest =
+        reinterpret_cast<struct pldm_msg*>(closeRequestMsg.data());
+
+    struct pldm_file_df_close_req close_req{};
+    close_req.file_descriptor = new_fd;
+    size_t closePayloadLength = PLDM_DF_CLOSE_REQ_BYTES;
+    encode_rc = encode_pldm_file_df_close_req(0, &close_req, closeRequest,
+                                              &closePayloadLength);
+    ASSERT_EQ(encode_rc, 0);
+
+    // Send the request to the handler
+    auto close_response = handler.handle(0, PLDM_FILE_CMD_DF_CLOSE,
+                                         closeRequest, PLDM_DF_CLOSE_REQ_BYTES);
+
+    ASSERT_NE(close_response.data(), nullptr);
+    auto closeResponsePtr = reinterpret_cast<pldm_msg*>(close_response.data());
+    EXPECT_EQ(closeResponsePtr->payload[0], PLDM_SUCCESS);
+}
+
+// Close with a invalid file descriptor
+TEST_F(FileTransferTests, DfCloseInvalidFileDescriptor)
+{
+    // Attempt to close a file descriptor that was never opened.
+    uint16_t invalidFileDescriptor = 999;
+    std::vector<uint8_t> requestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_CLOSE_REQ_BYTES);
+    auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+
+    struct pldm_file_df_close_req req{};
+    req.file_descriptor = invalidFileDescriptor;
+    size_t payloadLength = PLDM_DF_CLOSE_REQ_BYTES;
+    int rc = encode_pldm_file_df_close_req(0, &req, request, &payloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto response = handler.handle(0, PLDM_FILE_CMD_DF_CLOSE, request,
+                                   PLDM_DF_CLOSE_REQ_BYTES);
+
+    ASSERT_NE(response.data(), nullptr);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    EXPECT_EQ(responsePtr->payload[0], PLDM_FILE_CC_INVALID_FILE_DESCRIPTOR);
+}
+
+// Invalid DfClose request length
+TEST_F(FileTransferTests, DfCloseInvalidRequestLength)
+{
+    // Attempt to close with an incorrect payload length.
+    uint16_t fileDescriptor = 123; // Can be any value
+    std::vector<uint8_t> requestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_CLOSE_REQ_BYTES);
+    auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+
+    struct pldm_file_df_close_req req{};
+    req.file_descriptor = fileDescriptor;
+    size_t payloadLength = PLDM_DF_CLOSE_REQ_BYTES;
+    int rc = encode_pldm_file_df_close_req(0, &req, request, &payloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Use an invalid length
+    size_t invalidPayloadLength = PLDM_DF_CLOSE_REQ_BYTES - 1;
+    auto response = handler.handle(0, PLDM_FILE_CMD_DF_CLOSE, request,
+                                   invalidPayloadLength);
+
+    ASSERT_NE(response.data(), nullptr);
+    auto responsePtr = reinterpret_cast<pldm_msg*>(response.data());
+    EXPECT_EQ(responsePtr->payload[0], PLDM_ERROR);
+}
+
+// Open and read a file completely with multiple multipart request messages
+TEST_F(FileTransferTests, DfReadCompleteFile)
+{
+    uint16_t fileIdentifier = 2;
+    setupPDR(fileIdentifier, targetFilePath);
+
+    std::vector<uint8_t> openRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_OPEN_REQ_BYTES);
+    auto openRequest = reinterpret_cast<pldm_msg*>(openRequestMsg.data());
+    struct pldm_file_df_open_req req{};
+    req.file_identifier = fileIdentifier;
+    size_t openRequestPayloadLength = PLDM_DF_OPEN_REQ_BYTES;
+
+    int rc = encode_pldm_file_df_open_req(0, &req, openRequest,
+                                          &openRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto openResponse = handler.handle(0, PLDM_FILE_CMD_DF_OPEN, openRequest,
+                                       PLDM_DF_OPEN_REQ_BYTES);
+    ASSERT_NE(openResponse.data(), nullptr);
+
+    auto openResponsePtr = reinterpret_cast<pldm_msg*>(openResponse.data());
+    struct pldm_file_df_open_resp decodedOpenResp{};
+    decode_pldm_file_df_open_resp(openResponsePtr,
+                                  openResponse.size() - sizeof(pldm_msg_hdr),
+                                  &decodedOpenResp);
+    ASSERT_EQ(decodedOpenResp.completion_code, PLDM_SUCCESS);
+
+    uint16_t fileDescriptor = decodedOpenResp.file_descriptor;
+
+    // Initialize variables for multi-part read
+    uint32_t offset = 0;
+    uint32_t transferHandle = 0;
+    uint8_t transferFlag = PLDM_XFER_FIRST_PART;
+    std::vector<uint8_t> receivedData;
+    uint32_t lengthToRead = fileContent.size();
+    uint32_t final_checksum = 0;
+
+    std::vector<uint8_t> readRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    auto readRequest = reinterpret_cast<pldm_msg*>(readRequestMsg.data());
+
+    // Read the file in multiple parts
+    while (transferFlag != PLDM_END && transferFlag != PLDM_START_AND_END)
+    {
+        struct pldm_base_multipart_receive_req read_req{};
+        read_req.pldm_type = PLDM_FILE;
+        read_req.transfer_opflag = transferFlag;
+        read_req.transfer_ctx = fileDescriptor;
+        read_req.transfer_handle = transferHandle;
+        read_req.section_offset = offset;
+        read_req.section_length = lengthToRead;
+        size_t readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+        rc = encode_pldm_base_multipart_receive_req(0, &read_req, readRequest,
+                                                    &readRequestPayloadLength);
+        ASSERT_EQ(rc, 0);
+
+        // Send the request to the handler
+        auto readResponse =
+            handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                           PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+        ASSERT_NE(readResponse.data(), nullptr);
+
+        auto readResponsePtr = reinterpret_cast<pldm_msg*>(readResponse.data());
+
+        uint32_t data_integrity_checksum;
+        pldm_base_multipart_receive_resp resp;
+        ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                      readResponsePtr,
+                      readResponse.size() - sizeof(pldm_msg_hdr), &resp,
+                      &data_integrity_checksum),
+                  0);
+
+        EXPECT_EQ(resp.completion_code, PLDM_SUCCESS);
+
+        transferFlag = resp.transfer_flag;
+        final_checksum = data_integrity_checksum;
+        uint32_t dataLengthBytes = resp.data.length;
+
+        const uint8_t* responseData = resp.data.ptr;
+        receivedData.insert(receivedData.end(), responseData,
+                            responseData + dataLengthBytes);
+
+        offset += dataLengthBytes;
+        transferHandle = offset; // Update transferHandle for next request
+        lengthToRead = 0;
+        offset = 0;
+    }
+
+    // Verify that all data has been read correctly
+    ASSERT_EQ(receivedData.size(), fileContent.size());
+    EXPECT_EQ(0, memcmp(receivedData.data(), fileContent.data(),
+                        fileContent.size()));
+    EXPECT_EQ(final_checksum,
+              pldm_edac_crc32(fileContent.data(), fileContent.size()));
+
+    struct pldm_base_multipart_receive_req read_req{};
+    read_req.pldm_type = PLDM_FILE;
+    read_req.transfer_opflag = PLDM_XFER_COMPLETE;
+    read_req.transfer_ctx = fileDescriptor;
+    size_t readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req, readRequest,
+                                                &readRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto readResponse = handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                                       PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    ASSERT_NE(readResponse.data(), nullptr);
+    auto readResponsePtr = reinterpret_cast<pldm_msg*>(readResponse.data());
+
+    uint32_t data_integrity_checksum;
+    pldm_base_multipart_receive_resp resp;
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  readResponsePtr, readResponse.size() - sizeof(pldm_msg_hdr),
+                  &resp, &data_integrity_checksum),
+              0);
+
+    EXPECT_EQ(resp.completion_code, PLDM_SUCCESS);
+    EXPECT_EQ(resp.transfer_flag, PLDM_ACKNOWLEDGE_COMPLETION);
+}
+
+// Open and read a file at a given offset
+TEST_F(FileTransferTests, DfReadWithOffset)
+{
+    uint16_t fileIdentifier = 3;
+    setupPDR(fileIdentifier, targetFilePath);
+
+    std::vector<uint8_t> openRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_OPEN_REQ_BYTES);
+    auto openRequest = reinterpret_cast<pldm_msg*>(openRequestMsg.data());
+    struct pldm_file_df_open_req req{};
+    req.file_identifier = fileIdentifier;
+    size_t openRequestPayloadLength = PLDM_DF_OPEN_REQ_BYTES;
+
+    int rc = encode_pldm_file_df_open_req(0, &req, openRequest,
+                                          &openRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto openResponse = handler.handle(0, PLDM_FILE_CMD_DF_OPEN, openRequest,
+                                       PLDM_DF_OPEN_REQ_BYTES);
+    ASSERT_NE(openResponse.data(), nullptr);
+
+    auto openResponsePtr = reinterpret_cast<pldm_msg*>(openResponse.data());
+    struct pldm_file_df_open_resp decodedOpenResp{};
+    decode_pldm_file_df_open_resp(openResponsePtr,
+                                  openResponse.size() - sizeof(pldm_msg_hdr),
+                                  &decodedOpenResp);
+    ASSERT_EQ(decodedOpenResp.completion_code, PLDM_SUCCESS);
+
+    uint16_t fileDescriptor = decodedOpenResp.file_descriptor;
+
+    uint32_t offset = 35;
+    uint32_t lengthToRead = 100;
+    std::vector<uint8_t> readRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    auto readRequest = reinterpret_cast<pldm_msg*>(readRequestMsg.data());
+    size_t readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    struct pldm_base_multipart_receive_req read_req{};
+    read_req.pldm_type = PLDM_FILE;
+    read_req.transfer_opflag = PLDM_XFER_FIRST_PART;
+    read_req.transfer_ctx = fileDescriptor;
+    read_req.transfer_handle = 0;
+    read_req.section_offset = offset;
+    read_req.section_length = lengthToRead;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req, readRequest,
+                                                &readRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto readResponse = handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                                       PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    ASSERT_NE(readResponse.data(), nullptr);
+    auto readResponsePtr = reinterpret_cast<pldm_msg*>(readResponse.data());
+
+    uint32_t data_integrity_checksum;
+    pldm_base_multipart_receive_resp resp;
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  readResponsePtr, readResponse.size() - sizeof(pldm_msg_hdr),
+                  &resp, &data_integrity_checksum),
+              0);
+
+    EXPECT_EQ(resp.completion_code, PLDM_SUCCESS);
+    EXPECT_EQ(resp.transfer_flag, PLDM_START_AND_END);
+    EXPECT_EQ(resp.data.length, lengthToRead);
+
+    const uint8_t* responseData = resp.data.ptr;
+    EXPECT_EQ(0,
+              memcmp(responseData, fileContent.data() + offset, lengthToRead));
+}
+
+// Open and read a file then restart read without closing the file
+TEST_F(FileTransferTests, DfReadRestart)
+{
+    uint16_t fileIdentifier = 4;
+    setupPDR(fileIdentifier, targetFilePath);
+
+    std::vector<uint8_t> openRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_OPEN_REQ_BYTES);
+    auto openRequest = reinterpret_cast<pldm_msg*>(openRequestMsg.data());
+    struct pldm_file_df_open_req req{};
+    req.file_identifier = fileIdentifier;
+    size_t openRequestPayloadLength = PLDM_DF_OPEN_REQ_BYTES;
+
+    int rc = encode_pldm_file_df_open_req(0, &req, openRequest,
+                                          &openRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto openResponse = handler.handle(0, PLDM_FILE_CMD_DF_OPEN, openRequest,
+                                       PLDM_DF_OPEN_REQ_BYTES);
+    ASSERT_NE(openResponse.data(), nullptr);
+
+    auto openResponsePtr = reinterpret_cast<pldm_msg*>(openResponse.data());
+    struct pldm_file_df_open_resp decodedOpenResp{};
+    decode_pldm_file_df_open_resp(openResponsePtr,
+                                  openResponse.size() - sizeof(pldm_msg_hdr),
+                                  &decodedOpenResp);
+    ASSERT_EQ(decodedOpenResp.completion_code, PLDM_SUCCESS);
+
+    uint16_t fileDescriptor = decodedOpenResp.file_descriptor;
+
+    uint32_t offset1 = 0;
+    uint32_t lengthToRead1 = 100;
+    std::vector<uint8_t> readRequestMsg1(
+        sizeof(pldm_msg_hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    auto readRequest1 = reinterpret_cast<pldm_msg*>(readRequestMsg1.data());
+    size_t readRequestPayloadLength1 = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    struct pldm_base_multipart_receive_req read_req1{};
+    read_req1.pldm_type = PLDM_FILE;
+    read_req1.transfer_opflag = PLDM_XFER_FIRST_PART;
+    read_req1.transfer_ctx = fileDescriptor;
+    read_req1.transfer_handle = 0;
+    read_req1.section_offset = offset1;
+    read_req1.section_length = lengthToRead1;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req1, readRequest1,
+                                                &readRequestPayloadLength1);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto readResponse1 = handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest1,
+                                        PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    ASSERT_NE(readResponse1.data(), nullptr);
+    auto readResponsePtr1 = reinterpret_cast<pldm_msg*>(readResponse1.data());
+
+    uint32_t data_integrity_checksum;
+    pldm_base_multipart_receive_resp resp;
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  readResponsePtr1, readResponse1.size() - sizeof(pldm_msg_hdr),
+                  &resp, &data_integrity_checksum),
+              0);
+
+    EXPECT_EQ(resp.completion_code, PLDM_SUCCESS);
+    EXPECT_EQ(resp.transfer_flag, PLDM_START_AND_END);
+    EXPECT_EQ(resp.data.length, lengthToRead1);
+
+    const uint8_t* responseData1 = resp.data.ptr;
+    EXPECT_EQ(0, memcmp(responseData1, fileContent.data(), lengthToRead1));
+    EXPECT_EQ(data_integrity_checksum,
+              pldm_edac_crc32(fileContent.data(), lengthToRead1));
+
+    uint32_t offset2 = 35;
+    uint32_t lengthToRead2 = 200;
+    std::vector<uint8_t> readRequestMsg2(
+        sizeof(pldm_msg_hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    auto readRequest2 = reinterpret_cast<pldm_msg*>(readRequestMsg2.data());
+    size_t readRequestPayloadLength2 = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    struct pldm_base_multipart_receive_req read_req2{};
+    read_req2.pldm_type = PLDM_FILE;
+    read_req2.transfer_opflag = PLDM_XFER_FIRST_PART;
+    read_req2.transfer_ctx = fileDescriptor;
+    read_req2.transfer_handle = 0;
+    read_req2.section_offset = offset2;
+    read_req2.section_length = lengthToRead2;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req2, readRequest2,
+                                                &readRequestPayloadLength2);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto readResponse2 = handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest2,
+                                        PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    ASSERT_NE(readResponse2.data(), nullptr);
+
+    auto readResponsePtr2 = reinterpret_cast<pldm_msg*>(readResponse2.data());
+
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  readResponsePtr2, readResponse2.size() - sizeof(pldm_msg_hdr),
+                  &resp, &data_integrity_checksum),
+              0);
+
+    EXPECT_EQ(resp.completion_code, PLDM_SUCCESS);
+    EXPECT_EQ(resp.transfer_flag, PLDM_START_AND_END);
+    EXPECT_EQ(resp.data.length, lengthToRead2);
+
+    const uint8_t* responseData2 = resp.data.ptr;
+    EXPECT_EQ(0, memcmp(responseData2, fileContent.data() + offset2,
+                        lengthToRead2));
+    EXPECT_EQ(data_integrity_checksum,
+              pldm_edac_crc32(fileContent.data() + offset2, lengthToRead2));
+}
+
+// Open and read a file then re request the current part
+TEST_F(FileTransferTests, DfReadCurrentPart)
+{
+    uint16_t fileIdentifier = 5;
+    setupPDR(fileIdentifier, targetFilePath);
+
+    std::vector<uint8_t> openRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_OPEN_REQ_BYTES);
+    auto openRequest = reinterpret_cast<pldm_msg*>(openRequestMsg.data());
+    struct pldm_file_df_open_req req{};
+    req.file_identifier = fileIdentifier;
+    size_t openRequestPayloadLength = PLDM_DF_OPEN_REQ_BYTES;
+
+    int rc = encode_pldm_file_df_open_req(0, &req, openRequest,
+                                          &openRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto openResponse = handler.handle(0, PLDM_FILE_CMD_DF_OPEN, openRequest,
+                                       PLDM_DF_OPEN_REQ_BYTES);
+    ASSERT_NE(openResponse.data(), nullptr);
+
+    auto openResponsePtr = reinterpret_cast<pldm_msg*>(openResponse.data());
+    struct pldm_file_df_open_resp decodedOpenResp{};
+    decode_pldm_file_df_open_resp(openResponsePtr,
+                                  openResponse.size() - sizeof(pldm_msg_hdr),
+                                  &decodedOpenResp);
+    ASSERT_EQ(decodedOpenResp.completion_code, PLDM_SUCCESS);
+
+    uint16_t fileDescriptor = decodedOpenResp.file_descriptor;
+
+    uint32_t offset = 10;
+    uint32_t lengthToRead = 380;
+    std::vector<uint8_t> readRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    auto readRequest = reinterpret_cast<pldm_msg*>(readRequestMsg.data());
+    size_t readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+
+    // Send the read request for the first part
+    struct pldm_base_multipart_receive_req read_req1{};
+    read_req1.pldm_type = PLDM_FILE;
+    read_req1.transfer_opflag = PLDM_XFER_FIRST_PART;
+    read_req1.transfer_ctx = fileDescriptor;
+    read_req1.transfer_handle = 0;
+    read_req1.section_offset = offset;
+    read_req1.section_length = lengthToRead;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req1, readRequest,
+                                                &readRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto readResponse1 = handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                                        PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    ASSERT_NE(readResponse1.data(), nullptr);
+    auto readResponsePtr1 = reinterpret_cast<pldm_msg*>(readResponse1.data());
+
+    uint32_t data_integrity_checksum;
+    pldm_base_multipart_receive_resp resp;
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  readResponsePtr1, readResponse1.size() - sizeof(pldm_msg_hdr),
+                  &resp, &data_integrity_checksum),
+              0);
+
+    EXPECT_EQ(resp.completion_code, PLDM_SUCCESS);
+
+    uint32_t request2TransferHandle = resp.next_transfer_handle;
+    uint32_t dataLengthBytes = resp.data.length;
+    ASSERT_EQ(dataLengthBytes, 239);
+    EXPECT_EQ(0, memcmp(readResponsePtr1->payload +
+                            PLDM_BASE_MULTIPART_RECEIVE_RESP_MIN_BYTES,
+                        fileContent.data() + offset, dataLengthBytes));
+    EXPECT_EQ(data_integrity_checksum,
+              pldm_edac_crc32(fileContent.data() + offset, dataLengthBytes));
+
+    // Send the read request for the next part
+    struct pldm_base_multipart_receive_req read_req2{};
+    read_req2.pldm_type = PLDM_FILE;
+    read_req2.transfer_opflag = PLDM_XFER_NEXT_PART;
+    read_req2.transfer_ctx = fileDescriptor;
+    read_req2.transfer_handle = request2TransferHandle;
+    readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req2, readRequest,
+                                                &readRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto readResponse2 = handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                                        PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    ASSERT_NE(readResponse2.data(), nullptr);
+    auto readResponsePtr2 = reinterpret_cast<pldm_msg*>(readResponse2.data());
+
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  readResponsePtr2, readResponse2.size() - sizeof(pldm_msg_hdr),
+                  &resp, &data_integrity_checksum),
+              0);
+
+    EXPECT_EQ(resp.completion_code, PLDM_SUCCESS);
+    EXPECT_EQ(resp.data.length, 141);
+    EXPECT_EQ(0, memcmp(readResponsePtr2->payload +
+                            PLDM_BASE_MULTIPART_RECEIVE_RESP_MIN_BYTES,
+                        fileContent.data() + offset + 239, resp.data.length));
+    // Verify the checksum over data in both responses.
+    EXPECT_EQ(data_integrity_checksum,
+              pldm_edac_crc32(fileContent.data() + offset, lengthToRead));
+
+    // Try requesting the current part
+    struct pldm_base_multipart_receive_req read_req3{};
+    read_req3.pldm_type = PLDM_FILE;
+    read_req3.transfer_opflag = PLDM_XFER_CURRENT_PART;
+    read_req3.transfer_ctx = fileDescriptor;
+    read_req3.transfer_handle = request2TransferHandle;
+    readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req3, readRequest,
+                                                &readRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto readResponse3 = handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                                        PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    ASSERT_NE(readResponse3.data(), nullptr);
+    auto readResponsePtr3 = reinterpret_cast<pldm_msg*>(readResponse3.data());
+
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  readResponsePtr3, readResponse3.size() - sizeof(pldm_msg_hdr),
+                  &resp, &data_integrity_checksum),
+              0);
+
+    EXPECT_EQ(resp.completion_code, PLDM_SUCCESS);
+    EXPECT_EQ(readResponse2.size(), readResponse3.size());
+    EXPECT_EQ(0, memcmp(readResponsePtr3->payload +
+                            PLDM_BASE_MULTIPART_RECEIVE_RESP_MIN_BYTES,
+                        fileContent.data() + offset + 239, 141));
+    EXPECT_EQ(0, memcmp(readResponse2.data(), readResponse3.data(),
+                        readResponse2.size()));
+}
+
+// Open and read the first part and then abort the read
+TEST_F(FileTransferTests, DfReadAbort)
+{
+    // Setup PDR and open the file
+    uint16_t fileIdentifier = 9;
+    setupPDR(fileIdentifier, targetFilePath);
+
+    std::vector<uint8_t> openRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_OPEN_REQ_BYTES);
+    auto openRequest = reinterpret_cast<pldm_msg*>(openRequestMsg.data());
+    struct pldm_file_df_open_req req{};
+    req.file_identifier = fileIdentifier;
+    size_t openRequestPayloadLength = PLDM_DF_OPEN_REQ_BYTES;
+    int rc = encode_pldm_file_df_open_req(0, &req, openRequest,
+                                          &openRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto openResponse = handler.handle(0, PLDM_FILE_CMD_DF_OPEN, openRequest,
+                                       PLDM_DF_OPEN_REQ_BYTES);
+    ASSERT_NE(openResponse.data(), nullptr);
+    auto openResponsePtr = reinterpret_cast<pldm_msg*>(openResponse.data());
+    struct pldm_file_df_open_resp decodedOpenResp{};
+    decode_pldm_file_df_open_resp(openResponsePtr,
+                                  openResponse.size() - sizeof(pldm_msg_hdr),
+                                  &decodedOpenResp);
+    ASSERT_EQ(decodedOpenResp.completion_code, PLDM_SUCCESS);
+    uint16_t fileDescriptor = decodedOpenResp.file_descriptor;
+
+    // Send a first read request to start a transfer
+    uint32_t offset = 0;
+    uint32_t lengthToRead = 100;
+    std::vector<uint8_t> readRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    auto readRequest = reinterpret_cast<pldm_msg*>(readRequestMsg.data());
+    size_t readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    struct pldm_base_multipart_receive_req read_req1{};
+    read_req1.pldm_type = PLDM_FILE;
+    read_req1.transfer_opflag = PLDM_XFER_FIRST_PART;
+    read_req1.transfer_ctx = fileDescriptor;
+    read_req1.transfer_handle = 0;
+    read_req1.section_offset = offset;
+    read_req1.section_length = lengthToRead;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req1, readRequest,
+                                                &readRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto firstReadResponse =
+        handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                       PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    ASSERT_NE(firstReadResponse.data(), nullptr);
+    auto firstReadResponsePtr =
+        reinterpret_cast<pldm_msg*>(firstReadResponse.data());
+    ASSERT_EQ(firstReadResponsePtr->payload[0], PLDM_SUCCESS);
+
+    // Send an abort request
+    struct pldm_base_multipart_receive_req read_req2{};
+    read_req2.pldm_type = PLDM_FILE;
+    read_req2.transfer_opflag = PLDM_XFER_ABORT;
+    read_req2.transfer_ctx = fileDescriptor;
+    readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req2, readRequest,
+                                                &readRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto abortResponse = handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                                        PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+
+    // Verify the abort response
+    ASSERT_NE(abortResponse.data(), nullptr);
+    auto abortResponsePtr = reinterpret_cast<pldm_msg*>(abortResponse.data());
+
+    uint32_t data_integrity_checksum;
+    pldm_base_multipart_receive_resp resp;
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  abortResponsePtr, abortResponse.size() - sizeof(pldm_msg_hdr),
+                  &resp, &data_integrity_checksum),
+              0);
+
+    EXPECT_EQ(resp.completion_code, PLDM_SUCCESS);
+    EXPECT_EQ(resp.transfer_flag, PLDM_ACKNOWLEDGE_COMPLETION);
+    EXPECT_EQ(resp.data.length, 0);
+}
+
+// Open the file and send a read request with an invalid transfer handle
+TEST_F(FileTransferTests, DfReadFirstPartInvalidTransferHandle)
+{
+    // Setup PDR and open the file
+    uint16_t fileIdentifier = 6;
+    setupPDR(fileIdentifier, targetFilePath);
+
+    std::vector<uint8_t> openRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_OPEN_REQ_BYTES);
+    auto openRequest = reinterpret_cast<pldm_msg*>(openRequestMsg.data());
+    struct pldm_file_df_open_req req{};
+    req.file_identifier = fileIdentifier;
+    size_t openRequestPayloadLength = PLDM_DF_OPEN_REQ_BYTES;
+    int rc = encode_pldm_file_df_open_req(0, &req, openRequest,
+                                          &openRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto openResponse = handler.handle(0, PLDM_FILE_CMD_DF_OPEN, openRequest,
+                                       PLDM_DF_OPEN_REQ_BYTES);
+    ASSERT_NE(openResponse.data(), nullptr);
+    auto openResponsePtr = reinterpret_cast<pldm_msg*>(openResponse.data());
+    struct pldm_file_df_open_resp decodedOpenResp{};
+    decode_pldm_file_df_open_resp(openResponsePtr,
+                                  openResponse.size() - sizeof(pldm_msg_hdr),
+                                  &decodedOpenResp);
+    ASSERT_EQ(decodedOpenResp.completion_code, PLDM_SUCCESS);
+    uint16_t fileDescriptor = decodedOpenResp.file_descriptor;
+
+    // Send dfRead with non-zero transfer handle for FIRST_PART
+    uint32_t offset = 0;
+    uint32_t lengthToRead = 64;
+    uint32_t invalidTransferHandle = 1; // Should be 0 for FIRST_PART
+    std::vector<uint8_t> readRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    auto readRequest = reinterpret_cast<pldm_msg*>(readRequestMsg.data());
+    size_t readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    struct pldm_base_multipart_receive_req read_req{};
+    read_req.pldm_type = PLDM_FILE;
+    read_req.transfer_opflag = PLDM_XFER_FIRST_PART;
+    read_req.transfer_ctx = fileDescriptor;
+    read_req.transfer_handle = invalidTransferHandle;
+    read_req.section_offset = offset;
+    read_req.section_length = lengthToRead;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req, readRequest,
+                                                &readRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto readResponse = handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                                       PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+
+    // Verify the error response
+    ASSERT_NE(readResponse.data(), nullptr);
+    auto readResponsePtr = reinterpret_cast<pldm_msg*>(readResponse.data());
+
+    uint32_t data_integrity_checksum;
+    pldm_base_multipart_receive_resp resp;
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  readResponsePtr, readResponse.size() - sizeof(pldm_msg_hdr),
+                  &resp, &data_integrity_checksum),
+              0);
+    EXPECT_EQ(resp.completion_code, PLDM_ERROR_INVALID_DATA);
+}
+
+// Open the file and send a read request with an invalid offset
+TEST_F(FileTransferTests, DfReadFirstPartInvalidOffset)
+{
+    // Setup PDR and open the file
+    uint16_t fileIdentifier = 7;
+    setupPDR(fileIdentifier, targetFilePath);
+
+    std::vector<uint8_t> openRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_OPEN_REQ_BYTES);
+    auto openRequest = reinterpret_cast<pldm_msg*>(openRequestMsg.data());
+    struct pldm_file_df_open_req req{};
+    req.file_identifier = fileIdentifier;
+    size_t openRequestPayloadLength = PLDM_DF_OPEN_REQ_BYTES;
+    int rc = encode_pldm_file_df_open_req(0, &req, openRequest,
+                                          &openRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto openResponse = handler.handle(0, PLDM_FILE_CMD_DF_OPEN, openRequest,
+                                       PLDM_DF_OPEN_REQ_BYTES);
+    ASSERT_NE(openResponse.data(), nullptr);
+    auto openResponsePtr = reinterpret_cast<pldm_msg*>(openResponse.data());
+    struct pldm_file_df_open_resp decodedOpenResp{};
+    decode_pldm_file_df_open_resp(openResponsePtr,
+                                  openResponse.size() - sizeof(pldm_msg_hdr),
+                                  &decodedOpenResp);
+    ASSERT_EQ(decodedOpenResp.completion_code, PLDM_SUCCESS);
+    uint16_t fileDescriptor = decodedOpenResp.file_descriptor;
+
+    // Send dfRead with offset >= fileSize
+    uint32_t invalidOffset = fileContent.size(); // 400
+    uint32_t lengthToRead = 1;
+    std::vector<uint8_t> readRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    auto readRequest = reinterpret_cast<pldm_msg*>(readRequestMsg.data());
+    size_t readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    struct pldm_base_multipart_receive_req read_req{};
+    read_req.pldm_type = PLDM_FILE;
+    read_req.transfer_opflag = PLDM_XFER_FIRST_PART;
+    read_req.transfer_ctx = fileDescriptor;
+    read_req.transfer_handle = 0;
+    read_req.section_offset = invalidOffset;
+    read_req.section_length = lengthToRead;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req, readRequest,
+                                                &readRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto readResponse = handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                                       PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+
+    // Verify the error response
+    ASSERT_NE(readResponse.data(), nullptr);
+    auto readResponsePtr = reinterpret_cast<pldm_msg*>(readResponse.data());
+
+    uint32_t data_integrity_checksum;
+    pldm_base_multipart_receive_resp resp;
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  readResponsePtr, readResponse.size() - sizeof(pldm_msg_hdr),
+                  &resp, &data_integrity_checksum),
+              0);
+    EXPECT_EQ(resp.completion_code, PLDM_ERROR_INVALID_DATA);
+}
+
+// Open the file and send a read request with an invalid read length
+TEST_F(FileTransferTests, DfReadFirstPartInvalidLength)
+{
+    // Setup PDR and open the file
+    uint16_t fileIdentifier = 8;
+    setupPDR(fileIdentifier, targetFilePath);
+
+    std::vector<uint8_t> openRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_OPEN_REQ_BYTES);
+    auto openRequest = reinterpret_cast<pldm_msg*>(openRequestMsg.data());
+    struct pldm_file_df_open_req req{};
+    req.file_identifier = fileIdentifier;
+    size_t openRequestPayloadLength = PLDM_DF_OPEN_REQ_BYTES;
+    int rc = encode_pldm_file_df_open_req(0, &req, openRequest,
+                                          &openRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto openResponse = handler.handle(0, PLDM_FILE_CMD_DF_OPEN, openRequest,
+                                       PLDM_DF_OPEN_REQ_BYTES);
+    ASSERT_NE(openResponse.data(), nullptr);
+    auto openResponsePtr = reinterpret_cast<pldm_msg*>(openResponse.data());
+    struct pldm_file_df_open_resp decodedOpenResp{};
+    decode_pldm_file_df_open_resp(openResponsePtr,
+                                  openResponse.size() - sizeof(pldm_msg_hdr),
+                                  &decodedOpenResp);
+    ASSERT_EQ(decodedOpenResp.completion_code, PLDM_SUCCESS);
+    uint16_t fileDescriptor = decodedOpenResp.file_descriptor;
+
+    // Send dfRead with offset + length > fileSize
+    uint32_t offset = 100;
+    uint32_t invalidLength = fileContent.size() - offset + 1; // 301
+    std::vector<uint8_t> readRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    auto readRequest = reinterpret_cast<pldm_msg*>(readRequestMsg.data());
+    size_t readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    struct pldm_base_multipart_receive_req read_req{};
+    read_req.pldm_type = PLDM_FILE;
+    read_req.transfer_opflag = PLDM_XFER_FIRST_PART;
+    read_req.transfer_ctx = fileDescriptor;
+    read_req.transfer_handle = 0;
+    read_req.section_offset = offset;
+    read_req.section_length = invalidLength;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req, readRequest,
+                                                &readRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto readResponse = handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                                       PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+
+    // Verify the error response
+    ASSERT_NE(readResponse.data(), nullptr);
+    auto readResponsePtr = reinterpret_cast<pldm_msg*>(readResponse.data());
+
+    uint32_t data_integrity_checksum;
+    pldm_base_multipart_receive_resp resp;
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  readResponsePtr, readResponse.size() - sizeof(pldm_msg_hdr),
+                  &resp, &data_integrity_checksum),
+              0);
+    EXPECT_EQ(resp.completion_code, PLDM_ERROR_INVALID_DATA);
+}
+
+// Open the file and read the first part. Then send a transfer handle less than
+// the start offset
+TEST_F(FileTransferTests, DfReadNextPartInvalidTransferHandleTooSmall)
+{
+    // Setup PDR and open the file
+    uint16_t fileIdentifier = 10;
+    setupPDR(fileIdentifier, targetFilePath);
+
+    // Encode the request
+    std::vector<uint8_t> openRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_OPEN_REQ_BYTES);
+    auto openRequest = reinterpret_cast<pldm_msg*>(openRequestMsg.data());
+    struct pldm_file_df_open_req req{};
+    req.file_identifier = fileIdentifier;
+    size_t openRequestPayloadLength = PLDM_DF_OPEN_REQ_BYTES;
+    int rc = encode_pldm_file_df_open_req(0, &req, openRequest,
+                                          &openRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto openResponse = handler.handle(0, PLDM_FILE_CMD_DF_OPEN, openRequest,
+                                       PLDM_DF_OPEN_REQ_BYTES);
+    ASSERT_NE(openResponse.data(), nullptr);
+    auto openResponsePtr = reinterpret_cast<pldm_msg*>(openResponse.data());
+    struct pldm_file_df_open_resp decodedOpenResp{};
+    decode_pldm_file_df_open_resp(openResponsePtr,
+                                  openResponse.size() - sizeof(pldm_msg_hdr),
+                                  &decodedOpenResp);
+    ASSERT_EQ(decodedOpenResp.completion_code, PLDM_SUCCESS);
+    uint16_t fileDescriptor = decodedOpenResp.file_descriptor;
+
+    // Send a first read request to establish starting_offset
+    uint32_t offset = 100;
+    uint32_t lengthToRead = 50;
+    std::vector<uint8_t> readRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    auto readRequest = reinterpret_cast<pldm_msg*>(readRequestMsg.data());
+    size_t readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    struct pldm_base_multipart_receive_req read_req1{};
+    read_req1.pldm_type = PLDM_FILE;
+    read_req1.transfer_opflag = PLDM_XFER_FIRST_PART;
+    read_req1.transfer_ctx = fileDescriptor;
+    read_req1.transfer_handle = 0;
+    read_req1.section_offset = offset;
+    read_req1.section_length = lengthToRead;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req1, readRequest,
+                                                &readRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto firstReadResponse =
+        handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                       PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    ASSERT_NE(firstReadResponse.data(), nullptr);
+    auto firstReadResponsePtr =
+        reinterpret_cast<pldm_msg*>(firstReadResponse.data());
+
+    uint32_t data_integrity_checksum;
+    pldm_base_multipart_receive_resp resp;
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  firstReadResponsePtr,
+                  firstReadResponse.size() - sizeof(pldm_msg_hdr), &resp,
+                  &data_integrity_checksum),
+              0);
+    EXPECT_EQ(resp.completion_code, PLDM_SUCCESS);
+
+    // Send a next part request with transfer_handle < starting_offset
+    uint32_t invalidTransferHandle = offset - 1;
+    struct pldm_base_multipart_receive_req read_req2{};
+    read_req2.pldm_type = PLDM_FILE;
+    read_req2.transfer_opflag = PLDM_XFER_NEXT_PART;
+    read_req2.transfer_ctx = fileDescriptor;
+    read_req2.transfer_handle = invalidTransferHandle;
+    readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req2, readRequest,
+                                                &readRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto errorResponse = handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                                        readRequestPayloadLength);
+
+    // Verify the error response
+    ASSERT_NE(errorResponse.data(), nullptr);
+    auto errorResponsePtr = reinterpret_cast<pldm_msg*>(errorResponse.data());
+
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  errorResponsePtr, errorResponse.size() - sizeof(pldm_msg_hdr),
+                  &resp, &data_integrity_checksum),
+              0);
+    EXPECT_EQ(resp.completion_code, PLDM_ERROR_INVALID_DATA);
+}
+
+// Open the file and read the first part. Then send a transfer handle greater
+// than the end offset
+TEST_F(FileTransferTests, DfReadNextPartInvalidTransferHandleTooLarge)
+{
+    // Setup PDR and open the file
+    uint16_t fileIdentifier = 11;
+    setupPDR(fileIdentifier, targetFilePath);
+
+    // Encode the request
+    std::vector<uint8_t> openRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_DF_OPEN_REQ_BYTES);
+    auto openRequest = reinterpret_cast<pldm_msg*>(openRequestMsg.data());
+    struct pldm_file_df_open_req req{};
+    req.file_identifier = fileIdentifier;
+    size_t openRequestPayloadLength = PLDM_DF_OPEN_REQ_BYTES;
+    int rc = encode_pldm_file_df_open_req(0, &req, openRequest,
+                                          &openRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+
+    // Send the request to the handler
+    auto openResponse = handler.handle(0, PLDM_FILE_CMD_DF_OPEN, openRequest,
+                                       PLDM_DF_OPEN_REQ_BYTES);
+
+    // Verify the response
+    ASSERT_NE(openResponse.data(), nullptr);
+    auto openResponsePtr = reinterpret_cast<pldm_msg*>(openResponse.data());
+    struct pldm_file_df_open_resp decodedOpenResp{};
+    decode_pldm_file_df_open_resp(openResponsePtr,
+                                  openResponse.size() - sizeof(pldm_msg_hdr),
+                                  &decodedOpenResp);
+    ASSERT_EQ(decodedOpenResp.completion_code, PLDM_SUCCESS);
+
+    // Send a first read request to establish starting_offset and section_length
+    uint16_t fileDescriptor = decodedOpenResp.file_descriptor;
+    uint32_t offset = 100;
+    uint32_t lengthToRead = 50;
+    std::vector<uint8_t> readRequestMsg(
+        sizeof(pldm_msg_hdr) + PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    auto readRequest = reinterpret_cast<pldm_msg*>(readRequestMsg.data());
+    size_t readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    struct pldm_base_multipart_receive_req read_req1{};
+    read_req1.pldm_type = PLDM_FILE;
+    read_req1.transfer_opflag = PLDM_XFER_FIRST_PART;
+    read_req1.transfer_ctx = fileDescriptor;
+    read_req1.transfer_handle = 0;
+    read_req1.section_offset = offset;
+    read_req1.section_length = lengthToRead;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req1, readRequest,
+                                                &readRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+    auto firstReadResponse =
+        handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                       PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+    ASSERT_NE(firstReadResponse.data(), nullptr);
+    auto firstReadResponsePtr =
+        reinterpret_cast<pldm_msg*>(firstReadResponse.data());
+
+    uint32_t data_integrity_checksum;
+    pldm_base_multipart_receive_resp resp;
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  firstReadResponsePtr,
+                  firstReadResponse.size() - sizeof(pldm_msg_hdr), &resp,
+                  &data_integrity_checksum),
+              0);
+    EXPECT_EQ(resp.completion_code, PLDM_SUCCESS);
+
+    // Send a next part request with transfer_handle >= starting_offset +
+    // section_length
+    uint32_t invalidTransferHandle = offset + lengthToRead;
+    struct pldm_base_multipart_receive_req read_req2{};
+    read_req2.pldm_type = PLDM_FILE;
+    read_req2.transfer_opflag = PLDM_XFER_NEXT_PART;
+    read_req2.transfer_ctx = fileDescriptor;
+    read_req2.transfer_handle = invalidTransferHandle;
+    readRequestPayloadLength = PLDM_MULTIPART_RECEIVE_REQ_BYTES;
+    rc = encode_pldm_base_multipart_receive_req(0, &read_req2, readRequest,
+                                                &readRequestPayloadLength);
+    ASSERT_EQ(rc, 0);
+    auto errorResponse = handler.handle(0, PLDM_FILE_CMD_DF_READ, readRequest,
+                                        PLDM_MULTIPART_RECEIVE_REQ_BYTES);
+
+    // Verify the error response
+    ASSERT_NE(errorResponse.data(), nullptr);
+    auto errorResponsePtr = reinterpret_cast<pldm_msg*>(errorResponse.data());
+
+    ASSERT_EQ(decode_pldm_base_multipart_receive_resp(
+                  errorResponsePtr, errorResponse.size() - sizeof(pldm_msg_hdr),
+                  &resp, &data_integrity_checksum),
+              0);
+    EXPECT_EQ(resp.completion_code, PLDM_ERROR_INVALID_DATA);
+}
diff --git a/libpldmresponder/test/meson.build b/libpldmresponder/test/meson.build
index 9cb5ca9..72f658b 100644
--- a/libpldmresponder/test/meson.build
+++ b/libpldmresponder/test/meson.build
@@ -6,6 +6,7 @@
     'libpldmresponder_bios_integer_attribute_test',
     'libpldmresponder_bios_string_attribute_test',
     'libpldmresponder_bios_table_test',
+    'libpldmresponder_file_transfer_test',
     'libpldmresponder_fru_test',
     'libpldmresponder_platform_test',
     'libpldmresponder_pdr_effecter_test',