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