blob: e187808aeaa056dc215c5064006a8775487a7e91 [file] [log] [blame]
// 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