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 // This file defines classes for managing the in-flash format for KVS entires.
16 #pragma once
17 
18 #include <algorithm>
19 #include <array>
20 #include <cstddef>
21 #include <cstdint>
22 #include <span>
23 
24 #include "pw_kvs/alignment.h"
25 #include "pw_kvs/checksum.h"
26 #include "pw_kvs/flash_memory.h"
27 #include "pw_kvs/format.h"
28 #include "pw_kvs/internal/hash.h"
29 #include "pw_kvs/internal/key_descriptor.h"
30 #include "pw_kvs/key.h"
31 
32 namespace pw {
33 namespace kvs {
34 namespace internal {
35 
36 // Entry represents a key-value entry in a flash partition.
37 class Entry {
38  public:
39   static constexpr size_t kMinAlignmentBytes = sizeof(EntryHeader);
40   static constexpr size_t kMaxKeyLength = 0b111111;
41 
42   using Address = FlashPartition::Address;
43 
44   // Buffer capable of holding any valid key (without a null terminator);
45   using KeyBuffer = std::array<char, kMaxKeyLength>;
46 
47   // Returns flash partition Read error codes, or one of the following:
48   //
49   //          OK: successfully read the header and initialized the Entry
50   //   NOT_FOUND: read the header, but the data appears to be erased
51   //   DATA_LOSS: read the header, but it contained invalid data
52   //
53   static Status Read(FlashPartition& partition,
54                      Address address,
55                      const internal::EntryFormats& formats,
56                      Entry* entry);
57 
58   // Reads a key into a buffer, which must be at least key_length bytes.
59   static Status ReadKey(FlashPartition& partition,
60                         Address address,
61                         size_t key_length,
62                         char* key);
63 
64   // Creates a new Entry for a valid (non-deleted) entry.
Valid(FlashPartition & partition,Address address,const EntryFormat & format,Key key,std::span<const std::byte> value,uint32_t transaction_id)65   static Entry Valid(FlashPartition& partition,
66                      Address address,
67                      const EntryFormat& format,
68                      Key key,
69                      std::span<const std::byte> value,
70                      uint32_t transaction_id) {
71     return Entry(
72         partition, address, format, key, value, value.size(), transaction_id);
73   }
74 
75   // Creates a new Entry for a tombstone entry, which marks a deleted key.
Tombstone(FlashPartition & partition,Address address,const EntryFormat & format,Key key,uint32_t transaction_id)76   static Entry Tombstone(FlashPartition& partition,
77                          Address address,
78                          const EntryFormat& format,
79                          Key key,
80                          uint32_t transaction_id) {
81     return Entry(partition,
82                  address,
83                  format,
84                  key,
85                  {},
86                  kDeletedValueLength,
87                  transaction_id);
88   }
89 
90   Entry() = default;
91 
descriptor(Key key)92   KeyDescriptor descriptor(Key key) const { return descriptor(Hash(key)); }
93 
descriptor(uint32_t key_hash)94   KeyDescriptor descriptor(uint32_t key_hash) const {
95     return KeyDescriptor{key_hash,
96                          transaction_id(),
97                          deleted() ? EntryState::kDeleted : EntryState::kValid};
98   }
99 
100   StatusWithSize Write(Key key, std::span<const std::byte> value) const;
101 
102   // Changes the format and transcation ID for this entry. In order to calculate
103   // the new checksum, the entire entry is read into a small stack-allocated
104   // buffer. The updated entry may be written to flash using the Copy function.
105   Status Update(const EntryFormat& new_format, uint32_t new_transaction_id);
106 
107   // Writes this entry at a new address. The key and value are read from the
108   // entry's current address. The Entry object's header, which may be newer than
109   // what is in flash, is used.
110   StatusWithSize Copy(Address new_address) const;
111 
112   // Reads a key into a buffer, which must be large enough for a max-length key.
113   // If successful, the size is returned in the StatusWithSize. The key is not
114   // null terminated.
115   template <size_t kSize>
ReadKey(std::array<char,kSize> & key)116   StatusWithSize ReadKey(std::array<char, kSize>& key) const {
117     static_assert(kSize >= kMaxKeyLength);
118     return StatusWithSize(
119         ReadKey(partition(), address_, key_length(), key.data()), key_length());
120   }
121 
122   StatusWithSize ReadValue(std::span<std::byte> buffer,
123                            size_t offset_bytes = 0) const;
124 
125   Status ValueMatches(std::span<const std::byte> value) const;
126 
127   Status VerifyChecksum(Key key, std::span<const std::byte> value) const;
128 
129   Status VerifyChecksumInFlash() const;
130 
131   // Calculates the total size of an entry, including padding.
size(const FlashPartition & partition,Key key,std::span<const std::byte> value)132   static size_t size(const FlashPartition& partition,
133                      Key key,
134                      std::span<const std::byte> value) {
135     return AlignUp(sizeof(EntryHeader) + key.size() + value.size(),
136                    std::max(partition.alignment_bytes(), kMinAlignmentBytes));
137   }
138 
139   // Byte size of overhead (not-key, not-value) in an entry. Does not include
140   // any paddding used to get proper size alignment.
entry_overhead()141   static constexpr size_t entry_overhead() { return sizeof(EntryHeader); }
142 
address()143   Address address() const { return address_; }
144 
set_address(Address address)145   void set_address(Address address) { address_ = address; }
146 
147   // The address at which the next possible entry could be located.
next_address()148   Address next_address() const { return address() + size(); }
149 
150   // Total size of this entry, including padding.
size()151   size_t size() const { return AlignUp(content_size(), alignment_bytes()); }
152 
153   // The length of the key in bytes. Keys are not null terminated.
key_length()154   size_t key_length() const { return header_.key_length_bytes; }
155 
156   // The size of the value, without padding. The size is 0 if this is a
157   // tombstone entry.
value_size()158   size_t value_size() const {
159     return deleted() ? 0u : header_.value_size_bytes;
160   }
161 
magic()162   uint32_t magic() const { return header_.magic; }
163 
transaction_id()164   uint32_t transaction_id() const { return header_.transaction_id; }
165 
166   // True if this is a tombstone entry.
deleted()167   bool deleted() const {
168     return header_.value_size_bytes == kDeletedValueLength;
169   }
170 
171   void DebugLog() const;
172 
173  private:
174   static constexpr uint16_t kDeletedValueLength = 0xFFFF;
175 
176   Entry(FlashPartition& partition,
177         Address address,
178         const EntryFormat& format,
179         Key key,
180         std::span<const std::byte> value,
181         uint16_t value_size_bytes,
182         uint32_t transaction_id);
183 
Entry(FlashPartition * partition,Address address,const EntryFormat & format,EntryHeader header)184   constexpr Entry(FlashPartition* partition,
185                   Address address,
186                   const EntryFormat& format,
187                   EntryHeader header)
188       : partition_(partition),
189         address_(address),
190         checksum_algo_(format.checksum),
191         header_(header) {}
192 
partition()193   FlashPartition& partition() const { return *partition_; }
194 
alignment_bytes()195   size_t alignment_bytes() const { return (header_.alignment_units + 1) * 16; }
196 
197   // The total size of the entry, excluding padding.
content_size()198   size_t content_size() const {
199     return sizeof(EntryHeader) + key_length() + value_size();
200   }
201 
checksum_bytes()202   std::span<const std::byte> checksum_bytes() const {
203     return std::as_bytes(std::span<const uint32_t>(&header_.checksum, 1));
204   }
205 
206   std::span<const std::byte> CalculateChecksum(
207       Key key, std::span<const std::byte> value) const;
208 
209   Status CalculateChecksumFromFlash();
210 
211   // Update the checksum with 0s to pad the entry to its alignment boundary.
212   void AddPaddingBytesToChecksum() const;
213 
alignment_bytes_to_units(size_t alignment_bytes)214   static constexpr uint8_t alignment_bytes_to_units(size_t alignment_bytes) {
215     return (alignment_bytes + 15) / 16 - 1;  // An alignment of 0 is invalid.
216   }
217 
218   FlashPartition* partition_;
219   Address address_;
220   ChecksumAlgorithm* checksum_algo_;
221   EntryHeader header_;
222 };
223 
224 }  // namespace internal
225 }  // namespace kvs
226 }  // namespace pw
227