// Copyright 2020 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_kvs/internal/entry_cache.h" #include "gtest/gtest.h" #include "pw_bytes/array.h" #include "pw_kvs/fake_flash_memory.h" #include "pw_kvs/flash_memory.h" #include "pw_kvs/internal/hash.h" #include "pw_kvs/internal/key_descriptor.h" namespace pw::kvs::internal { namespace { using std::byte; class EmptyEntryCache : public ::testing::Test { protected: static constexpr size_t kMaxEntries = 32; static constexpr size_t kRedundancy = 3; EmptyEntryCache() : entries_(descriptors_, addresses_, kRedundancy) {} Vector descriptors_; EntryCache::AddressList addresses_; EntryCache entries_; }; constexpr char kTheKey[] = "The Key"; constexpr KeyDescriptor kDescriptor = {.key_hash = Hash(kTheKey), .transaction_id = 123, .state = EntryState::kValid}; TEST_F(EmptyEntryCache, AddNew) { EntryMetadata metadata = entries_.AddNew(kDescriptor, 5); EXPECT_EQ(kDescriptor.key_hash, metadata.hash()); EXPECT_EQ(kDescriptor.transaction_id, metadata.transaction_id()); EXPECT_EQ(kDescriptor.state, metadata.state()); EXPECT_EQ(5u, metadata.first_address()); EXPECT_EQ(1u, metadata.addresses().size()); } TEST_F(EmptyEntryCache, EntryMetadata_AddNewAddress) { EntryMetadata metadata = entries_.AddNew(kDescriptor, 100); metadata.AddNewAddress(999); EXPECT_EQ(2u, metadata.addresses().size()); EXPECT_EQ(100u, metadata.first_address()); EXPECT_EQ(100u, metadata.addresses()[0]); EXPECT_EQ(999u, metadata.addresses()[1]); } TEST_F(EmptyEntryCache, EntryMetadata_Reset) { EntryMetadata metadata = entries_.AddNew(kDescriptor, 100); metadata.AddNewAddress(999); metadata.Reset( {.key_hash = 987, .transaction_id = 5, .state = EntryState::kDeleted}, 8888); EXPECT_EQ(987u, metadata.hash()); EXPECT_EQ(5u, metadata.transaction_id()); EXPECT_EQ(EntryState::kDeleted, metadata.state()); EXPECT_EQ(1u, metadata.addresses().size()); EXPECT_EQ(8888u, metadata.first_address()); EXPECT_EQ(8888u, metadata.addresses()[0]); } TEST_F(EmptyEntryCache, AddNewOrUpdateExisting_NewEntry) { ASSERT_EQ(OkStatus(), entries_.AddNewOrUpdateExisting(kDescriptor, 1000, 2000)); EXPECT_EQ(1u, entries_.present_entries()); for (const EntryMetadata& entry : entries_) { EXPECT_EQ(1000u, entry.first_address()); EXPECT_EQ(kDescriptor.key_hash, entry.hash()); EXPECT_EQ(kDescriptor.transaction_id, entry.transaction_id()); } } TEST_F(EmptyEntryCache, AddNewOrUpdateExisting_NewEntry_Full) { for (uint32_t i = 0; i < kMaxEntries; ++i) { ASSERT_EQ( // Fill up the cache OkStatus(), entries_.AddNewOrUpdateExisting({i, i, EntryState::kValid}, i, 1)); } ASSERT_EQ(kMaxEntries, entries_.total_entries()); ASSERT_TRUE(entries_.full()); EXPECT_EQ(Status::ResourceExhausted(), entries_.AddNewOrUpdateExisting(kDescriptor, 1000, 1)); EXPECT_EQ(kMaxEntries, entries_.total_entries()); } TEST_F(EmptyEntryCache, AddNewOrUpdateExisting_UpdatedEntry) { KeyDescriptor kd = kDescriptor; kd.transaction_id += 3; ASSERT_EQ(OkStatus(), entries_.AddNewOrUpdateExisting(kd, 3210, 2000)); EXPECT_EQ(1u, entries_.present_entries()); for (const EntryMetadata& entry : entries_) { EXPECT_EQ(3210u, entry.first_address()); EXPECT_EQ(kDescriptor.key_hash, entry.hash()); EXPECT_EQ(kDescriptor.transaction_id + 3, entry.transaction_id()); } } TEST_F(EmptyEntryCache, AddNewOrUpdateExisting_AddDuplicateEntry) { ASSERT_EQ(OkStatus(), entries_.AddNewOrUpdateExisting(kDescriptor, 1000, 2000)); ASSERT_EQ(OkStatus(), entries_.AddNewOrUpdateExisting(kDescriptor, 3000, 2000)); ASSERT_EQ(OkStatus(), entries_.AddNewOrUpdateExisting(kDescriptor, 7000, 2000)); // Duplicates beyond the redundancy are ignored. ASSERT_EQ(OkStatus(), entries_.AddNewOrUpdateExisting(kDescriptor, 9000, 2000)); EXPECT_EQ(1u, entries_.present_entries()); for (const EntryMetadata& entry : entries_) { EXPECT_EQ(3u, entry.addresses().size()); EXPECT_EQ(1000u, entry.addresses()[0]); EXPECT_EQ(3000u, entry.addresses()[1]); EXPECT_EQ(7000u, entry.addresses()[2]); EXPECT_EQ(kDescriptor.key_hash, entry.hash()); EXPECT_EQ(kDescriptor.transaction_id, entry.transaction_id()); } } TEST_F(EmptyEntryCache, AddNewOrUpdateExisting_AddDuplicateEntryInSameSector) { ASSERT_EQ(OkStatus(), entries_.AddNewOrUpdateExisting(kDescriptor, 1000, 1000)); EXPECT_EQ(Status::DataLoss(), entries_.AddNewOrUpdateExisting(kDescriptor, 1950, 1000)); EXPECT_EQ(1u, entries_.present_entries()); for (const EntryMetadata& entry : entries_) { EXPECT_EQ(1u, entry.addresses().size()); EXPECT_EQ(1000u, entry.addresses()[0]); EXPECT_EQ(kDescriptor.key_hash, entry.hash()); EXPECT_EQ(kDescriptor.transaction_id, entry.transaction_id()); } } TEST_F(EmptyEntryCache, Iterator_MutableFromConst_CanModify) { entries_.AddNew(kDescriptor, 1); EntryCache::iterator it = static_cast(entries_).begin(); static_assert(kRedundancy > 1); it->AddNewAddress(1234); EXPECT_EQ(1u, it->first_address()); EXPECT_EQ(1u, (*it).addresses()[0]); EXPECT_EQ(1234u, it->addresses()[1]); } TEST_F(EmptyEntryCache, Iterator_Const) { entries_.AddNew(kDescriptor, 99); EntryCache::const_iterator it = entries_.cbegin(); EXPECT_EQ(99u, (*it).first_address()); EXPECT_EQ(99u, it->first_address()); } TEST_F(EmptyEntryCache, Iterator_Const_CanBeAssignedFromMutable) { entries_.AddNew(kDescriptor, 99); EntryCache::const_iterator it = entries_.begin(); EXPECT_EQ(99u, (*it).first_address()); EXPECT_EQ(99u, it->first_address()); } constexpr size_t kSectorSize = 64; constexpr uint32_t kMagic = 0xa14ae726; // For KVS entry magic value always use a random 32 bit integer rather than a // human readable 4 bytes. See pw_kvs/format.h for more information. constexpr auto kTheEntry = bytes::Concat(uint32_t(kMagic), // magic uint32_t(0), // checksum uint8_t(0), // alignment (16 B) uint8_t(sizeof(kTheKey) - 1), // key length uint16_t(0), // value size uint32_t(123), // transaction ID bytes::String(kTheKey)); constexpr std::array kPadding1{}; constexpr size_t kSize1 = kTheEntry.size() + kPadding1.size(); constexpr char kCollision1[] = "9FDC"; constexpr char kCollision2[] = "axzzK"; // For KVS entry magic value always use a random 32 bit integer rather than a // human readable 4 bytes. See pw_kvs/format.h for more information. constexpr auto kCollisionEntry = bytes::Concat(uint32_t(kMagic), // magic uint32_t(0), // checksum uint8_t(0), // alignment (16 B) uint8_t(sizeof(kCollision1) - 1), // key length uint16_t(0), // value size uint32_t(123), // transaction ID bytes::String(kCollision1)); constexpr std::array kPadding2{}; constexpr size_t kSize2 = kCollisionEntry.size() + kPadding2.size(); // For KVS entry magic value always use a random 32 bit integer rather than a // human readable 4 bytes. See pw_kvs/format.h for more information. constexpr auto kDeletedEntry = bytes::Concat(uint32_t(kMagic), // magic uint32_t(0), // checksum uint8_t(0), // alignment (16 B) uint8_t(sizeof("delorted") - 1), // key length uint16_t(0xffff), // value size (deleted) uint32_t(123), // transaction ID bytes::String("delorted")); constexpr std::array kPadding3{}; // For KVS entry magic value always use a random 32 bit integer rather than a // human readable 4 bytes. See pw_kvs/format.h for more information. constexpr EntryFormat kFormat{.magic = uint32_t(kMagic), .checksum = nullptr}; class InitializedEntryCache : public EmptyEntryCache { protected: static_assert(Hash(kCollision1) == Hash(kCollision2)); InitializedEntryCache() : flash_(bytes::Concat(kTheEntry, kPadding1, kTheEntry, kPadding1, kCollisionEntry, kPadding2, kDeletedEntry, kPadding3)), partition_(&flash_), sectors_(sector_descriptors_, partition_, nullptr), format_(kFormat) { sectors_.Reset(); size_t address = 0; auto entry = entries_.AddNew(kDescriptor, address); address += kSize1; entry.AddNewAddress(kSize1); address += kSize1; entries_.AddNew({.key_hash = Hash(kCollision1), .transaction_id = 125, .state = EntryState::kDeleted}, address); address += kSize2; entries_.AddNew({.key_hash = Hash("delorted"), .transaction_id = 256, .state = EntryState::kDeleted}, address); } void CheckForCorruptSectors(SectorDescriptor* sector1 = nullptr, SectorDescriptor* sector2 = nullptr) { for (const auto& sector : sectors_) { bool expect_corrupt = ((sector1 && §or == sector1) || (sector2 && §or == sector2)); EXPECT_EQ(expect_corrupt, sector.corrupt()); } } static constexpr size_t kTotalSectors = 128; FakeFlashMemoryBuffer flash_; FlashPartition partition_; Vector sector_descriptors_; Sectors sectors_; EntryFormats format_; }; TEST_F(InitializedEntryCache, EntryCounts) { EXPECT_EQ(3u, entries_.total_entries()); EXPECT_EQ(1u, entries_.present_entries()); EXPECT_EQ(kMaxEntries, entries_.max_entries()); } TEST_F(InitializedEntryCache, Reset_ClearsEntryCounts) { entries_.Reset(); EXPECT_EQ(0u, entries_.total_entries()); EXPECT_EQ(0u, entries_.present_entries()); EXPECT_EQ(kMaxEntries, entries_.max_entries()); } TEST_F(InitializedEntryCache, Find_PresentEntry) { EntryMetadata metadata; StatusWithSize result = entries_.Find(partition_, sectors_, format_, kTheKey, &metadata); ASSERT_EQ(OkStatus(), result.status()); EXPECT_EQ(0u, result.size()); EXPECT_EQ(Hash(kTheKey), metadata.hash()); EXPECT_EQ(EntryState::kValid, metadata.state()); CheckForCorruptSectors(); } TEST_F(InitializedEntryCache, Find_PresentEntryWithSingleReadError) { // Inject 2 read errors so that the initial key read and the follow-up full // read of the first entry fail. flash_.InjectReadError(FlashError::Unconditional(Status::Internal(), 2)); EntryMetadata metadata; StatusWithSize result = entries_.Find(partition_, sectors_, format_, kTheKey, &metadata); ASSERT_EQ(OkStatus(), result.status()); EXPECT_EQ(1u, result.size()); EXPECT_EQ(Hash(kTheKey), metadata.hash()); EXPECT_EQ(EntryState::kValid, metadata.state()); CheckForCorruptSectors(§ors_.FromAddress(0)); } TEST_F(InitializedEntryCache, Find_PresentEntryWithMultiReadError) { flash_.InjectReadError(FlashError::Unconditional(Status::Internal(), 4)); EntryMetadata metadata; StatusWithSize result = entries_.Find(partition_, sectors_, format_, kTheKey, &metadata); ASSERT_EQ(Status::DataLoss(), result.status()); EXPECT_EQ(1u, result.size()); CheckForCorruptSectors(§ors_.FromAddress(0), §ors_.FromAddress(kSize1)); } TEST_F(InitializedEntryCache, Find_DeletedEntry) { EntryMetadata metadata; StatusWithSize result = entries_.Find(partition_, sectors_, format_, "delorted", &metadata); ASSERT_EQ(OkStatus(), result.status()); EXPECT_EQ(0u, result.size()); EXPECT_EQ(Hash("delorted"), metadata.hash()); EXPECT_EQ(EntryState::kDeleted, metadata.state()); CheckForCorruptSectors(); } TEST_F(InitializedEntryCache, Find_MissingEntry) { EntryMetadata metadata; StatusWithSize result = entries_.Find(partition_, sectors_, format_, "3.141", &metadata); ASSERT_EQ(Status::NotFound(), result.status()); EXPECT_EQ(0u, result.size()); CheckForCorruptSectors(); } TEST_F(InitializedEntryCache, Find_Collision) { EntryMetadata metadata; StatusWithSize result = entries_.Find(partition_, sectors_, format_, kCollision2, &metadata); EXPECT_EQ(Status::AlreadyExists(), result.status()); EXPECT_EQ(0u, result.size()); CheckForCorruptSectors(); } } // namespace } // namespace pw::kvs::internal