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, §ionOffset, §ionLength); + 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',