• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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