| // Copyright 2020 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 "src/storage/fvm/snapshot_metadata.h" |
| |
| #include <zircon/errors.h> |
| |
| #include <random> |
| #include <vector> |
| |
| #include <fbl/span.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "src/storage/fvm/snapshot_metadata_format.h" |
| |
| namespace fvm { |
| |
| static std::default_random_engine rand(::zxtest::Runner::GetInstance()->random_seed()); |
| |
| SnapshotExtentType CreateExtentTypeEntry(uint16_t vpart) { |
| static uint64_t gVslice = 0; |
| std::uniform_int_distribution<uint64_t> d1(1024u); |
| uint64_t extent_length = d1(rand); |
| uint64_t vslice = gVslice; |
| gVslice += extent_length; |
| std::uniform_int_distribution<uint8_t> d2(static_cast<uint8_t>(ExtentType::kDefault), |
| static_cast<uint8_t>(ExtentType::kMax)); |
| ExtentType type = static_cast<ExtentType>(d2(rand)); |
| return SnapshotExtentType(vpart, vslice, extent_length, type); |
| } |
| |
| PartitionSnapshotState CreatePartitionStateEntry() { return PartitionSnapshotState(); } |
| |
| void ValidateMetadata(const SnapshotMetadata& metadata, |
| const std::vector<SnapshotExtentType>& expected_extents) { |
| const SnapshotMetadataHeader& header = metadata.GetHeader(); |
| |
| ASSERT_GE(header.ExtentTypeTableNumEntries(), expected_extents.size()); |
| unsigned i = 0; |
| for (; i < expected_extents.size(); ++i) { |
| EXPECT_BYTES_EQ(&expected_extents[i], &metadata.GetExtentTypeEntry(i), |
| sizeof(SnapshotExtentType)); |
| } |
| for (; i < header.ExtentTypeTableNumEntries(); ++i) { |
| EXPECT_TRUE(metadata.GetExtentTypeEntry(i).IsFree()); |
| } |
| } |
| |
| void CheckMetadataContainSameEntries(const SnapshotMetadata& a, const SnapshotMetadata& b) { |
| const SnapshotMetadataHeader& header_a = a.GetHeader(); |
| const SnapshotMetadataHeader& header_b = b.GetHeader(); |
| |
| unsigned i = 0, j = 0; |
| for (; i < header_a.ExtentTypeTableNumEntries() && j < header_b.ExtentTypeTableNumEntries(); |
| ++i, ++j) { |
| EXPECT_BYTES_EQ(&a.GetExtentTypeEntry(i), &b.GetExtentTypeEntry(j), sizeof(SnapshotExtentType)); |
| } |
| for (; i < header_a.ExtentTypeTableNumEntries(); ++i) { |
| EXPECT_TRUE(a.GetExtentTypeEntry(i).IsFree()); |
| } |
| for (; j < header_b.ExtentTypeTableNumEntries(); ++j) { |
| EXPECT_TRUE(b.GetExtentTypeEntry(j).IsFree()); |
| } |
| } |
| |
| TEST(ValidateMetadata, DefaultValid) { |
| SnapshotMetadataHeader header; |
| |
| std::string unused; |
| ASSERT_TRUE(header.IsValid(unused)); |
| } |
| |
| TEST(ValidateMetadata, BadMagicFails) { |
| SnapshotMetadataHeader header; |
| header.magic = 0u; |
| |
| std::string unused; |
| ASSERT_FALSE(header.IsValid(unused)); |
| } |
| |
| TEST(ValidateMetadata, BadPartitionStateTableSizeFails) { |
| SnapshotMetadataHeader header; |
| header.partition_state_table_entry_count = kSnapshotMetadataHeaderMinPartitions - 1; |
| |
| std::string unused; |
| ASSERT_FALSE(header.IsValid(unused)); |
| |
| header.partition_state_table_entry_count = kSnapshotMetadataHeaderMaxPartitions + 1; |
| ASSERT_FALSE(header.IsValid(unused)); |
| } |
| |
| TEST(ValidateMetadata, BadExtentTypeTableSizeFails) { |
| SnapshotMetadataHeader header; |
| header.extent_type_table_entry_count = kSnapshotMetadataHeaderMinExtentTypes - 1; |
| |
| std::string unused; |
| ASSERT_FALSE(header.IsValid(unused)); |
| |
| header.extent_type_table_entry_count = kSnapshotMetadataHeaderMaxExtentTypes + 1; |
| ASSERT_FALSE(header.IsValid(unused)); |
| } |
| |
| TEST(ValidateMetadata, PartitionStateTableOverlapsHeaderFails) { |
| SnapshotMetadataHeader header; |
| header.partition_state_table_offset = sizeof(SnapshotMetadataHeader) - 1; |
| |
| std::string unused; |
| ASSERT_FALSE(header.IsValid(unused)); |
| } |
| |
| TEST(ValidateMetadata, ExtentTypeTableOverlapsHeaderFails) { |
| SnapshotMetadataHeader header; |
| header.extent_type_table_offset = sizeof(SnapshotMetadataHeader) - 1; |
| |
| std::string unused; |
| ASSERT_FALSE(header.IsValid(unused)); |
| } |
| |
| TEST(ValidateMetadata, ExtentTypeTableOverlapsPartitionStateTableFails) { |
| SnapshotMetadataHeader header; |
| header.extent_type_table_offset = |
| header.partition_state_table_offset + header.PartitionStateTableSizeBytes() - 1; |
| |
| std::string unused; |
| ASSERT_FALSE(header.IsValid(unused)); |
| } |
| |
| TEST(ValidateMetadata, ExtentTypeTableOverlapsSecondHeaderFails) { |
| SnapshotMetadataHeader header; |
| header.extent_type_table_offset = |
| header.HeaderOffset(SnapshotMetadataCopy::kSecondary) - header.ExtentTypeTableSizeBytes() + 1; |
| |
| std::string unused; |
| ASSERT_FALSE(header.IsValid(unused)); |
| } |
| |
| TEST(CreateMetadata, Empty) { |
| auto result = SnapshotMetadata::Synthesize(nullptr, 0, nullptr, 0); |
| ASSERT_EQ(result.status_value(), ZX_OK); |
| ValidateMetadata(result.value(), std::vector<SnapshotExtentType>()); |
| } |
| |
| TEST(CreateMetadata, OneExtent) { |
| std::vector<SnapshotExtentType> extents{CreateExtentTypeEntry(1u)}; |
| |
| auto result = SnapshotMetadata::Synthesize(nullptr, 0, extents.data(), extents.size()); |
| ASSERT_EQ(result.status_value(), ZX_OK); |
| ValidateMetadata(result.value(), extents); |
| } |
| |
| TEST(CreateMetadata, SeveralExtents) { |
| std::vector<SnapshotExtentType> extents{ |
| CreateExtentTypeEntry(1u), |
| CreateExtentTypeEntry(1u), |
| CreateExtentTypeEntry(2u), |
| }; |
| |
| auto result = SnapshotMetadata::Synthesize(nullptr, 0, extents.data(), extents.size()); |
| ASSERT_EQ(result.status_value(), ZX_OK); |
| ValidateMetadata(result.value(), extents); |
| } |
| |
| TEST(CreateMetadata, FullExtentTable) { |
| std::vector<SnapshotExtentType> extents(kSnapshotMetadataHeaderMaxExtentTypes); |
| std::generate(extents.begin(), extents.end(), []() { return CreateExtentTypeEntry(1u); }); |
| |
| auto result = SnapshotMetadata::Synthesize(nullptr, 0, extents.data(), extents.size()); |
| ASSERT_EQ(result.status_value(), ZX_OK); |
| ValidateMetadata(result.value(), extents); |
| } |
| |
| TEST(CreateMetadata, TooManyExtents) { |
| std::vector<SnapshotExtentType> extents(kSnapshotMetadataHeaderMaxExtentTypes + 1); |
| std::generate(extents.begin(), extents.end(), []() { return CreateExtentTypeEntry(1u); }); |
| |
| auto result = SnapshotMetadata::Synthesize(nullptr, 0, extents.data(), extents.size()); |
| ASSERT_NE(result.status_value(), ZX_OK); |
| } |
| |
| TEST(Metadata, HeaderOffsets) { |
| auto result = SnapshotMetadata::Synthesize(nullptr, 0, nullptr, 0); |
| ASSERT_EQ(result.status_value(), ZX_OK); |
| |
| ASSERT_EQ(result->active_header(), SnapshotMetadataCopy::kPrimary); |
| ASSERT_EQ(result->GetInactiveHeaderOffset(), kSnapshotMetadataSecondHeaderOffset); |
| |
| result->SwitchActiveHeaders(); |
| |
| ASSERT_EQ(result->active_header(), SnapshotMetadataCopy::kSecondary); |
| ASSERT_EQ(result->GetInactiveHeaderOffset(), 0u); |
| } |
| |
| TEST(PickValidMetadata, BothValidTakesFirst) { |
| std::vector<SnapshotExtentType> extents{CreateExtentTypeEntry(1u)}; |
| auto result1 = SnapshotMetadata::Synthesize(nullptr, 0, extents.data(), extents.size()); |
| auto result2 = SnapshotMetadata::Synthesize(nullptr, 0, nullptr, 0); |
| ASSERT_EQ(result1.status_value(), ZX_OK); |
| ASSERT_EQ(result2.status_value(), ZX_OK); |
| ASSERT_EQ(result1->Get()->size(), result2->Get()->size()); |
| |
| std::optional<SnapshotMetadataCopy> copy = SnapshotMetadata::PickValid( |
| result1->Get()->data(), result2->Get()->data(), result1->Get()->size()); |
| ASSERT_TRUE(copy); |
| EXPECT_EQ(*copy, SnapshotMetadataCopy::kPrimary); |
| } |
| |
| TEST(PickValidMetadata, FirstInvalid) { |
| std::vector<SnapshotExtentType> extents{CreateExtentTypeEntry(1u)}; |
| auto result1 = SnapshotMetadata::Synthesize(nullptr, 0, extents.data(), extents.size()); |
| auto result2 = SnapshotMetadata::Synthesize(nullptr, 0, nullptr, 0); |
| ASSERT_EQ(result1.status_value(), ZX_OK); |
| ASSERT_EQ(result2.status_value(), ZX_OK); |
| ASSERT_EQ(result1->Get()->size(), result2->Get()->size()); |
| // Zero out the magic |
| bzero(result1->Get()->data(), sizeof(SnapshotMetadataHeader::magic)); |
| |
| std::optional<SnapshotMetadataCopy> copy = SnapshotMetadata::PickValid( |
| result1->Get()->data(), result2->Get()->data(), result1->Get()->size()); |
| ASSERT_TRUE(copy); |
| EXPECT_EQ(*copy, SnapshotMetadataCopy::kSecondary); |
| } |
| |
| TEST(PickValidMetadata, SecondInvalid) { |
| std::vector<SnapshotExtentType> extents{CreateExtentTypeEntry(1u)}; |
| auto result1 = SnapshotMetadata::Synthesize(nullptr, 0, extents.data(), extents.size()); |
| auto result2 = SnapshotMetadata::Synthesize(nullptr, 0, nullptr, 0); |
| ASSERT_EQ(result1.status_value(), ZX_OK); |
| ASSERT_EQ(result2.status_value(), ZX_OK); |
| ASSERT_EQ(result1->Get()->size(), result2->Get()->size()); |
| // Zero out the magic |
| bzero(result2->Get()->data(), sizeof(SnapshotMetadataHeader::magic)); |
| |
| std::optional<SnapshotMetadataCopy> copy = SnapshotMetadata::PickValid( |
| result1->Get()->data(), result2->Get()->data(), result1->Get()->size()); |
| ASSERT_TRUE(copy); |
| EXPECT_EQ(*copy, SnapshotMetadataCopy::kPrimary); |
| } |
| |
| TEST(PickValidMetadata, BothInvalid) { |
| std::vector<SnapshotExtentType> extents{CreateExtentTypeEntry(1u)}; |
| auto result1 = SnapshotMetadata::Synthesize(nullptr, 0, extents.data(), extents.size()); |
| auto result2 = SnapshotMetadata::Synthesize(nullptr, 0, nullptr, 0); |
| ASSERT_EQ(result1.status_value(), ZX_OK); |
| ASSERT_EQ(result2.status_value(), ZX_OK); |
| ASSERT_EQ(result1->Get()->size(), result2->Get()->size()); |
| // Zero out the magic |
| bzero(result1->Get()->data(), sizeof(SnapshotMetadataHeader::magic)); |
| bzero(result2->Get()->data(), sizeof(SnapshotMetadataHeader::magic)); |
| |
| std::optional<SnapshotMetadataCopy> copy = SnapshotMetadata::PickValid( |
| result1->Get()->data(), result2->Get()->data(), result1->Get()->size()); |
| ASSERT_FALSE(copy); |
| } |
| |
| } // namespace fvm |