Skip to content
Snippets Groups Projects
Unverified Commit 690db41d authored by Julien Cretin's avatar Julien Cretin Committed by GitHub
Browse files

Merge pull request #73 from ia0/wipe

Wipe sensitive data on entry deletion
parents 284b1426 1a337ab9
No related branches found
No related tags found
No related merge requests found
...@@ -198,6 +198,7 @@ impl PersistentStore { ...@@ -198,6 +198,7 @@ impl PersistentStore {
.insert(StoreEntry { .insert(StoreEntry {
tag: MASTER_KEYS, tag: MASTER_KEYS,
data: &master_keys, data: &master_keys,
sensitive: true,
}) })
.unwrap(); .unwrap();
} }
...@@ -206,6 +207,7 @@ impl PersistentStore { ...@@ -206,6 +207,7 @@ impl PersistentStore {
.insert(StoreEntry { .insert(StoreEntry {
tag: PIN_RETRIES, tag: PIN_RETRIES,
data: &[MAX_PIN_RETRIES], data: &[MAX_PIN_RETRIES],
sensitive: false,
}) })
.unwrap(); .unwrap();
} }
...@@ -245,6 +247,7 @@ impl PersistentStore { ...@@ -245,6 +247,7 @@ impl PersistentStore {
let new_entry = StoreEntry { let new_entry = StoreEntry {
tag: TAG_CREDENTIAL, tag: TAG_CREDENTIAL,
data: &credential, data: &credential,
sensitive: true,
}; };
match old_entry { match old_entry {
None => self.store.insert(new_entry)?, None => self.store.insert(new_entry)?,
...@@ -299,6 +302,7 @@ impl PersistentStore { ...@@ -299,6 +302,7 @@ impl PersistentStore {
.insert(StoreEntry { .insert(StoreEntry {
tag: GLOBAL_SIGNATURE_COUNTER, tag: GLOBAL_SIGNATURE_COUNTER,
data: &buffer, data: &buffer,
sensitive: false,
}) })
.unwrap(); .unwrap();
} }
...@@ -312,6 +316,7 @@ impl PersistentStore { ...@@ -312,6 +316,7 @@ impl PersistentStore {
StoreEntry { StoreEntry {
tag: GLOBAL_SIGNATURE_COUNTER, tag: GLOBAL_SIGNATURE_COUNTER,
data: &buffer, data: &buffer,
sensitive: false,
}, },
) )
.unwrap(); .unwrap();
...@@ -339,6 +344,7 @@ impl PersistentStore { ...@@ -339,6 +344,7 @@ impl PersistentStore {
let entry = StoreEntry { let entry = StoreEntry {
tag: PIN_HASH, tag: PIN_HASH,
data: pin_hash, data: pin_hash,
sensitive: true,
}; };
match self.store.find_one(&Key::PinHash) { match self.store.find_one(&Key::PinHash) {
None => self.store.insert(entry).unwrap(), None => self.store.insert(entry).unwrap(),
...@@ -368,6 +374,7 @@ impl PersistentStore { ...@@ -368,6 +374,7 @@ impl PersistentStore {
StoreEntry { StoreEntry {
tag: PIN_RETRIES, tag: PIN_RETRIES,
data: &[new_value], data: &[new_value],
sensitive: false,
}, },
) )
.unwrap(); .unwrap();
...@@ -381,6 +388,7 @@ impl PersistentStore { ...@@ -381,6 +388,7 @@ impl PersistentStore {
StoreEntry { StoreEntry {
tag: PIN_RETRIES, tag: PIN_RETRIES,
data: &[MAX_PIN_RETRIES], data: &[MAX_PIN_RETRIES],
sensitive: false,
}, },
) )
.unwrap(); .unwrap();
...@@ -466,9 +474,9 @@ mod test { ...@@ -466,9 +474,9 @@ mod test {
let storage = Storage::new(store, options); let storage = Storage::new(store, options);
let store = embedded_flash::Store::new(storage, Config).unwrap(); let store = embedded_flash::Store::new(storage, Config).unwrap();
// We can replace 3 bytes with minimal overhead. // We can replace 3 bytes with minimal overhead.
assert_eq!(store.replace_len(0), 2 * WORD_SIZE); assert_eq!(store.replace_len(false, 0), 2 * WORD_SIZE);
assert_eq!(store.replace_len(3), 2 * WORD_SIZE); assert_eq!(store.replace_len(false, 3), 3 * WORD_SIZE);
assert_eq!(store.replace_len(4), 3 * WORD_SIZE); assert_eq!(store.replace_len(false, 4), 3 * WORD_SIZE);
} }
#[test] #[test]
......
...@@ -55,6 +55,11 @@ impl ByteGap { ...@@ -55,6 +55,11 @@ impl ByteGap {
bit + 8 * self.length bit + 8 * self.length
} }
} }
/// Returns the slice of `data` corresponding to the gap.
pub fn slice(self, data: &[u8]) -> &[u8] {
&data[self.start..self.start + self.length]
}
} }
/// Returns whether a bit is set in a sequence of bits. /// Returns whether a bit is set in a sequence of bits.
......
...@@ -59,6 +59,17 @@ pub struct Format { ...@@ -59,6 +59,17 @@ pub struct Format {
/// - 1 for insert entries. /// - 1 for insert entries.
replace_bit: usize, replace_bit: usize,
/// Whether a user entry has sensitive data.
///
/// - 0 for sensitive data.
/// - 1 for non-sensitive data.
///
/// When a user entry with sensitive data is deleted, the data is overwritten with zeroes. This
/// feature is subject to the same guarantees as all other features of the store, in particular
/// deleting a sensitive entry is atomic. See the store module-level documentation for more
/// information.
sensitive_bit: usize,
/// The data length of a user entry. /// The data length of a user entry.
length_range: bitfield::BitRange, length_range: bitfield::BitRange,
...@@ -138,8 +149,9 @@ impl Format { ...@@ -138,8 +149,9 @@ impl Format {
let deleted_bit = present_bit + 1; let deleted_bit = present_bit + 1;
let internal_bit = deleted_bit + 1; let internal_bit = deleted_bit + 1;
let replace_bit = internal_bit + 1; let replace_bit = internal_bit + 1;
let sensitive_bit = replace_bit + 1;
let length_range = bitfield::BitRange { let length_range = bitfield::BitRange {
start: replace_bit + 1, start: sensitive_bit + 1,
length: byte_bits, length: byte_bits,
}; };
let tag_range = bitfield::BitRange { let tag_range = bitfield::BitRange {
...@@ -182,6 +194,7 @@ impl Format { ...@@ -182,6 +194,7 @@ impl Format {
deleted_bit, deleted_bit,
internal_bit, internal_bit,
replace_bit, replace_bit,
sensitive_bit,
length_range, length_range,
tag_range, tag_range,
replace_page_range, replace_page_range,
...@@ -196,10 +209,11 @@ impl Format { ...@@ -196,10 +209,11 @@ impl Format {
// Make sure all the following conditions hold: // Make sure all the following conditions hold:
// - The page header is one word. // - The page header is one word.
// - The internal entry is one word. // - The internal entry is one word.
// - The entry header fits in one word. // - The entry header fits in one word (which is equivalent to the entry header size being
// exactly one word for sensitive entries).
if format.page_header_size() != word_size if format.page_header_size() != word_size
|| format.internal_entry_size() != word_size || format.internal_entry_size() != word_size
|| format.header_size() > word_size || format.header_size(true) != word_size
{ {
return None; return None;
} }
...@@ -220,28 +234,46 @@ impl Format { ...@@ -220,28 +234,46 @@ impl Format {
/// Returns the entry header length in bytes. /// Returns the entry header length in bytes.
/// ///
/// This is the smallest number of bytes necessary to store all fields of the entry info up to /// This is the smallest number of bytes necessary to store all fields of the entry info up to
/// and including `length`. /// and including `length`. For sensitive entries, the result is word-aligned.
pub fn header_size(&self) -> usize { pub fn header_size(&self, sensitive: bool) -> usize {
self.bits_to_bytes(self.length_range.end()) let mut size = self.bits_to_bytes(self.length_range.end());
if sensitive {
// We need to align to the next word boundary so that wiping the user data will not
// count as a write to the header.
size = self.align_word(size);
}
size
}
/// Returns the entry header length in bytes.
///
/// This is a convenience function for `header_size` above.
fn header_offset(&self, entry: &[u8]) -> usize {
self.header_size(self.is_sensitive(entry))
} }
/// Returns the entry info length in bytes. /// Returns the entry info length in bytes.
/// ///
/// This is the number of bytes necessary to store all fields of the entry info. This also /// This is the number of bytes necessary to store all fields of the entry info. This also
/// includes the internal padding to protect the `committed` bit from the `deleted` bit. /// includes the internal padding to protect the `committed` bit from the `deleted` bit and to
fn info_size(&self, is_replace: IsReplace) -> usize { /// protect the entry info from the user data for sensitive entries.
fn info_size(&self, is_replace: IsReplace, sensitive: bool) -> usize {
let suffix_bits = 2; // committed + complete let suffix_bits = 2; // committed + complete
let info_bits = match is_replace { let info_bits = match is_replace {
IsReplace::Replace => self.replace_byte_range.end() + suffix_bits, IsReplace::Replace => self.replace_byte_range.end() + suffix_bits,
IsReplace::Insert => self.tag_range.end() + suffix_bits, IsReplace::Insert => self.tag_range.end() + suffix_bits,
}; };
let info_size = self.bits_to_bytes(info_bits); let mut info_size = self.bits_to_bytes(info_bits);
// If the suffix bits would end up in the header, we need to add one byte for them. // If the suffix bits would end up in the header, we need to add one byte for them.
if info_size == self.header_size() { let header_size = self.header_size(sensitive);
info_size + 1 if info_size <= header_size {
} else { info_size = header_size + 1;
info_size }
// If the entry is sensitive, we need to align to the next word boundary.
if sensitive {
info_size = self.align_word(info_size);
} }
info_size
} }
/// Returns the length in bytes of an entry. /// Returns the length in bytes of an entry.
...@@ -249,8 +281,8 @@ impl Format { ...@@ -249,8 +281,8 @@ impl Format {
/// This depends on the length of the user data and whether the entry replaces an old entry or /// This depends on the length of the user data and whether the entry replaces an old entry or
/// is an insertion. This also includes the internal padding to protect the `committed` bit from /// is an insertion. This also includes the internal padding to protect the `committed` bit from
/// the `deleted` bit. /// the `deleted` bit.
pub fn entry_size(&self, is_replace: IsReplace, length: usize) -> usize { pub fn entry_size(&self, is_replace: IsReplace, sensitive: bool, length: usize) -> usize {
let mut entry_size = length + self.info_size(is_replace); let mut entry_size = length + self.info_size(is_replace, sensitive);
let word_size = self.word_size; let word_size = self.word_size;
entry_size = self.align_word(entry_size); entry_size = self.align_word(entry_size);
// The entry must be at least 2 words such that the `committed` and `deleted` bits are on // The entry must be at least 2 words such that the `committed` and `deleted` bits are on
...@@ -308,6 +340,14 @@ impl Format { ...@@ -308,6 +340,14 @@ impl Format {
bitfield::set_zero(self.replace_bit, header, bitfield::NO_GAP) bitfield::set_zero(self.replace_bit, header, bitfield::NO_GAP)
} }
pub fn is_sensitive(&self, header: &[u8]) -> bool {
bitfield::is_zero(self.sensitive_bit, header, bitfield::NO_GAP)
}
pub fn set_sensitive(&self, header: &mut [u8]) {
bitfield::set_zero(self.sensitive_bit, header, bitfield::NO_GAP)
}
pub fn get_length(&self, header: &[u8]) -> usize { pub fn get_length(&self, header: &[u8]) -> usize {
bitfield::get_range(self.length_range, header, bitfield::NO_GAP) bitfield::get_range(self.length_range, header, bitfield::NO_GAP)
} }
...@@ -317,16 +357,19 @@ impl Format { ...@@ -317,16 +357,19 @@ impl Format {
} }
pub fn get_data<'a>(&self, entry: &'a [u8]) -> &'a [u8] { pub fn get_data<'a>(&self, entry: &'a [u8]) -> &'a [u8] {
&entry[self.header_size()..][..self.get_length(entry)] &entry[self.header_offset(entry)..][..self.get_length(entry)]
} }
/// Returns the span of user data in an entry. /// Returns the span of user data in an entry.
/// ///
/// The complement of this gap in the entry is exactly the entry info. The header is before the /// The complement of this gap in the entry is exactly the entry info. The header is before the
/// gap and the footer is after the gap. /// gap and the footer is after the gap.
fn entry_gap(&self, entry: &[u8]) -> bitfield::ByteGap { pub fn entry_gap(&self, entry: &[u8]) -> bitfield::ByteGap {
let start = self.header_size(); let start = self.header_offset(entry);
let length = self.get_length(entry); let mut length = self.get_length(entry);
if self.is_sensitive(entry) {
length = self.align_word(length);
}
bitfield::ByteGap { start, length } bitfield::ByteGap { start, length }
} }
...@@ -406,16 +449,23 @@ impl Format { ...@@ -406,16 +449,23 @@ impl Format {
/// Builds an entry for replace or insert operations. /// Builds an entry for replace or insert operations.
pub fn build_entry(&self, replace: Option<Index>, user_entry: StoreEntry) -> Vec<u8> { pub fn build_entry(&self, replace: Option<Index>, user_entry: StoreEntry) -> Vec<u8> {
let StoreEntry { tag, data } = user_entry; let StoreEntry {
tag,
data,
sensitive,
} = user_entry;
let is_replace = match replace { let is_replace = match replace {
None => IsReplace::Insert, None => IsReplace::Insert,
Some(_) => IsReplace::Replace, Some(_) => IsReplace::Replace,
}; };
let entry_len = self.entry_size(is_replace, data.len()); let entry_len = self.entry_size(is_replace, sensitive, data.len());
let mut entry = Vec::with_capacity(entry_len); let mut entry = Vec::with_capacity(entry_len);
// Build the header. // Build the header.
entry.resize(self.header_size(), 0xff); entry.resize(self.header_size(sensitive), 0xff);
self.set_present(&mut entry[..]); self.set_present(&mut entry[..]);
if sensitive {
self.set_sensitive(&mut entry[..]);
}
self.set_length(&mut entry[..], data.len()); self.set_length(&mut entry[..], data.len());
// Add the data. // Add the data.
entry.extend_from_slice(data); entry.extend_from_slice(data);
......
...@@ -43,6 +43,28 @@ ...@@ -43,6 +43,28 @@
//! The data-structure can be configured with the `StoreConfig` trait. By implementing this trait, //! The data-structure can be configured with the `StoreConfig` trait. By implementing this trait,
//! the number of possible tags and the association between keys and entries are defined. //! the number of possible tags and the association between keys and entries are defined.
//! //!
//! # Properties
//!
//! The data-structure provides the following properties:
//! - When an operation returns success, then the represented multi-set is updated accordingly. For
//! example, an inserted entry can be found without alteration until replaced or deleted.
//! - When an operation returns an error, the resulting multi-set state is described in the error
//! documentation.
//! - When power is lost before an operation returns, the operation will either succeed or be
//! rolled-back on the next initialization. So the multi-set would be either left unchanged or
//! updated accordingly.
//!
//! Those properties rely on the following assumptions:
//! - Writing a word to flash is atomic. When power is lost, the word is either fully written or not
//! written at all.
//! - Reading a word from flash is deterministic. When power is lost while writing or erasing a word
//! (erasing a page containing that word), reading that word repeatedly returns the same result
//! (until it is written or its page is erased).
//! - To decide whether a page has been erased, it is enough to test if all its bits are equal to 1.
//!
//! The properties may still hold outside those assumptions but with weaker probabilities as the
//! usage diverges from the assumptions.
//!
//! # Implementation //! # Implementation
//! //!
//! The store is a page-aligned sequence of bits. It matches the following grammar: //! The store is a page-aligned sequence of bits. It matches the following grammar:
...@@ -57,7 +79,7 @@ ...@@ -57,7 +79,7 @@
//! new_page:page_bits //! new_page:page_bits
//! Padding(word) //! Padding(word)
//! Entry := Header Data Footer //! Entry := Header Data Footer
//! // Let X be the byte following `length` in `Info`. //! // Let X be the byte (word-aligned for sensitive queries) following `length` in `Info`.
//! Header := Info[..X] // must fit in one word //! Header := Info[..X] // must fit in one word
//! Footer := Info[X..] // must fit in one word //! Footer := Info[X..] // must fit in one word
//! Info := //! Info :=
...@@ -65,6 +87,7 @@ ...@@ -65,6 +87,7 @@
//! deleted:1 //! deleted:1
//! internal=1 //! internal=1
//! replace:1 //! replace:1
//! sensitive:1
//! length:byte_bits //! length:byte_bits
//! tag:tag_bits //! tag:tag_bits
//! [ // present if `replace` is 0 //! [ // present if `replace` is 0
...@@ -109,15 +132,16 @@ ...@@ -109,15 +132,16 @@
//! 0.1 deleted //! 0.1 deleted
//! 0.2 internal //! 0.2 internal
//! 0.3 replace //! 0.3 replace
//! 0.4 length (9 bits) //! 0.4 sensitive
//! 1.5 tag (least significant 3 bits out of 5) //! 0.5 length (9 bits)
//! 1.6 tag (least significant 2 bits out of 5)
//! (the header ends at the first byte boundary after `length`) //! (the header ends at the first byte boundary after `length`)
//! 2.0 <user data> (2 bytes in this example) //! 2.0 <user data> (2 bytes in this example)
//! (the footer starts immediately after the user data) //! (the footer starts immediately after the user data)
//! 4.0 tag (most significant 2 bits out of 5) //! 4.0 tag (most significant 3 bits out of 5)
//! 4.2 replace_page (6 bits) //! 4.3 replace_page (6 bits)
//! 5.0 replace_byte (9 bits) //! 5.1 replace_byte (9 bits)
//! 6.1 padding (make sure the 2 properties below hold) //! 6.2 padding (make sure the 2 properties below hold)
//! 7.6 committed //! 7.6 committed
//! 7.7 complete (on a different word than `present`) //! 7.7 complete (on a different word than `present`)
//! 8.0 <end> (word-aligned) //! 8.0 <end> (word-aligned)
...@@ -203,6 +227,11 @@ pub struct StoreEntry<'a> { ...@@ -203,6 +227,11 @@ pub struct StoreEntry<'a> {
/// The data of the entry. /// The data of the entry.
pub data: &'a [u8], pub data: &'a [u8],
/// Whether the data is sensitive.
///
/// Sensitive data is overwritten with zeroes when the entry is deleted.
pub sensitive: bool,
} }
/// Implements a configurable multi-set on top of any storage. /// Implements a configurable multi-set on top of any storage.
...@@ -262,6 +291,7 @@ impl<S: Storage, C: StoreConfig> Store<S, C> { ...@@ -262,6 +291,7 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
StoreEntry { StoreEntry {
tag: self.format.get_tag(entry), tag: self.format.get_tag(entry),
data: self.format.get_data(entry), data: self.format.get_data(entry),
sensitive: self.format.is_sensitive(entry),
}, },
)) ))
} else { } else {
...@@ -326,7 +356,7 @@ impl<S: Storage, C: StoreConfig> Store<S, C> { ...@@ -326,7 +356,7 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
self.format.validate_entry(new)?; self.format.validate_entry(new)?;
let mut old_index = old.index; let mut old_index = old.index;
// Find a slot. // Find a slot.
let entry_len = self.replace_len(new.data.len()); let entry_len = self.replace_len(new.sensitive, new.data.len());
let index = self.find_slot_for_write(entry_len, Some(&mut old_index))?; let index = self.find_slot_for_write(entry_len, Some(&mut old_index))?;
// Build a new entry replacing the old one. // Build a new entry replacing the old one.
let entry = self.format.build_entry(Some(old_index), new); let entry = self.format.build_entry(Some(old_index), new);
...@@ -360,17 +390,20 @@ impl<S: Storage, C: StoreConfig> Store<S, C> { ...@@ -360,17 +390,20 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
/// Returns the byte cost of a replace operation. /// Returns the byte cost of a replace operation.
/// ///
/// Computes the length in bytes that would be used in the storage if a replace operation is /// Computes the length in bytes that would be used in the storage if a replace operation is
/// executed provided the data of the new entry has `length` bytes. /// executed provided the data of the new entry has `length` bytes and whether this data is
pub fn replace_len(&self, length: usize) -> usize { /// sensitive.
self.format.entry_size(IsReplace::Replace, length) pub fn replace_len(&self, sensitive: bool, length: usize) -> usize {
self.format
.entry_size(IsReplace::Replace, sensitive, length)
} }
/// Returns the byte cost of an insert operation. /// Returns the byte cost of an insert operation.
/// ///
/// Computes the length in bytes that would be used in the storage if an insert operation is /// Computes the length in bytes that would be used in the storage if an insert operation is
/// executed provided the data of the inserted entry has `length` bytes. /// executed provided the data of the inserted entry has `length` bytes and whether this data is
pub fn insert_len(&self, length: usize) -> usize { /// sensitive.
self.format.entry_size(IsReplace::Insert, length) pub fn insert_len(&self, sensitive: bool, length: usize) -> usize {
self.format.entry_size(IsReplace::Insert, sensitive, length)
} }
/// Returns the erase count of all pages. /// Returns the erase count of all pages.
...@@ -410,8 +443,11 @@ impl<S: Storage, C: StoreConfig> Store<S, C> { ...@@ -410,8 +443,11 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
let entry_index = index; let entry_index = index;
let entry = self.read_entry(index); let entry = self.read_entry(index);
index.byte += entry.len(); index.byte += entry.len();
if !self.format.is_alive(entry) { if !self.format.is_present(entry) {
// Skip deleted entries (or the page padding). // Reached the end of the page.
} else if self.format.is_deleted(entry) {
// Wipe sensitive data if needed.
self.wipe_sensitive_data(entry_index);
} else if self.format.is_internal(entry) { } else if self.format.is_internal(entry) {
// Finish page compaction. // Finish page compaction.
self.erase_page(entry_index); self.erase_page(entry_index);
...@@ -449,6 +485,31 @@ impl<S: Storage, C: StoreConfig> Store<S, C> { ...@@ -449,6 +485,31 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
/// The provided index must point to the beginning of an entry. /// The provided index must point to the beginning of an entry.
fn delete_index(&mut self, index: Index) { fn delete_index(&mut self, index: Index) {
self.update_word(index, |format, word| format.set_deleted(word)); self.update_word(index, |format, word| format.set_deleted(word));
self.wipe_sensitive_data(index);
}
/// Wipes the data of a sensitive entry.
///
/// If the entry at the provided index is sensitive, overwrites the data with zeroes. Otherwise,
/// does nothing.
fn wipe_sensitive_data(&mut self, mut index: Index) {
let entry = self.read_entry(index);
debug_assert!(self.format.is_present(entry));
debug_assert!(self.format.is_deleted(entry));
if self.format.is_internal(entry) || !self.format.is_sensitive(entry) {
// No need to wipe the data.
return;
}
let gap = self.format.entry_gap(entry);
let data = gap.slice(entry);
if data.iter().all(|&byte| byte == 0x00) {
// The data is already wiped.
return;
}
index.byte += gap.start;
self.storage
.write_slice(index, &vec![0; gap.length])
.unwrap();
} }
/// Finds a page with enough free space. /// Finds a page with enough free space.
...@@ -555,10 +616,13 @@ impl<S: Storage, C: StoreConfig> Store<S, C> { ...@@ -555,10 +616,13 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
} else if self.format.is_internal(first_byte) { } else if self.format.is_internal(first_byte) {
self.format.internal_entry_size() self.format.internal_entry_size()
} else { } else {
let header = self.read_slice(index, self.format.header_size()); // We don't know if the entry is sensitive or not, but it doesn't matter here. We just
// need to read the replace, sensitive, and length fields.
let header = self.read_slice(index, self.format.header_size(false));
let replace = self.format.is_replace(header); let replace = self.format.is_replace(header);
let sensitive = self.format.is_sensitive(header);
let length = self.format.get_length(header); let length = self.format.get_length(header);
self.format.entry_size(replace, length) self.format.entry_size(replace, sensitive, length)
}; };
// Truncate the length to fit the page. This can only happen in case of corruption or // Truncate the length to fit the page. This can only happen in case of corruption or
// partial writes. // partial writes.
...@@ -673,7 +737,7 @@ impl<S: Storage, C: StoreConfig> Store<S, C> { ...@@ -673,7 +737,7 @@ impl<S: Storage, C: StoreConfig> Store<S, C> {
// Save the old page index and erase count to the new page. // Save the old page index and erase count to the new page.
let erase_index = new_index; let erase_index = new_index;
let erase_entry = self.format.build_erase_entry(old_page, erase_count); let erase_entry = self.format.build_erase_entry(old_page, erase_count);
self.storage.write_slice(new_index, &erase_entry).unwrap(); self.write_entry(new_index, &erase_entry);
// Erase the page. // Erase the page.
self.erase_page(erase_index); self.erase_page(erase_index);
// Increase generation. // Increase generation.
...@@ -728,6 +792,25 @@ impl<C: StoreConfig> Store<BufferStorage, C> { ...@@ -728,6 +792,25 @@ impl<C: StoreConfig> Store<BufferStorage, C> {
pub fn set_erase_count(&mut self, page: usize, erase_count: usize) { pub fn set_erase_count(&mut self, page: usize, erase_count: usize) {
self.initialize_page(page, erase_count); self.initialize_page(page, erase_count);
} }
/// Returns whether all deleted sensitive entries have been wiped.
pub fn deleted_entries_are_wiped(&self) -> bool {
for (_, entry) in Iter::new(self) {
if !self.format.is_present(entry)
|| !self.format.is_deleted(entry)
|| self.format.is_internal(entry)
|| !self.format.is_sensitive(entry)
{
continue;
}
let gap = self.format.entry_gap(entry);
let data = gap.slice(entry);
if !data.iter().all(|&byte| byte == 0x00) {
return false;
}
}
true
}
} }
/// Maps an index from an old page to a new page if needed. /// Maps an index from an old page to a new page if needed.
...@@ -843,7 +926,27 @@ mod tests { ...@@ -843,7 +926,27 @@ mod tests {
let tag = 0; let tag = 0;
let key = 1; let key = 1;
let data = &[key, 2]; let data = &[key, 2];
let entry = StoreEntry { tag, data }; let entry = StoreEntry {
tag,
data,
sensitive: false,
};
store.insert(entry).unwrap();
assert_eq!(store.iter().count(), 1);
assert_eq!(store.find_one(&key).unwrap().1, entry);
}
#[test]
fn insert_sensitive_ok() {
let mut store = new_store();
let tag = 0;
let key = 1;
let data = &[key, 4];
let entry = StoreEntry {
tag,
data,
sensitive: true,
};
store.insert(entry).unwrap(); store.insert(entry).unwrap();
assert_eq!(store.iter().count(), 1); assert_eq!(store.iter().count(), 1);
assert_eq!(store.find_one(&key).unwrap().1, entry); assert_eq!(store.find_one(&key).unwrap().1, entry);
...@@ -857,6 +960,25 @@ mod tests { ...@@ -857,6 +960,25 @@ mod tests {
let entry = StoreEntry { let entry = StoreEntry {
tag, tag,
data: &[key, 2], data: &[key, 2],
sensitive: false,
};
store.insert(entry).unwrap();
assert_eq!(store.find_all(&key).count(), 1);
let (index, _) = store.find_one(&key).unwrap();
store.delete(index).unwrap();
assert_eq!(store.find_all(&key).count(), 0);
assert_eq!(store.iter().count(), 0);
}
#[test]
fn delete_sensitive_ok() {
let mut store = new_store();
let tag = 0;
let key = 1;
let entry = StoreEntry {
tag,
data: &[key, 2],
sensitive: true,
}; };
store.insert(entry).unwrap(); store.insert(entry).unwrap();
assert_eq!(store.find_all(&key).count(), 1); assert_eq!(store.find_all(&key).count(), 1);
...@@ -864,6 +986,7 @@ mod tests { ...@@ -864,6 +986,7 @@ mod tests {
store.delete(index).unwrap(); store.delete(index).unwrap();
assert_eq!(store.find_all(&key).count(), 0); assert_eq!(store.find_all(&key).count(), 0);
assert_eq!(store.iter().count(), 0); assert_eq!(store.iter().count(), 0);
assert!(store.deleted_entries_are_wiped());
} }
#[test] #[test]
...@@ -875,6 +998,7 @@ mod tests { ...@@ -875,6 +998,7 @@ mod tests {
.insert(StoreEntry { .insert(StoreEntry {
tag, tag,
data: &[key, 0], data: &[key, 0],
sensitive: false,
}) })
.is_ok() .is_ok()
{ {
...@@ -892,6 +1016,7 @@ mod tests { ...@@ -892,6 +1016,7 @@ mod tests {
.insert(StoreEntry { .insert(StoreEntry {
tag, tag,
data: &[key, 0], data: &[key, 0],
sensitive: false,
}) })
.is_ok() .is_ok()
{ {
...@@ -903,6 +1028,7 @@ mod tests { ...@@ -903,6 +1028,7 @@ mod tests {
.insert(StoreEntry { .insert(StoreEntry {
tag: 0, tag: 0,
data: &[key, 0], data: &[key, 0],
sensitive: false,
}) })
.unwrap(); .unwrap();
for k in 1..=key { for k in 1..=key {
...@@ -916,7 +1042,11 @@ mod tests { ...@@ -916,7 +1042,11 @@ mod tests {
let tag = 0; let tag = 0;
let key = 1; let key = 1;
let data = &[key, 2]; let data = &[key, 2];
let entry = StoreEntry { tag, data }; let entry = StoreEntry {
tag,
data,
sensitive: false,
};
store.insert(entry).unwrap(); store.insert(entry).unwrap();
// Reboot the store. // Reboot the store.
...@@ -934,10 +1064,12 @@ mod tests { ...@@ -934,10 +1064,12 @@ mod tests {
let old_entry = StoreEntry { let old_entry = StoreEntry {
tag, tag,
data: &[key, 2, 3, 4, 5, 6], data: &[key, 2, 3, 4, 5, 6],
sensitive: false,
}; };
let new_entry = StoreEntry { let new_entry = StoreEntry {
tag, tag,
data: &[key, 7, 8, 9], data: &[key, 7, 8, 9],
sensitive: false,
}; };
let mut delay = 0; let mut delay = 0;
loop { loop {
...@@ -973,6 +1105,7 @@ mod tests { ...@@ -973,6 +1105,7 @@ mod tests {
.insert(StoreEntry { .insert(StoreEntry {
tag, tag,
data: &[key, 0], data: &[key, 0],
sensitive: false,
}) })
.is_ok() .is_ok()
{ {
...@@ -983,7 +1116,14 @@ mod tests { ...@@ -983,7 +1116,14 @@ mod tests {
let (index, _) = store.find_one(&1).unwrap(); let (index, _) = store.find_one(&1).unwrap();
store.arm_snapshot(delay); store.arm_snapshot(delay);
store store
.replace(index, StoreEntry { tag, data: &[1, 1] }) .replace(
index,
StoreEntry {
tag,
data: &[1, 1],
sensitive: false,
},
)
.unwrap(); .unwrap();
let (complete, store) = match store.get_snapshot() { let (complete, store) = match store.get_snapshot() {
Err(_) => (true, store.get_storage()), Err(_) => (true, store.get_storage()),
...@@ -995,7 +1135,11 @@ mod tests { ...@@ -995,7 +1135,11 @@ mod tests {
assert_eq!(store.find_all(&k).count(), 1); assert_eq!(store.find_all(&k).count(), 1);
assert_eq!( assert_eq!(
store.find_one(&k).unwrap().1, store.find_one(&k).unwrap().1,
StoreEntry { tag, data: &[k, 0] } StoreEntry {
tag,
data: &[k, 0],
sensitive: false,
}
); );
} }
assert_eq!(store.find_all(&1).count(), 1); assert_eq!(store.find_all(&1).count(), 1);
...@@ -1012,7 +1156,11 @@ mod tests { ...@@ -1012,7 +1156,11 @@ mod tests {
#[test] #[test]
fn invalid_tag() { fn invalid_tag() {
let mut store = new_store(); let mut store = new_store();
let entry = StoreEntry { tag: 1, data: &[] }; let entry = StoreEntry {
tag: 1,
data: &[],
sensitive: false,
};
assert_eq!(store.insert(entry), Err(StoreError::InvalidTag)); assert_eq!(store.insert(entry), Err(StoreError::InvalidTag));
} }
...@@ -1022,6 +1170,7 @@ mod tests { ...@@ -1022,6 +1170,7 @@ mod tests {
let entry = StoreEntry { let entry = StoreEntry {
tag: 0, tag: 0,
data: &[0; PAGE_SIZE], data: &[0; PAGE_SIZE],
sensitive: false,
}; };
assert_eq!(store.insert(entry), Err(StoreError::StoreFull)); assert_eq!(store.insert(entry), Err(StoreError::StoreFull));
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment