| // Copyright 2019 The Chromium OS 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 "biod/cros_fp_firmware.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <sys/types.h> |
| |
| #include <string> |
| #include <unordered_set> |
| #include <vector> |
| |
| #include <base/check.h> |
| #include <base/files/file.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/logging.h> |
| #include <fmap.h> |
| #include <gtest/gtest.h> |
| |
| #include "biod/utils.h" |
| |
| namespace { |
| |
| constexpr int kTestImageBaseAddr = 0x8000000; |
| constexpr int kTestImageSize = 2 * 1024 * 1024; |
| constexpr char kTestImageFwName[] = "EC_FMAP"; |
| constexpr char kTestImageROIDLabel[] = "RO_FRID"; |
| constexpr char kTestImageRWIDLabel[] = "RW_FWID"; |
| constexpr char kTestImageFileName[] = "nocturne_fp_v2.2.110-b936c0a3c.bin"; |
| constexpr char kTestImageROVersion[] = "nocturne_fp_v2.2.64-58cf5974e"; |
| constexpr char kTestImageRWVersion[] = "nocturne_fp_v2.2.110-b936c0a3c"; |
| |
| const std::vector<biod::CrosFpFirmware::Status> kCrosFpFirmwareStatuses = { |
| biod::CrosFpFirmware::Status::kUninitialized, |
| biod::CrosFpFirmware::Status::kOk, |
| biod::CrosFpFirmware::Status::kNotFound, |
| biod::CrosFpFirmware::Status::kOpenError, |
| biod::CrosFpFirmware::Status::kBadFmap, |
| }; |
| |
| class Fmap { |
| public: |
| Fmap() : fmap_(nullptr) {} |
| Fmap(const Fmap&) = delete; |
| Fmap& operator=(const Fmap&) = delete; |
| ~Fmap() { Destroy(); } |
| |
| bool Create(uint64_t base, uint32_t size, const char* name) { |
| Destroy(); |
| // fmap_create does not modify name internally |
| fmap_ = fmap_create( |
| base, size, |
| const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(name))); |
| return (fmap_ != nullptr); |
| } |
| bool AppendArea(uint32_t offset, |
| uint32_t size, |
| const char* name, |
| uint16_t flags) { |
| CHECK(IsValid()); |
| return fmap_append_area(&fmap_, offset, size, |
| reinterpret_cast<const uint8_t*>(name), flags) >= 0; |
| } |
| bool IsValid() { return fmap_ != nullptr; } |
| const char* GetData() { return reinterpret_cast<char*>(fmap_); } |
| int GetDataLength() { return fmap_size(fmap_); } |
| |
| private: |
| void Destroy() { |
| if (IsValid()) { |
| fmap_destroy(fmap_); |
| } |
| } |
| |
| struct fmap* fmap_; |
| }; |
| |
| } // namespace |
| |
| namespace biod { |
| |
| class CrosFpFirmwareTest : public ::testing::Test { |
| protected: |
| void SetUp() override { CHECK(temp_dir_.CreateUniqueTempDir()); } |
| |
| void TearDown() override { EXPECT_TRUE(temp_dir_.Delete()); } |
| |
| const base::FilePath& GetTestTempDir() const { return temp_dir_.GetPath(); } |
| |
| bool CreateFakeImage(const base::FilePath& abspath, |
| const std::string& ro_version, |
| const std::string& rw_version, |
| uint32_t fmap_report_size = kTestImageSize, |
| bool fmap_ro_include = true, |
| bool fmap_rw_include = true, |
| uint32_t ver_area_offset = 0, |
| uint32_t ver_area_size = FMAP_STRLEN) { |
| if (!GetTestTempDir().IsParent(abspath)) { |
| LOG(ERROR) << "Asked to PlaceFakeImage outside test environment."; |
| return false; |
| } |
| |
| LOG(INFO) << "Creating fake image at: " << abspath.value(); |
| |
| // FMAP_STRLEN is the max size of the string including a null character |
| if (ro_version.length() >= FMAP_STRLEN) { |
| LOG(ERROR) << "Error - ro_version, '" << ro_version |
| << "', is too long. Must be max " << FMAP_STRLEN |
| << " with null terminator."; |
| return false; |
| } |
| if (rw_version.length() >= FMAP_STRLEN) { |
| LOG(ERROR) << "Error - rw_version, '" << rw_version |
| << "', is too long. Must be max " << FMAP_STRLEN |
| << " with null terminator."; |
| return false; |
| } |
| |
| base::File file(abspath, |
| base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| if (!file.IsValid()) { |
| return false; |
| } |
| |
| std::vector<char> verbuf(FMAP_STRLEN * 2); |
| ro_version.copy(&verbuf[0 * FMAP_STRLEN], FMAP_STRLEN - 1); |
| rw_version.copy(&verbuf[1 * FMAP_STRLEN], FMAP_STRLEN - 1); |
| |
| // place ro and rw versions at the front of the file |
| if (file.WriteAtCurrentPos(&verbuf[0], 2 * FMAP_STRLEN) < 0) { |
| LOG(ERROR) << "Failed to write version strings into fake image."; |
| return false; |
| } |
| |
| Fmap fmap; |
| if (!fmap.Create(kTestImageBaseAddr, fmap_report_size, kTestImageFwName)) { |
| LOG(ERROR) << "Failed to allocate fmap struct"; |
| return false; |
| } |
| if (fmap_ro_include) { |
| if (!fmap.AppendArea(ver_area_offset + (0 * FMAP_STRLEN), ver_area_size, |
| kTestImageROIDLabel, FMAP_AREA_RO)) { |
| LOG(ERROR) << "Failed to append " << kTestImageROIDLabel << " FW area."; |
| return false; |
| } |
| } |
| if (fmap_rw_include) { |
| if (!fmap.AppendArea(ver_area_offset + (1 * FMAP_STRLEN), ver_area_size, |
| kTestImageRWIDLabel, FMAP_AREA_RO)) { |
| LOG(ERROR) << "Failed to append " << kTestImageRWIDLabel << " FW area."; |
| return false; |
| } |
| } |
| if (!fmap.IsValid()) { |
| LOG(ERROR) << "Fmap data or size are invalid."; |
| return false; |
| } |
| if (file.WriteAtCurrentPos(fmap.GetData(), fmap.GetDataLength()) < 0) { |
| LOG(ERROR) << "Failed to write fmap into fake image."; |
| return false; |
| } |
| |
| // we must grow the file to match or be larger than FMAP reported size |
| if (!file.SetLength(kTestImageSize)) { |
| LOG(ERROR) << "Failed to elongate fake image to typical size."; |
| return false; |
| } |
| |
| EXPECT_TRUE(base::PathExists(abspath)); |
| return true; |
| } |
| |
| void TestExpectFailure(const base::FilePath& image_path, |
| biod::CrosFpFirmware::Status expect_status) { |
| biod::CrosFpFirmware fw(image_path); |
| |
| EXPECT_STREQ(fw.GetPath().value().c_str(), image_path.value().c_str()); |
| EXPECT_EQ(fw.GetStatus(), expect_status); |
| EXPECT_FALSE(fw.IsValid()); |
| EXPECT_STREQ(fw.GetStatusString().c_str(), |
| CrosFpFirmware::StatusToString(expect_status).c_str()); |
| biod::CrosFpFirmware::ImageVersion fwver = fw.GetVersion(); |
| LOG(INFO) << "Passed"; |
| } |
| |
| void TestExpectSuccess(const base::FilePath& image_path, |
| const std::string& expect_ro_version, |
| const std::string& expect_rw_version) { |
| biod::CrosFpFirmware fw(image_path); |
| |
| EXPECT_STREQ(fw.GetPath().value().c_str(), image_path.value().c_str()); |
| EXPECT_EQ(fw.GetStatus(), biod::CrosFpFirmware::Status::kOk) |
| << "The returned status is not the Ok status."; |
| EXPECT_TRUE(fw.IsValid()); |
| EXPECT_STREQ( |
| fw.GetStatusString().c_str(), |
| CrosFpFirmware::StatusToString(biod::CrosFpFirmware::Status::kOk) |
| .c_str()) |
| << "The status string returned did not match that of the Ok status."; |
| biod::CrosFpFirmware::ImageVersion fwver = fw.GetVersion(); |
| EXPECT_STREQ(fwver.ro_version.c_str(), expect_ro_version.c_str()) |
| << "The decoded RO version string did not match."; |
| EXPECT_STREQ(fwver.rw_version.c_str(), expect_rw_version.c_str()) |
| << "The decoded RW version string did not match."; |
| LOG(INFO) << "Passed"; |
| } |
| |
| // this proxy function allows us to keep the core CrosFpFirmware class |
| // clean from lots of friend declarations for each unit test fixture |
| static std::string TestStatusToString(CrosFpFirmware::Status status) { |
| return CrosFpFirmware::StatusToString(status); |
| } |
| |
| base::ScopedTempDir temp_dir_; |
| |
| CrosFpFirmwareTest() = default; |
| CrosFpFirmwareTest(const CrosFpFirmwareTest&) = delete; |
| CrosFpFirmwareTest& operator=(const CrosFpFirmwareTest&) = delete; |
| |
| ~CrosFpFirmwareTest() override = default; |
| |
| private: |
| FRIEND_TEST(CrosFpFirmwareTest, UniqueErrorMessages); |
| }; |
| |
| TEST_F(CrosFpFirmwareTest, InvalidPathBlank) { |
| TestExpectFailure( |
| // Given an empty firmware file path, |
| base::FilePath(""), |
| // expect to receive a firmware file not found error. |
| biod::CrosFpFirmware::Status::kNotFound); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, InavlidPathOddChars) { |
| TestExpectFailure( |
| // Given a firmware file path "--", |
| base::FilePath("--"), |
| // expect to receive a firmware file not found error. |
| biod::CrosFpFirmware::Status::kNotFound); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, GivenDirectory) { |
| TestExpectFailure( |
| // Given a directory as the firmware file path, |
| GetTestTempDir(), |
| // expect to receive a firmware file not found error. |
| biod::CrosFpFirmware::Status::kNotFound); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, GivenEmptyFile) { |
| // Given an empty file (of size 0), |
| const auto image_path = GetTestTempDir().Append(kTestImageFileName); |
| base::File file(image_path, |
| base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| file.Close(); |
| EXPECT_TRUE(base::PathExists(image_path)); |
| |
| // expect to receive an open file error (from mmap). |
| TestExpectFailure(image_path, biod::CrosFpFirmware::Status::kOpenError); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, NoFMAP) { |
| // Given a file that does not contain an FMAP, |
| const auto image_path = GetTestTempDir().Append(kTestImageFileName); |
| base::File file(image_path, |
| base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| EXPECT_GE(file.WriteAtCurrentPos("a", 1), 1); |
| file.Close(); |
| EXPECT_TRUE(base::PathExists(image_path)); |
| |
| // expect to receive a bad-fmap error. |
| TestExpectFailure(image_path, biod::CrosFpFirmware::Status::kBadFmap); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, FMAPReportsLargerSizeThanFileSize) { |
| // Given a firmware file |
| const auto image_path = GetTestTempDir().Append(kTestImageFileName); |
| EXPECT_TRUE(CreateFakeImage(image_path, kTestImageROVersion, |
| kTestImageRWVersion, |
| // whose FMAP reports an overall size larger |
| // than the actual file's size, |
| kTestImageSize + 1)); |
| |
| // expect to receive a bad-fmap error. |
| TestExpectFailure(image_path, biod::CrosFpFirmware::Status::kBadFmap); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, FMAPReportsZeroSize) { |
| // Given a firmware file |
| const auto image_path = GetTestTempDir().Append(kTestImageFileName); |
| EXPECT_TRUE(CreateFakeImage(image_path, kTestImageROVersion, |
| kTestImageRWVersion, |
| // whose FMAP reports an overall size of 0, |
| 0)); |
| |
| // expect to receive a bad-fmap error. |
| TestExpectFailure(image_path, biod::CrosFpFirmware::Status::kBadFmap); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, GoodImageFile_DefaultVerAndFileName) { |
| // Given a firmware file with a proper FMAP, |
| const auto image_path = GetTestTempDir().Append(kTestImageFileName); |
| EXPECT_TRUE( |
| CreateFakeImage(image_path, kTestImageROVersion, kTestImageRWVersion)); |
| |
| // expect properly decoded version strings. |
| TestExpectSuccess(image_path, kTestImageROVersion, kTestImageRWVersion); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, GoodImageFile_UnknownVerAndFileName) { |
| // Given a firmware file with a proper FMAP and different |
| // version string, |
| const char image_ro_version[] = "unknown_fp_v12.34.567-abc123456"; |
| const char image_rw_version[] = "unknown_fp_v765.43.21-abc123456"; |
| const auto image_path = GetTestTempDir().Append(kTestImageFileName); |
| EXPECT_TRUE(CreateFakeImage(image_path, image_ro_version, image_rw_version)); |
| |
| // expect properly decoded version strings. |
| TestExpectSuccess(image_path, image_ro_version, image_rw_version); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, GoodImageFile_BlankVerAndMinimalFileName) { |
| // Given a firmware file with a proper FMAP and blank version strings, |
| const char image_ro_version[] = ""; |
| const char image_rw_version[] = ""; |
| const auto image_path = GetTestTempDir().Append(kTestImageFileName); |
| EXPECT_TRUE(CreateFakeImage(image_path, image_ro_version, image_rw_version)); |
| |
| // expect properly decoded (empty) version strings. |
| TestExpectSuccess(image_path, image_ro_version, image_rw_version); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, FMAPMissingROArea) { |
| // Given a firmware file |
| const auto image_path = GetTestTempDir().Append(kTestImageFileName); |
| EXPECT_TRUE(CreateFakeImage(image_path, kTestImageROVersion, |
| kTestImageRWVersion, kTestImageSize, |
| // whose FMAP is missing an RO version area, |
| false, true)); |
| |
| // expect to receive a bad-fmap error. |
| TestExpectFailure(image_path, biod::CrosFpFirmware::Status::kBadFmap); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, FMAPMissingRWArea) { |
| // Given a firmware file |
| const auto image_path = GetTestTempDir().Append(kTestImageFileName); |
| EXPECT_TRUE(CreateFakeImage(image_path, kTestImageROVersion, |
| kTestImageRWVersion, kTestImageSize, |
| // whose FMAP is missing an RW version area, |
| true, false)); |
| |
| // expect to receive a bad-fmap error. |
| TestExpectFailure(image_path, biod::CrosFpFirmware::Status::kBadFmap); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, FMAPMissingRORWArea) { |
| // Given a firmware file |
| const auto image_path = GetTestTempDir().Append(kTestImageFileName); |
| EXPECT_TRUE(CreateFakeImage(image_path, kTestImageROVersion, |
| kTestImageRWVersion, kTestImageSize, |
| // whose FMAP is missing an RO and RW |
| // version area, |
| false, false)); |
| |
| // expect to receive a bad-fmap error. |
| TestExpectFailure(image_path, biod::CrosFpFirmware::Status::kBadFmap); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, FMAPVersionAreaOffsetPastFileLimit) { |
| // Given a firmware file |
| const auto image_path = GetTestTempDir().Append(kTestImageFileName); |
| EXPECT_TRUE(CreateFakeImage(image_path, kTestImageROVersion, |
| kTestImageRWVersion, kTestImageSize, true, true, |
| // whose FMAP version areas report offsets |
| // pointing outside the actual file, |
| kTestImageSize)); |
| |
| // expect to receive a bad-fmap error. |
| TestExpectFailure(image_path, biod::CrosFpFirmware::Status::kBadFmap); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, FMAPVersionAreaSizeLargerThanFile) { |
| // Given a firmware file |
| const auto image_path = GetTestTempDir().Append(kTestImageFileName); |
| EXPECT_TRUE(CreateFakeImage(image_path, kTestImageROVersion, |
| kTestImageRWVersion, kTestImageSize, true, true, |
| 0, |
| // whose FMAP version areas report sizes |
| // which are larger than the actual file, |
| kTestImageSize + 1)); |
| |
| // expect to receive a bad-fmap error. |
| TestExpectFailure(image_path, biod::CrosFpFirmware::Status::kBadFmap); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, FMAPVersionAreaSizeIsZero) { |
| // Given a firmware file |
| const auto image_path = GetTestTempDir().Append(kTestImageFileName); |
| EXPECT_TRUE(CreateFakeImage(image_path, kTestImageROVersion, |
| kTestImageRWVersion, kTestImageSize, true, true, |
| 0, |
| // whose FMAP version areas report sizes of 0, |
| 0)); |
| |
| // expect properly decoded blank version strings. |
| TestExpectSuccess(image_path, "", ""); |
| } |
| |
| TEST_F(CrosFpFirmwareTest, NonblankStatusMessages) { |
| // Given a CrosFpFirmware status |
| for (auto status : kCrosFpFirmwareStatuses) { |
| // when we ask for the human readable string |
| std::string msg = TestStatusToString(status); |
| // expect it to not be "". |
| EXPECT_FALSE(msg.empty()) << "Status " << to_utype(status) |
| << " converts to a blank status string."; |
| } |
| } |
| |
| TEST_F(CrosFpFirmwareTest, UniqueStatusMessages) { |
| // Given a set of all CrosFpFirmware status messages, |
| std::unordered_set<std::string> status_msgs; |
| for (auto status : kCrosFpFirmwareStatuses) { |
| status_msgs.insert(TestStatusToString(status)); |
| } |
| |
| // expect the set to contain the same number of unique messages |
| // as there are original statuses. |
| EXPECT_EQ(status_msgs.size(), kCrosFpFirmwareStatuses.size()) |
| << "There are one or more non-unique status messages."; |
| } |
| |
| } // namespace biod |