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(std::as_bytes(std::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,std::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 std::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 std::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,std::span<const byte> value) const101 StatusWithSize Entry::Write(Key key, std::span<const byte> value) const {
102 FlashPartition::Output flash(partition(), address_);
103 return AlignedWrite<kWriteBufferSize>(flash,
104 alignment_bytes(),
105 {std::as_bytes(std::span(&header_, 1)),
106 std::as_bytes(std::span(key)),
107 value});
108 }
109
Update(const EntryFormat & new_format,uint32_t new_transaction_id)110 Status Entry::Update(const EntryFormat& new_format,
111 uint32_t new_transaction_id) {
112 checksum_algo_ = new_format.checksum;
113 header_.magic = new_format.magic;
114 header_.alignment_units =
115 alignment_bytes_to_units(partition_->alignment_bytes());
116 header_.transaction_id = new_transaction_id;
117
118 // If we could write the header last, we could avoid reading the entry twice
119 // when moving an entry. However, to support alignments greater than the
120 // header size, we first read the entire value to calculate the new checksum,
121 // then write the full entry in WriteFrom.
122 return CalculateChecksumFromFlash();
123 }
124
Copy(Address new_address) const125 StatusWithSize Entry::Copy(Address new_address) const {
126 PW_LOG_DEBUG("Copying entry from %u to %u as ID %" PRIu32,
127 unsigned(address()),
128 unsigned(new_address),
129 transaction_id());
130
131 FlashPartition::Output output(partition(), new_address);
132 AlignedWriterBuffer<kWriteBufferSize> writer(alignment_bytes(), output);
133
134 // Use this object's header rather than the header in flash of flash, since
135 // this Entry may have been updated.
136 PW_TRY_WITH_SIZE(writer.Write(&header_, sizeof(header_)));
137
138 // Write only the key and value from the original entry.
139 FlashPartition::Input input(partition(), address() + sizeof(EntryHeader));
140 PW_TRY_WITH_SIZE(writer.Write(input, key_length() + value_size()));
141 return writer.Flush();
142 }
143
ReadValue(std::span<byte> buffer,size_t offset_bytes) const144 StatusWithSize Entry::ReadValue(std::span<byte> buffer,
145 size_t offset_bytes) const {
146 if (offset_bytes > value_size()) {
147 return StatusWithSize::OutOfRange();
148 }
149
150 const size_t remaining_bytes = value_size() - offset_bytes;
151 const size_t read_size = std::min(buffer.size(), remaining_bytes);
152
153 StatusWithSize result = partition().Read(
154 address_ + sizeof(EntryHeader) + key_length() + offset_bytes,
155 buffer.subspan(0, read_size));
156 PW_TRY_WITH_SIZE(result);
157
158 if (read_size != remaining_bytes) {
159 return StatusWithSize::ResourceExhausted(read_size);
160 }
161 return StatusWithSize(read_size);
162 }
163
ValueMatches(std::span<const std::byte> value) const164 Status Entry::ValueMatches(std::span<const std::byte> value) const {
165 if (value_size() != value.size_bytes()) {
166 return Status::NotFound();
167 }
168
169 Address address = address_ + sizeof(EntryHeader) + key_length();
170 Address end = address + value_size();
171 const std::byte* value_ptr = value.data();
172
173 std::array<std::byte, 2 * kMinAlignmentBytes> buffer;
174 while (address < end) {
175 const size_t read_size = std::min(size_t(end - address), buffer.size());
176 PW_TRY(partition_->Read(address, std::span(buffer).first(read_size)));
177
178 if (std::memcmp(buffer.data(), value_ptr, read_size) != 0) {
179 return Status::NotFound();
180 }
181
182 address += read_size;
183 value_ptr += read_size;
184 }
185
186 return OkStatus();
187 }
188
VerifyChecksum(Key key,std::span<const byte> value) const189 Status Entry::VerifyChecksum(Key key, std::span<const byte> value) const {
190 if (checksum_algo_ == nullptr) {
191 return header_.checksum == 0 ? OkStatus() : Status::DataLoss();
192 }
193 CalculateChecksum(key, value);
194 return checksum_algo_->Verify(checksum_bytes());
195 }
196
VerifyChecksumInFlash() const197 Status Entry::VerifyChecksumInFlash() const {
198 // Read the entire entry piece-by-piece into a small buffer. If the entry is
199 // 32 B or less, only one read is required.
200 union {
201 EntryHeader header_to_verify;
202 byte buffer[sizeof(EntryHeader) * 2];
203 };
204
205 size_t bytes_to_read = size();
206 size_t read_size = std::min(sizeof(buffer), bytes_to_read);
207
208 Address read_address = address_;
209
210 // Read the first chunk, which includes the header, and compare the checksum.
211 PW_TRY(partition().Read(read_address, read_size, buffer));
212
213 if (header_to_verify.checksum != header_.checksum) {
214 PW_LOG_ERROR("Expected checksum 0x%08" PRIx32 ", found 0x%08" PRIx32,
215 header_.checksum,
216 header_to_verify.checksum);
217 return Status::DataLoss();
218 }
219
220 if (checksum_algo_ == nullptr) {
221 return header_.checksum == 0 ? OkStatus() : Status::DataLoss();
222 }
223
224 // The checksum is calculated as if the header's checksum field were 0.
225 header_to_verify.checksum = 0;
226
227 checksum_algo_->Reset();
228
229 while (true) {
230 // Add the chunk in the buffer to the checksum.
231 checksum_algo_->Update(buffer, read_size);
232
233 bytes_to_read -= read_size;
234 if (bytes_to_read == 0u) {
235 break;
236 }
237
238 // Read the next chunk into the buffer.
239 read_address += read_size;
240 read_size = std::min(sizeof(buffer), bytes_to_read);
241 PW_TRY(partition().Read(read_address, read_size, buffer));
242 }
243
244 checksum_algo_->Finish();
245 return checksum_algo_->Verify(checksum_bytes());
246 }
247
DebugLog() const248 void Entry::DebugLog() const {
249 PW_LOG_DEBUG("Entry [%s]: ", deleted() ? "tombstone" : "present");
250 PW_LOG_DEBUG(" Address = 0x%x", unsigned(address_));
251 PW_LOG_DEBUG(" Transaction = %u", unsigned(transaction_id()));
252 PW_LOG_DEBUG(" Magic = 0x%x", unsigned(magic()));
253 PW_LOG_DEBUG(" Checksum = 0x%x", unsigned(header_.checksum));
254 PW_LOG_DEBUG(" Key length = 0x%x", unsigned(key_length()));
255 PW_LOG_DEBUG(" Value length = 0x%x", unsigned(value_size()));
256 PW_LOG_DEBUG(" Entry size = 0x%x", unsigned(size()));
257 PW_LOG_DEBUG(" Alignment = 0x%x", unsigned(alignment_bytes()));
258 }
259
CalculateChecksum(const Key key,std::span<const byte> value) const260 std::span<const byte> Entry::CalculateChecksum(
261 const Key key, std::span<const byte> value) const {
262 checksum_algo_->Reset();
263
264 {
265 EntryHeader header_for_checksum = header_;
266 header_for_checksum.checksum = 0;
267
268 checksum_algo_->Update(&header_for_checksum, sizeof(header_for_checksum));
269 checksum_algo_->Update(std::as_bytes(std::span(key)));
270 checksum_algo_->Update(value);
271 }
272
273 AddPaddingBytesToChecksum();
274
275 return checksum_algo_->Finish();
276 }
277
CalculateChecksumFromFlash()278 Status Entry::CalculateChecksumFromFlash() {
279 header_.checksum = 0;
280
281 if (checksum_algo_ == nullptr) {
282 return OkStatus();
283 }
284
285 checksum_algo_->Reset();
286 checksum_algo_->Update(&header_, sizeof(header_));
287
288 Address address = address_ + sizeof(EntryHeader);
289 // To handle alignment changes, do not read the padding. The padding is added
290 // after checksumming the key and value from flash.
291 const Address end = address_ + content_size();
292
293 std::array<std::byte, 2 * kMinAlignmentBytes> buffer;
294 while (address < end) {
295 const size_t read_size = std::min(size_t(end - address), buffer.size());
296 PW_TRY(partition_->Read(address, std::span(buffer).first(read_size)));
297
298 checksum_algo_->Update(buffer.data(), read_size);
299 address += read_size;
300 }
301
302 AddPaddingBytesToChecksum();
303
304 std::span checksum = checksum_algo_->Finish();
305 std::memcpy(&header_.checksum,
306 checksum.data(),
307 std::min(checksum.size(), sizeof(header_.checksum)));
308 return OkStatus();
309 }
310
AddPaddingBytesToChecksum() const311 void Entry::AddPaddingBytesToChecksum() const {
312 constexpr byte padding[kMinAlignmentBytes - 1] = {};
313 size_t padding_to_add = Padding(content_size(), alignment_bytes());
314
315 while (padding_to_add != 0u) {
316 const size_t chunk_size = std::min(padding_to_add, sizeof(padding));
317 checksum_algo_->Update(padding, chunk_size);
318 padding_to_add -= chunk_size;
319 }
320 }
321
322 } // namespace pw::kvs::internal
323