xref: /aosp_15_r20/external/pigweed/pw_kvs/entry_cache.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #define PW_LOG_MODULE_NAME "KVS"
16 #define PW_LOG_LEVEL PW_KVS_LOG_LEVEL
17 
18 #include "pw_kvs/internal/entry_cache.h"
19 
20 #include <cinttypes>
21 
22 #include "pw_assert/check.h"
23 #include "pw_kvs/flash_memory.h"
24 #include "pw_kvs/internal/entry.h"
25 #include "pw_kvs/internal/hash.h"
26 #include "pw_kvs_private/config.h"
27 #include "pw_log/log.h"
28 
29 namespace pw::kvs::internal {
30 namespace {
31 
32 constexpr FlashPartition::Address kNoAddress = FlashPartition::Address(-1);
33 
34 }  // namespace
35 
RemoveAddress(Address address_to_remove)36 void EntryMetadata::RemoveAddress(Address address_to_remove) {
37   // Find the index of the address to remove.
38   for (Address& address : addresses_) {
39     if (address == address_to_remove) {
40       // Move the address at the back of the list to the slot of the address
41       // being removed. Do this unconditionally, even if the address to remove
42       // is the last slot since the logic still works.
43       address = addresses_.back();
44 
45       // Remove the back entry of the address list.
46       addresses_.back() = kNoAddress;
47       addresses_ = span(addresses_.begin(), addresses_.size() - 1);
48       break;
49     }
50   }
51 }
52 
Reset(const KeyDescriptor & descriptor,Address address)53 void EntryMetadata::Reset(const KeyDescriptor& descriptor, Address address) {
54   *descriptor_ = descriptor;
55 
56   addresses_[0] = address;
57   for (size_t i = 1; i < addresses_.size(); ++i) {
58     addresses_[i] = kNoAddress;
59   }
60   addresses_ = addresses_.first(1);
61 }
62 
Find(FlashPartition & partition,const Sectors & sectors,const EntryFormats & formats,std::string_view key,EntryMetadata * metadata) const63 StatusWithSize EntryCache::Find(FlashPartition& partition,
64                                 const Sectors& sectors,
65                                 const EntryFormats& formats,
66                                 std::string_view key,
67                                 EntryMetadata* metadata) const {
68   const uint32_t hash = internal::Hash(key);
69   Entry::KeyBuffer key_buffer;
70   bool error_detected = false;
71 
72   for (size_t i = 0; i < descriptors_.size(); ++i) {
73     if (descriptors_[i].key_hash == hash) {
74       bool key_found = false;
75       std::string_view read_key;
76 
77       for (Address address : addresses(i)) {
78         Status read_result =
79             Entry::ReadKey(partition, address, key.size(), key_buffer.data());
80 
81         read_key = std::string_view(key_buffer.data(), key.size());
82 
83         if (read_result.ok() && hash == internal::Hash(read_key)) {
84           key_found = true;
85           break;
86         } else {
87           // A hash mismatch can be caused by reading invalid data or a key hash
88           // collision of keys with differing size. To verify the data read from
89           // flash is good, validate the entry.
90           Entry entry;
91           read_result = Entry::Read(partition, address, formats, &entry);
92           if (read_result.ok() && entry.VerifyChecksumInFlash().ok()) {
93             key_found = true;
94             break;
95           }
96 
97           PW_LOG_WARN(
98               "   Found corrupt entry, invalidating this copy of the key");
99           error_detected = true;
100           sectors.FromAddress(address).mark_corrupt();
101         }
102       }
103       size_t error_val = error_detected ? 1 : 0;
104 
105       if (!key_found) {
106         PW_LOG_ERROR("No valid entries for key. Data has been lost!");
107         return StatusWithSize::DataLoss(error_val);
108       } else if (key == read_key) {
109         PW_LOG_DEBUG("Found match for key hash 0x%08" PRIx32, hash);
110         *metadata = EntryMetadata(descriptors_[i], addresses(i));
111         return StatusWithSize(error_val);
112       } else {
113         PW_LOG_WARN("Found key hash collision for 0x%08" PRIx32, hash);
114         return StatusWithSize::AlreadyExists(error_val);
115       }
116     }
117   }
118   return StatusWithSize::NotFound();
119 }
120 
AddNew(const KeyDescriptor & descriptor,Address address) const121 EntryMetadata EntryCache::AddNew(const KeyDescriptor& descriptor,
122                                  Address address) const {
123   // TODO(hepler): DCHECK(!full());
124   Address* first_address = ResetAddresses(descriptors_.size(), address);
125   descriptors_.push_back(descriptor);
126   return EntryMetadata(descriptors_.back(), span(first_address, 1));
127 }
128 
129 // Removes an existing entry from the cache
RemoveEntry(iterator & entry_it)130 EntryCache::iterator EntryCache::RemoveEntry(iterator& entry_it) {
131   PW_DCHECK_PTR_EQ(entry_it.entry_cache_, this);
132 
133   const unsigned int index_to_remove =
134       entry_it.metadata_.descriptor_ - &descriptors_.front();
135   const KeyDescriptor last_desc = descriptors_[descriptors_.size() - 1];
136 
137   // Since order is not important, this copies the last descriptor into the
138   // deleted descriptor's space and then pops the last entry.
139   Address* addresses_at_end = first_address(descriptors_.size() - 1);
140 
141   if (index_to_remove < descriptors_.size() - 1) {
142     Address* addresses_to_remove = first_address(index_to_remove);
143     for (unsigned int i = 0; i < redundancy_; i++) {
144       addresses_to_remove[i] = addresses_at_end[i];
145     }
146     descriptors_[index_to_remove] = last_desc;
147   }
148 
149   // Erase the last entry since it was copied over the entry being deleted.
150   descriptors_.pop_back();
151 
152   return {this, descriptors_.data() + index_to_remove};
153 }
154 
155 // TODO(hepler): This method is the trigger of the O(valid_entries *
156 // all_entries) time complexity for reading. At some cost to memory, this could
157 // be optimized by using a hash table instead of scanning, but in practice this
158 // should be fine for a small number of keys
AddNewOrUpdateExisting(const KeyDescriptor & descriptor,Address address,size_t sector_size_bytes) const159 Status EntryCache::AddNewOrUpdateExisting(const KeyDescriptor& descriptor,
160                                           Address address,
161                                           size_t sector_size_bytes) const {
162   // With the new key descriptor, either add it to the descriptor table or
163   // overwrite an existing entry with an older version of the key.
164   const int index = FindIndex(descriptor.key_hash);
165 
166   // Write a new entry if there is room.
167   if (index == -1) {
168     if (full()) {
169       return Status::ResourceExhausted();
170     }
171     AddNew(descriptor, address);
172     return OkStatus();
173   }
174 
175   // Existing entry is old; replace the existing entry with the new one.
176   if (descriptor.transaction_id > descriptors_[index].transaction_id) {
177     descriptors_[index] = descriptor;
178     ResetAddresses(index, address);
179     return OkStatus();
180   }
181 
182   // If the entries have a duplicate transaction ID, add the new (redundant)
183   // entry to the existing descriptor.
184   if (descriptors_[index].transaction_id == descriptor.transaction_id) {
185     if (descriptors_[index].key_hash != descriptor.key_hash) {
186       PW_LOG_ERROR("Duplicate entry for key 0x%08" PRIx32
187                    " with transaction ID %" PRIu32 " has non-matching hash",
188                    descriptor.key_hash,
189                    descriptor.transaction_id);
190       return Status::DataLoss();
191     }
192 
193     // Verify that this entry is not in the same sector as an existing copy of
194     // this same key.
195     for (Address existing_address : addresses(index)) {
196       if (existing_address / sector_size_bytes == address / sector_size_bytes) {
197         PW_LOG_DEBUG("Multiple Redundant entries in same sector %u",
198                      unsigned(address / sector_size_bytes));
199         return Status::DataLoss();
200       }
201     }
202 
203     AddAddressIfRoom(index, address);
204   } else {
205     PW_LOG_DEBUG("Found stale entry when appending; ignoring");
206   }
207   return OkStatus();
208 }
209 
present_entries() const210 size_t EntryCache::present_entries() const {
211   size_t present_entries = 0;
212 
213   for (const KeyDescriptor& descriptor : descriptors_) {
214     if (descriptor.state != EntryState::kDeleted) {
215       present_entries += 1;
216     }
217   }
218 
219   return present_entries;
220 }
221 
FindIndex(uint32_t key_hash) const222 int EntryCache::FindIndex(uint32_t key_hash) const {
223   for (size_t i = 0; i < descriptors_.size(); ++i) {
224     if (descriptors_[i].key_hash == key_hash) {
225       return i;
226     }
227   }
228   return -1;
229 }
230 
AddAddressIfRoom(size_t descriptor_index,Address address) const231 void EntryCache::AddAddressIfRoom(size_t descriptor_index,
232                                   Address address) const {
233   Address* const existing = first_address(descriptor_index);
234 
235   for (size_t i = 0; i < redundancy(); ++i) {
236     if (existing[i] == kNoAddress) {
237       existing[i] = address;
238       return;
239     }
240   }
241 }
242 
addresses(size_t descriptor_index) const243 span<EntryCache::Address> EntryCache::addresses(size_t descriptor_index) const {
244   Address* const addresses = first_address(descriptor_index);
245 
246   size_t size = 0;
247   while (size < redundancy() && addresses[size] != kNoAddress) {
248     size += 1;
249   }
250 
251   return span(addresses, size);
252 }
253 
ResetAddresses(size_t descriptor_index,Address address) const254 EntryCache::Address* EntryCache::ResetAddresses(size_t descriptor_index,
255                                                 Address address) const {
256   Address* first = first_address(descriptor_index);
257   *first = address;
258 
259   // Clear the additional addresses, if any.
260   for (size_t i = 1; i < redundancy_; ++i) {
261     first[i] = kNoAddress;
262   }
263 
264   return first;
265 }
266 
267 }  // namespace pw::kvs::internal
268