• 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 <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