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