| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "fastboot.h" |
| |
| #include <lib/abr/abr.h> |
| #include <lib/abr/data.h> |
| #include <lib/abr/util.h> |
| #include <lib/fastboot/test/test-transport.h> |
| #include <lib/zbitl/view.h> |
| #include <lib/zircon_boot/test/mock_zircon_boot_ops.h> |
| #include <lib/zircon_boot/zbi_utils.h> |
| #include <sparse_format.h> |
| |
| #include "zircon/hw/gpt.h" |
| |
| #undef error // Macro from sparse_format.h that interferes with other symbols. |
| |
| #include <algorithm> |
| #include <cctype> |
| #include <numeric> |
| #include <vector> |
| |
| #include <fbl/vector.h> |
| #include <gtest/gtest.h> |
| |
| #include "backends.h" |
| #include "boot_zbi_items.h" |
| #include "gmock/gmock.h" |
| #include "gpt.h" |
| #include "lib/fit/internal/result.h" |
| #include "lib/fit/result.h" |
| #include "mock_boot_service.h" |
| #include "mock_efi_variables.h" |
| #include "partition.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| #include "utils.h" |
| |
| // For "ABC"sv literal string view operator |
| using namespace std::literals; |
| |
| using ::testing::_; |
| using ::testing::DoAll; |
| using ::testing::ElementsAreArray; |
| using ::testing::IsEmpty; |
| using ::testing::Return; |
| using ::testing::SetArgReferee; |
| using ::testing::StrEq; |
| |
| namespace gigaboot { |
| |
| class PartitionCustomizer { |
| public: |
| static std::span<const PartitionMap::PartitionEntry> PARTITION_SPAN; |
| |
| explicit PartitionCustomizer(std::span<const PartitionMap::PartitionEntry> span) { |
| old_span_ = PARTITION_SPAN; |
| PARTITION_SPAN = span; |
| } |
| |
| ~PartitionCustomizer() { PARTITION_SPAN = old_span_; } |
| |
| private: |
| std::span<const PartitionMap::PartitionEntry> old_span_; |
| }; |
| |
| std::span<const PartitionMap::PartitionEntry> PartitionCustomizer::PARTITION_SPAN = {}; |
| |
| const std::span<const PartitionMap::PartitionEntry> GetPartitionCustomizations() { |
| return PartitionCustomizer::PARTITION_SPAN; |
| } |
| |
| namespace { |
| |
| class TestTcpTransport : public TcpTransportInterface { |
| public: |
| // Add data to the input stream. |
| void AddInputData(const void* data, size_t size) { |
| const uint8_t* start = static_cast<const uint8_t*>(data); |
| in_data_.insert(in_data_.end(), start, start + size); |
| } |
| |
| // Add a fastboot packet (length-prefixed byte sequence) to input stream. |
| void AddFastbootPacket(const void* data, uint64_t size) { |
| uint64_t be_size = ToBigEndian(size); |
| AddInputData(&be_size, sizeof(be_size)); |
| AddInputData(data, size); |
| } |
| |
| bool Read(void* out, size_t size) override { |
| if (offset_ + size > in_data_.size()) { |
| return false; |
| } |
| |
| memcpy(out, in_data_.data() + offset_, size); |
| offset_ += size; |
| return true; |
| } |
| |
| bool Write(const void* data, size_t size) override { |
| const uint8_t* start = static_cast<const uint8_t*>(data); |
| out_data_.insert(out_data_.end(), start, start + size); |
| return true; |
| } |
| |
| const std::vector<uint8_t>& GetOutData() { return out_data_; } |
| |
| void PopOutput(size_t size) { |
| ASSERT_GE(out_data_.size(), size); |
| out_data_ = std::vector<uint8_t>(out_data_.begin() + size, out_data_.end()); |
| } |
| |
| void PopAndCheckOutput(std::string_view expected) { |
| ASSERT_GE(out_data_.size(), expected.size()); |
| std::string_view actual(reinterpret_cast<char*>(out_data_.data()), expected.size()); |
| ASSERT_EQ(actual, expected); |
| PopOutput(expected.size()); |
| } |
| |
| private: |
| size_t offset_ = 0; |
| std::vector<uint8_t> in_data_; |
| std::vector<uint8_t> out_data_; |
| }; |
| |
| constexpr size_t kDownloadBufferSize = 8192; |
| uint8_t download_buffer[kDownloadBufferSize]; |
| |
| void CheckPacketsEqual(const fastboot::Packets& lhs, const fastboot::Packets& rhs) { |
| ASSERT_EQ(lhs.size(), rhs.size()); |
| for (size_t i = 0; i < lhs.size(); i++) { |
| ASSERT_EQ(lhs[i], rhs[i]); |
| } |
| } |
| |
| TEST(FastbootTest, FastbootContinueTest) { |
| Fastboot fastboot(download_buffer, ZirconBootOps()); |
| fastboot::TestTransport transport; |
| transport.AddInPacket(std::string("continue")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| |
| ASSERT_EQ(transport.GetOutPackets().size(), 1ULL); |
| ASSERT_EQ(transport.GetOutPackets().back(), "OKAY"); |
| ASSERT_TRUE(fastboot.IsContinue()); |
| } |
| |
| struct BasicVarTestCase { |
| const std::string_view var; |
| const std::string_view expected_val; |
| }; |
| |
| using BasicVarTest = ::testing::TestWithParam<BasicVarTestCase>; |
| |
| TEST_P(BasicVarTest, TestBasicVar) { |
| BasicVarTestCase const& test_case = GetParam(); |
| |
| MockZirconBootOps zb_ops; |
| Fastboot fastboot(download_buffer, zb_ops.GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| |
| std::string command("getvar:"); |
| command += test_case.var; |
| transport.AddInPacket(command); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| |
| ASSERT_NO_FATAL_FAILURE( |
| CheckPacketsEqual(transport.GetOutPackets(), |
| fastboot::Packets{std::string("OKAY").append(test_case.expected_val)})); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(FastbootGetVarsTests, BasicVarTest, |
| testing::ValuesIn<BasicVarTest::ParamType>({ |
| {"slot-count", "2"}, |
| {"slot-suffixes", "a,b"}, |
| {"max-download-size", "0x00002000"}, |
| {"hw-revision", BOARD_NAME}, |
| {"version", "0.4"}, |
| }), |
| [](testing::TestParamInfo<BasicVarTest::ParamType> const& info) { |
| const std::string_view& v = info.param.var; |
| std::string name; |
| std::transform( |
| v.cbegin(), v.cend(), std::back_inserter(name), |
| [](unsigned char c) { return std::isalnum(c) ? c : '_'; }); |
| return name; |
| }); |
| |
| TEST(FastbootTcpSessionTest, ExitOnFastbootContinue) { |
| Fastboot fastboot(download_buffer, ZirconBootOps()); |
| TestTcpTransport transport; |
| |
| // Handshake message |
| const char handshake_message[] = "FB01"; |
| transport.AddInputData(handshake_message, strlen(handshake_message)); |
| |
| // fastboot continue |
| const char fastboot_cmd_continue[] = "continue"; |
| transport.AddFastbootPacket(fastboot_cmd_continue, strlen(fastboot_cmd_continue)); |
| |
| // Add another packet, which should not be processed. |
| const char fastboot_cmd_not_processed[] = "not-processed"; |
| transport.AddFastbootPacket(fastboot_cmd_not_processed, strlen(fastboot_cmd_not_processed)); |
| |
| FastbootTcpSession(transport, fastboot); |
| |
| // API should return the same "FB01" |
| ASSERT_NO_FATAL_FAILURE(transport.PopAndCheckOutput("FB01")); |
| |
| // Continue should return "OKAY" |
| ASSERT_NO_FATAL_FAILURE( |
| transport.PopAndCheckOutput("\x00\x00\x00\x00\x00\x00\x00\x04" |
| "OKAY"sv)); |
| |
| // The 'not-processed' command packet should not be processed. We shouldn't get |
| // any new data in the output. (in this case it will be a failure message if processed). |
| ASSERT_TRUE(transport.GetOutData().empty()); |
| } |
| |
| TEST(FastbootTcpSessionTest, HandshakeFailsNotFB) { |
| Fastboot fastboot(download_buffer, ZirconBootOps()); |
| TestTcpTransport transport; |
| |
| // Handshake message |
| const char handshake_message[] = "AC01"; |
| transport.AddInputData(handshake_message, strlen(handshake_message)); |
| |
| FastbootTcpSession(transport, fastboot); |
| |
| // API should write the same "FB01" no matter what is received |
| ASSERT_NO_FATAL_FAILURE(transport.PopAndCheckOutput("FB01")); |
| |
| // Nothing should have been written. |
| ASSERT_TRUE(transport.GetOutData().empty()); |
| } |
| |
| TEST(FastbootTcpSessionTest, HandshakeFailsNotNumericVersion) { |
| Fastboot fastboot(download_buffer, ZirconBootOps()); |
| TestTcpTransport transport; |
| |
| // Handshake message |
| const char handshake_message[] = "FBxx"; |
| transport.AddInputData(handshake_message, strlen(handshake_message)); |
| |
| FastbootTcpSession(transport, fastboot); |
| |
| // API should write the same "FB01" no matter what is received |
| ASSERT_NO_FATAL_FAILURE(transport.PopAndCheckOutput("FB01")); |
| |
| // Nothing should have been written. |
| ASSERT_TRUE(transport.GetOutData().empty()); |
| } |
| |
| TEST(FastbootTcpSessionTest, ExitWhenNoMoreData) { |
| Fastboot fastboot(download_buffer, ZirconBootOps()); |
| TestTcpTransport transport; |
| |
| // Handshake message |
| const char handshake_message[] = "FB01"; |
| transport.AddInputData(handshake_message, strlen(handshake_message)); |
| |
| FastbootTcpSession(transport, fastboot); |
| |
| // API should returns the same "FB01" |
| ASSERT_NO_FATAL_FAILURE(transport.PopAndCheckOutput("FB01")); |
| |
| // No more data should be written. |
| ASSERT_TRUE(transport.GetOutData().empty()); |
| } |
| |
| TEST(FastbootTcpSessionTest, ExitOnCommandFailure) { |
| Fastboot fastboot(download_buffer, ZirconBootOps()); |
| TestTcpTransport transport; |
| |
| // Handshake message |
| const char handshake_message[] = "FB01"; |
| transport.AddInputData(handshake_message, strlen(handshake_message)); |
| |
| // fastboot continue |
| const char fastboot_cmd_continue[] = "unknown-cmd"; |
| transport.AddFastbootPacket(fastboot_cmd_continue, strlen(fastboot_cmd_continue)); |
| |
| FastbootTcpSession(transport, fastboot); |
| |
| // Check and skip handshake message |
| ASSERT_NO_FATAL_FAILURE(transport.PopAndCheckOutput("FB01")); |
| // Skips 8- bytes length prefix |
| ASSERT_NO_FATAL_FAILURE(transport.PopOutput(8)); |
| ASSERT_NO_FATAL_FAILURE(transport.PopAndCheckOutput("FAIL")); |
| } |
| |
| class FastbootFlashTest : public ::testing::Test { |
| public: |
| FastbootFlashTest() |
| : image_device_({"path-A", "path-B", "path-C", "image"}), |
| block_device_({"path-A", "path-B", "path-C"}, 1024) { |
| stub_service_.AddDevice(&image_device_); |
| |
| // Add a block device for fastboot flash test. |
| stub_service_.AddDevice(&block_device_); |
| block_device_.InitializeGpt(); |
| } |
| |
| void AddPartition(const gpt_entry_t& new_entry) { block_device_.AddGptPartition(new_entry); } |
| |
| uint8_t* BlockDeviceStart() { return block_device_.fake_disk_io_protocol().contents(0).data(); } |
| |
| // A helper to download data to fastboot |
| void DownloadData(Fastboot& fastboot, const std::vector<uint8_t>& download_content) { |
| char download_command[fastboot::kMaxCommandPacketSize]; |
| snprintf(download_command, fastboot::kMaxCommandPacketSize, "download:%08zx", |
| download_content.size()); |
| fastboot::TestTransport transport; |
| transport.AddInPacket(std::string(download_command)); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| // Download |
| transport.AddInPacket(download_content); |
| ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| |
| char expected_data_message[fastboot::kMaxCommandPacketSize]; |
| snprintf(expected_data_message, fastboot::kMaxCommandPacketSize, "DATA%08zx", |
| download_content.size()); |
| CheckPacketsEqual(transport.GetOutPackets(), { |
| expected_data_message, |
| "OKAY", |
| }); |
| } |
| |
| MockStubService& stub_service() { return stub_service_; } |
| Device& image_device() { return image_device_; } |
| BlockDevice& block_device() { return block_device_; } |
| MockZirconBootOps& mock_zb_ops() { return mock_zb_ops_; } |
| |
| private: |
| MockStubService stub_service_; |
| Device image_device_; |
| BlockDevice block_device_; |
| MockZirconBootOps mock_zb_ops_; |
| }; |
| |
| class FastbootSlotTest : public ::testing::Test { |
| public: |
| void InitializeAbr(AbrSlotIndex slot) { |
| mock_zb_ops_.AddPartition(GPT_DURABLE_BOOT_NAME, sizeof(AbrData)); |
| ZirconBootOps zb_ops = mock_zb_ops_.GetZirconBootOps(); |
| AbrOps abr_ops = GetAbrOpsFromZirconBootOps(&zb_ops); |
| AbrResult res; |
| |
| if (slot == kAbrSlotIndexR) { |
| res = AbrMarkSlotUnbootable(&abr_ops, kAbrSlotIndexA, kAbrUnbootableReasonNone); |
| ASSERT_EQ(res, kAbrResultOk); |
| res = AbrMarkSlotUnbootable(&abr_ops, kAbrSlotIndexB, kAbrUnbootableReasonNone); |
| ASSERT_EQ(res, kAbrResultOk); |
| } else { |
| res = AbrMarkSlotActive(&abr_ops, slot); |
| ASSERT_EQ(res, kAbrResultOk); |
| } |
| } |
| |
| void MarkUnbootable(AbrSlotIndex slot, AbrUnbootableReason reason = kAbrUnbootableReasonNone) { |
| mock_zb_ops_.AddPartition(GPT_DURABLE_BOOT_NAME, sizeof(AbrData)); |
| ZirconBootOps zb_ops = mock_zb_ops_.GetZirconBootOps(); |
| AbrOps abr_ops = GetAbrOpsFromZirconBootOps(&zb_ops); |
| AbrResult res = AbrMarkSlotUnbootable(&abr_ops, slot, reason); |
| ASSERT_EQ(res, kAbrResultOk); |
| } |
| |
| void MarkSuccesful(AbrSlotIndex slot) { |
| mock_zb_ops_.AddPartition(GPT_DURABLE_BOOT_NAME, sizeof(AbrData)); |
| ZirconBootOps zb_ops = mock_zb_ops_.GetZirconBootOps(); |
| AbrOps abr_ops = GetAbrOpsFromZirconBootOps(&zb_ops); |
| AbrResult res = AbrMarkSlotSuccessful(&abr_ops, slot, false); |
| ASSERT_EQ(res, kAbrResultOk); |
| } |
| |
| MockZirconBootOps& mock_zb_ops() { return mock_zb_ops_; } |
| |
| private: |
| MockZirconBootOps mock_zb_ops_; |
| }; |
| |
| struct FastbootSlotTestCase { |
| AbrSlotIndex slot_index; |
| char const* slot_str; |
| }; |
| |
| class FastbootSlotAllSlotsTest : public FastbootSlotTest, |
| public testing::WithParamInterface<FastbootSlotTestCase> {}; |
| |
| TEST_P(FastbootSlotAllSlotsTest, TestFastbootGetSlot) { |
| FastbootSlotTestCase const& test_case = GetParam(); |
| |
| ZirconBootOps zb_ops = mock_zb_ops().GetZirconBootOps(); |
| Fastboot fastboot(download_buffer, zb_ops); |
| fastboot::TestTransport transport; |
| |
| InitializeAbr(test_case.slot_index); |
| transport.AddInPacket(std::string("getvar:current-slot")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| |
| fastboot::Packets expected_packets = {std::string{"OKAY"} + test_case.slot_str}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| FastbootSlotAllSlotsTests, FastbootSlotAllSlotsTest, |
| testing::ValuesIn<FastbootSlotAllSlotsTest::ParamType>({ |
| {kAbrSlotIndexA, "a"}, |
| {kAbrSlotIndexB, "b"}, |
| {kAbrSlotIndexR, "r"}, |
| }), |
| [](testing::TestParamInfo<FastbootSlotAllSlotsTest::ParamType> const& info) { |
| return info.param.slot_str; |
| }); |
| |
| using FastbootSlotABTest = FastbootSlotAllSlotsTest; |
| |
| TEST_P(FastbootSlotABTest, TestFastbootSetActive) { |
| FastbootSlotTestCase const& test_case = GetParam(); |
| |
| ZirconBootOps zb_ops = mock_zb_ops().GetZirconBootOps(); |
| Fastboot fastboot(download_buffer, zb_ops); |
| fastboot::TestTransport transport; |
| |
| InitializeAbr(kAbrSlotIndexR); |
| transport.AddInPacket(std::string{"set_active:"} + test_case.slot_str); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| |
| fastboot::Packets expected_packets = {"OKAY"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| transport.ClearOutPackets(); |
| |
| transport.AddInPacket(std::string("getvar:current-slot")); |
| ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| |
| expected_packets[0] += test_case.slot_str; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| TEST_P(FastbootSlotABTest, GetVarSlotLastSetActive) { |
| FastbootSlotTestCase const& test_case = GetParam(); |
| |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| |
| InitializeAbr(test_case.slot_index); |
| transport.AddInPacket(std::string("getvar:slot-last-set-active")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| |
| fastboot::Packets expected_packets{std::string{"OKAY"} + test_case.slot_str}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| TEST_P(FastbootSlotABTest, GetVarSlotUnbootable) { |
| FastbootSlotTestCase const& test_case = GetParam(); |
| |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| |
| MarkSuccesful(test_case.slot_index); |
| |
| std::string command = std::string{"getvar:slot-unbootable:"} + test_case.slot_str; |
| transport.AddInPacket(command); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), {"OKAYno"})); |
| |
| MarkUnbootable(test_case.slot_index); |
| transport.ClearOutPackets(); |
| transport.AddInPacket(command); |
| ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), {"OKAYyes"})); |
| } |
| |
| TEST_P(FastbootSlotABTest, GetVarSlotUnbootableReason) { |
| FastbootSlotTestCase const& test_case = GetParam(); |
| |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| |
| MarkSuccesful(test_case.slot_index); |
| |
| std::string command = std::string{"getvar:slot-unbootable-reason:"} + test_case.slot_str; |
| |
| // Bootable -> "N/A" |
| transport.AddInPacket(command); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), {"OKAYN/A"})); |
| |
| // No more tries -> "1 (no more attempts)" |
| MarkUnbootable(test_case.slot_index, kAbrUnbootableReasonNoMoreTries); |
| transport.ClearOutPackets(); |
| transport.AddInPacket(command); |
| ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| ASSERT_NO_FATAL_FAILURE( |
| CheckPacketsEqual(transport.GetOutPackets(), {"OKAY1 (no more attempts)"})); |
| } |
| |
| TEST_P(FastbootSlotABTest, GetVarSlotRetryCount) { |
| FastbootSlotTestCase const& test_case = GetParam(); |
| |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| |
| InitializeAbr(test_case.slot_index); |
| std::string command = std::string("getvar:slot-retry-count:") + test_case.slot_str; |
| transport.AddInPacket(command); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), {"OKAY7"})); |
| |
| MarkUnbootable(test_case.slot_index); |
| transport.ClearOutPackets(); |
| |
| transport.AddInPacket(command); |
| ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), {"OKAY0"})); |
| } |
| |
| TEST_P(FastbootSlotABTest, GetVarSlotSuccessful) { |
| FastbootSlotTestCase const& test_case = GetParam(); |
| |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| |
| MarkSuccesful(test_case.slot_index); |
| std::string command = std::string{"getvar:slot-successful:"} + test_case.slot_str; |
| transport.AddInPacket(command); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), {"OKAYyes"})); |
| |
| MarkUnbootable(test_case.slot_index); |
| transport.ClearOutPackets(); |
| transport.AddInPacket(command); |
| ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), {"OKAYno"})); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(FastbootSlotABTests, FastbootSlotABTest, |
| testing::ValuesIn<FastbootSlotABTest::ParamType>({ |
| {kAbrSlotIndexA, "a"}, |
| {kAbrSlotIndexB, "b"}, |
| }), |
| [](testing::TestParamInfo<FastbootSlotABTest::ParamType> const& info) { |
| return info.param.slot_str; |
| }); |
| |
| TEST_F(FastbootSlotTest, GetVarSlotSuccessfulR) { |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| InitializeAbr(kAbrSlotIndexR); |
| |
| std::string command = std::string{"getvar:slot-successful:r"}; |
| transport.AddInPacket(command); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), {"OKAYyes"})); |
| } |
| |
| TEST_F(FastbootSlotTest, GetVarSlotBootableR) { |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| InitializeAbr(kAbrSlotIndexR); |
| |
| std::string command = std::string{"getvar:slot-unbootable:r"}; |
| transport.AddInPacket(command); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), {"OKAYno"})); |
| } |
| |
| TEST_F(FastbootSlotTest, GetVarAll) { |
| ZirconBootOps zb_ops = mock_zb_ops().GetZirconBootOps(); |
| Fastboot fastboot(download_buffer, zb_ops); |
| fastboot::TestTransport transport; |
| |
| InitializeAbr(kAbrSlotIndexA); |
| transport.AddInPacket(std::string_view("getvar:all")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| |
| // Check for a few variables, making sure that we check |
| // * Compile time constant variables |
| // * Runtime dynamic variables with no extra parameters |
| // * Runtime dynamic variables with multiple possible parameters |
| const fastboot::Packets& actual = transport.GetOutPackets(); |
| EXPECT_NE(std::find(actual.begin(), actual.end(), "INFOversion:0.4"), actual.end()); |
| EXPECT_NE(std::find(actual.begin(), actual.end(), "INFOcurrent-slot:a"), actual.end()); |
| EXPECT_NE(std::find(actual.begin(), actual.end(), "INFOslot-successful:a:no"), actual.end()); |
| EXPECT_NE(std::find(actual.begin(), actual.end(), "INFOslot-successful:b:no"), actual.end()); |
| } |
| |
| TEST_F(FastbootSlotTest, GetVarAllErrors) { |
| ZirconBootOps zb_ops = mock_zb_ops().GetZirconBootOps(); |
| Fastboot fastboot(download_buffer, zb_ops); |
| fastboot::TestTransport transport; |
| |
| mock_zb_ops().RemoveAllPartitions(); |
| transport.AddInPacket(std::string_view("getvar:all")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_error()); |
| |
| // We should still get some output, but not any variables that read partitions. |
| const fastboot::Packets& actual = transport.GetOutPackets(); |
| EXPECT_EQ(std::find(actual.begin(), actual.end(), "INFOcurrent-slot:a"), actual.end()); |
| EXPECT_EQ(std::find(actual.begin(), actual.end(), "INFOcurrent-slot:b"), actual.end()); |
| |
| // Failures should give some message |
| EXPECT_NE(std::find(actual.begin(), actual.end(), "FAILFailed to get slot last set active(-1)"), |
| actual.end()); |
| |
| // We should get variables emitted before the error without a problem |
| EXPECT_NE(std::find(actual.begin(), actual.end(), "INFOmax-download-size:0x00002000"), |
| actual.end()); |
| } |
| |
| struct FastbootSetActiveErrorTestCase { |
| char const* name; |
| char const* user_str; |
| }; |
| |
| class FastbootSetActiveUserErrorTest |
| : public FastbootSlotTest, |
| public testing::WithParamInterface<FastbootSetActiveErrorTestCase> {}; |
| |
| TEST_P(FastbootSetActiveUserErrorTest, TestFastbootSetActiveUserError) { |
| FastbootSetActiveErrorTestCase const& test_case = GetParam(); |
| |
| InitializeAbr(kAbrSlotIndexR); |
| ZirconBootOps zb_ops = mock_zb_ops().GetZirconBootOps(); |
| Fastboot fastboot(download_buffer, zb_ops); |
| fastboot::TestTransport transport; |
| |
| std::string cmd = std::string{"set_active"} + test_case.user_str; |
| transport.AddInPacket(cmd); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| |
| auto sent_packets = transport.GetOutPackets(); |
| ASSERT_EQ(sent_packets.size(), 1ULL); |
| ASSERT_EQ(sent_packets[0].compare(0, 4, "FAIL"), 0); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| FastbootSetActiveUserErrorTests, FastbootSetActiveUserErrorTest, |
| testing::ValuesIn<FastbootSetActiveUserErrorTest::ParamType>({ |
| {"no_name", ""}, |
| {"no_such_name", ":squid"}, |
| {"cant_set_r", ":r"}, |
| }), |
| [](testing::TestParamInfo<FastbootSetActiveUserErrorTest::ParamType> const& info) { |
| return info.param.name; |
| }); |
| |
| TEST_F(FastbootSlotTest, SetActiveSlotWriteFailure) { |
| // Do NOT call InitializeAbr in order to simulate |
| // a write error due to a missing partition. |
| |
| ZirconBootOps zb_ops = mock_zb_ops().GetZirconBootOps(); |
| |
| Fastboot fastboot(download_buffer, zb_ops); |
| fastboot::TestTransport transport; |
| |
| transport.AddInPacket(std::string("set_active:b")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_error()); |
| auto sent_packets = transport.GetOutPackets(); |
| ASSERT_EQ(sent_packets.size(), 1ULL); |
| ASSERT_EQ(sent_packets[0].compare(0, 4, "FAIL"), 0); |
| } |
| |
| struct VarErrorTestCase { |
| char const* test_name; |
| char const* var; |
| }; |
| |
| using VarErrorTest = ::testing::TestWithParam<VarErrorTestCase>; |
| |
| TEST_P(VarErrorTest, TestVarError) { |
| VarErrorTestCase const& test_case = GetParam(); |
| |
| MockZirconBootOps mock_zb_ops; |
| Fastboot fastboot(download_buffer, mock_zb_ops.GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| std::string command = std::string{"getvar:"} + test_case.var; |
| |
| transport.AddInPacket(command); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| |
| auto sent_packets = transport.GetOutPackets(); |
| ASSERT_EQ(sent_packets.size(), 1ULL); |
| ASSERT_EQ(sent_packets[0].compare(0, 4, "FAIL"), 0); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(VarErrorTests, VarErrorTest, |
| testing::ValuesIn<VarErrorTest::ParamType>({ |
| // slot-retry-count |
| {"slot_retry_count_too_few_args", "slot-retry-count"}, |
| {"slot_retry_count_invalid_name", "slot-retry-count:r"}, |
| {"slot_retry_count_read_error", "slot-retry-count:a"}, |
| // slot-successful |
| {"slot_successful_too_few_args", "slot-successful"}, |
| {"slot_successful_invalid_name", "slot-successful:squid"}, |
| {"slot_successful_read_error", "slot-successful:a"}, |
| // slot-last-set-actvie |
| {"slot_last_set_active_read_error", "slot-last-set-active"}, |
| // slot-unbootable |
| {"slot_unbootable_too_few_args", "slot-unbootable"}, |
| {"slot_unbootable_invalid_name", "slot-unbootable:squid"}, |
| {"slot_unbootable_read_error", "slot-unbootable:a"}, |
| // nonexistent variable |
| {"nonexistent_variable", "non-existing"}, |
| // too few args |
| {"no_var", ""}, |
| }), |
| [](testing::TestParamInfo<VarErrorTest::ParamType> const& info) { |
| return info.param.test_name; |
| }); |
| |
| constexpr gpt_entry_t GenerateBasicGptEntry(const char* name, size_t partition_size) { |
| gpt_entry_t entry = { |
| // The GUID and flags aren't important for the lookup. |
| .first = kGptFirstUsableBlocks, |
| .last = kGptFirstUsableBlocks + DivideRoundUp(partition_size, kBlockSize), |
| }; |
| SetGptEntryName(name, entry); |
| return entry; |
| } |
| |
| TEST_F(FastbootFlashTest, FlashPartition) { |
| constexpr size_t partition_size = 0x100; |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| AddPartition(GenerateBasicGptEntry(GPT_ZIRCON_A_NAME, partition_size)); |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| |
| // Download some data to flash to the partition. |
| std::vector<uint8_t> download_content(partition_size); |
| std::iota(download_content.begin(), download_content.end(), 0); |
| ASSERT_NO_FATAL_FAILURE(DownloadData(fastboot, download_content)); |
| |
| fastboot::TestTransport transport; |
| |
| transport.AddInPacket(std::string("flash:") + GPT_ZIRCON_A_NAME); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| |
| fastboot::Packets expected_packets = {"OKAY"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| |
| uint8_t read_buf[partition_size] = {0}; |
| auto gpt_device = FindEfiGptDevice(); |
| ASSERT_TRUE(gpt_device.is_ok()); |
| ASSERT_TRUE(gpt_device->ReadPartition(GPT_ZIRCON_A_NAME, 0, sizeof(read_buf), read_buf).is_ok()); |
| ASSERT_TRUE(memcmp(read_buf, download_content.data(), download_content.size()) == 0); |
| } |
| |
| TEST_F(FastbootFlashTest, DownloadTooLarge) { |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| |
| char download_command[fastboot::kMaxCommandPacketSize]; |
| snprintf(download_command, fastboot::kMaxCommandPacketSize, "download:%08zx", |
| kDownloadBufferSize + 1); |
| |
| fastboot::TestTransport transport; |
| transport.AddInPacket(std::string(download_command)); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_error()); |
| auto sent_packets = transport.GetOutPackets(); |
| ASSERT_EQ(sent_packets.size(), 1ULL); |
| ASSERT_EQ(sent_packets[0].compare(0, 4, "FAIL"), 0); |
| } |
| |
| TEST_F(FastbootFlashTest, FlashPartitionFailedToWritePartition) { |
| // Do NOT add any partitions. Write should fail. |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| |
| // Download some data to flash to the partition. |
| std::vector<uint8_t> download_content(128, 0); |
| ASSERT_NO_FATAL_FAILURE(DownloadData(fastboot, download_content)); |
| |
| fastboot::TestTransport transport; |
| |
| transport.AddInPacket(std::string("flash:") + GPT_ZIRCON_A_NAME); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_error()); |
| |
| // Should fail while searching for gpt device. |
| auto sent_packets = transport.GetOutPackets(); |
| ASSERT_EQ(sent_packets.size(), 1ULL); |
| ASSERT_EQ(sent_packets[0].compare(0, 4, "FAIL"), 0); |
| } |
| |
| template <typename T> |
| void AddBytes(std::vector<uint8_t>& vec, const T& data) { |
| const uint8_t* data_bytes = reinterpret_cast<const uint8_t*>(&data); |
| vec.insert(vec.end(), data_bytes, data_bytes + sizeof(data)); |
| } |
| |
| std::vector<uint8_t> SetupSparseImage(size_t partition_size, size_t chunk_blocks) { |
| std::vector<uint8_t> buffer; |
| buffer.reserve((sizeof(sparse_header_t) + sizeof(chunk_header_t) + sizeof(uint32_t))); |
| sparse_header_t header = { |
| SPARSE_HEADER_MAGIC, |
| 1, |
| 0, |
| sizeof(sparse_header_t), |
| sizeof(chunk_header_t), |
| static_cast<uint32_t>(partition_size), |
| 1, |
| 1, |
| 0xDEADBEEF, |
| }; |
| AddBytes(buffer, header); |
| chunk_header_t chunk = { |
| CHUNK_TYPE_FILL, |
| 0, |
| static_cast<uint16_t>(chunk_blocks), |
| sizeof(chunk_header_t) + sizeof(uint32_t), |
| }; |
| AddBytes(buffer, chunk); |
| uint32_t payload = 0xFAFAFAFA; |
| AddBytes(buffer, payload); |
| |
| return buffer; |
| } |
| |
| TEST_F(FastbootFlashTest, FlashSparseImage) { |
| constexpr size_t partition_size = 0x100; |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| AddPartition(GenerateBasicGptEntry(GPT_ZIRCON_A_NAME, partition_size)); |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| |
| std::vector<uint8_t> sparse_image = SetupSparseImage(partition_size, 1); |
| |
| ASSERT_NO_FATAL_FAILURE(DownloadData(fastboot, sparse_image)); |
| |
| fastboot::TestTransport transport; |
| |
| transport.AddInPacket(std::string("flash:") + GPT_ZIRCON_A_NAME); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), {"OKAY"})); |
| |
| std::vector<uint8_t> actual(partition_size); |
| auto gpt_device = FindEfiGptDevice(); |
| ASSERT_TRUE(gpt_device.is_ok()); |
| ASSERT_TRUE( |
| gpt_device->ReadPartition(GPT_ZIRCON_A_NAME, 0, actual.size(), actual.data()).is_ok()); |
| |
| std::vector<uint8_t> expected(partition_size, 0xFA); |
| ASSERT_EQ(actual, expected); |
| } |
| |
| TEST_F(FastbootFlashTest, FlashSparseImageOldName) { |
| constexpr size_t partition_size = 0x100; |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| AddPartition(GenerateBasicGptEntry(GUID_FVM_NAME, partition_size)); |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| |
| std::vector<uint8_t> sparse_image = SetupSparseImage(partition_size, 1); |
| |
| ASSERT_NO_FATAL_FAILURE(DownloadData(fastboot, sparse_image)); |
| |
| fastboot::TestTransport transport; |
| |
| transport.AddInPacket(std::string("flash:") + GPT_FVM_NAME); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), {"OKAY"})); |
| |
| std::vector<uint8_t> actual(partition_size); |
| auto gpt_device = FindEfiGptDevice(); |
| ASSERT_TRUE(gpt_device.is_ok()); |
| ASSERT_TRUE(gpt_device->ReadPartition(GUID_FVM_NAME, 0, actual.size(), actual.data()).is_ok()); |
| |
| std::vector<uint8_t> expected(partition_size, 0xFA); |
| ASSERT_EQ(actual, expected); |
| } |
| |
| TEST_F(FastbootFlashTest, FlashSparseImageNoSuchPartition) { |
| constexpr size_t partition_size = 0x100; |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| AddPartition(GenerateBasicGptEntry(GPT_ZIRCON_A_NAME, partition_size)); |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| |
| std::vector<uint8_t> sparse_image = SetupSparseImage(partition_size, 1); |
| |
| ASSERT_NO_FATAL_FAILURE(DownloadData(fastboot, sparse_image)); |
| |
| fastboot::TestTransport transport; |
| |
| transport.AddInPacket(std::string("flash:") + "nonexistant"); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_error()); |
| } |
| |
| TEST_F(FastbootFlashTest, FlashSparseImageTooBig) { |
| constexpr size_t partition_size = 0x100; |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| AddPartition(GenerateBasicGptEntry(GPT_ZIRCON_A_NAME, partition_size)); |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| |
| std::vector<uint8_t> sparse_image = SetupSparseImage(partition_size, 0x100); |
| |
| ASSERT_NO_FATAL_FAILURE(DownloadData(fastboot, sparse_image)); |
| |
| fastboot::TestTransport transport; |
| |
| transport.AddInPacket(std::string("flash:") + GPT_ZIRCON_A_NAME); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_error()); |
| } |
| |
| // TODO(b/235489025): Extends `StubBootServices` to cover the mock of efi_runtime_services. |
| EFIAPI efi_status ResetSystemSucceed(efi_reset_type, efi_status, size_t, void*) { |
| return EFI_SUCCESS; |
| } |
| |
| class EfiBootbyteOwner { |
| public: |
| EfiBootbyteOwner() { |
| EFI_RETVAL = 0; |
| DATA = 0; |
| } |
| |
| EfiBootbyteOwner(efi_status status, RebootMode mode) { |
| EFI_RETVAL = status; |
| DATA = static_cast<uint8_t>(mode); |
| } |
| |
| static EFIAPI efi_status GetVariable(char16_t* var_name, efi_guid* vendor_guid, |
| uint32_t* attributes, size_t* data_size, void* data) { |
| *data_size = sizeof(DATA); |
| *reinterpret_cast<uint8_t*>(data) = DATA; |
| |
| return EFI_RETVAL; |
| } |
| |
| static EFIAPI efi_status SetVariable(char16_t* var_name, efi_guid* vendor_guid, |
| uint32_t attributes, size_t data_size, const void* data) { |
| DATA = *reinterpret_cast<uint8_t const*>(data); |
| |
| return EFI_RETVAL; |
| } |
| |
| ~EfiBootbyteOwner() { |
| DATA = 0; |
| EFI_RETVAL = EFI_SUCCESS; |
| } |
| |
| private: |
| static uint8_t DATA; |
| static efi_status EFI_RETVAL; |
| }; |
| |
| uint8_t EfiBootbyteOwner::DATA = 0; |
| efi_status EfiBootbyteOwner::EFI_RETVAL = 0; |
| |
| TEST_F(FastbootFlashTest, RebootNormal) { |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| EfiBootbyteOwner efi_var; |
| efi_runtime_services runtime_services{ |
| .GetVariable = efi_var.GetVariable, |
| .SetVariable = efi_var.SetVariable, |
| .ResetSystem = ResetSystemSucceed, |
| }; |
| gEfiSystemTable->RuntimeServices = &runtime_services; |
| |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| mock_zb_ops().AddPartition(GPT_DURABLE_BOOT_NAME, sizeof(AbrData)); |
| |
| // Set to a different initial boot mode. |
| ASSERT_TRUE(SetRebootMode(RebootMode::kBootloader)); |
| |
| transport.AddInPacket(std::string("reboot")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| std::vector<std::string> expected_packets = {"OKAY"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| |
| AbrOps abr_ops = mock_zb_ops().GetAbrOps(); |
| AbrDataOneShotFlags one_shot_flags; |
| ASSERT_EQ(AbrGetAndClearOneShotFlags(&abr_ops, &one_shot_flags), kAbrResultOk); |
| std::optional<RebootMode> mode_option = GetOneShotRebootMode(one_shot_flags); |
| ASSERT_TRUE(mode_option); |
| ASSERT_EQ(*mode_option, RebootMode::kNormal); |
| } |
| |
| TEST_F(FastbootFlashTest, RebootBootloader) { |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| EfiBootbyteOwner efi_var; |
| efi_runtime_services runtime_services{ |
| .GetVariable = efi_var.GetVariable, |
| .SetVariable = efi_var.SetVariable, |
| .ResetSystem = ResetSystemSucceed, |
| }; |
| gEfiSystemTable->RuntimeServices = &runtime_services; |
| |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| mock_zb_ops().AddPartition(GPT_DURABLE_BOOT_NAME, sizeof(AbrData)); |
| |
| // Set to a different initial boot mode. |
| ASSERT_TRUE(SetRebootMode(RebootMode::kNormal)); |
| |
| transport.AddInPacket(std::string("reboot-bootloader")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| std::vector<std::string> expected_packets = {"OKAY"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| |
| AbrOps abr_ops = mock_zb_ops().GetAbrOps(); |
| AbrDataOneShotFlags one_shot_flags; |
| ASSERT_EQ(AbrGetAndClearOneShotFlags(&abr_ops, &one_shot_flags), kAbrResultOk); |
| std::optional<RebootMode> mode_option = GetOneShotRebootMode(one_shot_flags); |
| ASSERT_TRUE(mode_option); |
| ASSERT_EQ(*mode_option, RebootMode::kBootloader); |
| } |
| |
| TEST_F(FastbootFlashTest, RebootRecovery) { |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| EfiBootbyteOwner efi_var; |
| efi_runtime_services runtime_services{ |
| .GetVariable = efi_var.GetVariable, |
| .SetVariable = efi_var.SetVariable, |
| .ResetSystem = ResetSystemSucceed, |
| }; |
| gEfiSystemTable->RuntimeServices = &runtime_services; |
| |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| mock_zb_ops().AddPartition(GPT_DURABLE_BOOT_NAME, sizeof(AbrData)); |
| |
| // Set to a different initial boot mode. |
| ASSERT_TRUE(SetRebootMode(RebootMode::kNormal)); |
| |
| transport.AddInPacket(std::string("reboot-recovery")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| std::vector<std::string> expected_packets = {"OKAY"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| |
| AbrOps abr_ops = mock_zb_ops().GetAbrOps(); |
| AbrDataOneShotFlags one_shot_flags; |
| ASSERT_EQ(AbrGetAndClearOneShotFlags(&abr_ops, &one_shot_flags), kAbrResultOk); |
| std::optional<RebootMode> mode_option = GetOneShotRebootMode(one_shot_flags); |
| ASSERT_TRUE(mode_option); |
| ASSERT_EQ(*mode_option, RebootMode::kRecovery); |
| } |
| |
| TEST_F(FastbootFlashTest, RebootSetRebootModeFail) { |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| |
| EfiBootbyteOwner efi_var(EFI_DEVICE_ERROR, RebootMode::kNormal); |
| efi_runtime_services runtime_services{ |
| .GetVariable = efi_var.GetVariable, |
| .SetVariable = efi_var.SetVariable, |
| .ResetSystem = ResetSystemSucceed, |
| }; |
| gEfiSystemTable->RuntimeServices = &runtime_services; |
| |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| |
| transport.AddInPacket(std::string("reboot")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_error()); |
| |
| auto sent_packets = transport.GetOutPackets(); |
| ASSERT_EQ(sent_packets.size(), 1ULL); |
| ASSERT_EQ(sent_packets[0].compare(0, 4, "FAIL"), 0); |
| } |
| |
| EFIAPI efi_status ResetSystemFailed(efi_reset_type, efi_status, size_t, void*) { |
| return EFI_ABORTED; |
| } |
| |
| TEST_F(FastbootFlashTest, RebootResetSystemFail) { |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| EfiBootbyteOwner efi_var; |
| efi_runtime_services runtime_services{ |
| .GetVariable = efi_var.GetVariable, |
| .SetVariable = efi_var.SetVariable, |
| .ResetSystem = ResetSystemFailed, |
| }; |
| gEfiSystemTable->RuntimeServices = &runtime_services; |
| |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| |
| transport.AddInPacket(std::string("reboot")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_error()); |
| |
| // We should still receive an OKAY packet. |
| std::vector<std::string> expected_packets = {"OKAY"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| TEST_F(FastbootFlashTest, GptReinitialize) { |
| PartitionMap::PartitionEntry custom_partitions[] = { |
| {GPT_DURABLE_BOOT_NAME, 0x1000, GPT_DURABLE_BOOT_TYPE_GUID}, |
| {GPT_FVM_NAME, SIZE_MAX, GPT_FVM_TYPE_GUID}, |
| }; |
| PartitionCustomizer customizer(custom_partitions); |
| |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| |
| auto res = FindEfiGptDevice(); |
| ASSERT_TRUE(res.is_ok()); |
| EfiGptBlockDevice gpt_device = std::move(res.value()); |
| |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| |
| transport.AddInPacket(std::string("oem gpt-init")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), {"OKAY"})); |
| |
| // Check the durable_boot partition |
| gpt_entry_t const* durable_boot_entry = gpt_device.FindPartition("durable_boot"); |
| ASSERT_NE(durable_boot_entry, nullptr); |
| |
| PartitionMap::PartitionEntry const& durable_boot_partition = custom_partitions[0]; |
| size_t durable_boot_size_bytes = |
| (durable_boot_entry->last - durable_boot_entry->first + 1) * gpt_device.BlockSize(); |
| |
| ASSERT_EQ(durable_boot_size_bytes, durable_boot_partition.min_size_bytes); |
| ASSERT_TRUE(memcmp(durable_boot_entry->type, durable_boot_partition.type_guid, |
| sizeof(durable_boot_partition.type_guid)) == 0); |
| |
| // Check the fvm partition |
| gpt_entry_t const* fvm_entry = gpt_device.FindPartition("fvm"); |
| ASSERT_NE(fvm_entry, nullptr); |
| |
| PartitionMap::PartitionEntry const& fvm_partition = *(std::end(custom_partitions) - 1); |
| |
| // The fvm partition takes all remaining space on disk, |
| // so its last block is the block right before the backup GPT. |
| ASSERT_EQ(fvm_entry->last, gpt_device.GptHeader().last); |
| ASSERT_TRUE(memcmp(fvm_entry->type, fvm_partition.type_guid, sizeof(fvm_partition.type_guid)) == |
| 0); |
| |
| auto names = gpt_device.ListPartitionNames(); |
| ASSERT_EQ(names.size(), 2UL); |
| ASSERT_EQ(names[0].data(), custom_partitions[0].name); |
| ASSERT_EQ(names[1].data(), custom_partitions[1].name); |
| } |
| |
| TEST_F(FastbootFlashTest, GptReinitializeNoMaxSize) { |
| PartitionMap::PartitionEntry custom_partitions[] = { |
| {GPT_DURABLE_BOOT_NAME, 0x1000, GPT_DURABLE_BOOT_TYPE_GUID}, |
| {GPT_FACTORY_NAME, 0x1000, GPT_FACTORY_TYPE_GUID}, |
| }; |
| PartitionCustomizer customizer(custom_partitions); |
| |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| |
| auto res = FindEfiGptDevice(); |
| ASSERT_TRUE(res.is_ok()); |
| EfiGptBlockDevice gpt_device = std::move(res.value()); |
| |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| |
| transport.AddInPacket(std::string("oem gpt-init")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), {"OKAY"})); |
| |
| // Check the durable_boot partition |
| gpt_entry_t const* durable_boot_entry = gpt_device.FindPartition("durable_boot"); |
| ASSERT_NE(durable_boot_entry, nullptr); |
| |
| PartitionMap::PartitionEntry const& durable_boot_partition = custom_partitions[0]; |
| size_t durable_boot_size_bytes = |
| (durable_boot_entry->last - durable_boot_entry->first + 1) * gpt_device.BlockSize(); |
| |
| ASSERT_EQ(durable_boot_size_bytes, durable_boot_partition.min_size_bytes); |
| ASSERT_TRUE(memcmp(durable_boot_entry->type, durable_boot_partition.type_guid, |
| sizeof(durable_boot_partition.type_guid)) == 0); |
| |
| // Check the durable partition |
| gpt_entry_t const* durable_entry = gpt_device.FindPartition("factory"); |
| ASSERT_NE(durable_entry, nullptr); |
| |
| PartitionMap::PartitionEntry const& durable_partition = custom_partitions[1]; |
| size_t durable_size_bytes = |
| (durable_entry->last - durable_entry->first + 1) * gpt_device.BlockSize(); |
| |
| ASSERT_EQ(durable_size_bytes, durable_partition.min_size_bytes); |
| ASSERT_TRUE(memcmp(durable_entry->type, durable_partition.type_guid, |
| sizeof(durable_partition.type_guid)) == 0); |
| } |
| |
| TEST_F(FastbootFlashTest, GptReinitializeTooBigPartitionsFailure) { |
| // There are only 1024 blocks in the mock disk device, |
| // which translates to 0x80000 bytes assuming 512 byte blocks. |
| PartitionMap::PartitionEntry custom_partitions[] = { |
| {GPT_DURABLE_BOOT_NAME, 0xFFFFFF, GPT_DURABLE_BOOT_TYPE_GUID}, |
| }; |
| PartitionCustomizer customizer(custom_partitions); |
| |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| |
| auto res = FindEfiGptDevice(); |
| ASSERT_TRUE(res.is_ok()); |
| EfiGptBlockDevice gpt_device = std::move(res.value()); |
| |
| // Quick check to make sure the partition will in fact be too large. |
| ASSERT_EQ(gpt_device.BlockSize(), 512UL); |
| |
| Fastboot fastboot(download_buffer, mock_zb_ops().GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| |
| transport.AddInPacket(std::string("oem gpt-init")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_error()); |
| |
| auto sent_packets = transport.GetOutPackets(); |
| ASSERT_EQ(sent_packets.size(), 1ULL); |
| ASSERT_EQ(sent_packets[0].compare(0, 4, "FAIL"), 0); |
| } |
| |
| TEST(FastbootTest, GptReinitializeDiskFailure) { |
| MockStubService stub_service; |
| Device image_device({"path-A", "path-B", "path-C", "image"}); |
| BlockDevice block_device({"path-A", "path-B", "path-D"}, 1024); |
| auto cleanup = SetupEfiGlobalState(stub_service, image_device); |
| |
| stub_service.AddDevice(&image_device); |
| stub_service.AddDevice(&block_device); |
| |
| auto res = FindEfiGptDevice(); |
| ASSERT_TRUE(res.is_error()); |
| |
| MockZirconBootOps mock_zb_ops; |
| Fastboot fastboot(download_buffer, mock_zb_ops.GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| |
| transport.AddInPacket(std::string("oem gpt-init")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_error()); |
| |
| auto sent_packets = transport.GetOutPackets(); |
| ASSERT_EQ(sent_packets.size(), 1ULL); |
| ASSERT_EQ(sent_packets[0].compare(0, 4, "FAIL"), 0); |
| } |
| |
| TEST_F(FastbootFlashTest, GptReinitializeTwoMaxPartFailure) { |
| PartitionMap::PartitionEntry custom_partitions[] = { |
| {GPT_DURABLE_BOOT_NAME, SIZE_MAX}, |
| {GPT_FACTORY_NAME, SIZE_MAX}, |
| }; |
| PartitionCustomizer customizer(custom_partitions); |
| |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| |
| auto res = FindEfiGptDevice(); |
| ASSERT_TRUE(res.is_ok()); |
| |
| MockZirconBootOps mock_zb_ops; |
| Fastboot fastboot(download_buffer, mock_zb_ops.GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| |
| transport.AddInPacket(std::string("oem gpt-init")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_error()); |
| } |
| |
| TEST_F(FastbootFlashTest, GptReinitializeMaxNotLastFailure) { |
| PartitionMap::PartitionEntry custom_partitions[] = { |
| {GPT_DURABLE_BOOT_NAME, SIZE_MAX}, |
| {GPT_FACTORY_NAME, 0x1000}, |
| }; |
| PartitionCustomizer customizer(custom_partitions); |
| |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| |
| auto res = FindEfiGptDevice(); |
| ASSERT_TRUE(res.is_ok()); |
| |
| MockZirconBootOps mock_zb_ops; |
| Fastboot fastboot(download_buffer, mock_zb_ops.GetZirconBootOps()); |
| fastboot::TestTransport transport; |
| |
| transport.AddInPacket(std::string("oem gpt-init")); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_error()); |
| } |
| |
| TEST_F(FastbootFlashTest, AddBootloaderFile) { |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| ClearBootloaderFiles(); |
| |
| MockZirconBootOps mock_zb_ops; |
| Fastboot fastboot(download_buffer, mock_zb_ops.GetZirconBootOps()); |
| |
| // Download some data to use as file content. |
| constexpr char kFileContent[] = "file content"; |
| std::vector<uint8_t> download_content(kFileContent, kFileContent + sizeof(kFileContent)); |
| ASSERT_NO_FATAL_FAILURE(DownloadData(fastboot, download_content)); |
| |
| std::string file_name = "ssh.authorized_keys"; |
| std::string command = "oem add-staged-bootloader-file " + file_name; |
| fastboot::TestTransport transport; |
| transport.AddInPacket(command); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| std::vector<std::string> expected_packets = {"OKAY"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| |
| // Extract the bootloader file content. |
| std::vector<zbitl::ByteView> items = FindItems(GetZbiFiles().data(), ZBI_TYPE_BOOTLOADER_FILE); |
| ASSERT_EQ(items.size(), 1ULL); |
| |
| // Validate file content. |
| std::string_view expected("\x13ssh.authorized_keysfile content"); |
| ASSERT_EQ(expected.size() + 1, items[0].size()); |
| ASSERT_EQ(memcmp(expected.data(), items[0].data(), expected.size()), 0); |
| } |
| |
| TEST_F(FastbootFlashTest, AddBootloaderFileNotEnoughtArguments) { |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| ClearBootloaderFiles(); |
| |
| MockZirconBootOps mock_zb_ops; |
| Fastboot fastboot(download_buffer, mock_zb_ops.GetZirconBootOps()); |
| |
| // Download some data to use as file content. |
| constexpr char kFileContent[] = "file content"; |
| std::vector<uint8_t> download_content(kFileContent, kFileContent + sizeof(kFileContent)); |
| ASSERT_NO_FATAL_FAILURE(DownloadData(fastboot, download_content)); |
| |
| std::string command = "oem add-staged-bootloader-file"; |
| fastboot::TestTransport transport; |
| transport.AddInPacket(command); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| |
| auto sent_packets = transport.GetOutPackets(); |
| ASSERT_EQ(sent_packets.size(), 1ULL); |
| ASSERT_EQ(sent_packets[0].compare(0, 4, "FAIL"), 0); |
| } |
| |
| TEST_F(FastbootFlashTest, AddBootloaderFileTooLarge) { |
| auto cleanup = SetupEfiGlobalState(stub_service(), image_device()); |
| ClearBootloaderFiles(); |
| |
| MockZirconBootOps mock_zb_ops; |
| Fastboot fastboot(download_buffer, mock_zb_ops.GetZirconBootOps()); |
| |
| // Download some data to use as file content. |
| const std::string kFileContent(kDownloadBufferSize, 'a'); |
| std::vector<uint8_t> download_content(kFileContent.begin(), kFileContent.end()); |
| ASSERT_NO_FATAL_FAILURE(DownloadData(fastboot, download_content)); |
| |
| std::string command = "oem add-staged-bootloader-file ssh.authorized_keys"; |
| fastboot::TestTransport transport; |
| transport.AddInPacket(command); |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_error()); |
| auto sent_packets = transport.GetOutPackets(); |
| ASSERT_EQ(sent_packets.size(), 1ULL); |
| ASSERT_EQ(sent_packets[0].compare(0, 4, "FAIL"), 0); |
| } |
| |
| // Fake printer for test to capture dump output. Class wrapper is |
| // to ensure that the capture doesn't leak between tests since we |
| // need to use a global singleton to hook into the raw C function. |
| class PrintCapture { |
| public: |
| static std::unique_ptr<PrintCapture> Create() { |
| if (PrintCapture::instance_) { |
| ADD_FAILURE() << "Can't create 2 PrintCapture objects at a time"; |
| return nullptr; |
| } |
| |
| PrintCapture::instance_ = new PrintCapture(); |
| return std::unique_ptr<PrintCapture>(PrintCapture::instance_); |
| } |
| |
| ~PrintCapture() { instance_ = nullptr; } |
| |
| static int PrintFunction(const char* fmt, ...) { |
| if (!instance_) { |
| ADD_FAILURE() << "No PrintCapture exists"; |
| return -1; |
| } |
| |
| va_list ap; |
| va_start(ap, fmt); |
| VPrintFunction(fmt, ap); |
| va_end(ap); |
| |
| return 0; |
| } |
| |
| static int VPrintFunction(const char* fmt, va_list ap) { |
| if (!instance_) { |
| ADD_FAILURE() << "No PrintCapture exists"; |
| return -1; |
| } |
| |
| fxl::StringVAppendf(&instance_->contents(), fmt, ap); |
| return 0; |
| } |
| |
| std::string& contents() { return contents_; } |
| |
| std::string dump_contents() { |
| std::string ret(std::move(contents_)); |
| contents_.clear(); |
| return ret; |
| } |
| |
| private: |
| // Private constructor to prevent instantiating 2 at once. |
| PrintCapture() = default; |
| |
| // Singleton to dispatch calls to. |
| static PrintCapture* instance_; |
| |
| std::string contents_; |
| }; |
| PrintCapture* PrintCapture::instance_; |
| |
| class FastbootEfiVarTest : public ::testing::Test { |
| public: |
| void SetUp() override { |
| capture = PrintCapture::Create(); |
| fastboot.SetVPrintFunction(PrintCapture::VPrintFunction); |
| } |
| |
| void TearDown() override { capture.reset(); } |
| |
| std::unique_ptr<PrintCapture> capture; |
| fastboot::TestTransport transport; |
| MockZirconBootOps mock_zb_ops; |
| MockEfiVariables mock_efi_variables; |
| Fastboot fastboot = |
| Fastboot(download_buffer, mock_zb_ops.GetZirconBootOps(), &mock_efi_variables); |
| }; |
| |
| TEST_F(FastbootEfiVarTest, EfiGetVarInfo_Error) { |
| EXPECT_CALL(mock_efi_variables, EfiQueryVariableInfo()) |
| .WillOnce(Return(fit::error(EFI_NOT_FOUND))); |
| |
| transport.AddInPacket(std::string("oem efi-getvarinfo")); |
| |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| EXPECT_THAT(capture->dump_contents(), IsEmpty()); |
| |
| ASSERT_EQ(transport.GetOutPackets().size(), 1ULL); |
| fastboot::Packets expected_packets = {"FAILQueryVariableInfo() failed"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| TEST_F(FastbootEfiVarTest, EfiGetVarInfo_Ok) { |
| EXPECT_CALL(mock_efi_variables, EfiQueryVariableInfo()) |
| .WillOnce(Return(fit::success(EfiVariables::EfiVariableInfo{0, 1, 2}))); |
| constexpr std::string_view expected_output = |
| "\n Max Storage Size: 0\n Remaining Variable Storage Size: 1\n Max Variable Size: 2\n"; |
| |
| transport.AddInPacket(std::string("oem efi-getvarinfo")); |
| |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| EXPECT_EQ(capture->dump_contents(), expected_output); |
| |
| ASSERT_EQ(transport.GetOutPackets().size(), 1ULL); |
| fastboot::Packets expected_packets = {"OKAY"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| TEST_F(FastbootEfiVarTest, EfiGetVarNames_Error) { |
| EXPECT_CALL(mock_efi_variables, EfiGetNextVariableName(_)) |
| .WillOnce(Return(fit::error(EFI_INVALID_PARAMETER))); |
| |
| transport.AddInPacket(std::string("oem efi-getvarnames")); |
| |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| EXPECT_THAT(capture->dump_contents(), IsEmpty()); |
| |
| ASSERT_EQ(transport.GetOutPackets().size(), 1ULL); |
| fastboot::Packets expected_packets = {"FAILEfiVariableName iteration failed"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| TEST_F(FastbootEfiVarTest, EfiGetVarNames_Empty) { |
| EXPECT_CALL(mock_efi_variables, EfiGetNextVariableName(_)) |
| .WillOnce(Return(fit::error(EFI_NOT_FOUND))); |
| const auto expected_output = "\n"; |
| |
| transport.AddInPacket(std::string("oem efi-getvarnames")); |
| |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| EXPECT_EQ(capture->dump_contents(), expected_output); |
| |
| ASSERT_EQ(transport.GetOutPackets().size(), 1ULL); |
| fastboot::Packets expected_packets = {"OKAY"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| constexpr efi_guid kGuid[] = { |
| {0x0, 0x0, 0x0, {0x0}}, |
| {0x1, 0x1, 0x1, {0x1}}, |
| {0x00010203, 0x0405, 0x0607, {0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}}, |
| }; |
| constexpr size_t kVariableIdSize = std::size(kGuid); |
| |
| const std::array<efi::VariableId, kVariableIdSize>& VariableIds() { |
| static std::array<efi::VariableId, kVariableIdSize>* variable_id = |
| new std::array<efi::VariableId, kVariableIdSize>{ |
| efi::VariableId{efi::String("var_0"), kGuid[0]}, |
| efi::VariableId{efi::String("var_1"), kGuid[1]}, |
| efi::VariableId{efi::String("var_2"), kGuid[2]}, |
| }; |
| return *variable_id; |
| } |
| |
| const std::array<std::vector<uint8_t>, kVariableIdSize>& VariableValues() { |
| static std::array<std::vector<uint8_t>, kVariableIdSize>* values = |
| new std::array<std::vector<uint8_t>, kVariableIdSize>{ |
| std::vector<uint8_t>{0x00}, |
| std::vector<uint8_t>{0x01, 0x02}, |
| std::vector<uint8_t>{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, |
| 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, |
| 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}, |
| }; |
| return *values; |
| } |
| |
| TEST_F(FastbootEfiVarTest, EfiGetVarNames_Values) { |
| EXPECT_CALL(mock_efi_variables, EfiGetNextVariableName(_)) |
| .WillOnce(DoAll(SetArgReferee<0>(VariableIds()[0]), Return(fit::success()))) |
| .WillOnce(DoAll(SetArgReferee<0>(VariableIds()[1]), Return(fit::success()))) |
| .WillOnce(DoAll(SetArgReferee<0>(VariableIds()[2]), Return(fit::success()))) |
| .WillOnce(Return(fit::error(EFI_NOT_FOUND))); |
| const auto expected_output = |
| "00000000-0000-0000-0000-000000000000 var_0\n" |
| "00000001-0001-0001-0100-000000000000 var_1\n" |
| "00010203-0405-0607-0809-0a0b0c0d0e0f var_2\n" |
| "\n"; |
| |
| transport.AddInPacket(std::string("oem efi-getvarnames")); |
| |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| EXPECT_EQ(capture->dump_contents(), expected_output); |
| |
| ASSERT_EQ(transport.GetOutPackets().size(), 1ULL); |
| fastboot::Packets expected_packets = {"OKAY"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| TEST_F(FastbootEfiVarTest, EfiGetVarValue_NameWithGuid) { |
| fbl::Vector<uint8_t> kVal; |
| kVal.resize(VariableValues()[2].size()); |
| std::copy(VariableValues()[2].begin(), VariableValues()[2].end(), kVal.begin()); |
| const auto expected_output = |
| "00010203-0405-0607-0809-0a0b0c0d0e0f var_2:\n" |
| "00010203 04050607 08090a0b 0c0d0e0f |................\n" |
| "10111213 14151617 18191a1b 1c1d1e1f |................\n"; |
| |
| EXPECT_CALL(mock_efi_variables, EfiGetVariable(VariableIds()[2])) |
| .WillOnce(Return(fit::success(std::move(kVal)))); |
| |
| transport.AddInPacket(std::string("oem efi-getvar var_2 00010203-0405-0607-0809-0a0b0c0d0e0f")); |
| |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| EXPECT_EQ(capture->dump_contents(), expected_output); |
| |
| ASSERT_EQ(transport.GetOutPackets().size(), 1ULL); |
| fastboot::Packets expected_packets = {"OKAY"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| TEST_F(FastbootEfiVarTest, EfiGetVarValue_NameWithoutGuid) { |
| fbl::Vector<uint8_t> kVal; |
| kVal.resize(VariableValues()[2].size()); |
| std::copy(VariableValues()[2].begin(), VariableValues()[2].end(), kVal.begin()); |
| const auto expected_output = |
| "00010203-0405-0607-0809-0a0b0c0d0e0f var_2:\n" |
| "00010203 04050607 08090a0b 0c0d0e0f |................\n" |
| "10111213 14151617 18191a1b 1c1d1e1f |................\n"; |
| |
| EXPECT_CALL(mock_efi_variables, EfiGetVariable(VariableIds()[2])) |
| .WillOnce(Return(fit::success(std::move(kVal)))); |
| EXPECT_CALL(mock_efi_variables, GetGuid(_)).WillOnce(Return(fit::success(kGuid[2]))); |
| |
| transport.AddInPacket(std::string("oem efi-getvar var_2")); |
| |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| EXPECT_EQ(capture->dump_contents(), expected_output); |
| |
| ASSERT_EQ(transport.GetOutPackets().size(), 1ULL); |
| fastboot::Packets expected_packets = {"OKAY"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| TEST_F(FastbootEfiVarTest, EfiGetVarValue_NameWithGuidError) { |
| fbl::Vector<uint8_t> kVal; |
| kVal.resize(VariableValues()[2].size()); |
| std::copy(VariableValues()[2].begin(), VariableValues()[2].end(), kVal.begin()); |
| const auto expected_output = "00010203-0405-0607-0809-0a0b0c0d0e0f var_2:\n"; |
| |
| EXPECT_CALL(mock_efi_variables, EfiGetVariable(VariableIds()[2])) |
| .WillOnce(Return(fit::error(EFI_INVALID_PARAMETER))); |
| |
| transport.AddInPacket(std::string("oem efi-getvar var_2 00010203-0405-0607-0809-0a0b0c0d0e0f")); |
| |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| EXPECT_EQ(capture->dump_contents(), expected_output); |
| |
| ASSERT_EQ(transport.GetOutPackets().size(), 1ULL); |
| fastboot::Packets expected_packets = {"FAILGetVariable() failed"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| TEST_F(FastbootEfiVarTest, EfiGetVarValue_NameWithoutGuidError) { |
| fbl::Vector<uint8_t> kVal; |
| kVal.resize(VariableValues()[2].size()); |
| std::copy(VariableValues()[2].begin(), VariableValues()[2].end(), kVal.begin()); |
| const auto expected_output = "Vendor GUID search failed (EFI_INVALID_PARAMETER) for: 'var_2'\n"; |
| |
| EXPECT_CALL(mock_efi_variables, GetGuid(_)).WillOnce(Return(fit::error(EFI_INVALID_PARAMETER))); |
| |
| transport.AddInPacket(std::string("oem efi-getvar var_2")); |
| |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| EXPECT_EQ(capture->dump_contents(), expected_output); |
| |
| ASSERT_EQ(transport.GetOutPackets().size(), 1ULL); |
| fastboot::Packets expected_packets = { |
| "FAILMultiple entries found with specified name. Please provide G"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| TEST_F(FastbootEfiVarTest, EfiDumpVars_Multiple) { |
| fbl::Vector<uint8_t> kVal[kVariableIdSize]; |
| for (size_t i = 0; i < kVariableIdSize; i++) { |
| kVal[i].resize(VariableValues()[i].size()); |
| std::copy(VariableValues()[i].begin(), VariableValues()[i].end(), kVal[i].begin()); |
| } |
| const auto expected_output = |
| "00000000-0000-0000-0000-000000000000 var_0:\n" |
| "00 |.\n" |
| "00000001-0001-0001-0100-000000000000 var_1:\n" |
| "0102 |..\n" |
| "00010203-0405-0607-0809-0a0b0c0d0e0f var_2:\n" |
| "00010203 04050607 08090a0b 0c0d0e0f |................\n" |
| "10111213 14151617 18191a1b 1c1d1e1f |................\n" |
| "\n"; |
| |
| EXPECT_CALL(mock_efi_variables, EfiGetNextVariableName(_)) |
| .WillOnce(DoAll(SetArgReferee<0>(VariableIds()[0]), Return(fit::success()))) |
| .WillOnce(DoAll(SetArgReferee<0>(VariableIds()[1]), Return(fit::success()))) |
| .WillOnce(DoAll(SetArgReferee<0>(VariableIds()[2]), Return(fit::success()))) |
| .WillOnce(Return(fit::error(EFI_NOT_FOUND))); |
| |
| for (size_t i = 0; i < kVariableIdSize; i++) { |
| EXPECT_CALL(mock_efi_variables, EfiGetVariable(VariableIds()[i])) |
| .WillOnce(Return(fit::success(std::move(kVal[i])))); |
| } |
| |
| transport.AddInPacket(std::string("oem efi-dumpvars")); |
| |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| EXPECT_EQ(capture->dump_contents(), expected_output); |
| |
| ASSERT_EQ(transport.GetOutPackets().size(), 1ULL); |
| fastboot::Packets expected_packets = {"OKAY"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| TEST_F(FastbootEfiVarTest, EfiDumpVars_Empty) { |
| const auto expected_output = "\n"; |
| |
| EXPECT_CALL(mock_efi_variables, EfiGetNextVariableName(_)) |
| .WillOnce(Return(fit::error(EFI_NOT_FOUND))); |
| |
| transport.AddInPacket(std::string("oem efi-dumpvars")); |
| |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| EXPECT_EQ(capture->dump_contents(), expected_output); |
| |
| ASSERT_EQ(transport.GetOutPackets().size(), 1ULL); |
| fastboot::Packets expected_packets = {"OKAY"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| TEST_F(FastbootEfiVarTest, EfiDumpVars_Error) { |
| EXPECT_CALL(mock_efi_variables, EfiGetNextVariableName(_)) |
| .WillOnce(Return(fit::error(EFI_INVALID_PARAMETER))); |
| |
| transport.AddInPacket(std::string("oem efi-dumpvars")); |
| |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| EXPECT_THAT(capture->dump_contents(), IsEmpty()); |
| |
| ASSERT_EQ(transport.GetOutPackets().size(), 1ULL); |
| fastboot::Packets expected_packets = {"FAILEfiVariableName iteration failed"}; |
| ASSERT_NO_FATAL_FAILURE(CheckPacketsEqual(transport.GetOutPackets(), expected_packets)); |
| } |
| |
| class FastbootPrint : public ::testing::Test { |
| public: |
| fastboot::TestTransport transport; |
| MockZirconBootOps mock_zb_ops; |
| MockEfiVariables mock_efi_variables; |
| Fastboot fastboot = |
| Fastboot(download_buffer, mock_zb_ops.GetZirconBootOps(), &mock_efi_variables); |
| }; |
| |
| TEST_F(FastbootPrint, EfiGetInfoPrint2kInfo) { |
| EXPECT_CALL(mock_efi_variables, EfiQueryVariableInfo()) |
| .WillOnce(Return(fit::success(EfiVariables::EfiVariableInfo{0, 1, 2}))); |
| constexpr std::string_view expected_output[] = { |
| "INFO", |
| "INFO Max Storage Size: 0", |
| "INFO Remaining Variable Storage Size: 1", |
| "INFO Max Variable Size: 2", |
| "OKAY", |
| }; |
| |
| transport.AddInPacket(std::string("oem efi-getvarinfo")); |
| |
| zx::result ret = fastboot.ProcessPacket(&transport); |
| ASSERT_TRUE(ret.is_ok()); |
| |
| ASSERT_EQ(transport.GetOutPackets().size(), 5ULL); |
| EXPECT_THAT(transport.GetOutPackets(), ElementsAreArray(expected_output)); |
| } |
| |
| } // namespace |
| |
| } // namespace gigaboot |