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.h"
19
20 #include <cinttypes>
21 #include <cstring>
22
23 #include "pw_kvs_private/config.h"
24 #include "pw_log/log.h"
25 #include "pw_status/try.h"
26
27 namespace pw::kvs::internal {
28
29 static_assert(
30 kMaxFlashAlignment >= Entry::kMinAlignmentBytes,
31 "Flash alignment is required to be at least Entry::kMinAlignmentBytes");
32
33 constexpr size_t kWriteBufferSize =
34 std::max(kMaxFlashAlignment, 4 * Entry::kMinAlignmentBytes);
35
36 using std::byte;
37
Read(FlashPartition & partition,Address address,const internal::EntryFormats & formats,Entry * entry)38 Status Entry::Read(FlashPartition& partition,
39 Address address,
40 const internal::EntryFormats& formats,
41 Entry* entry) {
42 EntryHeader header;
43 PW_TRY(partition.Read(address, sizeof(header), &header));
44
45 if (partition.AppearsErased(as_bytes(span(&header.magic, 1)))) {
46 return Status::NotFound();
47 }
48 if (header.key_length_bytes > kMaxKeyLength) {
49 return Status::DataLoss();
50 }
51
52 const EntryFormat* format = formats.Find(header.magic);
53 if (format == nullptr) {
54 PW_LOG_ERROR("Found corrupt magic: %" PRIx32 " at address %u",
55 header.magic,
56 unsigned(address));
57 return Status::DataLoss();
58 }
59
60 *entry = Entry(&partition, address, *format, header);
61 return OkStatus();
62 }
63
ReadKey(FlashPartition & partition,Address address,size_t key_length,char * key)64 Status Entry::ReadKey(FlashPartition& partition,
65 Address address,
66 size_t key_length,
67 char* key) {
68 if (key_length == 0u || key_length > kMaxKeyLength) {
69 return Status::DataLoss();
70 }
71
72 return partition.Read(address + sizeof(EntryHeader), key_length, key)
73 .status();
74 }
75
Entry(FlashPartition & partition,Address address,const EntryFormat & format,Key key,span<const byte> value,uint16_t value_size_bytes,uint32_t transaction_id)76 Entry::Entry(FlashPartition& partition,
77 Address address,
78 const EntryFormat& format,
79 Key key,
80 span<const byte> value,
81 uint16_t value_size_bytes,
82 uint32_t transaction_id)
83 : Entry(&partition,
84 address,
85 format,
86 {.magic = format.magic,
87 .checksum = 0,
88 .alignment_units =
89 alignment_bytes_to_units(partition.alignment_bytes()),
90 .key_length_bytes = static_cast<uint8_t>(key.size()),
91 .value_size_bytes = value_size_bytes,
92 .transaction_id = transaction_id}) {
93 if (checksum_algo_ != nullptr) {
94 span<const byte> checksum = CalculateChecksum(key, value);
95 std::memcpy(&header_.checksum,
96 checksum.data(),
97 std::min(checksum.size(), sizeof(header_.checksum)));
98 }
99 }
100
Write(Key key,span<const byte> value) const101 StatusWithSize Entry::Write(Key key, span<const byte> value) const {
102 FlashPartition::Output flash(partition(), address_);
103 return AlignedWrite<kWriteBufferSize>(
104 flash,
105 alignment_bytes(),
106 {as_bytes(span(&header_, 1)), as_bytes(span(key)), value});
107 }
108
Update(const EntryFormat & new_format,uint32_t new_transaction_id)109 Status Entry::Update(const EntryFormat& new_format,
110 uint32_t new_transaction_id) {
111 checksum_algo_ = new_format.checksum;
112 header_.magic = new_format.magic;
113 header_.alignment_units =
114 alignment_bytes_to_units(partition_->alignment_bytes());
115 header_.transaction_id = new_transaction_id;
116
117 // If we could write the header last, we could avoid reading the entry twice
118 // when moving an entry. However, to support alignments greater than the
119 // header size, we first read the entire value to calculate the new checksum,
120 // then write the full entry in WriteFrom.
121 return CalculateChecksumFromFlash();
122 }
123
Copy(Address new_address) const124 StatusWithSize Entry::Copy(Address new_address) const {
125 PW_LOG_DEBUG("Copying entry from %u to %u as ID %" PRIu32,
126 unsigned(address()),
127 unsigned(new_address),
128 transaction_id());
129
130 FlashPartition::Output output(partition(), new_address);
131 AlignedWriterBuffer<kWriteBufferSize> writer(alignment_bytes(), output);
132
133 // Use this object's header rather than the header in flash of flash, since
134 // this Entry may have been updated.
135 PW_TRY_WITH_SIZE(writer.Write(&header_, sizeof(header_)));
136
137 // Write only the key and value from the original entry.
138 FlashPartition::Input input(partition(), address() + sizeof(EntryHeader));
139 PW_TRY_WITH_SIZE(writer.Write(input, key_length() + value_size()));
140 return writer.Flush();
141 }
142
ReadValue(span<byte> buffer,size_t offset_bytes) const143 StatusWithSize Entry::ReadValue(span<byte> buffer, size_t offset_bytes) const {
144 if (offset_bytes > value_size()) {
145 return StatusWithSize::OutOfRange();
146 }
147
148 const size_t remaining_bytes = value_size() - offset_bytes;
149 const size_t read_size = std::min(buffer.size(), remaining_bytes);
150
151 StatusWithSize result = partition().Read(
152 address_ + sizeof(EntryHeader) + key_length() + offset_bytes,
153 buffer.subspan(0, read_size));
154 PW_TRY_WITH_SIZE(result);
155
156 if (read_size != remaining_bytes) {
157 return StatusWithSize::ResourceExhausted(read_size);
158 }
159 return StatusWithSize(read_size);
160 }
161
ValueMatches(span<const std::byte> value) const162 Status Entry::ValueMatches(span<const std::byte> value) const {
163 if (value_size() != value.size_bytes()) {
164 return Status::NotFound();
165 }
166
167 Address address = address_ + sizeof(EntryHeader) + key_length();
168 Address end = address + value_size();
169 const std::byte* value_ptr = value.data();
170
171 std::array<std::byte, 2 * kMinAlignmentBytes> buffer;
172 while (address < end) {
173 const size_t read_size = std::min(size_t(end - address), buffer.size());
174 PW_TRY(partition_->Read(address, span(buffer).first(read_size)));
175
176 if (std::memcmp(buffer.data(), value_ptr, read_size) != 0) {
177 return Status::NotFound();
178 }
179
180 address += read_size;
181 value_ptr += read_size;
182 }
183
184 return OkStatus();
185 }
186
VerifyChecksum(Key key,span<const byte> value) const187 Status Entry::VerifyChecksum(Key key, span<const byte> value) const {
188 if (checksum_algo_ == nullptr) {
189 return header_.checksum == 0 ? OkStatus() : Status::DataLoss();
190 }
191 CalculateChecksum(key, value);
192 return checksum_algo_->Verify(checksum_bytes());
193 }
194
VerifyChecksumInFlash() const195 Status Entry::VerifyChecksumInFlash() const {
196 // Read the entire entry piece-by-piece into a small buffer. If the entry is
197 // 32 B or less, only one read is required.
198 union {
199 EntryHeader header_to_verify;
200 byte buffer[sizeof(EntryHeader) * 2];
201 };
202
203 size_t bytes_to_read = size();
204 size_t read_size = std::min(sizeof(buffer), bytes_to_read);
205
206 Address read_address = address_;
207
208 // Read the first chunk, which includes the header, and compare the checksum.
209 PW_TRY(partition().Read(read_address, read_size, buffer));
210
211 if (header_to_verify.checksum != header_.checksum) {
212 PW_LOG_ERROR("Expected checksum 0x%08" PRIx32 ", found 0x%08" PRIx32,
213 header_.checksum,
214 header_to_verify.checksum);
215 return Status::DataLoss();
216 }
217
218 if (checksum_algo_ == nullptr) {
219 return header_.checksum == 0 ? OkStatus() : Status::DataLoss();
220 }
221
222 // The checksum is calculated as if the header's checksum field were 0.
223 header_to_verify.checksum = 0;
224
225 checksum_algo_->Reset();
226
227 while (true) {
228 // Add the chunk in the buffer to the checksum.
229 checksum_algo_->Update(buffer, read_size);
230
231 bytes_to_read -= read_size;
232 if (bytes_to_read == 0u) {
233 break;
234 }
235
236 // Read the next chunk into the buffer.
237 read_address += read_size;
238 read_size = std::min(sizeof(buffer), bytes_to_read);
239 PW_TRY(partition().Read(read_address, read_size, buffer));
240 }
241
242 checksum_algo_->Finish();
243 return checksum_algo_->Verify(checksum_bytes());
244 }
245
DebugLog() const246 void Entry::DebugLog() const {
247 PW_LOG_DEBUG("Entry [%s]: ", deleted() ? "tombstone" : "present");
248 PW_LOG_DEBUG(" Address = 0x%x", unsigned(address_));
249 PW_LOG_DEBUG(" Transaction = %u", unsigned(transaction_id()));
250 PW_LOG_DEBUG(" Magic = 0x%x", unsigned(magic()));
251 PW_LOG_DEBUG(" Checksum = 0x%x", unsigned(header_.checksum));
252 PW_LOG_DEBUG(" Key length = 0x%x", unsigned(key_length()));
253 PW_LOG_DEBUG(" Value length = 0x%x", unsigned(value_size()));
254 PW_LOG_DEBUG(" Entry size = 0x%x", unsigned(size()));
255 PW_LOG_DEBUG(" Alignment = 0x%x", unsigned(alignment_bytes()));
256 }
257
CalculateChecksum(const Key key,span<const byte> value) const258 span<const byte> Entry::CalculateChecksum(const Key key,
259 span<const byte> value) const {
260 checksum_algo_->Reset();
261
262 {
263 EntryHeader header_for_checksum = header_;
264 header_for_checksum.checksum = 0;
265
266 checksum_algo_->Update(&header_for_checksum, sizeof(header_for_checksum));
267 checksum_algo_->Update(as_bytes(span(key)));
268 checksum_algo_->Update(value);
269 }
270
271 AddPaddingBytesToChecksum();
272
273 return checksum_algo_->Finish();
274 }
275
CalculateChecksumFromFlash()276 Status Entry::CalculateChecksumFromFlash() {
277 header_.checksum = 0;
278
279 if (checksum_algo_ == nullptr) {
280 return OkStatus();
281 }
282
283 checksum_algo_->Reset();
284 checksum_algo_->Update(&header_, sizeof(header_));
285
286 Address address = address_ + sizeof(EntryHeader);
287 // To handle alignment changes, do not read the padding. The padding is added
288 // after checksumming the key and value from flash.
289 const Address end = address_ + content_size();
290
291 std::array<std::byte, 2 * kMinAlignmentBytes> buffer;
292 while (address < end) {
293 const size_t read_size = std::min(size_t(end - address), buffer.size());
294 PW_TRY(partition_->Read(address, span(buffer).first(read_size)));
295
296 checksum_algo_->Update(buffer.data(), read_size);
297 address += read_size;
298 }
299
300 AddPaddingBytesToChecksum();
301
302 span checksum = checksum_algo_->Finish();
303 std::memcpy(&header_.checksum,
304 checksum.data(),
305 std::min(checksum.size(), sizeof(header_.checksum)));
306 return OkStatus();
307 }
308
AddPaddingBytesToChecksum() const309 void Entry::AddPaddingBytesToChecksum() const {
310 constexpr byte padding[kMinAlignmentBytes - 1] = {};
311 size_t padding_to_add = Padding(content_size(), alignment_bytes());
312
313 while (padding_to_add != 0u) {
314 const size_t chunk_size = std::min(padding_to_add, sizeof(padding));
315 checksum_algo_->Update(padding, chunk_size);
316 padding_to_add -= chunk_size;
317 }
318 }
319
320 } // namespace pw::kvs::internal
321