blob: 4528ab24d6ea6eeeb999c33c3c4f9135516fa4d7 [file] [log] [blame]
Wyatt Hepler7ded6da2020-03-11 18:24:43 -07001// 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#include "pw_kvs/internal/entry_cache.h"
16
17#include <cinttypes>
18
19#include "pw_kvs/flash_memory.h"
20#include "pw_kvs/internal/entry.h"
21#include "pw_kvs/internal/hash.h"
22#include "pw_kvs_private/macros.h"
23#include "pw_log/log.h"
24
25namespace pw::kvs::internal {
26namespace {
27
28using std::string_view;
29
30constexpr FlashPartition::Address kNoAddress = FlashPartition::Address(-1);
31
32} // namespace
33
David Rogers9abe3c72020-03-24 19:03:13 -070034void EntryMetadata::RemoveAddress(Address address_to_remove) {
35 // Find the index of the address to remove.
36 for (Address& address : addresses_) {
37 if (address == address_to_remove) {
38 // Move the address at the back of the list to the slot of the address
39 // being removed. Do this unconditionally, even if the address to remove
40 // is the last slot since the logic still works.
41 address = addresses_.back();
42
43 // Remove the back entry of the address list.
44 addresses_.back() = kNoAddress;
45 addresses_ = span(addresses_.begin(), addresses_.size() - 1);
46 break;
47 }
48 }
49}
50
Wyatt Hepler7ded6da2020-03-11 18:24:43 -070051void EntryMetadata::Reset(const KeyDescriptor& descriptor, Address address) {
52 *descriptor_ = descriptor;
53
54 addresses_[0] = address;
55 for (size_t i = 1; i < addresses_.size(); ++i) {
56 addresses_[i] = kNoAddress;
57 }
58 addresses_ = addresses_.first(1);
59}
60
David Rogers98fea472020-04-01 15:43:48 -070061StatusWithSize EntryCache::Find(FlashPartition& partition,
62 const Sectors& sectors,
63 const EntryFormats& formats,
64 string_view key,
65 EntryMetadata* metadata) const {
Wyatt Hepler7ded6da2020-03-11 18:24:43 -070066 const uint32_t hash = internal::Hash(key);
67 Entry::KeyBuffer key_buffer;
David Rogers98fea472020-04-01 15:43:48 -070068 bool error_detected = false;
Wyatt Hepler7ded6da2020-03-11 18:24:43 -070069
70 for (size_t i = 0; i < descriptors_.size(); ++i) {
71 if (descriptors_[i].key_hash == hash) {
David Rogers98fea472020-04-01 15:43:48 -070072 bool key_found = false;
73 string_view read_key;
Wyatt Hepler7ded6da2020-03-11 18:24:43 -070074
David Rogers98fea472020-04-01 15:43:48 -070075 for (Address address : addresses(i)) {
76 Status read_result =
77 Entry::ReadKey(partition, address, key.size(), key_buffer.data());
78
79 read_key = string_view(key_buffer.data(), key.size());
80
81 if (read_result.ok() && hash == internal::Hash(read_key)) {
82 key_found = true;
83 break;
84 } else {
85 // A hash mismatch can be caused by reading invalid data or a key hash
86 // collision of keys with differing size. To verify the data read from
87 // flash is good, validate the entry.
88 Entry entry;
89 read_result = Entry::Read(partition, address, formats, &entry);
90 if (read_result.ok() && entry.VerifyChecksumInFlash().ok()) {
91 key_found = true;
92 break;
93 }
94
95 PW_LOG_WARN(
96 " Found corrupt entry, invalidating this copy of the key");
97 error_detected = true;
98 sectors.FromAddress(address).mark_corrupt();
99 }
100 }
101 size_t error_val = error_detected ? 1 : 0;
102
103 if (!key_found) {
104 PW_LOG_ERROR("No valid entries for key. Data has been lost!");
105 return StatusWithSize(Status::DATA_LOSS, error_val);
106 } else if (key == read_key) {
Wyatt Hepler7ded6da2020-03-11 18:24:43 -0700107 PW_LOG_DEBUG("Found match for key hash 0x%08" PRIx32, hash);
108 *metadata = EntryMetadata(descriptors_[i], addresses(i));
David Rogers98fea472020-04-01 15:43:48 -0700109 return StatusWithSize(Status::OK, error_val);
Wyatt Hepler7ded6da2020-03-11 18:24:43 -0700110 } else {
111 PW_LOG_WARN("Found key hash collision for 0x%08" PRIx32, hash);
David Rogers98fea472020-04-01 15:43:48 -0700112 return StatusWithSize(Status::ALREADY_EXISTS, error_val);
Wyatt Hepler7ded6da2020-03-11 18:24:43 -0700113 }
114 }
115 }
David Rogers98fea472020-04-01 15:43:48 -0700116 return StatusWithSize::NOT_FOUND;
Wyatt Hepler7ded6da2020-03-11 18:24:43 -0700117}
118
119EntryMetadata EntryCache::AddNew(const KeyDescriptor& descriptor,
Wyatt Hepler883e7402020-04-06 09:31:18 -0700120 Address entry_address) const {
Wyatt Hepler7ded6da2020-03-11 18:24:43 -0700121 // TODO(hepler): DCHECK(!full());
122 Address* first_address = ResetAddresses(descriptors_.size(), entry_address);
123 descriptors_.push_back(descriptor);
124 return EntryMetadata(descriptors_.back(), span(first_address, 1));
125}
126
127// TODO: This method is the trigger of the O(valid_entries * all_entries) time
128// complexity for reading. At some cost to memory, this could be optimized by
129// using a hash table instead of scanning, but in practice this should be fine
130// for a small number of keys
131Status EntryCache::AddNewOrUpdateExisting(const KeyDescriptor& descriptor,
132 Address address,
Wyatt Hepler883e7402020-04-06 09:31:18 -0700133 size_t sector_size_bytes) const {
Wyatt Hepler7ded6da2020-03-11 18:24:43 -0700134 // With the new key descriptor, either add it to the descriptor table or
135 // overwrite an existing entry with an older version of the key.
136 const int index = FindIndex(descriptor.key_hash);
137
138 // Write a new entry if there is room.
139 if (index == -1) {
140 if (full()) {
141 return Status::RESOURCE_EXHAUSTED;
142 }
143 AddNew(descriptor, address);
144 return Status::OK;
145 }
146
147 // Existing entry is old; replace the existing entry with the new one.
148 if (descriptor.transaction_id > descriptors_[index].transaction_id) {
149 descriptors_[index] = descriptor;
150 ResetAddresses(index, address);
151 return Status::OK;
152 }
153
154 // If the entries have a duplicate transaction ID, add the new (redundant)
155 // entry to the existing descriptor.
156 if (descriptors_[index].transaction_id == descriptor.transaction_id) {
157 if (descriptors_[index].key_hash != descriptor.key_hash) {
158 PW_LOG_ERROR("Duplicate entry for key 0x%08" PRIx32
159 " with transaction ID %" PRIu32 " has non-matching hash",
160 descriptor.key_hash,
161 descriptor.transaction_id);
162 return Status::DATA_LOSS;
163 }
164
165 // Verify that this entry is not in the same sector as an existing copy of
166 // this same key.
167 for (Address existing_address : addresses(index)) {
168 if (existing_address / sector_size_bytes == address / sector_size_bytes) {
Wyatt Hepler09b3a2a2020-03-25 13:18:37 -0700169 PW_LOG_DEBUG("Multiple Redundant entries in same sector %u",
170 unsigned(address / sector_size_bytes));
Wyatt Hepler7ded6da2020-03-11 18:24:43 -0700171 return Status::DATA_LOSS;
172 }
173 }
174
175 AddAddressIfRoom(index, address);
176 } else {
177 PW_LOG_DEBUG("Found stale entry when appending; ignoring");
178 }
179 return Status::OK;
180}
181
182size_t EntryCache::present_entries() const {
183 size_t present_entries = 0;
184
185 for (const KeyDescriptor& descriptor : descriptors_) {
186 if (descriptor.state != EntryState::kDeleted) {
187 present_entries += 1;
188 }
189 }
190
191 return present_entries;
192}
193
194int EntryCache::FindIndex(uint32_t key_hash) const {
195 for (size_t i = 0; i < descriptors_.size(); ++i) {
196 if (descriptors_[i].key_hash == key_hash) {
197 return i;
198 }
199 }
200 return -1;
201}
202
Wyatt Hepler883e7402020-04-06 09:31:18 -0700203void EntryCache::AddAddressIfRoom(size_t descriptor_index,
204 Address address) const {
Wyatt Hepler7ded6da2020-03-11 18:24:43 -0700205 Address* const existing = first_address(descriptor_index);
206
207 for (size_t i = 0; i < redundancy(); ++i) {
208 if (existing[i] == kNoAddress) {
209 existing[i] = address;
Wyatt Hepler7ded6da2020-03-11 18:24:43 -0700210 return;
211 }
212 }
213}
214
215span<EntryCache::Address> EntryCache::addresses(size_t descriptor_index) const {
216 Address* const addresses = first_address(descriptor_index);
217
218 size_t size = 0;
219 while (size < redundancy() && addresses[size] != kNoAddress) {
220 size += 1;
221 }
222
223 return span(addresses, size);
224}
225
226EntryCache::Address* EntryCache::ResetAddresses(size_t descriptor_index,
Wyatt Hepler883e7402020-04-06 09:31:18 -0700227 Address address) const {
Wyatt Hepler7ded6da2020-03-11 18:24:43 -0700228 Address* first = first_address(descriptor_index);
229 *first = address;
230
231 // Clear the additional addresses, if any.
232 for (size_t i = 1; i < redundancy_; ++i) {
233 first[i] = kNoAddress;
234 }
235
236 return first;
237}
238
239} // namespace pw::kvs::internal