| #include "nvme_features.pb.h" |
| |
| #include "NVMeError.hpp" |
| #include "NVMeFeatureStore.hpp" |
| #include "NVMeMiFake.hpp" |
| |
| #include <boost/asio/spawn.hpp> |
| #include <sdbusplus/asio/connection.hpp> |
| #include <sdbusplus/asio/object_server.hpp> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| class NVMeMiMock : |
| public NVMeMiIntf, |
| public std::enable_shared_from_this<NVMeMiMock> |
| { |
| public: |
| ~NVMeMiMock() override = default; |
| MOCK_METHOD(void, start, (const std::shared_ptr<MctpEndpoint>&), |
| (override)); |
| MOCK_METHOD(void, stop, (), (override)); |
| MOCK_METHOD(void, recover, (), (override)); |
| MOCK_METHOD(void, miSubsystemHealthStatusPoll, |
| (std::function<void(const std::error_code&, |
| nvme_mi_nvm_ss_health_status*)>&&), |
| (override)); |
| MOCK_METHOD(void, miScanCtrl, |
| (std::function<void(const std::error_code&, |
| const std::vector<nvme_mi_ctrl_t>&)>), |
| (override)); |
| MOCK_METHOD(bool, flushOperations, (std::function<void()>&&), (override)); |
| MOCK_METHOD(void, adminIdentify, |
| (nvme_mi_ctrl_t ctrl, nvme_identify_cns cns, uint32_t nsid, |
| uint16_t cntid, |
| std::function<void(nvme_ex_ptr, std::span<uint8_t>)>&& cb), |
| (override)); |
| MOCK_METHOD(void, adminGetFeatures, |
| (nvme_mi_ctrl_t ctrl, const NVMeMiIntf::GetFeaturesRequest& req, |
| std::function<void(nvme_ex_ptr, uint32_t response, |
| std::span<uint8_t>)>&& cb), |
| (override)); |
| MOCK_METHOD(void, adminSetFeatures, |
| (nvme_mi_ctrl_t ctrl, const NVMeMiIntf::SetFeaturesRequest& req, |
| std::function<void(nvme_ex_ptr, uint32_t response)>&& cb), |
| (override)); |
| MOCK_METHOD( |
| void, adminGetLogPage, |
| (nvme_mi_ctrl_t ctrl, nvme_cmd_get_log_lid lid, uint32_t nsid, |
| uint8_t lsp, uint16_t lsi, |
| std::function<void(const std::error_code&, std::span<uint8_t>)>&& cb), |
| (override)); |
| MOCK_METHOD( |
| void, adminFwCommit, |
| (nvme_mi_ctrl_t ctrl, nvme_fw_commit_ca action, uint8_t slot, bool bpid, |
| std::function<void(const std::error_code&, nvme_status_field)>&& cb), |
| (override)); |
| MOCK_METHOD( |
| void, adminFwDownload, |
| (nvme_mi_ctrl_t ctrl, std::string firmwarefile, |
| std::function<void(const std::error_code&, nvme_status_field)>&& cb), |
| (override)); |
| MOCK_METHOD(void, adminXfer, |
| (nvme_mi_ctrl_t ctrl, const nvme_mi_admin_req_hdr& adminReq, |
| std::span<uint8_t> data, unsigned int timeoutMs, |
| std::function<void(const std::error_code& ec, |
| const nvme_mi_admin_resp_hdr& adminResp, |
| std::span<uint8_t> respData)>&& cb), |
| (override)); |
| MOCK_METHOD(void, adminSecuritySend, |
| (nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t specific, |
| std::span<uint8_t> data, |
| std::function<void(const std::error_code&, int)>&& cb), |
| (override)); |
| MOCK_METHOD(void, adminSecurityReceive, |
| (nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t specific, |
| uint32_t al, |
| std::function<void(const std::error_code&, int, |
| const std::span<uint8_t>)>&& cb), |
| (override)); |
| MOCK_METHOD( |
| void, adminNonDataCmd, |
| (nvme_mi_ctrl_t ctrl, uint8_t opcode, uint32_t cdw1, uint32_t cdw2, |
| uint32_t cdw3, uint32_t cdw10, uint32_t cdw11, uint32_t cdw12, |
| uint32_t cdw13, uint32_t cdw14, uint32_t cdw15, |
| std::function<void(const std::error_code&, int, uint32_t)>&& cb), |
| (override)); |
| MOCK_METHOD(void, createNamespace, |
| (nvme_mi_ctrl_t ctrl, uint64_t size, size_t lbaFormat, |
| bool metadataAtEnd, |
| std::function<void(nvme_ex_ptr)>&& submittedCb, |
| std::function<void(nvme_ex_ptr, NVMeNSIdentify)>&& finishedCb), |
| (override)); |
| MOCK_METHOD(void, adminDeleteNamespace, |
| (nvme_mi_ctrl_t ctrl, uint32_t nsid, |
| std::function<void(const std::error_code&, int)>&& cb), |
| (override)); |
| MOCK_METHOD(void, adminListNamespaces, |
| (nvme_mi_ctrl_t ctrl, |
| std::function<void(nvme_ex_ptr, std::vector<uint32_t>)>&& cb), |
| (override)); |
| MOCK_METHOD(void, adminAttachDetachNamespace, |
| (nvme_mi_ctrl_t ctrl, uint16_t ctrlid, uint32_t nsid, |
| bool attach, |
| std::function<void(const std::error_code&, int)>&& cb), |
| (override)); |
| MOCK_METHOD(void, adminSanitize, |
| (nvme_mi_ctrl_t ctrl, enum nvme_sanitize_sanact sanact, |
| uint8_t passes, uint32_t pattern, bool invertPattern, |
| std::function<void(nvme_ex_ptr)>&& cb), |
| (override)); |
| MOCK_METHOD( |
| void, adminLockdownInband, |
| (nvme_mi_ctrl_t ctrl, uint8_t prohibit, |
| const std::vector<uint8_t>& adminCmds, |
| const std::vector<uint8_t>& features, |
| const std::vector<uint8_t>& logPages, |
| std::function< |
| void(const std::error_code&, int nvmeStatus, uint32_t comptionDw0, |
| const std::string& failureScope, uint32_t failureId)>&& cb), |
| (override)); |
| }; |
| |
| class FeatureStoreTest : public ::testing::Test |
| { |
| public: |
| static std::vector<uint8_t> |
| callGetFeature(const std::shared_ptr<FeatureStore>& fs, |
| boost::asio::yield_context yield, |
| const std::string& featureName) |
| { |
| return fs->getFeature(std::move(yield), featureName); |
| } |
| |
| static void callSetFeature(const std::shared_ptr<FeatureStore>& fs, |
| boost::asio::yield_context yield, |
| const std::string& featureName, |
| const std::vector<uint8_t>& data) |
| { |
| fs->setFeature(std::move(yield), featureName, data); |
| } |
| |
| static std::map<uint8_t, uint32_t>& |
| getFeatureCaps(const std::shared_ptr<FeatureStore>& fs) |
| { |
| return fs->featureCaps; |
| } |
| }; |
| |
| TEST_F(FeatureStoreTest, GetFeatureArbitration) |
| { |
| boost::asio::io_context io; |
| auto conn = std::make_shared<sdbusplus::asio::connection>(io); |
| sdbusplus::asio::object_server objServer(conn); |
| std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0"; |
| |
| auto mockNvme = std::make_shared<NVMeMiMock>(); |
| nvme_mi_ctrl_t ctrl = nullptr; |
| |
| auto featureStore = std::make_shared<FeatureStore>( |
| io, objServer, conn, objectPath, mockNvme, ctrl, 0, |
| FeatureScope::Controller); |
| |
| EXPECT_CALL(*mockNvme, |
| adminGetFeatures(::testing::_, ::testing::_, ::testing::_)) |
| .WillOnce( |
| [](nvme_mi_ctrl_t, const NVMeMiIntf::GetFeaturesRequest& req, |
| std::function<void(nvme_ex_ptr, uint32_t, std::span<uint8_t>)>&& |
| cb) { |
| EXPECT_EQ(req.fid, 0x01); // Arbitration |
| cb(nullptr, 0x01020304, {}); // burst=4, low=3, med=2, high=1 |
| }); |
| |
| std::vector<uint8_t> result; |
| bool done = false; |
| boost::asio::spawn(io, [&](boost::asio::yield_context yield) { |
| result = FeatureStoreTest::callGetFeature( |
| featureStore, std::move(yield), "Arbitration_Current"); |
| done = true; |
| }); |
| while (!done) |
| { |
| io.run_one(); |
| } |
| EXPECT_TRUE(done); |
| |
| google::gbmc::nvme::base::Arbitration msg; |
| ASSERT_TRUE( |
| msg.ParseFromArray(result.data(), static_cast<int>(result.size()))); |
| EXPECT_EQ(msg.arbitration_burst(), 0x04); |
| EXPECT_EQ(msg.low_priority_weight(), 0x03); |
| EXPECT_EQ(msg.medium_priority_weight(), 0x02); |
| EXPECT_EQ(msg.high_priority_weight(), 0x01); |
| } |
| |
| TEST_F(FeatureStoreTest, SetFeatureArbitration) |
| { |
| boost::asio::io_context io; |
| auto conn = std::make_shared<sdbusplus::asio::connection>(io); |
| sdbusplus::asio::object_server objServer(conn); |
| std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0"; |
| |
| auto mockNvme = std::make_shared<NVMeMiMock>(); |
| nvme_mi_ctrl_t ctrl = nullptr; |
| |
| auto featureStore = std::make_shared<FeatureStore>( |
| io, objServer, conn, objectPath, mockNvme, ctrl, 0, |
| FeatureScope::Controller); |
| |
| google::gbmc::nvme::base::Arbitration msg; |
| msg.set_arbitration_burst(0x08); |
| msg.set_low_priority_weight(0x07); |
| msg.set_medium_priority_weight(0x06); |
| msg.set_high_priority_weight(0x05); |
| |
| std::vector<uint8_t> data(msg.ByteSizeLong()); |
| ASSERT_TRUE( |
| msg.SerializeToArray(data.data(), static_cast<int>(data.size()))); |
| |
| EXPECT_CALL(*mockNvme, |
| adminSetFeatures(::testing::_, ::testing::_, ::testing::_)) |
| .WillOnce([](nvme_mi_ctrl_t, const NVMeMiIntf::SetFeaturesRequest& req, |
| std::function<void(nvme_ex_ptr, uint32_t)>&& cb) { |
| EXPECT_EQ(req.fid, 0x01); // Arbitration |
| EXPECT_EQ(req.cdw11, 0x05060708); |
| cb(nullptr, 0); |
| }); |
| |
| bool done = false; |
| boost::asio::spawn(io, [&](boost::asio::yield_context yield) { |
| FeatureStoreTest::callSetFeature(featureStore, std::move(yield), |
| "Arbitration_Current", data); |
| done = true; |
| }); |
| while (!done) |
| { |
| io.run_one(); |
| } |
| EXPECT_TRUE(done); |
| } |
| |
| TEST_F(FeatureStoreTest, DiscoverFeaturesONCS) |
| { |
| boost::asio::io_context io; |
| auto conn = std::make_shared<sdbusplus::asio::connection>(io); |
| sdbusplus::asio::object_server objServer(conn); |
| std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0"; |
| |
| auto mockNvme = std::make_shared<NVMeMiMock>(); |
| nvme_mi_ctrl_t ctrl = nullptr; |
| |
| auto featureStore = std::make_shared<FeatureStore>( |
| io, objServer, conn, objectPath, mockNvme, ctrl, 0, |
| FeatureScope::Controller); |
| |
| // Mock Identify Controller to return ONCS with bit 4 set |
| EXPECT_CALL(*mockNvme, |
| adminIdentify(::testing::_, NVME_IDENTIFY_CNS_CTRL, |
| ::testing::_, ::testing::_, ::testing::_)) |
| .WillOnce( |
| [](nvme_mi_ctrl_t, nvme_identify_cns, uint32_t, uint16_t, |
| std::function<void(nvme_ex_ptr, std::span<uint8_t>)>&& cb) { |
| nvme_id_ctrl id{}; |
| id.vwc = 1; // VWC supported |
| id.oncs = htole16(1 << 4); // Save and Select supported |
| std::vector<uint8_t> data(sizeof(id)); |
| memcpy(data.data(), &id, sizeof(id)); |
| cb(nullptr, data); |
| }); |
| |
| // Mock Get Features for capabilities of Arbitration (FID 0x01) |
| // Return Saveable (bit 2) and Changeable (bit 0) |
| EXPECT_CALL( |
| *mockNvme, |
| adminGetFeatures(::testing::_, |
| ::testing::Field(&NVMeMiIntf::GetFeaturesRequest::sel, |
| NVME_GET_FEATURES_SEL_SUPPORTED), |
| ::testing::_)) |
| .WillRepeatedly( |
| [](nvme_mi_ctrl_t, const NVMeMiIntf::GetFeaturesRequest& req, |
| std::function<void(nvme_ex_ptr, uint32_t, std::span<uint8_t>)>&& |
| cb) { |
| if (req.fid == 0x01) |
| { |
| cb(nullptr, 0x5, {}); // Changeable | Saveable |
| } |
| else |
| { |
| cb(nullptr, 0x4, {}); // Just Changeable |
| } |
| }); |
| |
| bool done = false; |
| featureStore->init([&done](const std::error_code& ec) { |
| EXPECT_FALSE(ec); |
| done = true; |
| }); |
| |
| while (!done) |
| { |
| io.run_one(); |
| } |
| |
| auto caps = FeatureStoreTest::getFeatureCaps(featureStore); |
| |
| // Check for Arbitration states (FID 0x01) |
| auto it = caps.find(0x01); |
| ASSERT_TRUE(it != caps.end()); |
| uint32_t cap = it->second; |
| |
| EXPECT_TRUE(cap & 0x1); // Saveable |
| EXPECT_TRUE(cap & 0x4); // Changeable |
| } |
| |
| TEST_F(FeatureStoreTest, GetFeatureInvalidName) |
| { |
| boost::asio::io_context io; |
| auto conn = std::make_shared<sdbusplus::asio::connection>(io); |
| sdbusplus::asio::object_server objServer(conn); |
| std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0"; |
| |
| auto mockNvme = std::make_shared<NVMeMiMock>(); |
| nvme_mi_ctrl_t ctrl = nullptr; |
| |
| auto featureStore = std::make_shared<FeatureStore>( |
| io, objServer, conn, objectPath, mockNvme, ctrl, 0, |
| FeatureScope::Controller); |
| |
| bool threw = false; |
| bool done = false; |
| boost::asio::spawn(io, [&](boost::asio::yield_context yield) { |
| try |
| { |
| FeatureStoreTest::callGetFeature(featureStore, std::move(yield), |
| "InvalidFeature_Current"); |
| } |
| catch (const sdbusplus::xyz::openbmc_project::Common::Error:: |
| InvalidArgument&) |
| { |
| threw = true; |
| } |
| done = true; |
| }); |
| while (!done) |
| { |
| io.run_one(); |
| } |
| EXPECT_TRUE(threw); |
| } |
| |
| TEST_F(FeatureStoreTest, GetFeatureDbusError) |
| { |
| boost::asio::io_context io; |
| auto conn = std::make_shared<sdbusplus::asio::connection>(io); |
| sdbusplus::asio::object_server objServer(conn); |
| std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0"; |
| |
| auto mockNvme = std::make_shared<NVMeMiMock>(); |
| nvme_mi_ctrl_t ctrl = nullptr; |
| |
| auto featureStore = std::make_shared<FeatureStore>( |
| io, objServer, conn, objectPath, mockNvme, ctrl, 0, |
| FeatureScope::Controller); |
| |
| EXPECT_CALL(*mockNvme, |
| adminGetFeatures(::testing::_, ::testing::_, ::testing::_)) |
| .WillOnce( |
| [](nvme_mi_ctrl_t, const NVMeMiIntf::GetFeaturesRequest&, |
| std::function<void(nvme_ex_ptr, uint32_t, std::span<uint8_t>)>&& |
| cb) { |
| cb(makeLibNVMeError(std::make_error_code(std::errc::connection_aborted), |
| -1, "mock"), |
| 0, {}); |
| }); |
| |
| bool threw = false; |
| bool done = false; |
| boost::asio::spawn(io, [&](boost::asio::yield_context yield) { |
| try |
| { |
| FeatureStoreTest::callGetFeature(featureStore, std::move(yield), |
| "Arbitration_Current"); |
| } |
| catch ( |
| const sdbusplus::xyz::openbmc_project::Common::Error::Unavailable&) |
| { |
| threw = true; |
| } |
| done = true; |
| }); |
| while (!done) |
| { |
| io.run_one(); |
| } |
| EXPECT_TRUE(threw); |
| } |
| |
| TEST_F(FeatureStoreTest, SetFeatureInvalidName) |
| { |
| boost::asio::io_context io; |
| auto conn = std::make_shared<sdbusplus::asio::connection>(io); |
| sdbusplus::asio::object_server objServer(conn); |
| std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0"; |
| |
| auto mockNvme = std::make_shared<NVMeMiMock>(); |
| nvme_mi_ctrl_t ctrl = nullptr; |
| |
| auto featureStore = std::make_shared<FeatureStore>( |
| io, objServer, conn, objectPath, mockNvme, ctrl, 0, |
| FeatureScope::Controller); |
| |
| bool threw = false; |
| bool done = false; |
| boost::asio::spawn(io, [&](boost::asio::yield_context yield) { |
| try |
| { |
| FeatureStoreTest::callSetFeature(featureStore, std::move(yield), |
| "Arbitration_InvalidSelect", {}); |
| } |
| catch (const sdbusplus::xyz::openbmc_project::Common::Error:: |
| InvalidArgument&) |
| { |
| threw = true; |
| } |
| done = true; |
| }); |
| while (!done) |
| { |
| io.run_one(); |
| } |
| EXPECT_TRUE(threw); |
| } |
| |
| TEST_F(FeatureStoreTest, SetFeatureProtoDeserializationFailure) |
| { |
| boost::asio::io_context io; |
| auto conn = std::make_shared<sdbusplus::asio::connection>(io); |
| sdbusplus::asio::object_server objServer(conn); |
| std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0"; |
| |
| auto mockNvme = std::make_shared<NVMeMiMock>(); |
| nvme_mi_ctrl_t ctrl = nullptr; |
| |
| auto featureStore = std::make_shared<FeatureStore>( |
| io, objServer, conn, objectPath, mockNvme, ctrl, 0, |
| FeatureScope::Controller); |
| |
| std::vector<uint8_t> malformedData = {0xFF, 0xFF, 0xFF}; |
| |
| bool threw = false; |
| bool done = false; |
| boost::asio::spawn(io, [&](boost::asio::yield_context yield) { |
| try |
| { |
| FeatureStoreTest::callSetFeature(featureStore, std::move(yield), |
| "Arbitration_Current", |
| malformedData); |
| } |
| catch (const sdbusplus::xyz::openbmc_project::Common::Error:: |
| InvalidArgument&) |
| { |
| threw = true; |
| } |
| done = true; |
| }); |
| while (!done) |
| { |
| io.run_one(); |
| } |
| EXPECT_TRUE(threw); |
| } |
| |
| TEST_F(FeatureStoreTest, SetFeatureInvalidArgumentBounds) |
| { |
| boost::asio::io_context io; |
| auto conn = std::make_shared<sdbusplus::asio::connection>(io); |
| sdbusplus::asio::object_server objServer(conn); |
| std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0"; |
| |
| auto mockNvme = std::make_shared<NVMeMiMock>(); |
| nvme_mi_ctrl_t ctrl = nullptr; |
| |
| auto featureStore = std::make_shared<FeatureStore>( |
| io, objServer, conn, objectPath, mockNvme, ctrl, 0, |
| FeatureScope::Controller); |
| |
| google::gbmc::nvme::base::Arbitration msg; |
| msg.set_arbitration_burst(256); // Exceeds 8-bit bound (0-255) |
| |
| std::vector<uint8_t> data(msg.ByteSizeLong()); |
| ASSERT_TRUE( |
| msg.SerializeToArray(data.data(), static_cast<int>(data.size()))); |
| |
| bool threw = false; |
| bool done = false; |
| boost::asio::spawn(io, [&](boost::asio::yield_context yield) { |
| try |
| { |
| FeatureStoreTest::callSetFeature(featureStore, std::move(yield), |
| "Arbitration_Current", data); |
| } |
| catch (const sdbusplus::xyz::openbmc_project::Common::Error:: |
| InvalidArgument&) |
| { |
| threw = true; |
| } |
| done = true; |
| }); |
| while (!done) |
| { |
| io.run_one(); |
| } |
| EXPECT_TRUE(threw); |
| } |
| |
| TEST_F(FeatureStoreTest, SetFeatureNotChangeableOrSaveable) |
| { |
| boost::asio::io_context io; |
| auto conn = std::make_shared<sdbusplus::asio::connection>(io); |
| sdbusplus::asio::object_server objServer(conn); |
| std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0"; |
| |
| auto mockNvme = std::make_shared<NVMeMiMock>(); |
| nvme_mi_ctrl_t ctrl = nullptr; |
| |
| auto featureStore = std::make_shared<FeatureStore>( |
| io, objServer, conn, objectPath, mockNvme, ctrl, 0, |
| FeatureScope::Controller); |
| |
| // Seed featureCaps for Arbitration (FID 0x01) with 0 (neither changeable |
| // nor saveable) |
| FeatureStoreTest::getFeatureCaps(featureStore)[0x01] = 0; |
| |
| google::gbmc::nvme::base::Arbitration msg; |
| msg.set_arbitration_burst(4); |
| std::vector<uint8_t> data(msg.ByteSizeLong()); |
| ASSERT_TRUE( |
| msg.SerializeToArray(data.data(), static_cast<int>(data.size()))); |
| |
| // 1. Try to set Current (changeable=false) -> should throw NotAllowed |
| bool threwNotAllowedCurrent = false; |
| bool doneCurrent = false; |
| boost::asio::spawn(io, [&](boost::asio::yield_context yield) { |
| try |
| { |
| FeatureStoreTest::callSetFeature(featureStore, std::move(yield), |
| "Arbitration_Current", data); |
| } |
| catch ( |
| const sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed&) |
| { |
| threwNotAllowedCurrent = true; |
| } |
| doneCurrent = true; |
| }); |
| while (!doneCurrent) |
| { |
| io.run_one(); |
| } |
| EXPECT_TRUE(threwNotAllowedCurrent); |
| |
| // 2. Try to set Saved (saveable=false) -> should throw NotAllowed |
| io.restart(); |
| bool threwNotAllowedSaved = false; |
| bool doneSaved = false; |
| boost::asio::spawn(io, [&](boost::asio::yield_context yield) { |
| try |
| { |
| FeatureStoreTest::callSetFeature(featureStore, std::move(yield), |
| "Arbitration_Saved", data); |
| } |
| catch ( |
| const sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed&) |
| { |
| threwNotAllowedSaved = true; |
| } |
| doneSaved = true; |
| }); |
| while (!doneSaved) |
| { |
| io.run_one(); |
| } |
| EXPECT_TRUE(threwNotAllowedSaved); |
| } |
| |
| int main(int argc, char** argv) |
| { |
| ::testing::InitGoogleTest(&argc, argv); |
| return RUN_ALL_TESTS(); |
| } |