#include "libpldmresponder/file_io.hpp" #include "libpldmresponder/file_table.hpp" #include #include #include #include "libpldm/base.h" #include "libpldm/file_io.h" #include #include #include #define SD_JOURNAL_SUPPRESS_LOCATION #include std::vector logs; extern "C" { int sd_journal_send(const char* format, ...) { logs.push_back(format); return 0; } int sd_journal_send_with_location(const char* file, const char* line, const char* func, const char* format, ...) { logs.push_back(format); return 0; } } namespace fs = std::filesystem; using Json = nlohmann::json; using namespace pldm::filetable; class TestFileTable : public testing::Test { public: void SetUp() override { // Create a temporary directory to hold the config file and files to // populate the file table. char tmppldm[] = "/tmp/pldm_fileio_table.XXXXXX"; dir = fs::path(mkdtemp(tmppldm)); // Copy the sample image files to the directory fs::copy("./files", dir); imageFile = dir / "NVRAM-IMAGE"; auto jsonObjects = Json::array(); auto obj = Json::object(); obj["path"] = imageFile.c_str(); obj["file_traits"] = 1; jsonObjects.push_back(obj); obj.clear(); cksumFile = dir / "NVRAM-IMAGE-CKSUM"; obj["path"] = cksumFile.c_str(); obj["file_traits"] = 4; jsonObjects.push_back(obj); fileTableConfig = dir / "configFile.json"; std::ofstream file(fileTableConfig.c_str()); file << std::setw(4) << jsonObjects << std::endl; } void TearDown() override { fs::remove_all(dir); } fs::path dir; fs::path imageFile; fs::path cksumFile; fs::path fileTableConfig; // <4 bytes - File handle - 0 (0x00 0x00 0x00 0x00)>, // <2 bytes - Filename length - 11 (0x0b 0x00> // <11 bytes - Filename - ASCII for NVRAM-IMAGE> // <4 bytes - File size - 1024 (0x00 0x04 0x00 0x00)> // <4 bytes - File traits - 1 (0x01 0x00 0x00 0x00)> // <4 bytes - File handle - 1 (0x01 0x00 0x00 0x00)>, // <2 bytes - Filename length - 17 (0x11 0x00> // <17 bytes - Filename - ASCII for NVRAM-IMAGE-CKSUM> // <4 bytes - File size - 16 (0x0f 0x00 0x00 0x00)> // <4 bytes - File traits - 4 (0x04 0x00 0x00 0x00)> // No pad bytes added since the length for both the file entries in the // table is 56, which is a multiple of 4. // <4 bytes - Checksum - 2088303182(0x4e 0xfa 0x78 0x7c)> Table attrTable = { 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x4e, 0x56, 0x52, 0x41, 0x4d, 0x2d, 0x49, 0x4d, 0x41, 0x47, 0x45, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x4e, 0x56, 0x52, 0x41, 0x4d, 0x2d, 0x49, 0x4d, 0x41, 0x47, 0x45, 0x2d, 0x43, 0x4b, 0x53, 0x55, 0x4d, 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x4e, 0xfa, 0x78, 0x7c}; }; namespace pldm { namespace responder { namespace dma { class MockDMA { public: MOCK_METHOD5(transferDataHost, int(const fs::path& file, uint32_t offset, uint32_t length, uint64_t address, bool upstream)); }; } // namespace dma } // namespace responder } // namespace pldm using namespace pldm::responder; using ::testing::_; using ::testing::Return; TEST(TransferDataHost, GoodPath) { using namespace pldm::responder::dma; MockDMA dmaObj; fs::path path(""); // Minimum length of 16 and expect transferDataHost to be called once // returns the default value of 0 (the return type of transferDataHost is // int, the default value for int is 0) uint32_t length = minSize; EXPECT_CALL(dmaObj, transferDataHost(path, 0, length, 0, true)).Times(1); auto response = transferAll(&dmaObj, PLDM_READ_FILE_INTO_MEMORY, path, 0, length, 0, true); auto responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_SUCCESS); ASSERT_EQ(0, memcmp(responsePtr->payload + sizeof(responsePtr->payload[0]), &length, sizeof(length))); // maxsize of DMA length = maxSize; EXPECT_CALL(dmaObj, transferDataHost(path, 0, length, 0, true)).Times(1); response = transferAll(&dmaObj, PLDM_READ_FILE_INTO_MEMORY, path, 0, length, 0, true); responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_SUCCESS); ASSERT_EQ(0, memcmp(responsePtr->payload + sizeof(responsePtr->payload[0]), &length, sizeof(length))); // length greater than maxsize of DMA length = maxSize + minSize; EXPECT_CALL(dmaObj, transferDataHost(path, 0, maxSize, 0, true)).Times(1); EXPECT_CALL(dmaObj, transferDataHost(path, maxSize, minSize, maxSize, true)) .Times(1); response = transferAll(&dmaObj, PLDM_READ_FILE_INTO_MEMORY, path, 0, length, 0, true); responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_SUCCESS); ASSERT_EQ(0, memcmp(responsePtr->payload + sizeof(responsePtr->payload[0]), &length, sizeof(length))); // length greater than 2*maxsize of DMA length = 3 * maxSize; EXPECT_CALL(dmaObj, transferDataHost(_, _, _, _, true)).Times(3); response = transferAll(&dmaObj, PLDM_READ_FILE_INTO_MEMORY, path, 0, length, 0, true); responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_SUCCESS); ASSERT_EQ(0, memcmp(responsePtr->payload + sizeof(responsePtr->payload[0]), &length, sizeof(length))); // check for downstream(copy data from host to BMC) parameter length = minSize; EXPECT_CALL(dmaObj, transferDataHost(path, 0, length, 0, false)).Times(1); response = transferAll(&dmaObj, PLDM_READ_FILE_INTO_MEMORY, path, 0, length, 0, false); responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_SUCCESS); ASSERT_EQ(0, memcmp(responsePtr->payload + sizeof(responsePtr->payload[0]), &length, sizeof(length))); } TEST(TransferDataHost, BadPath) { using namespace pldm::responder::dma; MockDMA dmaObj; fs::path path(""); // Minimum length of 16 and transferDataHost returning a negative errno uint32_t length = minSize; EXPECT_CALL(dmaObj, transferDataHost(_, _, _, _, _)).WillOnce(Return(-1)); auto response = transferAll(&dmaObj, PLDM_READ_FILE_INTO_MEMORY, path, 0, length, 0, true); auto responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_ERROR); // length greater than maxsize of DMA and transferDataHost returning a // negative errno length = maxSize + minSize; EXPECT_CALL(dmaObj, transferDataHost(_, _, _, _, _)).WillOnce(Return(-1)); response = transferAll(&dmaObj, PLDM_READ_FILE_INTO_MEMORY, path, 0, length, 0, true); responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_ERROR); } TEST(ReadFileIntoMemory, BadPath) { uint32_t fileHandle = 0; uint32_t offset = 0; uint32_t length = 10; uint64_t address = 0; std::array requestMsg{}; auto request = reinterpret_cast(requestMsg.data()); memcpy(request->payload, &fileHandle, sizeof(fileHandle)); memcpy(request->payload + sizeof(fileHandle), &offset, sizeof(offset)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset), &length, sizeof(length)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset) + sizeof(length), &address, sizeof(address)); // Pass invalid payload length auto response = readFileIntoMemory(request, 0); auto responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_ERROR_INVALID_LENGTH); } TEST_F(TestFileTable, ReadFileInvalidFileHandle) { // Invalid file handle in the file table uint32_t fileHandle = 2; uint32_t offset = 0; uint32_t length = 0; uint64_t address = 0; std::array requestMsg{}; auto request = reinterpret_cast(requestMsg.data()); size_t requestPayloadLength = requestMsg.size() - sizeof(pldm_msg_hdr); memcpy(request->payload, &fileHandle, sizeof(fileHandle)); memcpy(request->payload + sizeof(fileHandle), &offset, sizeof(offset)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset), &length, sizeof(length)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset) + sizeof(length), &address, sizeof(address)); using namespace pldm::filetable; // Initialise the file table with 2 valid file handles 0 & 1. auto& table = buildFileTable(fileTableConfig.c_str()); auto response = readFileIntoMemory(request, requestPayloadLength); auto responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_INVALID_FILE_HANDLE); // Clear the file table contents. table.clear(); } TEST_F(TestFileTable, ReadFileInvalidOffset) { uint32_t fileHandle = 0; // The file size is 1024, so the offset is invalid uint32_t offset = 1024; uint32_t length = 0; uint64_t address = 0; std::array requestMsg{}; auto request = reinterpret_cast(requestMsg.data()); size_t requestPayloadLength = requestMsg.size() - sizeof(pldm_msg_hdr); memcpy(request->payload, &fileHandle, sizeof(fileHandle)); memcpy(request->payload + sizeof(fileHandle), &offset, sizeof(offset)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset), &length, sizeof(length)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset) + sizeof(length), &address, sizeof(address)); using namespace pldm::filetable; auto& table = buildFileTable(fileTableConfig.c_str()); auto response = readFileIntoMemory(request, requestPayloadLength); auto responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_DATA_OUT_OF_RANGE); // Clear the file table contents. table.clear(); } TEST_F(TestFileTable, ReadFileInvalidLength) { uint32_t fileHandle = 0; uint32_t offset = 100; // Length should be a multiple of dma min size(16) uint32_t length = 10; uint64_t address = 0; std::array requestMsg{}; auto request = reinterpret_cast(requestMsg.data()); size_t requestPayloadLength = requestMsg.size() - sizeof(pldm_msg_hdr); memcpy(request->payload, &fileHandle, sizeof(fileHandle)); memcpy(request->payload + sizeof(fileHandle), &offset, sizeof(offset)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset), &length, sizeof(length)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset) + sizeof(length), &address, sizeof(address)); using namespace pldm::filetable; auto& table = buildFileTable(fileTableConfig.c_str()); auto response = readFileIntoMemory(request, requestPayloadLength); auto responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_INVALID_READ_LENGTH); // Clear the file table contents. table.clear(); } TEST_F(TestFileTable, ReadFileInvalidEffectiveLength) { uint32_t fileHandle = 0; // valid offset uint32_t offset = 100; // length + offset exceeds the size, so effective length is // filesize(1024) - offset(100). The effective length is not a multiple of // DMA min size(16) uint32_t length = 1024; uint64_t address = 0; std::array requestMsg{}; auto request = reinterpret_cast(requestMsg.data()); size_t requestPayloadLength = requestMsg.size() - sizeof(pldm_msg_hdr); memcpy(request->payload, &fileHandle, sizeof(fileHandle)); memcpy(request->payload + sizeof(fileHandle), &offset, sizeof(offset)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset), &length, sizeof(length)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset) + sizeof(length), &address, sizeof(address)); using namespace pldm::filetable; auto& table = buildFileTable(fileTableConfig.c_str()); auto response = readFileIntoMemory(request, requestPayloadLength); auto responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_INVALID_READ_LENGTH); // Clear the file table contents. table.clear(); } TEST(WriteFileFromMemory, BadPath) { uint32_t fileHandle = 0; uint32_t offset = 0; uint32_t length = 10; uint64_t address = 0; std::array requestMsg{}; auto request = reinterpret_cast(requestMsg.data()); size_t requestPayloadLength = requestMsg.size() - sizeof(pldm_msg_hdr); memcpy(request->payload, &fileHandle, sizeof(fileHandle)); memcpy(request->payload + sizeof(fileHandle), &offset, sizeof(offset)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset), &length, sizeof(length)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset) + sizeof(length), &address, sizeof(address)); // Pass invalid payload length auto response = writeFileFromMemory(request, 0); auto responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_ERROR_INVALID_LENGTH); // The length field is not a multiple of DMA minsize response = writeFileFromMemory(request, requestPayloadLength); responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_INVALID_WRITE_LENGTH); } TEST_F(TestFileTable, WriteFileInvalidFileHandle) { // Invalid file handle in the file table uint32_t fileHandle = 2; uint32_t offset = 0; uint32_t length = 16; uint64_t address = 0; std::array requestMsg{}; auto request = reinterpret_cast(requestMsg.data()); size_t requestPayloadLength = requestMsg.size() - sizeof(pldm_msg_hdr); memcpy(request->payload, &fileHandle, sizeof(fileHandle)); memcpy(request->payload + sizeof(fileHandle), &offset, sizeof(offset)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset), &length, sizeof(length)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset) + sizeof(length), &address, sizeof(address)); using namespace pldm::filetable; // Initialise the file table with 2 valid file handles 0 & 1. auto& table = buildFileTable(fileTableConfig.c_str()); auto response = writeFileFromMemory(request, requestPayloadLength); auto responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_INVALID_FILE_HANDLE); // Clear the file table contents. table.clear(); } TEST_F(TestFileTable, WriteFileInvalidOffset) { uint32_t fileHandle = 0; // The file size is 1024, so the offset is invalid uint32_t offset = 1024; uint32_t length = 16; uint64_t address = 0; std::array requestMsg{}; auto request = reinterpret_cast(requestMsg.data()); size_t requestPayloadLength = requestMsg.size() - sizeof(pldm_msg_hdr); memcpy(request->payload, &fileHandle, sizeof(fileHandle)); memcpy(request->payload + sizeof(fileHandle), &offset, sizeof(offset)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset), &length, sizeof(length)); memcpy(request->payload + sizeof(fileHandle) + sizeof(offset) + sizeof(length), &address, sizeof(address)); using namespace pldm::filetable; // Initialise the file table with 2 valid file handles 0 & 1. auto& table = buildFileTable(TestFileTable::fileTableConfig.c_str()); auto response = writeFileFromMemory(request, requestPayloadLength); auto responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_DATA_OUT_OF_RANGE); // Clear the file table contents. table.clear(); } TEST(FileTable, ConfigNotExist) { logs.clear(); FileTable tableObj(""); EXPECT_EQ(logs.size(), 1); } TEST_F(TestFileTable, ValidateFileEntry) { FileTable tableObj(fileTableConfig.c_str()); // Test file handle 0, the file size is 1K bytes. auto value = tableObj.at(0); ASSERT_EQ(value.handle, 0); ASSERT_EQ(strcmp(value.fsPath.c_str(), imageFile.c_str()), 0); ASSERT_EQ(static_cast(fs::file_size(value.fsPath)), 1024); ASSERT_EQ(value.traits.value, 1); ASSERT_EQ(true, fs::exists(value.fsPath)); // Test file handle 1, the file size is 16 bytes auto value1 = tableObj.at(1); ASSERT_EQ(value1.handle, 1); ASSERT_EQ(strcmp(value1.fsPath.c_str(), cksumFile.c_str()), 0); ASSERT_EQ(static_cast(fs::file_size(value1.fsPath)), 16); ASSERT_EQ(value1.traits.value, 4); ASSERT_EQ(true, fs::exists(value1.fsPath)); // Test invalid file handle ASSERT_THROW(tableObj.at(2), std::out_of_range); } TEST_F(TestFileTable, ValidateFileTable) { FileTable tableObj(fileTableConfig.c_str()); // Validate file attribute table auto table = tableObj(); ASSERT_EQ(true, std::equal(attrTable.begin(), attrTable.end(), table.begin())); } TEST_F(TestFileTable, GetFileTableCommand) { // Initialise the file table with a valid handle of 0 & 1 auto& table = buildFileTable(fileTableConfig.c_str()); uint32_t transferHandle = 0; uint8_t opFlag = 0; uint8_t type = PLDM_FILE_ATTRIBUTE_TABLE; uint32_t nextTransferHandle = 0; uint8_t transferFlag = PLDM_START_AND_END; std::array requestMsg{}; auto requestMsgPtr = reinterpret_cast(requestMsg.data()); size_t requestPayloadLength = requestMsg.size() - sizeof(pldm_msg_hdr); auto request = reinterpret_cast( requestMsg.data() + sizeof(pldm_msg_hdr)); request->transfer_handle = transferHandle; request->operation_flag = opFlag; request->table_type = type; auto response = getFileTable(requestMsgPtr, requestPayloadLength); auto responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_SUCCESS); size_t offsetSize = sizeof(responsePtr->payload[0]); ASSERT_EQ(0, memcmp(responsePtr->payload + offsetSize, &nextTransferHandle, sizeof(nextTransferHandle))); offsetSize += sizeof(nextTransferHandle); ASSERT_EQ(0, memcmp(responsePtr->payload + offsetSize, &transferFlag, sizeof(transferFlag))); offsetSize += sizeof(transferFlag); ASSERT_EQ(0, memcmp(responsePtr->payload + offsetSize, attrTable.data(), attrTable.size())); table.clear(); } TEST_F(TestFileTable, GetFileTableCommandReqLengthMismatch) { std::array requestMsg{}; auto request = reinterpret_cast(requestMsg.data()); // Pass invalid command payload length auto response = getFileTable(request, 0); auto responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_ERROR_INVALID_LENGTH); } TEST_F(TestFileTable, GetFileTableCommandOEMAttrTable) { uint32_t transferHandle = 0; uint8_t opFlag = 0; uint8_t type = PLDM_OEM_FILE_ATTRIBUTE_TABLE; std::array requestMsg{}; auto requestMsgPtr = reinterpret_cast(requestMsg.data()); size_t requestPayloadLength = requestMsg.size() - sizeof(pldm_msg_hdr); auto request = reinterpret_cast( requestMsg.data() + sizeof(pldm_msg_hdr)); request->transfer_handle = transferHandle; request->operation_flag = opFlag; request->table_type = type; auto response = getFileTable(requestMsgPtr, requestPayloadLength); auto responsePtr = reinterpret_cast(response.data()); ASSERT_EQ(responsePtr->payload[0], PLDM_INVALID_FILE_TABLE_TYPE); }