• 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 #pragma once
15 
16 #include <array>
17 #include <cstddef>
18 #include <cstdint>
19 #include <span>
20 #include <type_traits>
21 
22 #include "pw_containers/vector.h"
23 #include "pw_kvs/checksum.h"
24 #include "pw_kvs/flash_memory.h"
25 #include "pw_kvs/format.h"
26 #include "pw_kvs/internal/entry.h"
27 #include "pw_kvs/internal/entry_cache.h"
28 #include "pw_kvs/internal/key_descriptor.h"
29 #include "pw_kvs/internal/sectors.h"
30 #include "pw_kvs/internal/span_traits.h"
31 #include "pw_kvs/key.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                      std::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   // std::as_writable_bytes(std::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 std::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, std::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, std::as_bytes(std::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(std::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 
252     void ReadKey();
253 
254     const KeyValueStore& kvs_;
255     internal::EntryCache::const_iterator iterator_;
256 
257     // Buffer large enough for a null-terminated version of any valid key.
258     std::array<char, internal::Entry::kMaxKeyLength + 1> key_buffer_;
259   };
260 
261   class iterator {
262    public:
263     iterator& operator++();
264 
265     iterator operator++(int) {
266       const iterator original(item_.kvs_, item_.iterator_);
267       operator++();
268       return original;
269     }
270 
271     // Reads the entry's key from flash.
272     const Item& operator*() {
273       item_.ReadKey();
274       return item_;
275     }
276 
277     const Item* operator->() {
278       return &operator*();  // Read the key into the Item object.
279     }
280 
281     constexpr bool operator==(const iterator& rhs) const {
282       return item_.iterator_ == rhs.item_.iterator_;
283     }
284 
285     constexpr bool operator!=(const iterator& rhs) const {
286       return item_.iterator_ != rhs.item_.iterator_;
287     }
288 
289    private:
290     friend class KeyValueStore;
291 
iterator(const KeyValueStore & kvs,const internal::EntryCache::const_iterator & item_iterator)292     constexpr iterator(
293         const KeyValueStore& kvs,
294         const internal::EntryCache::const_iterator& item_iterator)
295         : item_(kvs, item_iterator) {}
296 
297     Item item_;
298   };
299 
300   using const_iterator = iterator;  // Standard alias for iterable types.
301 
302   iterator begin() const;
end()303   iterator end() const { return iterator(*this, entry_cache_.end()); }
304 
305   // Returns the number of valid entries in the KeyValueStore.
size()306   size_t size() const { return entry_cache_.present_entries(); }
307 
max_size()308   size_t max_size() const { return entry_cache_.max_entries(); }
309 
empty()310   size_t empty() const { return size() == 0u; }
311 
312   // Returns the number of transactions that have occurred since the KVS was
313   // first used. This value is retained across initializations, but is reset if
314   // the underlying flash is erased.
transaction_count()315   uint32_t transaction_count() const { return last_transaction_id_; }
316 
317   struct StorageStats {
318     size_t writable_bytes;
319     size_t in_use_bytes;
320     size_t reclaimable_bytes;
321     size_t sector_erase_count;
322     size_t corrupt_sectors_recovered;
323     size_t missing_redundant_entries_recovered;
324   };
325 
326   StorageStats GetStorageStats() const;
327 
328   // Level of redundancy to use for writing entries.
redundancy()329   size_t redundancy() const { return entry_cache_.redundancy(); }
330 
error_detected()331   bool error_detected() const { return error_detected_; }
332 
333   // Maximum number of bytes allowed for a key-value combination.
max_key_value_size_bytes()334   size_t max_key_value_size_bytes() const {
335     return max_key_value_size_bytes(partition_.sector_size_bytes());
336   }
337 
338   // Maximum number of bytes allowed for a given sector size for a key-value
339   // combination.
max_key_value_size_bytes(size_t partition_sector_size_bytes)340   static constexpr size_t max_key_value_size_bytes(
341       size_t partition_sector_size_bytes) {
342     return partition_sector_size_bytes - Entry::entry_overhead();
343   }
344 
345   // Check KVS for any error conditions. Primarily intended for test and
346   // internal use.
347   bool CheckForErrors();
348 
349  protected:
350   using Address = FlashPartition::Address;
351   using Entry = internal::Entry;
352   using KeyDescriptor = internal::KeyDescriptor;
353   using SectorDescriptor = internal::SectorDescriptor;
354 
355   // In the future, will be able to provide additional EntryFormats for
356   // backwards compatibility.
357   KeyValueStore(FlashPartition* partition,
358                 std::span<const EntryFormat> formats,
359                 const Options& options,
360                 size_t redundancy,
361                 Vector<SectorDescriptor>& sector_descriptor_list,
362                 const SectorDescriptor** temp_sectors_to_skip,
363                 Vector<KeyDescriptor>& key_descriptor_list,
364                 Address* addresses);
365 
366  private:
367   using EntryMetadata = internal::EntryMetadata;
368   using EntryState = internal::EntryState;
369 
370   template <typename T>
CheckThatObjectCanBePutOrGet()371   static constexpr void CheckThatObjectCanBePutOrGet() {
372     static_assert(
373         std::is_trivially_copyable<T>::value && !std::is_pointer<T>::value,
374         "Only trivially copyable, non-pointer objects may be Put and Get by "
375         "value. Any value may be stored by converting it to a byte std::span "
376         "with std::as_bytes(std::span(&value, 1)) or "
377         "std::as_writable_bytes(std::span(&value, 1)).");
378   }
379 
380   Status InitializeMetadata();
381   Status LoadEntry(Address entry_address, Address* next_entry_address);
382   Status ScanForEntry(const SectorDescriptor& sector,
383                       Address start_address,
384                       Address* next_entry_address);
385 
386   Status PutBytes(Key key, std::span<const std::byte> value);
387 
388   StatusWithSize ValueSize(const EntryMetadata& metadata) const;
389 
390   Status ReadEntry(const EntryMetadata& metadata, Entry& entry) const;
391 
392   // Finds the metadata for an entry matching a particular key. Searches for a
393   // KeyDescriptor that matches this key and sets *metadata_out to point to it
394   // if one is found.
395   //
396   //             OK: there is a matching descriptor and *metadata is set
397   //      NOT_FOUND: there is no descriptor that matches this key, but this key
398   //                 has a unique hash (and could potentially be added to the
399   //                 KVS)
400   // ALREADY_EXISTS: there is no descriptor that matches this key, but the
401   //                 key's hash collides with the hash for an existing
402   //                 descriptor
403   //
404   Status FindEntry(Key key, EntryMetadata* metadata_out) const;
405 
406   // Searches for a KeyDescriptor that matches this key and sets *metadata_out
407   // to point to it if one is found.
408   //
409   //          OK: there is a matching descriptor and *metadata_out is set
410   //   NOT_FOUND: there is no descriptor that matches this key
411   //
412   Status FindExisting(Key key, EntryMetadata* metadata_out) const;
413 
414   StatusWithSize Get(Key key,
415                      const EntryMetadata& metadata,
416                      std::span<std::byte> value_buffer,
417                      size_t offset_bytes) const;
418 
419   Status FixedSizeGet(Key key, void* value, size_t size_bytes) const;
420 
421   Status FixedSizeGet(Key key,
422                       const EntryMetadata& metadata,
423                       void* value,
424                       size_t size_bytes) const;
425 
426   Status CheckWriteOperation(Key key) const;
427   Status CheckReadOperation(Key key) const;
428 
429   Status WriteEntryForExistingKey(EntryMetadata& metadata,
430                                   EntryState new_state,
431                                   Key key,
432                                   std::span<const std::byte> value);
433 
434   Status WriteEntryForNewKey(Key key, std::span<const std::byte> value);
435 
436   Status WriteEntry(Key key,
437                     std::span<const std::byte> value,
438                     EntryState new_state,
439                     EntryMetadata* prior_metadata = nullptr,
440                     const internal::Entry* prior_entry = nullptr);
441 
442   EntryMetadata CreateOrUpdateKeyDescriptor(const Entry& new_entry,
443                                             Key key,
444                                             EntryMetadata* prior_metadata,
445                                             size_t prior_size);
446 
447   EntryMetadata UpdateKeyDescriptor(const Entry& entry,
448                                     Address new_address,
449                                     EntryMetadata* prior_metadata,
450                                     size_t prior_size);
451 
452   Status GetAddressesForWrite(Address* write_addresses, size_t write_size);
453 
454   Status GetSectorForWrite(SectorDescriptor** sector,
455                            size_t entry_size,
456                            std::span<const Address> reserved_addresses);
457 
458   Status MarkSectorCorruptIfNotOk(Status status, SectorDescriptor* sector);
459 
460   Status AppendEntry(const Entry& entry,
461                      Key key,
462                      std::span<const std::byte> value);
463 
464   StatusWithSize CopyEntryToSector(Entry& entry,
465                                    SectorDescriptor* new_sector,
466                                    Address new_address);
467 
468   Status RelocateEntry(const EntryMetadata& metadata,
469                        KeyValueStore::Address& address,
470                        std::span<const Address> reserved_addresses);
471 
472   // Perform all maintenance possible, including all neeeded repairing of
473   // corruption and garbage collection of reclaimable space in the KVS. When
474   // configured for manual recovery, this is the only way KVS repair is
475   // triggered.
476   //
477   // - Regular will not garbage collect sectors with valid data unless the KVS
478   //   is mostly full.
479   // - Heavy will garbage collect all reclaimable space regardless of valid data
480   //   in the sector.
481   enum class MaintenanceType {
482     kRegular,
483     kHeavy,
484   };
485   Status FullMaintenanceHelper(MaintenanceType maintenance_type);
486 
487   // Find and garbage collect a singe sector that does not include a reserved
488   // address.
489   Status GarbageCollect(std::span<const Address> reserved_addresses);
490 
491   Status RelocateKeyAddressesInSector(
492       SectorDescriptor& sector_to_gc,
493       const EntryMetadata& metadata,
494       std::span<const Address> reserved_addresses);
495 
496   Status GarbageCollectSector(SectorDescriptor& sector_to_gc,
497                               std::span<const Address> reserved_addresses);
498 
499   // Ensure that all entries are on the primary (first) format. Entries that are
500   // not on the primary format are rewritten.
501   //
502   // Return: status + number of entries updated.
503   StatusWithSize UpdateEntriesToPrimaryFormat();
504 
505   Status AddRedundantEntries(EntryMetadata& metadata);
506 
507   Status RepairCorruptSectors();
508 
509   Status EnsureFreeSectorExists();
510 
511   Status EnsureEntryRedundancy();
512 
513   Status FixErrors();
514 
515   Status Repair();
516 
517   internal::Entry CreateEntry(Address address,
518                               Key key,
519                               std::span<const std::byte> value,
520                               EntryState state);
521 
522   void LogSectors() const;
523   void LogKeyDescriptor() const;
524 
525   FlashPartition& partition_;
526   const internal::EntryFormats formats_;
527 
528   // List of sectors used by this KVS.
529   internal::Sectors sectors_;
530 
531   // Unordered list of KeyDescriptors. Finding a key requires scanning and
532   // verifying a match by reading the actual entry.
533   internal::EntryCache entry_cache_;
534 
535   Options options_;
536 
537   // Threshold value for when to garbage collect all stale data. Above the
538   // threshold, GC all reclaimable bytes regardless of if valid data is in
539   // sector. Below the threshold, only GC sectors with reclaimable bytes and no
540   // valid bytes. The threshold is based on the portion of KVS capacity used by
541   // valid bytes.
542   static constexpr size_t kGcUsageThresholdPercentage = 70;
543 
544   enum class InitializationState {
545     // KVS Init() has not been called and KVS is not usable.
546     kNotInitialized,
547 
548     // KVS Init() run but not all the needed invariants are met for an operating
549     // KVS. KVS is not writable, but read operaions are possible.
550     kNeedsMaintenance,
551 
552     // KVS is fully ready for use.
553     kReady,
554   };
555   InitializationState initialized_;
556 
557   // error_detected_ needs to be set from const KVS methods (such as Get), so
558   // make it mutable.
559   mutable bool error_detected_;
560 
561   struct InternalStats {
562     size_t sector_erase_count;
563     size_t corrupt_sectors_recovered;
564     size_t missing_redundant_entries_recovered;
565   };
566   InternalStats internal_stats_;
567 
568   uint32_t last_transaction_id_;
569 };
570 
571 template <size_t kMaxEntries,
572           size_t kMaxUsableSectors,
573           size_t kRedundancy = 1,
574           size_t kEntryFormats = 1>
575 class KeyValueStoreBuffer : public KeyValueStore {
576  public:
577   // Constructs a KeyValueStore on the partition, with support for one
578   // EntryFormat (kEntryFormats must be 1).
579   KeyValueStoreBuffer(FlashPartition* partition,
580                       const EntryFormat& format,
581                       const Options& options = {})
KeyValueStoreBuffer(partition,std::span<const EntryFormat,kEntryFormats> (reinterpret_cast<const EntryFormat (&)[1]> (format)),options)582       : KeyValueStoreBuffer(
583             partition,
584             std::span<const EntryFormat, kEntryFormats>(
585                 reinterpret_cast<const EntryFormat (&)[1]>(format)),
586             options) {
587     static_assert(kEntryFormats == 1,
588                   "kEntryFormats EntryFormats must be specified");
589   }
590 
591   // Constructs a KeyValueStore on the partition. Supports multiple entry
592   // formats. The first EntryFormat is used for new entries.
593   KeyValueStoreBuffer(FlashPartition* partition,
594                       std::span<const EntryFormat, kEntryFormats> formats,
595                       const Options& options = {})
KeyValueStore(partition,formats_,options,kRedundancy,sectors_,temp_sectors_to_skip_,key_descriptors_,addresses_)596       : KeyValueStore(partition,
597                       formats_,
598                       options,
599                       kRedundancy,
600                       sectors_,
601                       temp_sectors_to_skip_,
602                       key_descriptors_,
603                       addresses_) {
604     std::copy(formats.begin(), formats.end(), formats_.begin());
605   }
606 
607  private:
608   static_assert(kMaxEntries > 0u);
609   static_assert(kMaxUsableSectors > 0u);
610   static_assert(kRedundancy > 0u);
611   static_assert(kEntryFormats > 0u);
612 
613   Vector<SectorDescriptor, kMaxUsableSectors> sectors_;
614 
615   // Allocate space for an list of SectorDescriptors to avoid while searching
616   // for space to write entries. This is used to avoid changing sectors that are
617   // reserved for a new entry or marked as having other redundant copies of an
618   // entry. Up to kRedundancy sectors are reserved for a new entry, and up to
619   // kRedundancy - 1 sectors can have redundant copies of an entry, giving a
620   // maximum of 2 * kRedundancy - 1 sectors to avoid.
621   const SectorDescriptor* temp_sectors_to_skip_[2 * kRedundancy - 1];
622 
623   // KeyDescriptors for use by the KVS's EntryCache.
624   Vector<KeyDescriptor, kMaxEntries> key_descriptors_;
625 
626   // An array of addresses associated with the KeyDescriptors for use with the
627   // EntryCache. To support having KeyValueStores with different redundancies,
628   // the addresses are stored in a parallel array instead of inside
629   // KeyDescriptors.
630   internal::EntryCache::AddressList<kRedundancy, kMaxEntries> addresses_;
631 
632   // EntryFormats that can be read by this KeyValueStore.
633   std::array<EntryFormat, kEntryFormats> formats_;
634 };
635 
636 }  // namespace kvs
637 }  // namespace pw
638