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