blob: 1f4dfa81df6fb0b43e112a5957ba798804554218 [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
61Status EntryCache::Find(FlashPartition& partition,
62 string_view key,
63 EntryMetadata* metadata) const {
64 const uint32_t hash = internal::Hash(key);
65 Entry::KeyBuffer key_buffer;
66
67 for (size_t i = 0; i < descriptors_.size(); ++i) {
68 if (descriptors_[i].key_hash == hash) {
69 TRY(Entry::ReadKey(
70 partition, *first_address(i), key.size(), key_buffer.data()));
71
72 if (key == string_view(key_buffer.data(), key.size())) {
73 PW_LOG_DEBUG("Found match for key hash 0x%08" PRIx32, hash);
74 *metadata = EntryMetadata(descriptors_[i], addresses(i));
75 return Status::OK;
76 } else {
77 PW_LOG_WARN("Found key hash collision for 0x%08" PRIx32, hash);
78 return Status::ALREADY_EXISTS;
79 }
80 }
81 }
82 return Status::NOT_FOUND;
83}
84
85Status EntryCache::FindExisting(FlashPartition& partition,
86 string_view key,
87 EntryMetadata* metadata) const {
88 Status status = Find(partition, key, metadata);
89
90 // If the key's hash collides with an existing key or if the key is deleted,
91 // treat it as if it is not in the KVS.
92 if (status == Status::ALREADY_EXISTS ||
Wyatt Hepler02946272020-03-18 10:36:22 -070093 (status.ok() && metadata->state() == EntryState::kDeleted)) {
Wyatt Hepler7ded6da2020-03-11 18:24:43 -070094 return Status::NOT_FOUND;
95 }
96 return status;
97}
98
99EntryMetadata EntryCache::AddNew(const KeyDescriptor& descriptor,
100 Address entry_address) {
101 // TODO(hepler): DCHECK(!full());
102 Address* first_address = ResetAddresses(descriptors_.size(), entry_address);
103 descriptors_.push_back(descriptor);
104 return EntryMetadata(descriptors_.back(), span(first_address, 1));
105}
106
107// TODO: This method is the trigger of the O(valid_entries * all_entries) time
108// complexity for reading. At some cost to memory, this could be optimized by
109// using a hash table instead of scanning, but in practice this should be fine
110// for a small number of keys
111Status EntryCache::AddNewOrUpdateExisting(const KeyDescriptor& descriptor,
112 Address address,
113 size_t sector_size_bytes) {
114 // With the new key descriptor, either add it to the descriptor table or
115 // overwrite an existing entry with an older version of the key.
116 const int index = FindIndex(descriptor.key_hash);
117
118 // Write a new entry if there is room.
119 if (index == -1) {
120 if (full()) {
121 return Status::RESOURCE_EXHAUSTED;
122 }
123 AddNew(descriptor, address);
124 return Status::OK;
125 }
126
127 // Existing entry is old; replace the existing entry with the new one.
128 if (descriptor.transaction_id > descriptors_[index].transaction_id) {
129 descriptors_[index] = descriptor;
130 ResetAddresses(index, address);
131 return Status::OK;
132 }
133
134 // If the entries have a duplicate transaction ID, add the new (redundant)
135 // entry to the existing descriptor.
136 if (descriptors_[index].transaction_id == descriptor.transaction_id) {
137 if (descriptors_[index].key_hash != descriptor.key_hash) {
138 PW_LOG_ERROR("Duplicate entry for key 0x%08" PRIx32
139 " with transaction ID %" PRIu32 " has non-matching hash",
140 descriptor.key_hash,
141 descriptor.transaction_id);
142 return Status::DATA_LOSS;
143 }
144
145 // Verify that this entry is not in the same sector as an existing copy of
146 // this same key.
147 for (Address existing_address : addresses(index)) {
148 if (existing_address / sector_size_bytes == address / sector_size_bytes) {
Wyatt Hepler09b3a2a2020-03-25 13:18:37 -0700149 PW_LOG_DEBUG("Multiple Redundant entries in same sector %u",
150 unsigned(address / sector_size_bytes));
Wyatt Hepler7ded6da2020-03-11 18:24:43 -0700151 return Status::DATA_LOSS;
152 }
153 }
154
155 AddAddressIfRoom(index, address);
156 } else {
157 PW_LOG_DEBUG("Found stale entry when appending; ignoring");
158 }
159 return Status::OK;
160}
161
162size_t EntryCache::present_entries() const {
163 size_t present_entries = 0;
164
165 for (const KeyDescriptor& descriptor : descriptors_) {
166 if (descriptor.state != EntryState::kDeleted) {
167 present_entries += 1;
168 }
169 }
170
171 return present_entries;
172}
173
174int EntryCache::FindIndex(uint32_t key_hash) const {
175 for (size_t i = 0; i < descriptors_.size(); ++i) {
176 if (descriptors_[i].key_hash == key_hash) {
177 return i;
178 }
179 }
180 return -1;
181}
182
183void EntryCache::AddAddressIfRoom(size_t descriptor_index, Address address) {
184 Address* const existing = first_address(descriptor_index);
185
186 for (size_t i = 0; i < redundancy(); ++i) {
187 if (existing[i] == kNoAddress) {
188 existing[i] = address;
Wyatt Hepler7ded6da2020-03-11 18:24:43 -0700189 return;
190 }
191 }
192}
193
194span<EntryCache::Address> EntryCache::addresses(size_t descriptor_index) const {
195 Address* const addresses = first_address(descriptor_index);
196
197 size_t size = 0;
198 while (size < redundancy() && addresses[size] != kNoAddress) {
199 size += 1;
200 }
201
202 return span(addresses, size);
203}
204
205EntryCache::Address* EntryCache::ResetAddresses(size_t descriptor_index,
206 Address address) {
207 Address* first = first_address(descriptor_index);
208 *first = address;
209
210 // Clear the additional addresses, if any.
211 for (size_t i = 1; i < redundancy_; ++i) {
212 first[i] = kNoAddress;
213 }
214
215 return first;
216}
217
218} // namespace pw::kvs::internal