blob: ab403cedad803267808b78bcea9c4e98e320ceab [file] [log] [blame]
#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 bytesRead = fread(dataPtr, 1, readSize, fp);
if (bytesRead != 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::base::Handler* baseHandler,
responder::platform::Handler* platformHandler) :
baseHandler(baseHandler), platformHandler(platformHandler),
nextFileDescriptor(0)
{
// Register command handlers. The "handlers" come from CmdHandler class.
handlers.emplace(
PLDM_FILE_CMD_DF_OPEN,
[this](pldm_tid_t tid, const pldm_msg* request, size_t payloadLength) {
return this->dfOpen(tid, request, payloadLength);
});
handlers.emplace(
PLDM_FILE_CMD_DF_CLOSE,
[this](pldm_tid_t tid, const pldm_msg* request, size_t payloadLength) {
return this->dfClose(tid, request, payloadLength);
});
}
std::optional<uint16_t> Handler::getFreeFileDescriptor()
{
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;
}
}
return this->nextFileDescriptor;
}
void Handler::removeFileDescriptor(uint16_t fileDescriptor)
{
fileDescriptors.erase(fileDescriptor);
}
Response Handler::dfOpen(pldm_tid_t tid, 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 fileIdentifier = decodedReq.file_identifier;
std::optional<uint16_t> fdOrRet = getFreeFileDescriptor();
if (!fdOrRet.has_value())
{
error(
"No free file descriptors available for tid:`{TID}` FileIdentifier: '{FILE_ID}'",
"TID", tid, "FILE_ID", fileIdentifier);
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(fileIdentifier, &fp, &fileSize);
if (ret != 0)
{
error("Requested FileIdentifier: '{FILE_ID}' not found in PDR",
"FILE_ID", fileIdentifier);
return CmdHandler::ccOnlyResponse(request,
PLDM_FILE_CC_INVALID_FILE_IDENTIFIER);
}
fileDescriptors.emplace(
fdOrRet.value(),
FileDescriptor{.fp = fp,
.tid = tid,
.index = fdOrRet.value(),
.fileSize = fileSize,
.startingOffset = 0,
.sectionLength = 0,
.prevChecksum = 0,
.fileIdentifier = fileIdentifier});
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);
error("Failed to encode DfOpen response. Err: '{ERROR}'", "ERROR", rc);
return CmdHandler::ccOnlyResponse(request, PLDM_ERROR);
}
info("DfOpen received. tid:{TID} fi:{FI} fd:{FD}", "TID", tid, "FI",
fileIdentifier, "FD", fdOrRet.value());
return response;
}
Response Handler::dfClose(pldm_tid_t tid, 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 fileDescriptor = decodedReq.file_descriptor;
auto it = fileDescriptors.find(fileDescriptor);
if (it == fileDescriptors.end())
{
error("Requested FileDescriptor: '{FILE_DES}' is not valid", "FILE_DES",
fileDescriptor);
return CmdHandler::ccOnlyResponse(request,
PLDM_FILE_CC_INVALID_FILE_DESCRIPTOR);
}
if (it->second.tid != tid)
{
error(
"Requested FileDescriptor: '{FILE_DES}' is not owned by TID: '{TID}'",
"FILE_DES", fileDescriptor, "TID", tid);
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)
{
error("Failed to encode DfClose response. Err: '{ERROR}'", "ERROR", rc);
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(fileDescriptor);
info("DfClose received. tid:{TID} fd:{FD}", "TID", tid, "FD",
fileDescriptor);
return response;
}
uint32_t Handler::getDataLengthBytesAndTransferFlag(
const FileDescriptor& fileDescriptor, uint32_t sentData,
uint16_t multipartSize, uint8_t* transferFlag)
{
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 currentOffset,
uint8_t transferFlag, uint32_t nextTransferHandle, uint32_t dataLengthBytes)
{
// dataLengthBytes shouldn't be 0 unless this is ACK response
if ((transferFlag != PLDM_ACKNOWLEDGE_COMPLETION) && (dataLengthBytes == 0))
{
error("dataLengthBytes 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,
currentOffset, 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);
}
if (transferFlag == PLDM_START)
{
info("Transferring DfRead first part. tid:{TID} fd:{FD}", "TID",
fileDescriptor.tid, "FD", fileDescriptor.index);
}
else if (transferFlag == PLDM_START_AND_END)
{
info("Transferring DfRead first and last part. tid:{TID} fd:{FD}",
"TID", fileDescriptor.tid, "FD", fileDescriptor.index);
}
else if (transferFlag == PLDM_END)
{
info("Transferring DfRead last part. tid:{TID} fd:{FD}", "TID",
fileDescriptor.tid, "FD", fileDescriptor.index);
}
fileDescriptor.prevChecksum = checksum;
return response;
}
Response Handler::dfReadTransferFirstPart(
const pldm_msg* request, const pldm_base_multipart_receive_req& decodedReq,
uint16_t multipartSize, 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 transferFlag;
uint32_t dataLengthBytes = getDataLengthBytesAndTransferFlag(
fileDescriptor, /*sentData=*/0, multipartSize, &transferFlag);
if (dataLengthBytes == 0)
{
// This can happen if the client is requesting data beyond initially
// negotiated values.
return CmdHandler::ccOnlyResponse(request, PLDM_ERROR_INVALID_DATA);
}
// If there are more future packets, then we need to derive a next transfer
// handle. (nextTransferHandle + fileDescriptor.starting_offset) will be
// used as the offset to the file.
uint32_t nextTransferHandle = 0;
if (transferFlag == PLDM_START)
{
nextTransferHandle = dataLengthBytes;
}
return createReadResponse(request, decodedReq, fileDescriptor,
fileDescriptor.startingOffset, transferFlag,
nextTransferHandle, dataLengthBytes);
}
Response Handler::dfReadTransferCurrentOrNextPart(
const pldm_msg* request, const pldm_base_multipart_receive_req& decodedReq,
uint16_t multipartSize, FileDescriptor& fileDescriptor)
{
// Sanity check for transfer_handle. It represent the already sent data
if (decodedReq.transfer_handle >= 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 transferFlag;
uint32_t dataLengthBytes = getDataLengthBytesAndTransferFlag(
fileDescriptor, decodedReq.transfer_handle, multipartSize,
&transferFlag);
if (dataLengthBytes == 0)
{
// This can happen if the client is requesting data beyond initially
// negotiated values.
return CmdHandler::ccOnlyResponse(request, PLDM_ERROR_INVALID_DATA);
}
// If there are more future packets, then we need to derive a next transfer
// handle. (nextTransferHandle + fileDescriptor.starting_offset) will be
// used as the offset to the file.
uint32_t nextTransferHandle = 0;
if ((transferFlag == PLDM_START) || (transferFlag == PLDM_MIDDLE))
{
nextTransferHandle = decodedReq.transfer_handle + dataLengthBytes;
}
// Derive the current offset we need to read
uint32_t currOffset =
fileDescriptor.startingOffset + decodedReq.transfer_handle;
return createReadResponse(request, decodedReq, fileDescriptor, currOffset,
transferFlag, nextTransferHandle,
dataLengthBytes);
}
Response Handler::dfReadTransferCompleteOrAbort(
const pldm_msg* request, const pldm_base_multipart_receive_req& decodedReq,
uint16_t multipartSize, 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, multipartSize,
fileDescriptor);
}
return createReadResponse(
request, decodedReq, fileDescriptor, /*currentOffset=*/0,
/*transferFlag*/ PLDM_ACKNOWLEDGE_COMPLETION,
/*nextTransferHandle*/ 0, /*dataLengthBytes*/ 0);
}
Response Handler::dfRead(pldm_tid_t tid, 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 fileDescriptor = transferCtx;
auto it = fileDescriptors.find(fileDescriptor);
if (it == fileDescriptors.end())
{
error("Requested FileDescriptor: '{FILE_DES}' is not valid", "FILE_DES",
fileDescriptor);
return CmdHandler::ccOnlyResponse(request,
PLDM_FILE_CC_INVALID_FILE_DESCRIPTOR);
}
if (it->second.tid != tid)
{
error(
"Requested FileDescriptor: '{FILE_DES}' is not owned by TID: '{TID}'",
"FILE_DES", fileDescriptor, "TID", tid);
return CmdHandler::ccOnlyResponse(request,
PLDM_FILE_CC_INVALID_FILE_DESCRIPTOR);
}
uint16_t multipartSize = baseHandler->getNegotiatedPartSize(tid, PLDM_FILE);
switch (transferOpflag)
{
case PLDM_XFER_FIRST_PART:
return dfReadTransferFirstPart(request, decodedReq, multipartSize,
it->second);
case PLDM_XFER_NEXT_PART:
return dfReadTransferCurrentOrNextPart(request, decodedReq,
multipartSize, it->second);
case PLDM_XFER_ABORT:
return dfReadTransferCompleteOrAbort(request, decodedReq,
multipartSize, it->second);
case PLDM_XFER_COMPLETE:
return dfReadTransferCompleteOrAbort(request, decodedReq,
multipartSize, it->second);
case PLDM_XFER_CURRENT_PART:
return dfReadTransferCurrentOrNextPart(request, decodedReq,
multipartSize, it->second);
default:
break;
}
return CmdHandler::ccOnlyResponse(request, PLDM_ERROR);
}
Response Handler::handleMultipartReceive(
pldm_tid_t tid, const pldm_msg* request, size_t reqMsgLen)
{
return dfRead(tid, request, reqMsgLen);
}
} // namespace file_transfer
} // namespace responder
} // namespace pldm