Update the documentation to use linking by name
See https://doc.rust-lang.org/stable/rustdoc/linking-to-items-by-name.html
This commit is contained in:
@@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Storage representation of a store.
|
||||
|
||||
#[macro_use]
|
||||
mod bitfield;
|
||||
|
||||
@@ -26,13 +28,14 @@ use core::convert::TryFrom;
|
||||
|
||||
/// Internal representation of a word in flash.
|
||||
///
|
||||
/// Currently, the store only supports storages where a word is 32 bits.
|
||||
/// Currently, the store only supports storages where a word is 32 bits, i.e. the [word
|
||||
/// size](Storage::word_size) is 4 bytes.
|
||||
type WORD = u32;
|
||||
|
||||
/// Abstract representation of a word in flash.
|
||||
///
|
||||
/// This type is kept abstract to avoid possible confusion with `Nat` if they happen to have the
|
||||
/// same representation. This is because they have different semantics, `Nat` represents natural
|
||||
/// This type is kept abstract to avoid possible confusion with [`Nat`] if they happen to have the
|
||||
/// same representation. This is because they have different semantics, [`Nat`] represents natural
|
||||
/// numbers while `Word` represents sequences of bits (and thus has no arithmetic).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Word(WORD);
|
||||
@@ -47,7 +50,7 @@ impl Word {
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `slice.len() != WORD_SIZE`.
|
||||
/// Panics if `slice.len()` is not [`WORD_SIZE`] bytes.
|
||||
pub fn from_slice(slice: &[u8]) -> Word {
|
||||
Word(WORD::from_le_bytes(<WordSlice>::try_from(slice).unwrap()))
|
||||
}
|
||||
@@ -60,47 +63,49 @@ impl Word {
|
||||
|
||||
/// Size of a word in bytes.
|
||||
///
|
||||
/// Currently, the store only supports storages where a word is 4 bytes.
|
||||
/// Currently, the store only supports storages where the [word size](Storage::word_size) is 4
|
||||
/// bytes.
|
||||
const WORD_SIZE: Nat = core::mem::size_of::<WORD>() as Nat;
|
||||
|
||||
/// Minimum number of words per page.
|
||||
///
|
||||
/// Currently, the store only supports storages where pages have at least 8 words.
|
||||
const MIN_NUM_WORDS_PER_PAGE: Nat = 8;
|
||||
/// Currently, the store only supports storages where pages have at least 8 [words](WORD_SIZE), i.e.
|
||||
/// the [page size](Storage::page_size) is at least 32 bytes.
|
||||
const MIN_PAGE_SIZE: Nat = 8;
|
||||
|
||||
/// Maximum size of a page in bytes.
|
||||
///
|
||||
/// Currently, the store only supports storages where pages are between 8 and 1024 [words].
|
||||
///
|
||||
/// [words]: constant.WORD_SIZE.html
|
||||
/// Currently, the store only supports storages where pages have at most 1024 [words](WORD_SIZE),
|
||||
/// i.e. the [page size](Storage::page_size) is at most 4096 bytes.
|
||||
const MAX_PAGE_SIZE: Nat = 4096;
|
||||
|
||||
/// Maximum number of erase cycles.
|
||||
///
|
||||
/// Currently, the store only supports storages where the maximum number of erase cycles fits on 16
|
||||
/// bits.
|
||||
/// Currently, the store only supports storages where the [maximum number of erase
|
||||
/// cycles](Storage::max_page_erases) fits in 16 bits, i.e. it is at most 65535.
|
||||
const MAX_ERASE_CYCLE: Nat = 65535;
|
||||
|
||||
/// Minimum number of pages.
|
||||
///
|
||||
/// Currently, the store only supports storages with at least 3 pages.
|
||||
/// Currently, the store only supports storages where the [number of pages](Storage::num_pages) is
|
||||
/// at least 3.
|
||||
const MIN_NUM_PAGES: Nat = 3;
|
||||
|
||||
/// Maximum page index.
|
||||
///
|
||||
/// Thus the maximum number of pages is one more than this number. Currently, the store only
|
||||
/// supports storages where the number of pages is between 3 and 64.
|
||||
/// Currently, the store only supports storages where the [number of pages](Storage::num_pages) is
|
||||
/// at most 64, i.e. the maximum page index is 63.
|
||||
const MAX_PAGE_INDEX: Nat = 63;
|
||||
|
||||
/// Maximum key index.
|
||||
///
|
||||
/// Thus the number of keys is one more than this number. Currently, the store only supports 4096
|
||||
/// keys.
|
||||
/// Currently, the store only supports 4096 keys, i.e. the maximum key index is 4095.
|
||||
const MAX_KEY_INDEX: Nat = 4095;
|
||||
|
||||
/// Maximum length in bytes of a user payload.
|
||||
///
|
||||
/// Currently, the store only supports values smaller than 1024 bytes.
|
||||
/// Currently, the store only supports values at most 1023 bytes long. This may be further reduced
|
||||
/// depending on the [page size](Storage::page_size), see [`Format::max_value_len`].
|
||||
const MAX_VALUE_LEN: Nat = 1023;
|
||||
|
||||
/// Maximum number of updates per transaction.
|
||||
@@ -109,9 +114,15 @@ const MAX_VALUE_LEN: Nat = 1023;
|
||||
const MAX_UPDATES: Nat = 31;
|
||||
|
||||
/// Maximum number of words per virtual page.
|
||||
const MAX_VIRT_PAGE_SIZE: Nat = div_ceil(MAX_PAGE_SIZE, WORD_SIZE) - CONTENT_WORD;
|
||||
///
|
||||
/// A virtual page has [`CONTENT_WORD`] less [words](WORD_SIZE) than the storage [page
|
||||
/// size](Storage::page_size). Those words are used to store the page header. Since a page has at
|
||||
/// least [8](MIN_PAGE_SIZE) words, a virtual page has at least 6 words.
|
||||
const MAX_VIRT_PAGE_SIZE: Nat = MAX_PAGE_SIZE / WORD_SIZE - CONTENT_WORD;
|
||||
|
||||
/// Word with all bits set to one.
|
||||
///
|
||||
/// After a page is erased, all words are equal to this value.
|
||||
const ERASED_WORD: Word = Word(!(0 as WORD));
|
||||
|
||||
/// Helpers for a given storage configuration.
|
||||
@@ -121,33 +132,31 @@ pub struct Format {
|
||||
///
|
||||
/// # Invariant
|
||||
///
|
||||
/// - Words divide a page evenly.
|
||||
/// - There are at least 8 words in a page.
|
||||
/// - There are at most `MAX_PAGE_SIZE` bytes in a page.
|
||||
/// - [Words](WORD_SIZE) divide a page evenly.
|
||||
/// - There are at least [`MIN_PAGE_SIZE`] words in a page.
|
||||
/// - There are at most [`MAX_PAGE_SIZE`] bytes in a page.
|
||||
page_size: Nat,
|
||||
|
||||
/// The number of pages in the storage.
|
||||
///
|
||||
/// # Invariant
|
||||
///
|
||||
/// - There are at least 3 pages.
|
||||
/// - There are at most `MAX_PAGE_INDEX + 1` pages.
|
||||
/// - There are at least [`MIN_NUM_PAGES`] pages.
|
||||
/// - There are at most [`MAX_PAGE_INDEX`] + 1 pages.
|
||||
num_pages: Nat,
|
||||
|
||||
/// The maximum number of times a page can be erased.
|
||||
///
|
||||
/// # Invariant
|
||||
///
|
||||
/// - A page can be erased at most `MAX_ERASE_CYCLE` times.
|
||||
/// - A page can be erased at most [`MAX_ERASE_CYCLE`] times.
|
||||
max_page_erases: Nat,
|
||||
}
|
||||
|
||||
impl Format {
|
||||
/// Extracts the format from a storage.
|
||||
///
|
||||
/// Returns `None` if the storage is not [supported].
|
||||
///
|
||||
/// [supported]: struct.Format.html#method.is_storage_supported
|
||||
/// Returns `None` if the storage is not [supported](Format::is_storage_supported).
|
||||
pub fn new<S: Storage>(storage: &S) -> Option<Format> {
|
||||
if Format::is_storage_supported(storage) {
|
||||
Some(Format {
|
||||
@@ -163,21 +172,12 @@ impl Format {
|
||||
/// Returns whether a storage is supported.
|
||||
///
|
||||
/// A storage is supported if the following conditions hold:
|
||||
/// - The size of a word is [`WORD_SIZE`] bytes.
|
||||
/// - The size of a word evenly divides the size of a page.
|
||||
/// - A page contains at least [`MIN_NUM_WORDS_PER_PAGE`] words.
|
||||
/// - A page contains at most [`MAX_PAGE_SIZE`] bytes.
|
||||
/// - There are at least [`MIN_NUM_PAGES`] pages.
|
||||
/// - There are at most [`MAX_PAGE_INDEX`]` + 1` pages.
|
||||
/// - A word can be written at least twice between erase cycles.
|
||||
/// - The maximum number of erase cycles is at most [`MAX_ERASE_CYCLE`].
|
||||
///
|
||||
/// [`WORD_SIZE`]: constant.WORD_SIZE.html
|
||||
/// [`MIN_NUM_WORDS_PER_PAGE`]: constant.MIN_NUM_WORDS_PER_PAGE.html
|
||||
/// [`MAX_PAGE_SIZE`]: constant.MAX_PAGE_SIZE.html
|
||||
/// [`MIN_NUM_PAGES`]: constant.MIN_NUM_PAGES.html
|
||||
/// [`MAX_PAGE_INDEX`]: constant.MAX_PAGE_INDEX.html
|
||||
/// [`MAX_ERASE_CYCLE`]: constant.MAX_ERASE_CYCLE.html
|
||||
/// - The [`Storage::word_size`] is [`WORD_SIZE`] bytes.
|
||||
/// - The [`Storage::word_size`] evenly divides the [`Storage::page_size`].
|
||||
/// - The [`Storage::page_size`] is between [`MIN_PAGE_SIZE`] words and [`MAX_PAGE_SIZE`] bytes.
|
||||
/// - The [`Storage::num_pages`] is between [`MIN_NUM_PAGES`] and [`MAX_PAGE_INDEX`] + 1.
|
||||
/// - The [`Storage::max_word_writes`] is at least 2.
|
||||
/// - The [`Storage::max_page_erases`] is at most [`MAX_ERASE_CYCLE`].
|
||||
fn is_storage_supported<S: Storage>(storage: &S) -> bool {
|
||||
let word_size = usize_to_nat(storage.word_size());
|
||||
let page_size = usize_to_nat(storage.page_size());
|
||||
@@ -186,7 +186,7 @@ impl Format {
|
||||
let max_page_erases = usize_to_nat(storage.max_page_erases());
|
||||
word_size == WORD_SIZE
|
||||
&& page_size % word_size == 0
|
||||
&& (MIN_NUM_WORDS_PER_PAGE * word_size <= page_size && page_size <= MAX_PAGE_SIZE)
|
||||
&& (MIN_PAGE_SIZE * word_size <= page_size && page_size <= MAX_PAGE_SIZE)
|
||||
&& (MIN_NUM_PAGES <= num_pages && num_pages <= MAX_PAGE_INDEX + 1)
|
||||
&& max_word_writes >= 2
|
||||
&& max_page_erases <= MAX_ERASE_CYCLE
|
||||
@@ -199,28 +199,28 @@ impl Format {
|
||||
|
||||
/// The size of a page in bytes.
|
||||
///
|
||||
/// We have `MIN_NUM_WORDS_PER_PAGE * self.word_size() <= self.page_size() <= MAX_PAGE_SIZE`.
|
||||
/// This is at least [`MIN_PAGE_SIZE`] [words](WORD_SIZE) and at most [`MAX_PAGE_SIZE`] bytes.
|
||||
pub fn page_size(&self) -> Nat {
|
||||
self.page_size
|
||||
}
|
||||
|
||||
/// The number of pages in the storage, denoted by `N`.
|
||||
/// The number of pages in the storage, denoted by N.
|
||||
///
|
||||
/// We have `MIN_NUM_PAGES <= N <= MAX_PAGE_INDEX + 1`.
|
||||
/// We have [`MIN_NUM_PAGES`] ≤ N ≤ [`MAX_PAGE_INDEX`] + 1.
|
||||
pub fn num_pages(&self) -> Nat {
|
||||
self.num_pages
|
||||
}
|
||||
|
||||
/// The maximum page index.
|
||||
///
|
||||
/// We have `2 <= self.max_page() <= MAX_PAGE_INDEX`.
|
||||
/// This is at least [`MIN_NUM_PAGES`] - 1 and at most [`MAX_PAGE_INDEX`].
|
||||
pub fn max_page(&self) -> Nat {
|
||||
self.num_pages - 1
|
||||
}
|
||||
|
||||
/// The maximum number of times a page can be erased, denoted by `E`.
|
||||
/// The maximum number of times a page can be erased, denoted by E.
|
||||
///
|
||||
/// We have `E <= MAX_ERASE_CYCLE`.
|
||||
/// We have E ≤ [`MAX_ERASE_CYCLE`].
|
||||
pub fn max_page_erases(&self) -> Nat {
|
||||
self.max_page_erases
|
||||
}
|
||||
@@ -235,19 +235,18 @@ impl Format {
|
||||
MAX_UPDATES
|
||||
}
|
||||
|
||||
/// The size of a virtual page in words, denoted by `Q`.
|
||||
/// The size of a virtual page in words, denoted by Q.
|
||||
///
|
||||
/// A virtual page is stored in a physical page after the page header.
|
||||
///
|
||||
/// We have `MIN_NUM_WORDS_PER_PAGE - 2 <= Q <= MAX_VIRT_PAGE_SIZE`.
|
||||
/// We have [`MIN_PAGE_SIZE`] - 2 ≤ Q ≤ [`MAX_VIRT_PAGE_SIZE`].
|
||||
pub fn virt_page_size(&self) -> Nat {
|
||||
self.page_size() / self.word_size() - CONTENT_WORD
|
||||
}
|
||||
|
||||
/// The maximum length in bytes of a user payload.
|
||||
///
|
||||
/// We have `(MIN_NUM_WORDS_PER_PAGE - 3) * self.word_size() <= self.max_value_len() <=
|
||||
/// MAX_VALUE_LEN`.
|
||||
/// This is at least [`MIN_PAGE_SIZE`] - 3 [words](WORD_SIZE) and at most [`MAX_VALUE_LEN`].
|
||||
pub fn max_value_len(&self) -> Nat {
|
||||
min(
|
||||
(self.virt_page_size() - 1) * self.word_size(),
|
||||
@@ -255,57 +254,50 @@ impl Format {
|
||||
)
|
||||
}
|
||||
|
||||
/// The maximum prefix length in words, denoted by `M`.
|
||||
/// The maximum prefix length in words, denoted by M.
|
||||
///
|
||||
/// A prefix is the first words of a virtual page that belong to the last entry of the previous
|
||||
/// virtual page. This happens because entries may overlap up to 2 virtual pages.
|
||||
///
|
||||
/// We have `MIN_NUM_WORDS_PER_PAGE - 3 <= M < Q`.
|
||||
/// We have [`MIN_PAGE_SIZE`] - 3 ≤ M < Q.
|
||||
pub fn max_prefix_len(&self) -> Nat {
|
||||
self.bytes_to_words(self.max_value_len())
|
||||
}
|
||||
|
||||
/// The total virtual capacity in words, denoted by `V`.
|
||||
/// The total virtual capacity in words, denoted by V.
|
||||
///
|
||||
/// We have `V = (N - 1) * (Q - 1) - M`.
|
||||
/// We have V = (N - 1) × (Q - 1) - M.
|
||||
///
|
||||
/// We can show `V >= (N - 2) * (Q - 1)` with the following steps:
|
||||
/// - `M <= Q - 1` from `M < Q` from [`M`] definition
|
||||
/// - `-M >= -(Q - 1)` from above
|
||||
/// - `V >= (N - 1) * (Q - 1) - (Q - 1)` from `V` definition
|
||||
///
|
||||
/// [`M`]: struct.Format.html#method.max_prefix_len
|
||||
/// We can show V ≥ (N - 2) × (Q - 1) with the following steps:
|
||||
/// - M ≤ Q - 1 from M < Q from [M](Format::max_prefix_len)'s definition
|
||||
/// - -M ≥ -(Q - 1) from above
|
||||
/// - V ≥ (N - 1) × (Q - 1) - (Q - 1) from V's definition
|
||||
pub fn virt_size(&self) -> Nat {
|
||||
(self.num_pages() - 1) * (self.virt_page_size() - 1) - self.max_prefix_len()
|
||||
}
|
||||
|
||||
/// The total user capacity in words, denoted by `C`.
|
||||
/// The total user capacity in words, denoted by C.
|
||||
///
|
||||
/// We have `C = V - N = (N - 1) * (Q - 2) - M - 1`.
|
||||
/// We have C = V - N = (N - 1) × (Q - 2) - M - 1.
|
||||
///
|
||||
/// We can show `C >= (N - 2) * (Q - 2) - 2` with the following steps:
|
||||
/// - `V >= (N - 2) * (Q - 1)` from [`V`] definition
|
||||
/// - `C >= (N - 2) * (Q - 1) - N` from `C` definition
|
||||
/// - `(N - 2) * (Q - 1) - N = (N - 2) * (Q - 2) - 2` by calculus
|
||||
///
|
||||
/// [`V`]: struct.Format.html#method.virt_size
|
||||
/// We can show C ≥ (N - 2) × (Q - 2) - 2 with the following steps:
|
||||
/// - V ≥ (N - 2) × (Q - 1) from [V](Format::virt_size)'s definition
|
||||
/// - C ≥ (N - 2) × (Q - 1) - N from C's definition
|
||||
/// - (N - 2) × (Q - 1) - N = (N - 2) × (Q - 2) - 2 by calculus
|
||||
pub fn total_capacity(&self) -> Nat {
|
||||
// From the virtual capacity, we reserve N - 1 words for `Erase` entries and 1 word for a
|
||||
// `Clear` entry.
|
||||
self.virt_size() - self.num_pages()
|
||||
}
|
||||
|
||||
/// The total virtual lifetime in words, denoted by `L`.
|
||||
/// The total virtual lifetime in words, denoted by L.
|
||||
///
|
||||
/// We have `L = (E * N + N - 1) * Q`.
|
||||
/// We have L = (E × N + N - 1) × Q.
|
||||
pub fn total_lifetime(&self) -> Position {
|
||||
Position::new(self, self.max_page_erases(), self.num_pages() - 1, 0)
|
||||
}
|
||||
|
||||
/// Returns the word position of the first entry of a page.
|
||||
///
|
||||
/// The init info of the page must be provided to know where the first entry of the page
|
||||
/// starts.
|
||||
pub fn page_head(&self, init: InitInfo, page: Nat) -> Position {
|
||||
Position::new(self, init.cycle, page, init.prefix)
|
||||
}
|
||||
@@ -557,7 +549,7 @@ impl Format {
|
||||
///
|
||||
/// # Preconditions
|
||||
///
|
||||
/// - `bytes + self.word_size()` does not overflow.
|
||||
/// - `bytes` + [`Self::word_size`] does not overflow.
|
||||
pub fn bytes_to_words(&self, bytes: Nat) -> Nat {
|
||||
div_ceil(bytes, self.word_size())
|
||||
}
|
||||
@@ -571,7 +563,7 @@ const COMPACT_WORD: Nat = 1;
|
||||
|
||||
/// The word index of the content of a page.
|
||||
///
|
||||
/// Since a page is at least 8 words, there is always at least 6 words of content.
|
||||
/// This is also the length in words of the page header.
|
||||
const CONTENT_WORD: Nat = 2;
|
||||
|
||||
/// The checksum for a single word.
|
||||
@@ -718,21 +710,21 @@ bitfield! {
|
||||
|
||||
/// The position of a word in the virtual storage.
|
||||
///
|
||||
/// With the notations defined in `Format`, let:
|
||||
/// - `w` a virtual word offset in a page which is between `0` and `Q - 1`
|
||||
/// - `p` a page offset which is between `0` and `N - 1`
|
||||
/// - `c` the number of erase cycles of a page which is between `0` and `E`
|
||||
/// With the notations defined in [`Format`], let:
|
||||
/// - w denote a word offset in a virtual page, thus between 0 and Q - 1
|
||||
/// - p denote a page offset, thus between 0 and N - 1
|
||||
/// - c denote the number of times a page was erased, thus between 0 and E
|
||||
///
|
||||
/// Then the position of a word is `(c*N + p)*Q + w`. This position monotonically increases and
|
||||
/// The position of a word is (c × N + p) × Q + w. This position monotonically increases and
|
||||
/// represents the consumed lifetime of the storage.
|
||||
///
|
||||
/// This type is kept abstract to avoid possible confusion with `Nat` and `Word` if they happen to
|
||||
/// have the same representation. Here is an overview of their semantics:
|
||||
/// This type is kept abstract to avoid possible confusion with [`Nat`] and [`Word`] if they happen
|
||||
/// to have the same representation. Here is an overview of their semantics:
|
||||
///
|
||||
/// | Name | Semantics | Arithmetic operations | Bit-wise operations |
|
||||
/// | ---------- | --------------------------- | --------------------- | ------------------- |
|
||||
/// | `Nat` | Natural numbers | Yes (no overflow) | No |
|
||||
/// | `Word` | Word in flash | No | Yes |
|
||||
/// | [`Nat`] | Natural numbers | Yes (no overflow) | No |
|
||||
/// | [`Word`] | Word in flash | No | Yes |
|
||||
/// | `Position` | Position in virtual storage | Yes (no overflow) | No |
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Position(Nat);
|
||||
@@ -763,9 +755,9 @@ impl Position {
|
||||
/// Create a word position given its coordinates.
|
||||
///
|
||||
/// The coordinates of a word are:
|
||||
/// - Its word index in its page.
|
||||
/// - Its word index in its virtual page.
|
||||
/// - Its page index in the storage.
|
||||
/// - The number of times that page was erased.
|
||||
/// - The number of times its page was erased.
|
||||
pub fn new(format: &Format, cycle: Nat, page: Nat, word: Nat) -> Position {
|
||||
Position((cycle * format.num_pages() + page) * format.virt_page_size() + word)
|
||||
}
|
||||
@@ -928,11 +920,11 @@ pub fn is_erased(slice: &[u8]) -> bool {
|
||||
|
||||
/// Divides then takes ceiling.
|
||||
///
|
||||
/// Returns `ceil(x / m)` in mathematical notations (not Rust code).
|
||||
/// Returns ⌈x / m⌉, i.e. the lowest natural number r such that r ≥ x / m.
|
||||
///
|
||||
/// # Preconditions
|
||||
///
|
||||
/// - `x + m` does not overflow.
|
||||
/// - x + m does not overflow.
|
||||
const fn div_ceil(x: Nat, m: Nat) -> Nat {
|
||||
(x + m - 1) / m
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user