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 #pragma once 15 16 #include <array> 17 #include <cstddef> 18 #include <cstdint> 19 #include <type_traits> 20 21 #include "pw_containers/vector.h" 22 #include "pw_kvs/checksum.h" 23 #include "pw_kvs/flash_memory.h" 24 #include "pw_kvs/format.h" 25 #include "pw_kvs/internal/entry.h" 26 #include "pw_kvs/internal/entry_cache.h" 27 #include "pw_kvs/internal/key_descriptor.h" 28 #include "pw_kvs/internal/sectors.h" 29 #include "pw_kvs/internal/span_traits.h" 30 #include "pw_kvs/key.h" 31 #include "pw_span/span.h" 32 #include "pw_status/status.h" 33 #include "pw_status/status_with_size.h" 34 35 namespace pw { 36 namespace kvs { 37 38 enum class GargbageCollectOnWrite { 39 // Disable all automatic garbage collection on write. 40 kDisabled, 41 42 // Allow up to a single sector, if needed, to be garbage collected on write. 43 kOneSector, 44 45 // Allow as many sectors as needed be garbage collected on write. 46 kAsManySectorsNeeded, 47 }; 48 49 enum class ErrorRecovery { 50 // Immediately do full recovery of any errors that are detected. 51 kImmediate, 52 53 // Recover from errors, but delay time consuming recovery steps until later 54 // as part of other time consuming operations. Such as waiting to garbage 55 // collect sectors with corrupt entries until the next garbage collection. 56 kLazy, 57 58 // Only recover from errors when manually triggered as part of maintenance 59 // operations. This is not recommended for normal use, only for test or 60 // read-only use. 61 kManual, 62 }; 63 64 struct Options { 65 // Perform garbage collection if necessary when writing. If not kDisabled, 66 // garbage collection is attempted if space for an entry cannot be found. This 67 // is a relatively lengthy operation. If kDisabled, Put calls that would 68 // require garbage collection fail with RESOURCE_EXHAUSTED. 69 GargbageCollectOnWrite gc_on_write = 70 GargbageCollectOnWrite::kAsManySectorsNeeded; 71 72 // When the KVS handles errors that are discovered, such as corrupt entries, 73 // not enough redundant copys of an entry, etc. 74 ErrorRecovery recovery = ErrorRecovery::kLazy; 75 76 // Verify an entry's checksum after reading it from flash. 77 bool verify_on_read = true; 78 79 // Verify an in-flash entry's checksum after writing it. 80 bool verify_on_write = true; 81 }; 82 83 class KeyValueStore { 84 public: 85 // KeyValueStores are declared as instances of 86 // KeyValueStoreBuffer<MAX_ENTRIES, MAX_SECTORS>, which allocates buffers for 87 // tracking entries and flash sectors. 88 89 // Initializes the key-value store. Must be called before calling other 90 // functions. 91 // 92 // Return values: 93 // 94 // OK: KVS successfully initialized. 95 // DATA_LOSS: KVS initialized and is usable, but contains corrupt data. 96 // UNKNOWN: Unknown error. KVS is not initialized. 97 // 98 Status Init(); 99 initialized()100 bool initialized() const { 101 return initialized_ == InitializationState::kReady; 102 } 103 104 // Reads the value of an entry in the KVS. The value is read into the provided 105 // buffer and the number of bytes read is returned. If desired, the read can 106 // be started at an offset. 107 // 108 // If the output buffer is too small for the value, Get returns 109 // RESOURCE_EXHAUSTED with the number of bytes read. The remainder of the 110 // value can be read by calling get with an offset. 111 // 112 // OK: the entry was successfully read 113 // NOT_FOUND: the key is not present in the KVS 114 // DATA_LOSS: found the entry, but the data was corrupted 115 // RESOURCE_EXHAUSTED: the buffer could not fit the entire value, but as 116 // many bytes as possible were written to it 117 // FAILED_PRECONDITION: the KVS is not initialized 118 // INVALID_ARGUMENT: key is empty or too long or value is too large 119 // 120 StatusWithSize Get(Key key, 121 span<std::byte> value, 122 size_t offset_bytes = 0) const; 123 124 // This overload of Get accepts a pointer to a trivially copyable object. 125 // If the value is an array, call Get with 126 // as_writable_bytes(span(array)), or pass a pointer to the array 127 // instead of the array itself. 128 template <typename Pointer, 129 typename = std::enable_if_t<std::is_pointer<Pointer>::value>> Get(const Key & key,const Pointer & pointer)130 Status Get(const Key& key, const Pointer& pointer) const { 131 using T = std::remove_reference_t<std::remove_pointer_t<Pointer>>; 132 CheckThatObjectCanBePutOrGet<T>(); 133 return FixedSizeGet(key, pointer, sizeof(T)); 134 } 135 136 // Adds a key-value entry to the KVS. If the key was already present, its 137 // value is overwritten. 138 // 139 // The value may be a span of bytes or a trivially copyable object. 140 // 141 // In the current implementation, all keys in the KVS must have a unique hash. 142 // If Put is called with a key whose hash matches an existing key, nothing 143 // is added and ALREADY_EXISTS is returned. 144 // 145 // OK: the entry was successfully added or updated 146 // DATA_LOSS: checksum validation failed after writing the data 147 // RESOURCE_EXHAUSTED: there is not enough space to add the entry 148 // ALREADY_EXISTS: the entry could not be added because a different key 149 // with the same hash is already in the KVS 150 // FAILED_PRECONDITION: the KVS is not initialized 151 // INVALID_ARGUMENT: key is empty or too long or value is too large 152 // 153 template <typename T, 154 typename std::enable_if_t<ConvertsToSpan<T>::value>* = nullptr> Put(const Key & key,const T & value)155 Status Put(const Key& key, const T& value) { 156 return PutBytes(key, as_bytes(internal::make_span(value))); 157 } 158 159 template <typename T, 160 typename std::enable_if_t<!ConvertsToSpan<T>::value>* = nullptr> Put(const Key & key,const T & value)161 Status Put(const Key& key, const T& value) { 162 CheckThatObjectCanBePutOrGet<T>(); 163 return PutBytes(key, as_bytes(span<const T>(&value, 1))); 164 } 165 166 // Removes a key-value entry from the KVS. 167 // 168 // OK: the entry was successfully added or updated 169 // NOT_FOUND: the key is not present in the KVS 170 // DATA_LOSS: checksum validation failed after recording the erase 171 // RESOURCE_EXHAUSTED: insufficient space to mark the entry as deleted 172 // FAILED_PRECONDITION: the KVS is not initialized 173 // INVALID_ARGUMENT: key is empty or too long 174 // 175 Status Delete(Key key); 176 177 // Returns the size of the value corresponding to the key. 178 // 179 // OK: the size was returned successfully 180 // NOT_FOUND: the key is not present in the KVS 181 // DATA_LOSS: checksum validation failed after reading the entry 182 // FAILED_PRECONDITION: the KVS is not initialized 183 // INVALID_ARGUMENT: key is empty or too long 184 // 185 StatusWithSize ValueSize(Key key) const; 186 187 // Perform all maintenance possible, including all neeeded repairing of 188 // corruption and garbage collection of reclaimable space in the KVS. When 189 // configured for manual recovery, this (along with FullMaintenance) is the 190 // only way KVS repair is triggered. 191 // 192 // - Heavy garbage collection of all reclaimable space, regardless of valid 193 // data in the sector. HeavyMaintenance()194 Status HeavyMaintenance() { 195 return FullMaintenanceHelper(MaintenanceType::kHeavy); 196 } 197 198 // Perform all maintenance possible, including all neeeded repairing of 199 // corruption and garbage collection of reclaimable space in the KVS. When 200 // configured for manual recovery, this (along with HeavyMaintenance) is the 201 // only way KVS repair is triggered. 202 // 203 // - Regular will not garbage collect sectors with valid data unless the KVS 204 // is mostly full. FullMaintenance()205 Status FullMaintenance() { 206 return FullMaintenanceHelper(MaintenanceType::kRegular); 207 } 208 209 // Perform a portion of KVS maintenance. If configured for at least lazy 210 // recovery, will do any needed repairing of corruption. Does garbage 211 // collection of part of the KVS, typically a single sector or similar unit 212 // that makes sense for the KVS implementation. 213 Status PartialMaintenance(); 214 215 void LogDebugInfo() const; 216 217 // Classes and functions to support STL-style iteration. 218 class iterator; 219 220 class Item { 221 public: 222 // The key as a null-terminated string. key()223 const char* key() const { return key_buffer_.data(); } 224 225 // Gets the value referred to by this iterator. Equivalent to 226 // KeyValueStore::Get. 227 StatusWithSize Get(span<std::byte> value_buffer, 228 size_t offset_bytes = 0) const { 229 return kvs_.Get(key(), *iterator_, value_buffer, offset_bytes); 230 } 231 232 template <typename Pointer, 233 typename = std::enable_if_t<std::is_pointer<Pointer>::value>> Get(const Pointer & pointer)234 Status Get(const Pointer& pointer) const { 235 using T = std::remove_reference_t<std::remove_pointer_t<Pointer>>; 236 CheckThatObjectCanBePutOrGet<T>(); 237 return kvs_.FixedSizeGet(key(), *iterator_, pointer, sizeof(T)); 238 } 239 240 // Reads the size of the value referred to by this iterator. Equivalent to 241 // KeyValueStore::ValueSize. ValueSize()242 StatusWithSize ValueSize() const { return kvs_.ValueSize(*iterator_); } 243 244 private: 245 friend class iterator; 246 Item(const KeyValueStore & kvs,const internal::EntryCache::const_iterator & item_iterator)247 constexpr Item(const KeyValueStore& kvs, 248 const internal::EntryCache::const_iterator& item_iterator) 249 : kvs_(kvs), iterator_(item_iterator), key_buffer_{} {} 250 251 void ReadKey(); 252 253 const KeyValueStore& kvs_; 254 internal::EntryCache::const_iterator iterator_; 255 256 // Buffer large enough for a null-terminated version of any valid key. 257 std::array<char, internal::Entry::kMaxKeyLength + 1> key_buffer_; 258 }; 259 260 class iterator { 261 public: 262 iterator& operator++(); 263 264 iterator operator++(int) { 265 const iterator original(item_.kvs_, item_.iterator_); 266 operator++(); 267 return original; 268 } 269 270 // Reads the entry's key from flash. 271 const Item& operator*() { 272 item_.ReadKey(); 273 return item_; 274 } 275 276 const Item* operator->() { 277 return &operator*(); // Read the key into the Item object. 278 } 279 280 constexpr bool operator==(const iterator& rhs) const { 281 return item_.iterator_ == rhs.item_.iterator_; 282 } 283 284 constexpr bool operator!=(const iterator& rhs) const { 285 return item_.iterator_ != rhs.item_.iterator_; 286 } 287 288 private: 289 friend class KeyValueStore; 290 iterator(const KeyValueStore & kvs,const internal::EntryCache::const_iterator & item_iterator)291 constexpr iterator( 292 const KeyValueStore& kvs, 293 const internal::EntryCache::const_iterator& item_iterator) 294 : item_(kvs, item_iterator) {} 295 296 Item item_; 297 }; 298 299 using const_iterator = iterator; // Standard alias for iterable types. 300 301 iterator begin() const; end()302 iterator end() const { return iterator(*this, entry_cache_.end()); } 303 304 // Returns the number of valid entries in the KeyValueStore. size()305 size_t size() const { return entry_cache_.present_entries(); } 306 307 // Returns the number of valid entries and deleted entries yet to be collected total_entries_with_deleted()308 size_t total_entries_with_deleted() const { 309 return entry_cache_.total_entries(); 310 } 311 max_size()312 size_t max_size() const { return entry_cache_.max_entries(); } 313 empty()314 size_t empty() const { return size() == 0u; } 315 316 // Returns the number of transactions that have occurred since the KVS was 317 // first used. This value is retained across initializations, but is reset if 318 // the underlying flash is erased. transaction_count()319 uint32_t transaction_count() const { return last_transaction_id_; } 320 321 struct StorageStats { 322 size_t writable_bytes; 323 size_t in_use_bytes; 324 size_t reclaimable_bytes; 325 size_t sector_erase_count; 326 size_t corrupt_sectors_recovered; 327 size_t missing_redundant_entries_recovered; 328 }; 329 330 StorageStats GetStorageStats() const; 331 332 // Level of redundancy to use for writing entries. redundancy()333 size_t redundancy() const { return entry_cache_.redundancy(); } 334 error_detected()335 bool error_detected() const { return error_detected_; } 336 337 // Maximum number of bytes allowed for a key-value combination. max_key_value_size_bytes()338 size_t max_key_value_size_bytes() const { 339 return max_key_value_size_bytes(partition_.sector_size_bytes()); 340 } 341 342 // Maximum number of bytes allowed for a given sector size for a key-value 343 // combination. max_key_value_size_bytes(size_t partition_sector_size_bytes)344 static constexpr size_t max_key_value_size_bytes( 345 size_t partition_sector_size_bytes) { 346 return partition_sector_size_bytes - Entry::entry_overhead(); 347 } 348 349 // Check KVS for any error conditions. Primarily intended for test and 350 // internal use. 351 bool CheckForErrors(); 352 353 protected: 354 using Address = FlashPartition::Address; 355 using Entry = internal::Entry; 356 using KeyDescriptor = internal::KeyDescriptor; 357 using SectorDescriptor = internal::SectorDescriptor; 358 359 // In the future, will be able to provide additional EntryFormats for 360 // backwards compatibility. 361 KeyValueStore(FlashPartition* partition, 362 span<const EntryFormat> formats, 363 const Options& options, 364 size_t redundancy, 365 Vector<SectorDescriptor>& sector_descriptor_list, 366 const SectorDescriptor** temp_sectors_to_skip, 367 Vector<KeyDescriptor>& key_descriptor_list, 368 Address* addresses); 369 370 private: 371 using EntryMetadata = internal::EntryMetadata; 372 using EntryState = internal::EntryState; 373 374 template <typename T> CheckThatObjectCanBePutOrGet()375 static constexpr void CheckThatObjectCanBePutOrGet() { 376 static_assert( 377 std::is_trivially_copyable<T>::value && !std::is_pointer<T>::value, 378 "Only trivially copyable, non-pointer objects may be Put and Get by " 379 "value. Any value may be stored by converting it to a byte span " 380 "with as_bytes(span(&value, 1)) or " 381 "as_writable_bytes(span(&value, 1))."); 382 } 383 384 Status InitializeMetadata(); 385 Status LoadEntry(Address entry_address, Address* next_entry_address); 386 Status ScanForEntry(const SectorDescriptor& sector, 387 Address start_address, 388 Address* next_entry_address); 389 390 // Remove deleted keys from the entry cache, including freeing sector bytes 391 // used by those keys. This must only be done directly after a full garbage 392 // collection, otherwise the current deleted entry could be garbage 393 // collected before the older stale entry producing a window for an 394 // invalid/corrupted KVS state if there was a power-fault, crash or other 395 // interruption. 396 Status RemoveDeletedKeyEntries(); 397 398 Status PutBytes(Key key, span<const std::byte> value); 399 400 StatusWithSize ValueSize(const EntryMetadata& metadata) const; 401 402 Status ReadEntry(const EntryMetadata& metadata, Entry& entry) const; 403 404 // Finds the metadata for an entry matching a particular key. Searches for a 405 // KeyDescriptor that matches this key and sets *metadata_out to point to it 406 // if one is found. 407 // 408 // OK: there is a matching descriptor and *metadata is set 409 // NOT_FOUND: there is no descriptor that matches this key, but this key 410 // has a unique hash (and could potentially be added to the 411 // KVS) 412 // ALREADY_EXISTS: there is no descriptor that matches this key, but the 413 // key's hash collides with the hash for an existing 414 // descriptor 415 // 416 Status FindEntry(Key key, EntryMetadata* metadata_out) const; 417 418 // Searches for a KeyDescriptor that matches this key and sets *metadata_out 419 // to point to it if one is found. 420 // 421 // OK: there is a matching descriptor and *metadata_out is set 422 // NOT_FOUND: there is no descriptor that matches this key 423 // 424 Status FindExisting(Key key, EntryMetadata* metadata_out) const; 425 426 StatusWithSize Get(Key key, 427 const EntryMetadata& metadata, 428 span<std::byte> value_buffer, 429 size_t offset_bytes) const; 430 431 Status FixedSizeGet(Key key, void* value, size_t size_bytes) const; 432 433 Status FixedSizeGet(Key key, 434 const EntryMetadata& metadata, 435 void* value, 436 size_t size_bytes) const; 437 438 Status CheckWriteOperation(Key key) const; 439 Status CheckReadOperation(Key key) const; 440 441 Status WriteEntryForExistingKey(EntryMetadata& metadata, 442 EntryState new_state, 443 Key key, 444 span<const std::byte> value); 445 446 Status WriteEntryForNewKey(Key key, span<const std::byte> value); 447 448 Status WriteEntry(Key key, 449 span<const std::byte> value, 450 EntryState new_state, 451 EntryMetadata* prior_metadata = nullptr, 452 const internal::Entry* prior_entry = nullptr); 453 454 EntryMetadata CreateOrUpdateKeyDescriptor(const Entry& new_entry, 455 Key key, 456 EntryMetadata* prior_metadata, 457 size_t prior_size); 458 459 EntryMetadata UpdateKeyDescriptor(const Entry& entry, 460 Address new_address, 461 EntryMetadata* prior_metadata, 462 size_t prior_size); 463 464 Status GetAddressesForWrite(Address* write_addresses, size_t write_size); 465 466 Status GetSectorForWrite(SectorDescriptor** sector, 467 size_t entry_size, 468 span<const Address> reserved_addresses); 469 470 Status MarkSectorCorruptIfNotOk(Status status, SectorDescriptor* sector); 471 472 Status AppendEntry(const Entry& entry, Key key, span<const std::byte> value); 473 474 StatusWithSize CopyEntryToSector(Entry& entry, 475 SectorDescriptor* new_sector, 476 Address new_address); 477 478 Status RelocateEntry(const EntryMetadata& metadata, 479 KeyValueStore::Address& address, 480 span<const Address> reserved_addresses); 481 482 // Perform all maintenance possible, including all neeeded repairing of 483 // corruption and garbage collection of reclaimable space in the KVS. When 484 // configured for manual recovery, this is the only way KVS repair is 485 // triggered. 486 // 487 // - Regular will not garbage collect sectors with valid data unless the KVS 488 // is mostly full. 489 // - Heavy will garbage collect all reclaimable space regardless of valid data 490 // in the sector. 491 enum class MaintenanceType { 492 kRegular, 493 kHeavy, 494 }; 495 Status FullMaintenanceHelper(MaintenanceType maintenance_type); 496 497 // Find and garbage collect a singe sector that does not include a reserved 498 // address. 499 Status GarbageCollect(span<const Address> reserved_addresses); 500 501 Status RelocateKeyAddressesInSector(SectorDescriptor& sector_to_gc, 502 const EntryMetadata& metadata, 503 span<const Address> reserved_addresses); 504 505 Status GarbageCollectSector(SectorDescriptor& sector_to_gc, 506 span<const Address> reserved_addresses); 507 508 // Ensure that all entries are on the primary (first) format. Entries that are 509 // not on the primary format are rewritten. 510 // 511 // Return: status + number of entries updated. 512 StatusWithSize UpdateEntriesToPrimaryFormat(); 513 514 Status AddRedundantEntries(EntryMetadata& metadata); 515 516 Status RepairCorruptSectors(); 517 518 Status EnsureFreeSectorExists(); 519 520 Status EnsureEntryRedundancy(); 521 522 Status FixErrors(); 523 524 Status Repair(); 525 526 internal::Entry CreateEntry(Address address, 527 Key key, 528 span<const std::byte> value, 529 EntryState state); 530 531 void LogSectors() const; 532 void LogKeyDescriptor() const; 533 534 FlashPartition& partition_; 535 const internal::EntryFormats formats_; 536 537 // List of sectors used by this KVS. 538 internal::Sectors sectors_; 539 540 // Unordered list of KeyDescriptors. Finding a key requires scanning and 541 // verifying a match by reading the actual entry. 542 internal::EntryCache entry_cache_; 543 544 Options options_; 545 546 // Threshold value for when to garbage collect all stale data. Above the 547 // threshold, GC all reclaimable bytes regardless of if valid data is in 548 // sector. Below the threshold, only GC sectors with reclaimable bytes and no 549 // valid bytes. The threshold is based on the portion of KVS capacity used by 550 // valid bytes. 551 static constexpr size_t kGcUsageThresholdPercentage = 70; 552 553 enum class InitializationState { 554 // KVS Init() has not been called and KVS is not usable. 555 kNotInitialized, 556 557 // KVS Init() run but not all the needed invariants are met for an operating 558 // KVS. KVS is not writable, but read operaions are possible. 559 kNeedsMaintenance, 560 561 // KVS is fully ready for use. 562 kReady, 563 }; 564 InitializationState initialized_; 565 566 // error_detected_ needs to be set from const KVS methods (such as Get), so 567 // make it mutable. 568 mutable bool error_detected_; 569 570 struct InternalStats { 571 size_t sector_erase_count; 572 size_t corrupt_sectors_recovered; 573 size_t missing_redundant_entries_recovered; 574 }; 575 InternalStats internal_stats_; 576 577 uint32_t last_transaction_id_; 578 }; 579 580 template <size_t kMaxEntries, 581 size_t kMaxUsableSectors, 582 size_t kRedundancy = 1, 583 size_t kEntryFormats = 1> 584 class KeyValueStoreBuffer : public KeyValueStore { 585 public: 586 // Constructs a KeyValueStore on the partition, with support for one 587 // EntryFormat (kEntryFormats must be 1). 588 KeyValueStoreBuffer(FlashPartition* partition, 589 const EntryFormat& format, 590 const Options& options = {}) KeyValueStoreBuffer(partition,span<const EntryFormat,kEntryFormats> (reinterpret_cast<const EntryFormat (&)[1]> (format)),options)591 : KeyValueStoreBuffer( 592 partition, 593 span<const EntryFormat, kEntryFormats>( 594 reinterpret_cast<const EntryFormat (&)[1]>(format)), 595 options) { 596 static_assert(kEntryFormats == 1, 597 "kEntryFormats EntryFormats must be specified"); 598 } 599 600 // Constructs a KeyValueStore on the partition. Supports multiple entry 601 // formats. The first EntryFormat is used for new entries. 602 KeyValueStoreBuffer(FlashPartition* partition, 603 span<const EntryFormat, kEntryFormats> formats, 604 const Options& options = {}) KeyValueStore(partition,formats_,options,kRedundancy,sectors_,temp_sectors_to_skip_,key_descriptors_,addresses_)605 : KeyValueStore(partition, 606 formats_, 607 options, 608 kRedundancy, 609 sectors_, 610 temp_sectors_to_skip_, 611 key_descriptors_, 612 addresses_), 613 sectors_(), 614 key_descriptors_(), 615 formats_() { 616 std::copy(formats.begin(), formats.end(), formats_.begin()); 617 } 618 619 private: 620 static_assert(kMaxEntries > 0u); 621 static_assert(kMaxUsableSectors > 0u); 622 static_assert(kRedundancy > 0u); 623 static_assert(kEntryFormats > 0u); 624 625 Vector<SectorDescriptor, kMaxUsableSectors> sectors_; 626 627 // Allocate space for an list of SectorDescriptors to avoid while searching 628 // for space to write entries. This is used to avoid changing sectors that are 629 // reserved for a new entry or marked as having other redundant copies of an 630 // entry. Up to kRedundancy sectors are reserved for a new entry, and up to 631 // kRedundancy - 1 sectors can have redundant copies of an entry, giving a 632 // maximum of 2 * kRedundancy - 1 sectors to avoid. 633 const SectorDescriptor* temp_sectors_to_skip_[2 * kRedundancy - 1]; 634 635 // KeyDescriptors for use by the KVS's EntryCache. 636 Vector<KeyDescriptor, kMaxEntries> key_descriptors_; 637 638 // An array of addresses associated with the KeyDescriptors for use with the 639 // EntryCache. To support having KeyValueStores with different redundancies, 640 // the addresses are stored in a parallel array instead of inside 641 // KeyDescriptors. 642 internal::EntryCache::AddressList<kRedundancy, kMaxEntries> addresses_; 643 644 // EntryFormats that can be read by this KeyValueStore. 645 std::array<EntryFormat, kEntryFormats> formats_; 646 }; 647 648 } // namespace kvs 649 } // namespace pw 650