| #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, §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 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 |