blob: 2a4cc8358a6cb27ce0cff1b88061773aafea1c8d [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 "hoth_unittest.hpp"
#include <hoth.hpp>
#include <xyz/openbmc_project/Control/Hoth/error.hpp>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>
#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::ExpectedInfoNotFound;
using sdbusplus::error::xyz::openbmc_project::control::hoth::FirmwareFailure;
using sdbusplus::error::xyz::openbmc_project::control::hoth::InterfaceError;
using sdbusplus::error::xyz::openbmc_project::control::hoth::ResponseFailure;
using google::hoth::Hoth;
using google::hoth::internal::FirmwareUpdater;
using google::hoth::internal::FirmwareMtdUpdater;
using google::hoth::internal::Persistence;
using google::hoth::internal::Side;
using ::testing::_;
using ::testing::ContainerEq;
using ::testing::InSequence;
using ::testing::InvokeWithoutArgs;
using ::testing::Return;
using ::testing::Throw;
static constexpr std::chrono::milliseconds threadDelay(100);
// Wait up to 100ms for loops when waiting for change in status
static constexpr std::chrono::milliseconds loopDelay(10);
static constexpr uint8_t maxLoopCount = 10;
class HothUpdateTest : public HothTest
{
public:
void asyncFnWait()
{
std::unique_lock<std::mutex> lck(cv_m);
// Wait until the end of the test
cv.wait(lck);
}
protected:
void waitWhileInProgress(const std::function<Hoth::FirmwareUpdateStatus()> &func)
{
do {
std::this_thread::sleep_for(loopDelay);
result = func();
loopCount++;
} while (result == Hoth::FirmwareUpdateStatus::InProgress &&
loopCount < maxLoopCount);
}
std::condition_variable cv;
std::mutex cv_m;
std::vector<uint8_t> mockFirmware;
Hoth::FirmwareUpdateStatus result = Hoth::FirmwareUpdateStatus::None;
uint8_t loopCount = 0;
};
// Does not use the HothUpdateTest TEST_F because creating multiple Hoth will
// lead to memory leaks.
TEST(HothUpdateTestMtd, noMtd)
{
// Ensure that calls to updateFirmware() and getFirmwareUpdateStatus()
// return proper errors in the case that we don't support the mtd firmware
// update protocol.
std::unique_ptr<FirmwareUpdater> firmwareUpdater =
std::make_unique<FirmwareMtdUpdater>(nullptr, nullptr);
sdbusplus::SdBusMock sdbus_mock;
sdbusplus::bus::bus bus = sdbusplus::get_mocked_new(&sdbus_mock);
std::unique_ptr<Hoth> hoth = std::make_unique<Hoth>(
bus, "/test/path", nullptr, nullptr, nullptr, firmwareUpdater.get());
EXPECT_EQ(Hoth::FirmwareUpdateStatus::None,
hoth->getFirmwareUpdateStatus());
EXPECT_NO_THROW(hoth->updateFirmware({}));
std::this_thread::sleep_for(threadDelay);
EXPECT_EQ(Hoth::FirmwareUpdateStatus::Error,
hoth->getFirmwareUpdateStatus());
}
TEST_F(HothUpdateTest, updateAlreadyInProgressFails)
{
// Call updateFirmware() once and make flashCopy() wait until the end of the
// test. Calling updateFirmware() once more will throw a FirmwareFailure as
// we detect an update already in progress.
EXPECT_CALL(mtd, findPartition(_)).WillOnce(Return(""));
EXPECT_CALL(mtd, flashCopy(_, _, _))
.WillOnce(InvokeWithoutArgs(this, &HothUpdateTest::asyncFnWait));
hoth->updateFirmware(mockFirmware);
EXPECT_THROW(hoth->updateFirmware(mockFirmware), FirmwareFailure);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothUpdateTest, updateUncheckedSucceeds)
{
// Call updateFirmware() twice, once to start off a process and then a
// second time without checking the status. This ensures we don't require
// callers to check the status of a previous update that completed.
EXPECT_CALL(mtd, findPartition(_)).Times(2).WillRepeatedly(Return(""));
EXPECT_CALL(mtd, flashCopy(_, _, _)).Times(2);
hoth->updateFirmware(mockFirmware);
std::this_thread::sleep_for(threadDelay);
hoth->updateFirmware(mockFirmware);
}
TEST_F(HothUpdateTest, beforeUpdateGetFirmwareUpdateStatusReturnsDefaultStatus)
{
// If getFirmwareUpdateStatus() is called before updateFirmware() has been
// called, we expect the default lastStatus to be returned
EXPECT_EQ(hoth->getFirmwareUpdateStatus(),
Hoth::FirmwareUpdateStatus::None);
}
TEST_F(HothUpdateTest, duringUpdateGetFirmwareUpdateStatusReturnsInProgress)
{
// If getFirmwareUpdateStatus() is called while flashCopy() is still in
// progress, we expect inProgress status
EXPECT_CALL(mtd, findPartition(_)).WillOnce(Return(""));
EXPECT_CALL(mtd, flashCopy(_, _, _))
.WillOnce(InvokeWithoutArgs(this, &HothUpdateTest::asyncFnWait));
hoth->updateFirmware(mockFirmware);
EXPECT_EQ(hoth->getFirmwareUpdateStatus(),
Hoth::FirmwareUpdateStatus::InProgress);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothUpdateTest, successfulGetFirmwareUpdateStatusReturnsDone)
{
// If getFirmwareUpdateStatus() is called after updateFirmware() has
// finished and it did not return an exception, we expect Done status
EXPECT_CALL(mtd, findPartition(_)).WillOnce(Return(""));
EXPECT_CALL(mtd, flashCopy(_, _, _)).WillOnce(Return());
hoth->updateFirmware(mockFirmware);
// Ensure our flashCopy thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getFirmwareUpdateStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Done);
}
TEST_F(HothUpdateTest, exceptionThrownGetFirmwareUpdateStatusReturnsError)
{
// If getFirmwareUpdateStatus() is called after updateFirmware() has thrown
// an exception, we expect Error status
EXPECT_CALL(mtd, findPartition(_)).WillOnce(Return(""));
EXPECT_CALL(mtd, flashCopy(_, _, _))
.WillOnce(Throw(std::runtime_error("This is a simulated exception")));
hoth->updateFirmware(mockFirmware);
// Ensure our flashCopy thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getFirmwareUpdateStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Error);
}
TEST_F(HothUpdateTest, getFirmwareUpdateStatusTwiceDone)
{
// If getFirmwareUpdateStatus() is called after getFirmwareUpdateStatus()
// already returned Done once, we expect the status to have been cached
EXPECT_CALL(mtd, findPartition(_)).WillOnce(Return(""));
EXPECT_CALL(mtd, flashCopy(_, _, _)).WillOnce(Return());
hoth->updateFirmware(mockFirmware);
// Ensure our flashCopy thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getFirmwareUpdateStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Done);
EXPECT_EQ(hoth->getFirmwareUpdateStatus(),
Hoth::FirmwareUpdateStatus::Done);
}
TEST_F(HothUpdateTest, getFirmwareUpdateStatusTwiceError)
{
// If getFirmwareUpdateStatus() is called after getFirmwareUpdateStatus()
// already returned Error once, we expect the status to have been cached
EXPECT_CALL(mtd, findPartition(_)).WillOnce(Return(""));
EXPECT_CALL(mtd, flashCopy(_, _, _))
.WillOnce(Throw(std::runtime_error("This is a simulated exception")));
hoth->updateFirmware(mockFirmware);
// Ensure our flashCopy thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getFirmwareUpdateStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Error);
EXPECT_EQ(hoth->getFirmwareUpdateStatus(),
Hoth::FirmwareUpdateStatus::Error);
}
class HothPayloadTest : public HothTest
{
public:
void asyncFnWait()
{
std::unique_lock<std::mutex> lck(cv_m);
// Wait until the end of the test
cv.wait(lck);
}
protected:
void waitWhileInProgress(const std::function<Hoth::FirmwareUpdateStatus()> &func)
{
do {
std::this_thread::sleep_for(loopDelay);
result = func();
loopCount++;
} while (result == Hoth::FirmwareUpdateStatus::InProgress &&
loopCount < maxLoopCount);
}
std::condition_variable cv;
std::mutex cv_m;
Hoth::FirmwareUpdateStatus result;
const std::string examplePath = "/run/initramfs/test-image";
uint8_t loopCount = 0;
};
class HothPayloadInitiateTest : public HothPayloadTest
{};
TEST_F(HothPayloadInitiateTest, initiateAlreadyInProgressFails)
{
// Call initiatePayload() once and make the async function wait until the
// end of the test. Calling initiatePayload() once more will throw an
// InterfaceError as we detect an async function already in progress.
EXPECT_CALL(payloadUpdate, initiate())
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->initiatePayload();
EXPECT_THROW(hoth->initiatePayload(), InterfaceError);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadInitiateTest, sendAlreadyInProgressFails)
{
// Call sendPayload() once and make the async function wait until the
// end of the test. Calling initiatePayload() will throw an
// InterfaceError as we detect an async function already in progress.
EXPECT_CALL(payloadUpdate, send(examplePath))
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->sendPayload(examplePath);
EXPECT_THROW(hoth->initiatePayload(), InterfaceError);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadInitiateTest, verifyAlreadyInProgressFails)
{
// Call verifyPayload() once and make the async function wait until the
// end of the test. Calling initiatePayload() once more will throw an
// InterfaceError as we detect an async function already in progress.
EXPECT_CALL(payloadUpdate, verify())
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->verifyPayload();
EXPECT_THROW(hoth->initiatePayload(), InterfaceError);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadInitiateTest, beforeInitiatePayloadReturnsDefaultStatus)
{
// If getInitiatePayloadStatus() is called before initiatePayload() has been
// called, we expect the default lastStatus to be returned
EXPECT_EQ(hoth->getInitiatePayloadStatus(),
Hoth::FirmwareUpdateStatus::None);
}
TEST_F(HothPayloadInitiateTest, duringInitiatePayloadReturnsInProgress)
{
// If getInitiatePayloadStatus() is called while initiate() is still in
// progress, we expect inProgress status
EXPECT_CALL(payloadUpdate, initiate())
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->initiatePayload();
EXPECT_EQ(hoth->getInitiatePayloadStatus(),
Hoth::FirmwareUpdateStatus::InProgress);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadInitiateTest, successfulInitiatePayloadReturnsDone)
{
// If getInitiatePayloadStatus() is called after initiatePayload() has
// finished and it did not return an exception, we expect Done status
EXPECT_CALL(payloadUpdate, initiate()).WillOnce(Return());
hoth->initiatePayload();
// Ensure our initiate thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getInitiatePayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Done);
}
TEST_F(HothPayloadInitiateTest, initiateAfterSuccessfulSendReturnsDone)
{
// If initiatePayload() is called after a successful sendPayload(), we
// expect initiatePayload() to succeed
InSequence seq;
EXPECT_CALL(payloadUpdate, send(examplePath)).WillOnce(Return());
EXPECT_CALL(payloadUpdate, initiate()).WillOnce(Return());
hoth->sendPayload(examplePath);
// Ensure our send thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getSendPayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Done);
hoth->initiatePayload();
loopCount = 0;
// Ensure our initiate thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getInitiatePayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Done);
}
TEST_F(HothPayloadInitiateTest, exceptionThrownReturnsError)
{
// If getInitiatePayloadStatus() is called after initiatePayload() has
// thrown an exception, we expect Error status
EXPECT_CALL(payloadUpdate, initiate())
.WillOnce(Throw(std::runtime_error("This is a simulated exception")));
hoth->initiatePayload();
// Ensure our initiate thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getInitiatePayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Error);
}
TEST_F(HothPayloadInitiateTest, getInitiatePayloadStatusTwiceDone)
{
// If getInitiatePayloadStatus() is called after getInitiatePayloadStatus()
// already returned Done once, we expect the status to have been cached
EXPECT_CALL(payloadUpdate, initiate()).WillOnce(Return());
hoth->initiatePayload();
// Ensure our initiate thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getInitiatePayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Done);
EXPECT_EQ(hoth->getInitiatePayloadStatus(),
Hoth::FirmwareUpdateStatus::Done);
}
TEST_F(HothPayloadInitiateTest, getInitiatePayloadStatusTwiceError)
{
// If getInitiatePayloadStatus() is called after getInitiatePayloadStatus()
// already returned Error once, we expect the status to have been cached
EXPECT_CALL(payloadUpdate, initiate())
.WillOnce(Throw(std::runtime_error("This is a simulated exception")));
hoth->initiatePayload();
// Ensure our initiate thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getInitiatePayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Error);
EXPECT_EQ(hoth->getInitiatePayloadStatus(),
Hoth::FirmwareUpdateStatus::Error);
}
class HothPayloadSendTest : public HothPayloadTest
{};
TEST_F(HothPayloadSendTest, sendAlreadyInProgressFails)
{
// Call sendPayload() once and make the async function wait until the
// end of the test. Calling sendPayload() once more will throw a
// InterfaceError as we detect an async function already in progress.
EXPECT_CALL(payloadUpdate, send(examplePath))
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->sendPayload(examplePath);
EXPECT_THROW(hoth->sendPayload(examplePath), InterfaceError);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadSendTest, initiateAlreadyInProgressFails)
{
// Call initiatePayload() once and make the async function wait until the
// end of the test. Calling sendPayload() will throw a
// InterfaceError as we detect an async function already in progress.
EXPECT_CALL(payloadUpdate, initiate())
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->initiatePayload();
EXPECT_THROW(hoth->sendPayload(examplePath), InterfaceError);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadSendTest, verifyAlreadyInProgressFails)
{
// Call verifyPayload() once and make the async function wait until the
// end of the test. Calling sendPayload() once more will throw an
// InterfaceError as we detect an async function already in progress.
EXPECT_CALL(payloadUpdate, verify())
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->verifyPayload();
EXPECT_THROW(hoth->sendPayload(examplePath), InterfaceError);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadSendTest, emptyPathSendPayloadFails)
{
// Calling sendPayload with empty path strings will throw a FirmwareFailure.
EXPECT_THROW(hoth->sendPayload(""), FirmwareFailure);
}
TEST_F(HothPayloadSendTest, beforeSendPayloadReturnsDefaultStatus)
{
// If getSendPayloadStatus() is called before sendPayload() has been
// called, we expect the default lastStatus to be returned
EXPECT_EQ(hoth->getSendPayloadStatus(), Hoth::FirmwareUpdateStatus::None);
}
TEST_F(HothPayloadSendTest, duringSendPayloadReturnsInProgress)
{
// If getSendPayloadStatus() is called while send() is still in
// progress, we expect inProgress status
EXPECT_CALL(payloadUpdate, send(examplePath))
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->sendPayload(examplePath);
EXPECT_EQ(hoth->getSendPayloadStatus(),
Hoth::FirmwareUpdateStatus::InProgress);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadSendTest, successfulSendPayloadReturnsDone)
{
// If getSendPayloadStatus() is called after sendPayload() has
// finished and it did not return an exception, we expect Done status
EXPECT_CALL(payloadUpdate, send(examplePath)).WillOnce(Return());
hoth->sendPayload(examplePath);
// Ensure our send thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getSendPayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Done);
}
TEST_F(HothPayloadSendTest, sendAfterSuccessfulInitiateReturnsDone)
{
// If sendPayload() is called after a successful initiatePayload(), we
// expect sendPayload() to succeed
InSequence seq;
EXPECT_CALL(payloadUpdate, initiate()).WillOnce(Return());
EXPECT_CALL(payloadUpdate, send(examplePath)).WillOnce(Return());
hoth->initiatePayload();
// Ensure our initiate thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getInitiatePayloadStatus(); });
hoth->sendPayload(examplePath);
loopCount = 0;
// Ensure our send thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getSendPayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Done);
}
TEST_F(HothPayloadSendTest, exceptionThrownReturnsError)
{
// If getSendPayloadStatus() is called after sendPayload() has
// thrown an exception, we expect Error status
EXPECT_CALL(payloadUpdate, send(examplePath))
.WillOnce(Throw(std::runtime_error("This is a simulated exception")));
hoth->sendPayload(examplePath);
// Ensure our send thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getSendPayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Error);
}
TEST_F(HothPayloadSendTest, getSendPayloadStatusTwiceDone)
{
// If getSendPayloadStatus() is called after getSendPayloadStatus()
// already returned Done once, we expect the status to have been cached
EXPECT_CALL(payloadUpdate, send(examplePath)).WillOnce(Return());
hoth->sendPayload(examplePath);
// Ensure our send thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getSendPayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Done);
EXPECT_EQ(hoth->getSendPayloadStatus(), Hoth::FirmwareUpdateStatus::Done);
}
TEST_F(HothPayloadSendTest, getSendPayloadStatusTwiceError)
{
// If getSendPayloadStatus() is called after getSendPayloadStatus()
// already returned Error once, we expect the status to have been cached
EXPECT_CALL(payloadUpdate, send(examplePath))
.WillOnce(Throw(std::runtime_error("This is a simulated exception")));
hoth->sendPayload(examplePath);
// Ensure our send thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getSendPayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Error);
EXPECT_EQ(hoth->getSendPayloadStatus(), Hoth::FirmwareUpdateStatus::Error);
}
class HothPayloadEraseTest : public HothPayloadTest
{
protected:
static constexpr uint32_t goodOffset = 0;
static constexpr uint32_t goodSize = 4096;
static constexpr uint32_t badOffset = 1;
static constexpr uint32_t badSize = goodSize + 1;
};
TEST_F(HothPayloadEraseTest, initiateAlreadyInProgressFails)
{
// Call initiatePayload() once and make the async function wait until the
// end of the test. Calling erasePayload() will throw a
// InterfaceError as we detect an async function already in progress.
EXPECT_CALL(payloadUpdate, initiate())
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->initiatePayload();
EXPECT_THROW(hoth->erasePayload(goodOffset, goodSize), InterfaceError);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadEraseTest, sendAlreadyInProgressFails)
{
// Call sendPayload() once and make the async function wait until the
// end of the test. Calling erasePayload() will throw a
// InterfaceError as we detect an async function already in progress.
EXPECT_CALL(payloadUpdate, send(examplePath))
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->sendPayload(examplePath);
EXPECT_THROW(hoth->erasePayload(goodOffset, goodSize), InterfaceError);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadEraseTest, verifyAlreadyInProgressFails)
{
// Call verifyPayload() once and make the async function wait until the
// end of the test. Calling erasePayload() once more will throw an
// InterfaceError as we detect an async function already in progress.
EXPECT_CALL(payloadUpdate, verify())
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->verifyPayload();
EXPECT_THROW(hoth->erasePayload(goodOffset, goodSize), InterfaceError);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadEraseTest, notAlignedOffsetErasePayloadThrows)
{
// If offset given is not aligned, erasePayload() will throw
EXPECT_THROW(hoth->erasePayload(badOffset, goodSize), CommandFailure);
}
TEST_F(HothPayloadEraseTest, notAlignedSizeErasePayloadThrows)
{
// If size given is not aligned, erasePayload() will throw
EXPECT_THROW(hoth->erasePayload(goodOffset, badSize), CommandFailure);
}
TEST_F(HothPayloadEraseTest, successfulErasePayloadNoThrow)
{
// If erase() does not throw, verifyPayload() will not throw
EXPECT_CALL(payloadUpdate, erase(goodOffset, goodSize)).WillOnce(Return());
EXPECT_NO_THROW(hoth->erasePayload(goodOffset, goodSize));
}
TEST_F(HothPayloadEraseTest, exceptionThrownErasePayloadThrows)
{
// If erase() throws, erasePayload() will also throw
EXPECT_CALL(payloadUpdate, erase(goodOffset, goodSize))
.WillOnce(Throw(ResponseFailure()));
EXPECT_THROW(hoth->erasePayload(goodOffset, goodSize), ResponseFailure);
}
class HothPayloadVerifyTest : public HothPayloadTest
{};
TEST_F(HothPayloadVerifyTest, initiateAlreadyInProgressFails)
{
// Call initiatePayload() once and make the async function wait until the
// end of the test. Calling verifyPayload() will throw a
// InterfaceError as we detect an async function already in progress.
EXPECT_CALL(payloadUpdate, initiate())
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->initiatePayload();
EXPECT_THROW(hoth->verifyPayload(), InterfaceError);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadVerifyTest, sendAlreadyInProgressFails)
{
// Call sendPayload() once and make the async function wait until the
// end of the test. Calling verifyPayload() will throw a
// InterfaceError as we detect an async function already in progress.
EXPECT_CALL(payloadUpdate, send(examplePath))
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->sendPayload(examplePath);
EXPECT_THROW(hoth->verifyPayload(), InterfaceError);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadVerifyTest, verifyAlreadyInProgressFails)
{
// Call verifyPayload() once and make the async function wait until the
// end of the test. Calling verifyPayload() once more will throw an
// InterfaceError as we detect an async function already in progress.
EXPECT_CALL(payloadUpdate, verify())
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->verifyPayload();
EXPECT_THROW(hoth->verifyPayload(), InterfaceError);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadVerifyTest, beforeVerifyPayloadReturnsDefaultStatus)
{
// If getVerifyPayloadStatus() is called before verifyPayload() has been
// called, we expect the default lastStatus to be returned
EXPECT_EQ(hoth->getVerifyPayloadStatus(), Hoth::FirmwareUpdateStatus::None);
}
TEST_F(HothPayloadVerifyTest, duringVerifyPayloadReturnsInProgress)
{
// If getVerifyPayloadStatus() is called while verify() is still in
// progress, we expect inProgress status
EXPECT_CALL(payloadUpdate, verify())
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->verifyPayload();
EXPECT_EQ(hoth->getVerifyPayloadStatus(),
Hoth::FirmwareUpdateStatus::InProgress);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadVerifyTest, successfulVerifyPayloadReturnsDone)
{
// If getVerifyPayloadStatus() is called after verifyPayload() has
// finished and it did not return an exception, we expect Done status
EXPECT_CALL(payloadUpdate, verify()).WillOnce(Return());
hoth->verifyPayload();
// Ensure our initiate thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getVerifyPayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Done);
}
TEST_F(HothPayloadVerifyTest, verifyAfterSuccessfulSendReturnsDone)
{
// If verifyPayload() is called after a successful sendPayload(), we
// expect verifyPayload() to succeed
InSequence seq;
EXPECT_CALL(payloadUpdate, send(examplePath)).WillOnce(Return());
EXPECT_CALL(payloadUpdate, verify()).WillOnce(Return());
hoth->sendPayload(examplePath);
// Ensure our send thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getSendPayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Done);
hoth->verifyPayload();
loopCount = 0;
// Ensure our initiate thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getVerifyPayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Done);
}
TEST_F(HothPayloadVerifyTest, exceptionThrownReturnsError)
{
// If getVerifyPayloadStatus() is called after verifyPayload() has
// thrown an exception, we expect Error status
EXPECT_CALL(payloadUpdate, verify())
.WillOnce(Throw(std::runtime_error("This is a simulated exception")));
hoth->verifyPayload();
// Ensure our initiate thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getVerifyPayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Error);
}
TEST_F(HothPayloadVerifyTest, getInitiatePayloadStatusTwiceDone)
{
// If getVerifyPayloadStatus() is called after getVerifyPayloadStatus()
// already returned Done once, we expect the status to have been cached
EXPECT_CALL(payloadUpdate, verify()).WillOnce(Return());
hoth->verifyPayload();
// Ensure our initiate thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getVerifyPayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Done);
EXPECT_EQ(hoth->getVerifyPayloadStatus(), Hoth::FirmwareUpdateStatus::Done);
}
TEST_F(HothPayloadVerifyTest, getVerifyPayloadStatusTwiceError)
{
// If getVerifyPayloadStatus() is called after getVerifyPayloadStatus()
// already returned Error once, we expect the status to have been cached
EXPECT_CALL(payloadUpdate, verify())
.WillOnce(Throw(std::runtime_error("This is a simulated exception")));
hoth->verifyPayload();
// Ensure our initiate thread has exited
waitWhileInProgress([thread = hoth.get()] { return thread->getVerifyPayloadStatus(); });
EXPECT_EQ(result, Hoth::FirmwareUpdateStatus::Error);
EXPECT_EQ(hoth->getVerifyPayloadStatus(),
Hoth::FirmwareUpdateStatus::Error);
}
class HothPayloadActivateTest : public HothPayloadTest
{
protected:
const static bool expectedPersistence = true;
};
TEST_F(HothPayloadActivateTest, initiateAlreadyInProgressFails)
{
// Call initiatePayload() once and make the async function wait until the
// end of the test. Calling activatePayload() will throw a
// InterfaceError as we detect an async function already in progress.
EXPECT_CALL(payloadUpdate, initiate())
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->initiatePayload();
EXPECT_THROW(hoth->activatePayload(expectedPersistence), InterfaceError);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadActivateTest, sendAlreadyInProgressFails)
{
// Call sendPayload() once and make the async function wait until the
// end of the test. Calling activatePayload() will throw a
// InterfaceError as we detect an async function already in progress.
EXPECT_CALL(payloadUpdate, send(examplePath))
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->sendPayload(examplePath);
EXPECT_THROW(hoth->activatePayload(expectedPersistence), InterfaceError);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadActivateTest, verifyAlreadyInProgressFails)
{
// Call verifyPayload() once and make the async function wait until the
// end of the test. Calling verifyPayload() once more will throw an
// InterfaceError as we detect an async function already in progress.
EXPECT_CALL(payloadUpdate, verify())
.WillOnce(InvokeWithoutArgs(this, &HothPayloadTest::asyncFnWait));
hoth->verifyPayload();
EXPECT_THROW(hoth->verifyPayload(), InterfaceError);
// Release the thread
std::this_thread::sleep_for(threadDelay);
cv.notify_one();
}
TEST_F(HothPayloadActivateTest, getStatusThrowsExceptionFails)
{
// If getStatus() throws an exception, expect activatePayload to throw the
// same exception
EXPECT_CALL(payloadUpdate, getStatus()).WillOnce(Throw(ResponseFailure()));
EXPECT_THROW(hoth->activatePayload(expectedPersistence), ResponseFailure);
}
TEST_F(HothPayloadActivateTest, activateThrowsExceptionFails)
{
// If activate() throws an exception, expect activatePayload to throw the
// same exception
payload_update_status expectedResponse;
EXPECT_CALL(payloadUpdate, getStatus()).WillOnce(Return(expectedResponse));
EXPECT_CALL(payloadUpdate, activate(_, _))
.WillOnce(Throw(ResponseFailure()));
EXPECT_THROW(hoth->activatePayload(expectedPersistence), ResponseFailure);
}
TEST_F(HothPayloadActivateTest, activateSuccessful)
{
// Call activatePayload successfully and expect the correct configuration to
// be passed in as parameters
static constexpr uint8_t activeHalf = 0;
static constexpr uint8_t otherHalf = 1;
payload_update_status expectedResponse;
expectedResponse.a_valid = 1;
expectedResponse.b_valid = 2;
expectedResponse.active_half = activeHalf;
expectedResponse.next_half = 0;
expectedResponse.persistent_half = 0;
EXPECT_CALL(payloadUpdate, getStatus()).WillOnce(Return(expectedResponse));
EXPECT_CALL(payloadUpdate,
activate(Side(otherHalf),
Persistence(expectedPersistence)))
.WillOnce(Return());
hoth->activatePayload(expectedPersistence);
}
TEST_F(HothPayloadActivateTest, deactivateSuccessful)
{
// Call deactivatePayload successfully and expect the correct configuration
// to be passed in as parameters
static constexpr uint8_t activeHalf = 0;
payload_update_status expectedResponse;
expectedResponse.a_valid = 1;
expectedResponse.b_valid = 2;
expectedResponse.active_half = activeHalf;
expectedResponse.next_half = 1;
expectedResponse.persistent_half = 0;
EXPECT_CALL(payloadUpdate, getStatus()).WillOnce(Return(expectedResponse));
EXPECT_CALL(payloadUpdate, activate(Side(activeHalf),
Persistence(true)))
.WillOnce(Return());
hoth->deactivatePayload();
}
class HothPayloadSizeTest : public HothPayloadTest
{};
ACTION_P(ReadSize, max_size)
{
if (max_size < arg0 + arg1.size())
{
throw ResponseFailure();
}
}
TEST_F(HothPayloadSizeTest, badFlash)
{
EXPECT_CALL(payloadUpdate, read(_, _)).WillRepeatedly(ReadSize(0u));
EXPECT_THROW(hoth->getPayloadSize(), InterfaceError);
}
TEST_F(HothPayloadSizeTest, goodSizes)
{
for (uint32_t i = 1; i < 17; ++i)
{
EXPECT_CALL(payloadUpdate, read(_, _)).WillRepeatedly(ReadSize(i));
EXPECT_EQ(hoth->getPayloadSize(), i);
EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(&payloadUpdate));
}
}
class HothCommandTest : public HothTest
{
protected:
static const std::vector<uint8_t> testLoadCBKCmdSlot1;
static const std::vector<uint8_t> testLoadCBKDataCmdSlot1;
static const std::vector<uint8_t> testLockCBKSlotCmdSlot1;
static const std::vector<uint8_t> testIncCBKCmdSlot1;
static const std::vector<uint8_t> testProdUnwrapCmdSlot1;
static const std::vector<uint8_t> testLoadCBKCmdSlot2;
static const std::vector<uint8_t> testLoadCBKDataCmdSlot2;
static const std::vector<uint8_t> testLockCBKSlotCmdSlot2;
static const std::vector<uint8_t> testIncCBKCmdSlot2;
static const std::vector<uint8_t> testProdUnwrapCmdSlot2;
static const std::vector<uint8_t> testLoadTokenCmd;
static const std::vector<uint8_t> testNonCriticalCmd;
static const std::vector<uint8_t> fakeHostCmdResponse;
};
// Format begin
// || echeader ||major|minor|
// |param cnt |param size | reserve || slot id ||
const std::vector<uint8_t> HothCommandTest::testLoadCBKCmdSlot1 = {
0x03, 0x37, 0x03, 0x3e, 0x00, 0x00, 0x20, 0x00, 0x01, 0x0A,
0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
const std::vector<uint8_t> HothCommandTest::testLoadCBKDataCmdSlot1 = {
0x03, 0x37, 0x03, 0x3e, 0x00, 0x00, 0x20, 0x00, 0x01, 0x0B,
0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
const std::vector<uint8_t> HothCommandTest::testLockCBKSlotCmdSlot1 = {
0x03, 0x37, 0x03, 0x3e, 0x00, 0x00, 0x20, 0x00, 0x01, 0x15,
0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
const std::vector<uint8_t> HothCommandTest::testIncCBKCmdSlot1 = {
0x03, 0x37, 0x03, 0x3e, 0x00, 0x00, 0x20, 0x00, 0x01, 0x17,
0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
const std::vector<uint8_t> HothCommandTest::testProdUnwrapCmdSlot1 = {
0x03, 0x37, 0x03, 0x3e, 0x00, 0x00, 0x20, 0x00, 0x05, 0x00,
0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
const std::vector<uint8_t> HothCommandTest::testLoadCBKCmdSlot2 = {
0x03, 0x37, 0x03, 0x3e, 0x00, 0x00, 0x20, 0x00, 0x01, 0x0A,
0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00};
const std::vector<uint8_t> HothCommandTest::testLoadCBKDataCmdSlot2 = {
0x03, 0x37, 0x03, 0x3e, 0x00, 0x00, 0x20, 0x00, 0x01, 0x0B,
0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00};
const std::vector<uint8_t> HothCommandTest::testLockCBKSlotCmdSlot2 = {
0x03, 0x37, 0x03, 0x3e, 0x00, 0x00, 0x20, 0x00, 0x01, 0x15,
0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00};
const std::vector<uint8_t> HothCommandTest::testIncCBKCmdSlot2 = {
0x03, 0x37, 0x03, 0x3e, 0x00, 0x00, 0x20, 0x00, 0x01, 0x17,
0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00};
const std::vector<uint8_t> HothCommandTest::testProdUnwrapCmdSlot2 = {
0x03, 0x37, 0x03, 0x3e, 0x00, 0x00, 0x20, 0x00, 0x05, 0x00,
0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00};
// Format end
const std::vector<uint8_t> HothCommandTest::testLoadTokenCmd = {
0x03, 0x37, 0x03, 0x3e, 0x00, 0x00, 0x20, 0x00, 0x05, 0x05, 0x00, 0x00};
const std::vector<uint8_t> HothCommandTest::testNonCriticalCmd = {
0x03, 0x37, 0x03, 0x3e, 0x00, 0x00, 0x20, 0x00, 0x05, 0x07, 0x00, 0x00};
const std::vector<uint8_t> HothCommandTest::fakeHostCmdResponse = {0x01, 0x23};
TEST_F(HothCommandTest, criticalCommand)
{
EXPECT_CALL(hostCmd, sendCommand(ContainerEq(testLoadTokenCmd)))
.WillRepeatedly(Return(fakeHostCmdResponse));
EXPECT_CALL(hostCmd, sendCommandAsync(ContainerEq(testLoadTokenCmd)))
.WillRepeatedly(Return(0));
EXPECT_CALL(hostCmd, sendCommand(ContainerEq(testLoadCBKCmdSlot1)))
.WillRepeatedly(Return(fakeHostCmdResponse));
EXPECT_CALL(hostCmd, sendCommand(ContainerEq(testLoadCBKCmdSlot2)))
.WillRepeatedly(Return(fakeHostCmdResponse));
EXPECT_CALL(hostCmd, sendCommand(ContainerEq(testLoadCBKDataCmdSlot1)))
.WillRepeatedly(Return(fakeHostCmdResponse));
EXPECT_CALL(hostCmd, sendCommand(ContainerEq(testLoadCBKDataCmdSlot2)))
.WillRepeatedly(Return(fakeHostCmdResponse));
EXPECT_CALL(hostCmd, sendCommand(ContainerEq(testLockCBKSlotCmdSlot1)))
.WillRepeatedly(Return(fakeHostCmdResponse));
EXPECT_CALL(hostCmd, sendCommand(ContainerEq(testLockCBKSlotCmdSlot2)))
.WillRepeatedly(Return(fakeHostCmdResponse));
EXPECT_CALL(hostCmd, sendCommand(ContainerEq(testIncCBKCmdSlot1)))
.WillRepeatedly(Return(fakeHostCmdResponse));
EXPECT_CALL(hostCmd, sendCommand(ContainerEq(testIncCBKCmdSlot2)))
.WillRepeatedly(Return(fakeHostCmdResponse));
EXPECT_CALL(hostCmd, sendCommand(ContainerEq(testProdUnwrapCmdSlot1)))
.WillRepeatedly(Return(fakeHostCmdResponse));
EXPECT_CALL(hostCmd, sendCommand(ContainerEq(testProdUnwrapCmdSlot2)))
.WillRepeatedly(Return(fakeHostCmdResponse));
// LoadTokens is banned except for sendTrustedHostCommand
const std::vector<uint8_t> bannedRsp = {3, 249, 4, 0, 0, 0, 0, 0};
EXPECT_THAT(hoth->sendHostCommand(testLoadCBKCmdSlot1),
ContainerEq(fakeHostCmdResponse));
EXPECT_THAT(hoth->sendHostCommand(testLoadCBKCmdSlot2),
ContainerEq(bannedRsp));
EXPECT_THAT(hoth->sendHostCommand(testLoadCBKDataCmdSlot1),
ContainerEq(fakeHostCmdResponse));
EXPECT_THAT(hoth->sendHostCommand(testLoadCBKDataCmdSlot2),
ContainerEq(bannedRsp));
EXPECT_THAT(hoth->sendHostCommand(testLockCBKSlotCmdSlot1),
ContainerEq(fakeHostCmdResponse));
EXPECT_THAT(hoth->sendHostCommand(testLockCBKSlotCmdSlot2),
ContainerEq(bannedRsp));
EXPECT_THAT(hoth->sendHostCommand(testIncCBKCmdSlot1),
ContainerEq(fakeHostCmdResponse));
EXPECT_THAT(hoth->sendHostCommand(testIncCBKCmdSlot2),
ContainerEq(bannedRsp));
EXPECT_THAT(hoth->sendHostCommand(testProdUnwrapCmdSlot1),
ContainerEq(fakeHostCmdResponse));
EXPECT_THAT(hoth->sendHostCommand(testProdUnwrapCmdSlot2),
ContainerEq(bannedRsp));
EXPECT_THAT(hoth->sendHostCommand(testLoadTokenCmd),
ContainerEq(bannedRsp));
EXPECT_THAT(hoth->sendTrustedHostCommand(testLoadTokenCmd),
ContainerEq(fakeHostCmdResponse));
}
TEST_F(HothCommandTest, nonCriticalCommand)
{
EXPECT_CALL(hostCmd, sendCommand(ContainerEq(testNonCriticalCmd)))
.WillRepeatedly(Return(fakeHostCmdResponse));
EXPECT_CALL(hostCmd, sendCommandAsync(ContainerEq(testNonCriticalCmd)))
.WillRepeatedly(Return(0));
// Non critical commands are allowed
EXPECT_THAT(hoth->sendHostCommand(testNonCriticalCmd),
ContainerEq(fakeHostCmdResponse));
EXPECT_EQ(hoth->sendHostCommandAsync(testNonCriticalCmd), 0);
EXPECT_THAT(hoth->sendTrustedHostCommand(testNonCriticalCmd),
ContainerEq(fakeHostCmdResponse));
}
class HothEcUtilTest : public HothTest
{};
class HothEcUtilTimingTest : public HothEcUtilTest
{
protected:
ec_response_statistics statistic;
};
TEST_F(HothEcUtilTimingTest, timingNotIncludedFails)
{
statistic.valid_words = 10;
EXPECT_CALL(ecUtil, getHothStatistics()).WillOnce(Return(statistic));
EXPECT_THROW(hoth->getTotalBootTime(), ExpectedInfoNotFound);
}
TEST_F(HothEcUtilTimingTest, timingInfoGood)
{
statistic.valid_words = 18;
EXPECT_CALL(ecUtil, getHothStatistics()).WillOnce(Return(statistic));
EXPECT_NO_THROW(hoth->getTotalBootTime());
}