| // Copyright 2024 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "google3/host_commands.h" |
| |
| #include "host_command_mock.hpp" |
| #include "payload_update.hpp" |
| #include "payload_update_interface.hpp" |
| #include "sys_mock.hpp" |
| #include "test/payload_update_mock.hpp" |
| |
| #include <fcntl.h> |
| |
| #include <stdplus/raw.hpp> |
| #include <xyz/openbmc_project/Control/Hoth/error.hpp> |
| |
| #include <cstdint> |
| #include <random> |
| #include <span> |
| #include <string> |
| #include <vector> |
| |
| #include "gmock/gmock.h" |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| using namespace std::literals; |
| using sdbusplus::error::xyz::openbmc_project::control::hoth::CommandFailure; |
| using sdbusplus::error::xyz::openbmc_project::control::hoth::ResponseFailure; |
| |
| using ::testing::_; |
| using ::testing::ContainerEq; |
| using ::testing::Invoke; |
| using ::testing::Return; |
| using ::testing::Throw; |
| |
| namespace google |
| { |
| namespace hoth |
| { |
| namespace internal |
| { |
| namespace |
| { |
| |
| auto const goodResponseStr = "\x03\xfd\x00\x00\x00\x00\x00\x00"s; |
| std::vector<uint8_t> const gooodResponse(goodResponseStr.begin(), |
| goodResponseStr.end()); |
| |
| class PayloadUpdateTest : public ::testing::Test |
| { |
| protected: |
| PayloadUpdateTest() : payloadUpdate(&hostCmd, &sys) |
| {} |
| // Sys mock |
| const internal::SysMock sys; |
| |
| // Host Command interface handle |
| internal::HostCommandMock hostCmd; |
| |
| // PayloadUpdate interface handle |
| internal::PayloadUpdateImpl payloadUpdate; |
| }; |
| |
| MATCHER_P(requestMatches, req, "") |
| { |
| const auto* const arg_req = static_cast<const payload_update_packet *>(arg); |
| return (arg_req->offset == req.offset && arg_req->len == req.len && |
| arg_req->type == req.type); |
| } |
| |
| class PayloadInitiateTest : public PayloadUpdateTest |
| { |
| protected: |
| // Expected request {offset, len, type} |
| const payload_update_packet expectedReq = {0, 0, PAYLOAD_UPDATE_INITIATE}; |
| }; |
| |
| TEST_F(PayloadInitiateTest, sendCommandExceptionFails) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Throw(CommandFailure())); |
| |
| EXPECT_THROW(payloadUpdate.initiate(), CommandFailure); |
| } |
| |
| TEST_F(PayloadInitiateTest, sendCommandReturnsEmptyResultFails) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Return(std::vector<uint8_t>{})); |
| |
| EXPECT_THROW(payloadUpdate.initiate(), std::runtime_error); |
| } |
| |
| TEST_F(PayloadInitiateTest, sendCommandReturnsBadResultFails) |
| { |
| std::vector<uint8_t> rsp = gooodResponse; |
| // Change the RspHeader.result to something other than EC_RES_SUCCESS |
| rsp[2] = internal::EC_RES_ERROR; |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_THROW(payloadUpdate.initiate(), ResponseFailure); |
| } |
| |
| TEST_F(PayloadInitiateTest, sendCommandReturnsGoodResponseSuccess) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Return(gooodResponse)); |
| |
| EXPECT_NO_THROW(payloadUpdate.initiate()); |
| } |
| |
| class PayloadEraseTest : public PayloadUpdateTest |
| { |
| protected: |
| const uint32_t expectedOffset = 0; |
| const uint32_t expectedSize = 4096; |
| // Expected request {offset, len, type} |
| const payload_update_packet expectedReq = {expectedOffset, expectedSize, |
| PAYLOAD_UPDATE_ERASE}; |
| }; |
| |
| TEST_F(PayloadEraseTest, sendCommandExceptionFails) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Throw(CommandFailure())); |
| |
| EXPECT_THROW(payloadUpdate.erase(expectedOffset, expectedSize), |
| CommandFailure); |
| } |
| |
| TEST_F(PayloadEraseTest, sendCommandReturnsBadResultFails) |
| { |
| std::vector<uint8_t> rsp = gooodResponse; |
| // Change the RspHeader.result to something other than EC_RES_SUCCESS |
| rsp[2] = internal::EC_RES_ERROR; |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_THROW(payloadUpdate.erase(expectedOffset, expectedSize), |
| ResponseFailure); |
| } |
| |
| TEST_F(PayloadEraseTest, sendCommandReturnsEmptyResultFails) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Return(std::vector<uint8_t>{})); |
| |
| EXPECT_THROW(payloadUpdate.erase(expectedOffset, expectedSize), |
| std::runtime_error); |
| } |
| |
| TEST_F(PayloadEraseTest, sendCommandReturnsGoodResponseSuccess) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Return(gooodResponse)); |
| |
| EXPECT_NO_THROW(payloadUpdate.erase(expectedOffset, expectedSize)); |
| } |
| |
| class PayloadReadTest : public PayloadUpdateTest |
| { |
| protected: |
| static constexpr uint32_t expectedOffset = 10; |
| static constexpr uint32_t expectedSize = 3; |
| // Expected request {offset, len, type} |
| const payload_update_packet expectedReq = {expectedOffset, expectedSize, |
| PAYLOAD_UPDATE_READ}; |
| |
| static std::vector<uint8_t> makeResponse(std::span<const uint8_t> data) |
| { |
| internal::RspHeader hdr; |
| auto hdrSpan = stdplus::raw::asSpan<uint8_t>(hdr); |
| hdr.struct_version = 3; |
| hdr.checksum = 0; |
| hdr.result = 0; |
| hdr.data_len = data.size(); |
| hdr.reserved = 0; |
| hdr.checksum += -internal::calculateChecksum(hdrSpan, data); |
| |
| std::vector<uint8_t> ret; |
| ret.insert(ret.end(), hdrSpan.begin(), hdrSpan.end()); |
| ret.insert(ret.end(), data.begin(), data.end()); |
| return ret; |
| } |
| }; |
| |
| TEST_F(PayloadReadTest, sendCommandReturnsShortResultFails) |
| { |
| constexpr std::array<uint8_t, expectedSize - 1> shortRsp = {1, 3}; |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Return(makeResponse(shortRsp))); |
| |
| std::array<uint8_t, expectedSize> rsp; |
| EXPECT_THROW(payloadUpdate.read(expectedOffset, rsp), ResponseFailure); |
| } |
| |
| TEST_F(PayloadReadTest, sendCommandReturnsGoodResponseSuccess) |
| { |
| constexpr std::array<uint8_t, expectedSize> expectedRsp = {4, 5, 6}; |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Return(makeResponse(expectedRsp))); |
| |
| std::array<uint8_t, expectedSize> rsp; |
| payloadUpdate.read(expectedOffset, rsp); |
| EXPECT_EQ(expectedRsp, rsp); |
| } |
| |
| std::vector<uint8_t> makeRsp(uint16_t result) |
| { |
| std::vector<uint8_t> ret(sizeof(RspHeader)); |
| auto& rsp = stdplus::raw::refFrom<RspHeader>(ret); |
| rsp.result = result; |
| return ret; |
| } |
| |
| class PayloadVerifyTest : public PayloadUpdateTest |
| { |
| protected: |
| // Expected request {offset, len, type} |
| const payload_update_packet expectedReq = {0, 0, PAYLOAD_UPDATE_VERIFY}; |
| const payload_update_packet expectedDescriptorReq = { |
| 0, 0, PAYLOAD_UPDATE_VERIFY_DESCRIPTOR}; |
| }; |
| |
| TEST_F(PayloadVerifyTest, descriptorVerificationNotSupported) |
| { |
| testing::InSequence seq; |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedDescriptorReq), |
| sizeof(expectedDescriptorReq))) |
| .WillOnce(Return(makeRsp(EC_RES_ERROR))); |
| EXPECT_CALL( |
| hostCmd, |
| sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), sizeof(expectedReq))) |
| .WillOnce(Return(makeRsp(EC_RES_SUCCESS))); |
| |
| // In case descriptor verfication is not successful, fall back to |
| // PAYLOAD_UPDATE_VERIFY. Thus the two EXPECT_CALLs above. |
| EXPECT_NO_THROW(payloadUpdate.verify()); |
| } |
| |
| TEST_F(PayloadVerifyTest, descriptorVerificationSuccess) |
| { |
| testing::InSequence seq; |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedDescriptorReq), |
| sizeof(expectedDescriptorReq))) |
| .WillOnce(Return(makeRsp(EC_RES_SUCCESS))); |
| |
| EXPECT_NO_THROW(payloadUpdate.verify()); |
| } |
| |
| TEST_F(PayloadVerifyTest, sendCommandExceptionFails) |
| { |
| testing::InSequence seq; |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedDescriptorReq), |
| sizeof(expectedDescriptorReq))) |
| .WillOnce(Return(makeRsp(EC_RES_INVALID_PARAM))); |
| EXPECT_CALL( |
| hostCmd, |
| sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), sizeof(expectedReq))) |
| .WillOnce(Throw(CommandFailure())); |
| |
| EXPECT_THROW(payloadUpdate.verify(), CommandFailure); |
| } |
| |
| TEST_F(PayloadVerifyTest, sendCommandReturnsBadResultFails) |
| { |
| testing::InSequence seq; |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedDescriptorReq), |
| sizeof(expectedDescriptorReq))) |
| .WillOnce(Return(makeRsp(EC_RES_INVALID_PARAM))); |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Return(makeRsp(EC_RES_ERROR))); |
| |
| EXPECT_THROW(payloadUpdate.verify(), ResponseFailure); |
| } |
| |
| TEST_F(PayloadVerifyTest, sendCommandReturnsEmptyResultFails) |
| { |
| testing::InSequence seq; |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedDescriptorReq), |
| sizeof(expectedDescriptorReq))) |
| .WillOnce(Return(makeRsp(EC_RES_INVALID_PARAM))); |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Return(std::vector<uint8_t>{})); |
| |
| EXPECT_THROW(payloadUpdate.verify(), std::runtime_error); |
| } |
| |
| TEST_F(PayloadVerifyTest, sendCommandReturnsGoodResponseSuccess) |
| { |
| testing::InSequence seq; |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedDescriptorReq), |
| sizeof(expectedDescriptorReq))) |
| .WillOnce(Return(makeRsp(EC_RES_INVALID_PARAM))); |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Return(gooodResponse)); |
| |
| EXPECT_NO_THROW(payloadUpdate.verify()); |
| } |
| |
| class PayloadGetStatusTest : public PayloadUpdateTest |
| { |
| protected: |
| // Expected request {offset, len, type} |
| const payload_update_packet expectedReq = {0, 0, PAYLOAD_UPDATE_GET_STATUS}; |
| }; |
| |
| TEST_F(PayloadGetStatusTest, sendCommandExceptionFails) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Throw(CommandFailure())); |
| |
| EXPECT_THROW(payloadUpdate.getStatus(), CommandFailure); |
| } |
| |
| TEST_F(PayloadGetStatusTest, sendCommandReturnsBadResultFails) |
| { |
| std::vector<uint8_t> rsp = gooodResponse; |
| // Change the RspHeader.result to something other than EC_RES_SUCCESS |
| rsp[2] = internal::EC_RES_ERROR; |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_THROW(payloadUpdate.getStatus(), ResponseFailure); |
| } |
| |
| TEST_F(PayloadGetStatusTest, sendCommandReturnsEmptyResultFails) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Return(std::vector<uint8_t>{})); |
| |
| EXPECT_THROW(payloadUpdate.getStatus(), std::runtime_error); |
| } |
| |
| TEST_F(PayloadGetStatusTest, sendCommandReturnsEmptyPayloadFails) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Return(gooodResponse)); |
| |
| EXPECT_THROW(payloadUpdate.getStatus(), std::runtime_error); |
| } |
| |
| TEST_F(PayloadGetStatusTest, getStatusCheckValidResponseSuccess) |
| { |
| payload_update_status expectedResponse; |
| expectedResponse.a_valid = 1; |
| expectedResponse.b_valid = 2; |
| expectedResponse.active_half = 0; |
| expectedResponse.next_half = 1; |
| expectedResponse.persistent_half = 1; |
| |
| std::vector<uint8_t> rsp = gooodResponse; |
| // Append expectedResponse to the response. Normally, checksum and data_len |
| // would also need to be changed in the packet but since we're mocking out |
| // sendCommand (which performs the check), we will just append. |
| rsp.insert(rsp.end(), reinterpret_cast<uint8_t*>(&expectedResponse), |
| reinterpret_cast<uint8_t*>(&expectedResponse) + |
| sizeof(expectedResponse)); |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, requestMatches(expectedReq), |
| sizeof(payload_update_packet))) |
| .WillOnce(Return(rsp)); |
| |
| auto response = payloadUpdate.getStatus(); |
| |
| EXPECT_EQ(response.a_valid, expectedResponse.a_valid); |
| EXPECT_EQ(response.b_valid, expectedResponse.b_valid); |
| EXPECT_EQ(response.active_half, expectedResponse.active_half); |
| EXPECT_EQ(response.next_half, expectedResponse.next_half); |
| EXPECT_EQ(response.persistent_half, expectedResponse.persistent_half); |
| } |
| |
| MATCHER_P(activateRequestMatches, req, "") |
| { |
| |
| const auto*const arg_req = static_cast<const internal::ActivateRequest*>(arg); |
| return (arg_req->header.offset == req.header.offset && |
| arg_req->header.len == req.header.len && |
| arg_req->header.type == req.header.type && |
| arg_req->activate.half == req.activate.half && |
| arg_req->activate.make_persistent == req.activate.make_persistent); |
| } |
| |
| class PayloadActivateTest : public PayloadUpdateTest |
| { |
| protected: |
| // Expected request header {offset, len, type} |
| const payload_update_packet expectedHeader = { |
| 0, sizeof(payload_update_activate), PAYLOAD_UPDATE_ACTIVATE}; |
| const internal::Side expectedSide = internal::Side::A; |
| const internal::Persistence expectedPersistence = |
| internal::Persistence::kNonPersistent; |
| const payload_update_activate expectedActivate = { |
| static_cast<uint8_t>(expectedSide), |
| static_cast<uint8_t>(expectedPersistence)}; |
| const internal::ActivateRequest expectedReq = {expectedHeader, |
| expectedActivate}; |
| |
| payload_update_status response; |
| }; |
| |
| TEST_F(PayloadActivateTest, sendCommandExceptionFails) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, activateRequestMatches(expectedReq), |
| sizeof(internal::ActivateRequest))) |
| .WillOnce(Throw(CommandFailure())); |
| |
| EXPECT_THROW(payloadUpdate.activate(expectedSide, expectedPersistence), |
| CommandFailure); |
| } |
| |
| TEST_F(PayloadActivateTest, sendCommandReturnsBadResultFails) |
| { |
| std::vector<uint8_t> rsp = gooodResponse; |
| // Change the RspHeader.result to something other than EC_RES_SUCCESS |
| rsp[2] = internal::EC_RES_ERROR; |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, activateRequestMatches(expectedReq), |
| sizeof(internal::ActivateRequest))) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_THROW(payloadUpdate.activate(expectedSide, expectedPersistence), |
| ResponseFailure); |
| } |
| |
| TEST_F(PayloadActivateTest, sendCommandReturnsEmptyResultFails) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, activateRequestMatches(expectedReq), |
| sizeof(internal::ActivateRequest))) |
| .WillOnce(Return(std::vector<uint8_t>{})); |
| |
| EXPECT_THROW(payloadUpdate.activate(expectedSide, expectedPersistence), |
| std::runtime_error); |
| } |
| |
| TEST_F(PayloadActivateTest, sendCommandReturnsGoodResponseSuccess) |
| { |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, activateRequestMatches(expectedReq), |
| sizeof(internal::ActivateRequest))) |
| .WillOnce(Return(gooodResponse)); |
| |
| EXPECT_NO_THROW(payloadUpdate.activate(expectedSide, expectedPersistence)); |
| } |
| |
| class PayloadConfirmTest : public PayloadUpdateTest |
| { |
| |
| protected: |
| // Expected request header {offset, len, type} |
| const payload_update_packet expectedHeader = { |
| 0, sizeof(payload_update_confirm), PAYLOAD_UPDATE_CONFIRM}; |
| |
| // Expected confirm |
| enum payload_update_confirm_option option = |
| payload_update_confirm_option::Confirm; |
| uint32_t timeoute_value = 3600; |
| uint64_t cookie = 0; |
| const payload_update_confirm expectedConfirm = { |
| option, {0, 0, 0}, 3600, cookie}; |
| const internal::ConfirmRequest expectedReq = {expectedHeader, |
| expectedConfirm}; |
| |
| std::optional<payload_update_confirm_response> response; |
| }; |
| |
| MATCHER_P(confirmRequestMatches, req, "") |
| { |
| |
| const auto *const arg_req = |
| static_cast<const internal::ConfirmRequest *>(arg); |
| return (arg_req->header.offset == req.header.offset && |
| arg_req->header.len == req.header.len && |
| arg_req->header.type == req.header.type && |
| arg_req->confirm.option == req.confirm.option && |
| arg_req->confirm.timeout_value == req.confirm.timeout_value && |
| arg_req->confirm.confirmation_cookie == |
| req.confirm.confirmation_cookie); |
| } |
| |
| TEST_F(PayloadConfirmTest, sendCommandReturnsGoodResponseSuccess) |
| { |
| // every things is good, and the command works |
| payload_update_confirm_response expectedResponse; |
| expectedResponse.timeout_values.min = 1; |
| expectedResponse.timeout_values.max = 2; |
| expectedResponse.timeout_values.default_val = 3; |
| expectedResponse.timeout_values.current = 4; |
| |
| std::vector<uint8_t> rsp = gooodResponse; |
| rsp.insert(rsp.end(), reinterpret_cast<uint8_t*>(&expectedResponse), |
| reinterpret_cast<uint8_t*>(&expectedResponse) + |
| sizeof(expectedResponse)); |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, confirmRequestMatches(expectedReq), |
| sizeof(internal::ConfirmRequest))) |
| .WillOnce(Return(rsp)); |
| std::optional<payload_update_confirm_response> response; |
| |
| EXPECT_NO_THROW(response = payloadUpdate.confirm( |
| payload_update_confirm_option::Confirm, 3600, 0)); |
| EXPECT_NE(response, std::nullopt); |
| EXPECT_EQ(response->timeout_values.min, |
| expectedResponse.timeout_values.min); |
| EXPECT_EQ(response->timeout_values.max, |
| expectedResponse.timeout_values.max); |
| EXPECT_EQ(response->timeout_values.default_val, |
| expectedResponse.timeout_values.default_val); |
| EXPECT_EQ(response->timeout_values.current, |
| expectedResponse.timeout_values.current); |
| } |
| |
| TEST_F(PayloadConfirmTest, sendCommandReturnsBadResultFails) |
| { |
| // the good call, but the fucntion will return a bad response |
| // this should cause a ResponseFailure |
| std::vector<uint8_t> rsp = gooodResponse; |
| rsp[2] = internal::EC_RES_ERROR; |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, confirmRequestMatches(expectedReq), |
| sizeof(internal::ConfirmRequest))) |
| .WillOnce(Return(rsp)); |
| |
| std::optional<payload_update_confirm_response> response; |
| EXPECT_THROW(response = payloadUpdate.confirm( |
| payload_update_confirm_option::Confirm, 3600, 0), |
| ResponseFailure); |
| } |
| |
| TEST_F(PayloadConfirmTest, sendCommandReturnsEmptyResultFails) |
| { |
| // the call is good, but out expected call (sendCmd) return a empty vector |
| // this should cause a runtime error |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, confirmRequestMatches(expectedReq), |
| sizeof(internal::ConfirmRequest))) |
| .WillOnce(Return(std::vector<uint8_t>{})); |
| |
| std::optional<payload_update_confirm_response> response; |
| EXPECT_THROW(response = payloadUpdate.confirm( |
| payload_update_confirm_option::Confirm, 3600, 0), |
| std::runtime_error); |
| } |
| |
| TEST_F(PayloadConfirmTest, sendCommandExceptionFails) |
| { |
| // the call is good, but out expected call creates a "commandFailure" |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| 0, confirmRequestMatches(expectedReq), |
| sizeof(internal::ConfirmRequest))) |
| .WillOnce(Throw(CommandFailure())); |
| |
| std::optional<payload_update_confirm_response> response; |
| EXPECT_THROW(response = payloadUpdate.confirm( |
| payload_update_confirm_option::Confirm, 3600, 0), |
| CommandFailure); |
| } |
| |
| class PayloadSendTest : public PayloadUpdateTest |
| { |
| protected: |
| const std::string expectedPath = "/run/initramfs/bmc-image"; |
| const int expectedFd = 1; |
| const int expectedRetClose = 0; |
| const ssize_t endRead = 0; |
| }; |
| |
| TEST_F(PayloadSendTest, sysOpenReturnNegativeFails) |
| { |
| EXPECT_CALL(sys, open(expectedPath.c_str(), O_RDONLY)).WillOnce(Return(-1)); |
| // We do not expect sys close to be called since open failed |
| EXPECT_CALL(sys, close(_)).Times(0); |
| |
| EXPECT_THROW(payloadUpdate.send(expectedPath), std::system_error); |
| } |
| |
| TEST_F(PayloadSendTest, sysReadReturnNegativeFails) |
| { |
| EXPECT_CALL(sys, open(expectedPath.c_str(), O_RDONLY)) |
| .WillOnce(Return(expectedFd)); |
| EXPECT_CALL(sys, read(expectedFd, _, internal::max_packet_size)) |
| .WillOnce(Return(-1)); |
| EXPECT_CALL(sys, close(expectedFd)).Times(1); |
| |
| EXPECT_THROW(payloadUpdate.send(expectedPath), std::system_error); |
| } |
| |
| TEST_F(PayloadSendTest, sysReadThrowsErrorFails) |
| { |
| EXPECT_CALL(sys, open(expectedPath.c_str(), O_RDONLY)) |
| .WillOnce(Return(expectedFd)); |
| EXPECT_CALL(sys, read(expectedFd, _, internal::max_packet_size)) |
| .WillOnce(Throw(internal::errnoException(""))); |
| EXPECT_CALL(sys, close(expectedFd)).Times(1); |
| |
| EXPECT_THROW(payloadUpdate.send(expectedPath), std::system_error); |
| } |
| |
| TEST_F(PayloadSendTest, hostCmdSendCommandThrowsErrorFails) |
| { |
| EXPECT_CALL(sys, open(expectedPath.c_str(), O_RDONLY)) |
| .WillOnce(Return(expectedFd)); |
| EXPECT_CALL(sys, read(expectedFd, _, internal::max_packet_size)) |
| .WillOnce([](int, void* data, size_t size) { |
| memset(data, 0, size); |
| return size; |
| }); |
| EXPECT_CALL(hostCmd, sendCommand(_, _, _, _)) |
| .WillOnce(Throw(ResponseFailure())); |
| EXPECT_CALL(sys, close(expectedFd)).Times(1); |
| |
| EXPECT_THROW(payloadUpdate.send(expectedPath), ResponseFailure); |
| } |
| |
| TEST_F(PayloadSendTest, sysCloseReturnPositiveSuccess) |
| { |
| EXPECT_CALL(sys, open(expectedPath.c_str(), O_RDONLY)) |
| .WillOnce(Return(expectedFd)); |
| // Return 0 to emulate read finishing a read of file 0 |
| EXPECT_CALL(sys, read(expectedFd, _, internal::max_packet_size)) |
| .WillOnce(Return(endRead)); |
| EXPECT_CALL(sys, close(expectedFd)).WillOnce(Return(expectedRetClose)); |
| |
| EXPECT_NO_THROW(payloadUpdate.send(expectedPath)); |
| } |
| |
| class PayloadFindDescriptorTest : public PayloadUpdateTest |
| { |
| protected: |
| const std::string expectedPath = "/run/initramfs/bmc-image"; |
| const int expectedFd = 1; |
| const int expectedRetClose = 0; |
| const ssize_t endRead = 0; |
| const int fsize = 32 * 1024; |
| uint32_t offset = 0; |
| }; |
| |
| TEST_F(PayloadFindDescriptorTest, sysOpenReturnNegativeFails) |
| { |
| EXPECT_CALL(sys, open(expectedPath.c_str(), O_RDONLY)).WillOnce(Return(-1)); |
| // We do not expect sys close to be called since open failed |
| EXPECT_CALL(sys, close(_)).Times(0); |
| |
| EXPECT_THROW(payloadUpdate.findDescriptor(expectedPath, &offset), |
| std::system_error); |
| } |
| |
| TEST_F(PayloadFindDescriptorTest, sysSeekReturnNegativeFails) |
| { |
| EXPECT_CALL(sys, open(expectedPath.c_str(), O_RDONLY)) |
| .WillOnce(Return(expectedFd)); |
| EXPECT_CALL(sys, lseek(expectedFd, _, _)).WillOnce(Return(-1)); |
| EXPECT_CALL(sys, close(_)).Times(1); |
| |
| EXPECT_THROW(payloadUpdate.findDescriptor(expectedPath, &offset), |
| std::system_error); |
| } |
| |
| TEST_F(PayloadFindDescriptorTest, sysReadReturnNegativeFails) |
| { |
| EXPECT_CALL(sys, open(expectedPath.c_str(), O_RDONLY)) |
| .WillOnce(Return(expectedFd)); |
| EXPECT_CALL(sys, lseek(expectedFd, _, _)).WillRepeatedly(Return(fsize)); |
| EXPECT_CALL(sys, read(expectedFd, _, _)).WillOnce(Return(-1)); |
| EXPECT_CALL(sys, close(_)).Times(1); |
| |
| EXPECT_THROW(payloadUpdate.findDescriptor(expectedPath, &offset), |
| std::system_error); |
| } |
| |
| TEST_F(PayloadFindDescriptorTest, sysCloseReturnPositiveSuccess) |
| { |
| EXPECT_CALL(sys, open(expectedPath.c_str(), O_RDONLY)) |
| .WillOnce(Return(expectedFd)); |
| EXPECT_CALL(sys, lseek(expectedFd, _, _)).WillRepeatedly(Return(fsize)); |
| |
| // Return 0 to emulate read finishing a read of file 0 |
| EXPECT_CALL(sys, read(expectedFd, _, _)).WillRepeatedly(Return(endRead)); |
| EXPECT_CALL(sys, close(expectedFd)).WillOnce(Return(expectedRetClose)); |
| |
| EXPECT_NO_THROW(payloadUpdate.findDescriptor(expectedPath, &offset)); |
| } |
| |
| class PayloadSendStaticWPTest : public PayloadUpdateTest |
| { |
| protected: |
| const std::string expectedPath = "/run/initramfs/bmc-image"; |
| const int expectedFd = 1; |
| const int expectedRetClose = 0; |
| const int fsize = 32 * 1024; |
| }; |
| |
| TEST_F(PayloadSendStaticWPTest, findDescriptorReturnNegativeFails) |
| { |
| |
| EXPECT_CALL(sys, open(expectedPath.c_str(), O_RDONLY)) |
| .WillOnce(Return(expectedFd)); |
| EXPECT_CALL(sys, lseek(expectedFd, _, _)).WillRepeatedly(Return(fsize)); |
| |
| EXPECT_CALL(sys, close(_)).Times(1); |
| |
| EXPECT_THROW(payloadUpdate.eraseAndSendStaticWP(expectedPath), |
| std::system_error); |
| } // namespace internal |
| |
| class PayloadSendStaticWPRegionsTest : public PayloadUpdateTest |
| { |
| protected: |
| const std::string expectedPath = "/run/initramfs/bmc-image"; |
| const int expectedFd = 1; |
| const int expectedRetClose = 0; |
| const ssize_t endRead = 0; |
| const int fsize = 32 * 1024; |
| uint32_t offset = 0; |
| }; |
| |
| TEST_F(PayloadSendStaticWPRegionsTest, sysOpenReturnNegativeFails) |
| { |
| EXPECT_CALL(sys, open(expectedPath.c_str(), O_RDONLY)).WillOnce(Return(-1)); |
| // We do not expect sys close to be called since open failed |
| EXPECT_CALL(sys, close(_)).Times(0); |
| |
| EXPECT_THROW( |
| payloadUpdate.eraseAndSendStaticWPRegions(expectedPath, offset), |
| std::system_error); |
| } |
| |
| TEST_F(PayloadSendStaticWPRegionsTest, sysSeekReturnNegativeFails) |
| { |
| EXPECT_CALL(sys, open(expectedPath.c_str(), O_RDONLY)) |
| .WillOnce(Return(expectedFd)); |
| EXPECT_CALL(sys, lseek(expectedFd, _, _)).WillOnce(Return(-1)); |
| EXPECT_CALL(sys, close(_)).Times(1); |
| |
| EXPECT_THROW( |
| payloadUpdate.eraseAndSendStaticWPRegions(expectedPath, offset), |
| std::system_error); |
| } |
| |
| TEST_F(PayloadSendStaticWPRegionsTest, sysReadReturnNegativeFails) |
| { |
| EXPECT_CALL(sys, open(expectedPath.c_str(), O_RDONLY)) |
| .WillOnce(Return(expectedFd)); |
| EXPECT_CALL(sys, lseek(expectedFd, _, _)).WillRepeatedly(Return(fsize)); |
| EXPECT_CALL(sys, read(expectedFd, _, _)).WillOnce(Return(-1)); |
| EXPECT_CALL(sys, close(_)).Times(1); |
| EXPECT_THROW( |
| payloadUpdate.eraseAndSendStaticWPRegions(expectedPath, offset), |
| std::system_error); |
| } // namespace internal |
| |
| TEST_F(PayloadSendStaticWPRegionsTest, invalidDescriptorFails) |
| { |
| EXPECT_CALL(sys, open(expectedPath.c_str(), O_RDONLY)) |
| .WillOnce(Return(expectedFd)); |
| EXPECT_CALL(sys, lseek(expectedFd, _, _)).WillRepeatedly(Return(fsize)); |
| EXPECT_CALL(sys, read(expectedFd, _, _)).WillOnce(Return(fsize)); |
| EXPECT_THROW( |
| payloadUpdate.eraseAndSendStaticWPRegions(expectedPath, offset), |
| std::system_error); |
| } // namespace internal |
| |
| constexpr uint32_t testFlashSize = 32 * 1024; |
| class PayloadChunkAndSendTest : public PayloadSendTest |
| { |
| // Needs to be public in order to be used in TEST_P |
| public: |
| PayloadChunkAndSendTest() |
| { |
| EXPECT_CALL(sys, open(expectedPath.c_str(), O_RDONLY)) |
| .WillOnce(Return(expectedFd)); |
| // Due to the RAII handler, we always expect close to be called if open |
| // succeeded |
| EXPECT_CALL(sys, close(expectedFd)).WillOnce(Return(expectedRetClose)); |
| } |
| }; |
| |
| class FakeFile |
| { |
| public: |
| // Generate all 0xff file |
| explicit FakeFile(uint32_t size) : file(size, 0xff) {} |
| // Generate all 0xff file with random data between the 2 params passed in |
| FakeFile(uint32_t randStartOffset, uint32_t randEndOffset, uint32_t size) : |
| file(size, 0xff) |
| { |
| std::mt19937 gen(0); |
| if (randEndOffset > size) |
| { |
| throw std::runtime_error( |
| "RandEndOffset cannot be bigger than size of file"); |
| } |
| |
| auto fileIt = file.begin(); |
| std::generate(fileIt + randStartOffset, fileIt + randEndOffset, gen); |
| } |
| // Copy the file passed in manually |
| explicit FakeFile(const std::vector<uint8_t> &file) |
| { |
| this->file = file; |
| } |
| |
| ssize_t read(int /*unused*/, void *buf, size_t size) |
| { |
| auto readSize = std::min(static_cast<off_t>(file.size()) - readOffset, |
| static_cast<off_t>(size)); |
| |
| if (readSize < 0) |
| { |
| return 0; |
| } |
| |
| std::memcpy(buf, file.data() + readOffset, readSize); |
| readOffset += readSize; |
| return readSize; |
| } |
| |
| std::vector<uint8_t> file; |
| off_t readOffset = 0; |
| }; |
| |
| class FakeFlash |
| { |
| public: |
| explicit FakeFlash(uint32_t size) : flash(size, 0xff) {} |
| |
| std::vector<uint8_t> sendCommand(uint16_t /*unused*/, uint8_t /*unused*/, |
| const void *request, size_t requestSize) |
| { |
| std::span<const uint8_t> reqSpan( |
| reinterpret_cast<const uint8_t *>(request), requestSize); |
| auto reqHeader = stdplus::raw::extract<payload_update_packet>(reqSpan); |
| EXPECT_EQ(reqHeader.type, PAYLOAD_UPDATE_CONTINUE); |
| |
| std::memcpy(flash.data() + reqHeader.offset, reqSpan.data(), |
| reqHeader.len); |
| // We need to ensure "result" member is EC_RES_SUCCESS so that |
| // PayloadUpdateImpl::sendCommand does not throw |
| return gooodResponse; |
| } |
| |
| std::vector<uint8_t> flash; |
| }; |
| |
| TEST_F(PayloadChunkAndSendTest, chunkAndSendAllFFsSuccess) |
| { |
| FakeFile file(testFlashSize); |
| FakeFlash flash(testFlashSize); |
| |
| EXPECT_CALL(sys, read(expectedFd, _, internal::max_packet_size)) |
| .WillRepeatedly(Invoke(&file, &FakeFile::read)); |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| _, _, _)) |
| .WillRepeatedly(Invoke(&flash, &FakeFlash::sendCommand)); |
| |
| EXPECT_NO_THROW(payloadUpdate.send(expectedPath)); |
| EXPECT_THAT(file.file, ContainerEq(flash.flash)); |
| } |
| |
| class PayloadChunkAndSendTestWithParam : |
| public PayloadChunkAndSendTest, |
| public testing::WithParamInterface<std::pair<uint32_t, uint32_t>> |
| {}; |
| |
| TEST_P(PayloadChunkAndSendTestWithParam, chunkAndSendPartialNonFFsSuccess) |
| { |
| FakeFile file(GetParam().first, GetParam().second, testFlashSize); |
| FakeFlash flash(testFlashSize); |
| |
| EXPECT_CALL(sys, read(expectedFd, _, internal::max_packet_size)) |
| .WillRepeatedly(Invoke(&file, &FakeFile::read)); |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_UPDATE, |
| _, _, _)) |
| .WillRepeatedly(Invoke(&flash, &FakeFlash::sendCommand)); |
| |
| EXPECT_NO_THROW(payloadUpdate.send(expectedPath)); |
| EXPECT_THAT(file.file, ContainerEq(flash.flash)); |
| } |
| |
| const std::vector<std::pair<uint32_t, uint32_t>> chunkAndSendTestParams{ |
| // Params: {randStartOffsets, randEndOffsets} |
| // These determine the [first, last] range to randomize the payload file |
| |
| // All non-0xffs |
| {0, testFlashSize}, |
| // First portion non-0xffs |
| {0, testFlashSize / 2}, |
| {0, testFlashSize / 3}, |
| // Last portion non-0xffs |
| {testFlashSize / 4, testFlashSize}, |
| {testFlashSize / 5, testFlashSize}, |
| // Middle portion non-0xffs |
| {testFlashSize / 6, testFlashSize / 2}, |
| {testFlashSize / 7, testFlashSize / 3}, |
| {testFlashSize / 8, 7 * testFlashSize / 8}, |
| {testFlashSize / 9, 8 * testFlashSize / 9}, |
| }; |
| |
| INSTANTIATE_TEST_CASE_P(WithFlags, PayloadChunkAndSendTestWithParam, |
| ::testing::ValuesIn(chunkAndSendTestParams)); |
| |
| } // namespace |
| } // namespace internal |
| } // namespace hoth |
| } // namespace google |