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 <string_view> 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_span/span.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,std::string_view key,span<const std::byte> value,uint32_t transaction_id)65 static Entry Valid(FlashPartition& partition, 66 Address address, 67 const EntryFormat& format, 68 std::string_view key, 69 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,std::string_view key,uint32_t transaction_id)76 static Entry Tombstone(FlashPartition& partition, 77 Address address, 78 const EntryFormat& format, 79 std::string_view 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(std::string_view key)92 KeyDescriptor descriptor(std::string_view key) const { 93 return descriptor(Hash(key)); 94 } 95 descriptor(uint32_t key_hash)96 KeyDescriptor descriptor(uint32_t key_hash) const { 97 return KeyDescriptor{key_hash, 98 transaction_id(), 99 deleted() ? EntryState::kDeleted : EntryState::kValid}; 100 } 101 102 StatusWithSize Write(std::string_view key, span<const std::byte> value) const; 103 104 // Changes the format and transcation ID for this entry. In order to calculate 105 // the new checksum, the entire entry is read into a small stack-allocated 106 // buffer. The updated entry may be written to flash using the Copy function. 107 Status Update(const EntryFormat& new_format, uint32_t new_transaction_id); 108 109 // Writes this entry at a new address. The key and value are read from the 110 // entry's current address. The Entry object's header, which may be newer than 111 // what is in flash, is used. 112 StatusWithSize Copy(Address new_address) const; 113 114 // Reads a key into a buffer, which must be large enough for a max-length key. 115 // If successful, the size is returned in the StatusWithSize. The key is not 116 // null terminated. 117 template <size_t kSize> ReadKey(std::array<char,kSize> & key)118 StatusWithSize ReadKey(std::array<char, kSize>& key) const { 119 static_assert(kSize >= kMaxKeyLength); 120 return StatusWithSize( 121 ReadKey(partition(), address_, key_length(), key.data()), key_length()); 122 } 123 124 StatusWithSize ReadValue(span<std::byte> buffer, 125 size_t offset_bytes = 0) const; 126 127 Status ValueMatches(span<const std::byte> value) const; 128 129 Status VerifyChecksum(std::string_view key, 130 span<const std::byte> value) const; 131 132 Status VerifyChecksumInFlash() const; 133 134 // Calculates the total size of an entry, including padding. size(const FlashPartition & partition,std::string_view key,span<const std::byte> value)135 static size_t size(const FlashPartition& partition, 136 std::string_view key, 137 span<const std::byte> value) { 138 return AlignUp(sizeof(EntryHeader) + key.size() + value.size(), 139 std::max(partition.alignment_bytes(), kMinAlignmentBytes)); 140 } 141 142 // Byte size of overhead (not-key, not-value) in an entry. Does not include 143 // any paddding used to get proper size alignment. entry_overhead()144 static constexpr size_t entry_overhead() { return sizeof(EntryHeader); } 145 address()146 Address address() const { return address_; } 147 set_address(Address address)148 void set_address(Address address) { address_ = address; } 149 150 // The address at which the next possible entry could be located. next_address()151 Address next_address() const { return address() + size(); } 152 153 // Total size of this entry, including padding. size()154 size_t size() const { return AlignUp(content_size(), alignment_bytes()); } 155 156 // The length of the key in bytes. Keys are not null terminated. key_length()157 size_t key_length() const { return header_.key_length_bytes; } 158 159 // The size of the value, without padding. The size is 0 if this is a 160 // tombstone entry. value_size()161 size_t value_size() const { 162 return deleted() ? 0u : header_.value_size_bytes; 163 } 164 magic()165 uint32_t magic() const { return header_.magic; } 166 transaction_id()167 uint32_t transaction_id() const { return header_.transaction_id; } 168 169 // True if this is a tombstone entry. deleted()170 bool deleted() const { 171 return header_.value_size_bytes == kDeletedValueLength; 172 } 173 174 void DebugLog() const; 175 176 private: 177 static constexpr uint16_t kDeletedValueLength = 0xFFFF; 178 179 Entry(FlashPartition& partition, 180 Address address, 181 const EntryFormat& format, 182 std::string_view key, 183 span<const std::byte> value, 184 uint16_t value_size_bytes, 185 uint32_t transaction_id); 186 Entry(FlashPartition * partition,Address address,const EntryFormat & format,const EntryHeader & header)187 constexpr Entry(FlashPartition* partition, 188 Address address, 189 const EntryFormat& format, 190 const EntryHeader& header) 191 : partition_(partition), 192 address_(address), 193 checksum_algo_(format.checksum), 194 header_(header) {} 195 partition()196 FlashPartition& partition() const { return *partition_; } 197 alignment_bytes()198 size_t alignment_bytes() const { return (header_.alignment_units + 1) * 16; } 199 200 // The total size of the entry, excluding padding. content_size()201 size_t content_size() const { 202 return sizeof(EntryHeader) + key_length() + value_size(); 203 } 204 checksum_bytes()205 span<const std::byte> checksum_bytes() const { 206 return as_bytes(span<const uint32_t>(&header_.checksum, 1)); 207 } 208 209 span<const std::byte> CalculateChecksum(std::string_view key, 210 span<const std::byte> value) const; 211 212 Status CalculateChecksumFromFlash(); 213 214 // Update the checksum with 0s to pad the entry to its alignment boundary. 215 void AddPaddingBytesToChecksum() const; 216 alignment_bytes_to_units(size_t alignment_bytes)217 static constexpr uint8_t alignment_bytes_to_units(size_t alignment_bytes) { 218 return (alignment_bytes + 15) / 16 - 1; // An alignment of 0 is invalid. 219 } 220 221 FlashPartition* partition_; 222 Address address_; 223 ChecksumAlgorithm* checksum_algo_; 224 EntryHeader header_; 225 }; 226 227 } // namespace internal 228 } // namespace kvs 229 } // namespace pw 230