Skip to main content

wasefire_store/
buffer.rs

1// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://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,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Flash storage for testing.
16//!
17//! [`BufferStorage`] implements the flash [`Storage`] interface but doesn't interface with an
18//! actual flash storage. Instead it uses a buffer in memory to represent the storage state.
19
20use alloc::borrow::{Borrow, Cow};
21use alloc::boxed::Box;
22use alloc::vec;
23
24use wasefire_error::{Code, Error, Space};
25
26use crate::{Storage, StorageIndex};
27
28/// Simulates a flash storage using a buffer in memory.
29///
30/// This buffer storage can be used in place of an actual flash storage. It is particularly useful
31/// for tests and fuzzing, for which it has dedicated functionalities.
32///
33/// This storage tracks how many times words are written between page erase cycles, how many times
34/// pages are erased, and whether an operation flips bits in the wrong direction. Operations panic
35/// if those conditions are broken (optional). This storage also permits to interrupt operations for
36/// inspection or to corrupt the operation.
37#[derive(Clone)]
38pub struct BufferStorage {
39    /// Content of the storage.
40    storage: Box<[u8]>,
41
42    /// Options of the storage.
43    options: BufferOptions,
44
45    /// Number of times a word was written since the last time its page was erased.
46    word_writes: Box<[usize]>,
47
48    /// Number of times a page was erased.
49    page_erases: Box<[usize]>,
50
51    /// Interruption state.
52    interruption: Interruption,
53}
54
55/// Options of a buffer storage.
56#[derive(Clone, Debug)]
57pub struct BufferOptions {
58    /// Size of a word in bytes.
59    pub word_size: usize,
60
61    /// Size of a page in bytes.
62    pub page_size: usize,
63
64    /// How many times a word can be written between page erase cycles.
65    pub max_word_writes: usize,
66
67    /// How many times a page can be erased.
68    pub max_page_erases: usize,
69
70    /// Whether the storage should check the flash invariant.
71    ///
72    /// When set, the following conditions would panic:
73    /// - A bit is written from 0 to 1.
74    /// - A word is written more than [`Self::max_word_writes`].
75    /// - A page is erased more than [`Self::max_page_erases`].
76    pub strict_mode: bool,
77}
78
79/// Corrupts a slice given actual and expected value.
80///
81/// A corruption function is called exactly once and takes 2 arguments:
82/// - A mutable slice representing the storage before the interrupted operation.
83/// - A shared slice representing what the storage would have been if the operation was not
84///   interrupted.
85///
86/// The corruption function may flip an arbitrary number of bits in the mutable slice, but may only
87/// flip bits that differ between both slices.
88pub type BufferCorruptFunction<'a> = Box<dyn FnOnce(&mut [u8], &[u8]) + 'a>;
89
90impl BufferStorage {
91    /// Creates a buffer storage.
92    ///
93    /// # Panics
94    ///
95    /// The following preconditions must hold:
96    /// - `options.word_size` must be a power of two.
97    /// - `options.page_size` must be a power of two.
98    /// - `options.page_size` must be word-aligned.
99    /// - `storage.len()` must be page-aligned.
100    pub fn new(storage: Box<[u8]>, options: BufferOptions) -> BufferStorage {
101        assert!(options.word_size.is_power_of_two());
102        assert!(options.page_size.is_power_of_two());
103        let num_words = storage.len() / options.word_size;
104        let num_pages = storage.len() / options.page_size;
105        let buffer = BufferStorage {
106            storage,
107            options,
108            word_writes: vec![0; num_words].into_boxed_slice(),
109            page_erases: vec![0; num_pages].into_boxed_slice(),
110            interruption: Interruption::Ready,
111        };
112        assert!(buffer.is_word_aligned(buffer.options.page_size));
113        assert!(buffer.is_page_aligned(buffer.storage.len()));
114        buffer
115    }
116
117    /// Arms an interruption after a given delay.
118    ///
119    /// Before each subsequent mutable operation (write or erase), the delay is decremented if
120    /// positive. If the delay is elapsed, the operation is saved and the [`INTERRUPTION`] error is
121    /// returned. Subsequent operations will panic until either of:
122    /// - The interrupted operation is [corrupted](BufferStorage::corrupt_operation).
123    /// - The interruption is [reset](BufferStorage::reset_interruption).
124    ///
125    /// # Panics
126    ///
127    /// Panics if an interruption is already armed.
128    pub fn arm_interruption(&mut self, delay: usize) {
129        self.interruption.arm(delay);
130    }
131
132    /// Disarms an interruption that did not trigger.
133    ///
134    /// Returns the remaining delay.
135    ///
136    /// # Panics
137    ///
138    /// Panics if any of the following conditions hold:
139    /// - An interruption was not [armed](BufferStorage::arm_interruption).
140    /// - An interruption was armed and it has triggered.
141    pub fn disarm_interruption(&mut self) -> usize {
142        self.interruption.get().err().unwrap()
143    }
144
145    /// Resets an interruption regardless of triggering.
146    ///
147    /// # Panics
148    ///
149    /// Panics if an interruption was not [armed](BufferStorage::arm_interruption).
150    pub fn reset_interruption(&mut self) {
151        let _ = self.interruption.get();
152    }
153
154    /// Corrupts an interrupted operation.
155    ///
156    /// Applies the corruption function to the storage. Counters are updated accordingly:
157    /// - If a word is fully written, its counter is incremented regardless of whether other words
158    ///   of the same operation have been fully written.
159    /// - If a page is fully erased, its counter is incremented (and its word counters are reset).
160    ///
161    /// # Panics
162    ///
163    /// Panics if any of the following conditions hold:
164    /// - An interruption was not [armed](BufferStorage::arm_interruption).
165    /// - An interruption was armed but did not trigger.
166    /// - The corruption function corrupts more bits than allowed.
167    /// - The interrupted operation itself would have panicked.
168    pub fn corrupt_operation(&mut self, corrupt: BufferCorruptFunction) {
169        let operation = self.interruption.get().unwrap();
170        let range = self.operation_range(&operation).unwrap();
171        let mut before = self.storage[range.clone()].to_vec().into_boxed_slice();
172        match operation {
173            BufferOperation::Write { value: after, .. } => {
174                corrupt(&mut before, &after);
175                self.incr_word_writes(range.start, &before, &after);
176            }
177            BufferOperation::Erase { page } => {
178                let after = vec![0xff; self.page_size()].into_boxed_slice();
179                corrupt(&mut before, &after);
180                if before == after {
181                    self.incr_page_erases(page);
182                }
183            }
184        };
185        self.storage[range].copy_from_slice(&before);
186    }
187
188    /// Returns the number of times a word was written.
189    pub fn get_word_writes(&self, word: usize) -> usize {
190        self.word_writes[word]
191    }
192
193    /// Returns the number of times a page was erased.
194    pub fn get_page_erases(&self, page: usize) -> usize {
195        self.page_erases[page]
196    }
197
198    /// Sets the number of times a page was erased.
199    pub fn set_page_erases(&mut self, page: usize, cycle: usize) {
200        self.page_erases[page] = cycle;
201    }
202}
203
204/// Error indicating that an operation was interrupted.
205pub const INTERRUPTION: Error = Error::new_const(Space::World as u8, INTERRUPT.code());
206// By generating the interruption error as an internal error and letting user test for it as a world
207// error, we make sure the error got popped at least once in between (thus making sure we didn't
208// forget to pop errors for mutable operations).
209const INTERRUPT: Error = Error::new_const(Space::Internal as u8, 0xffff);
210
211impl BufferStorage {
212    /// Returns whether a number is word-aligned.
213    fn is_word_aligned(&self, x: usize) -> bool {
214        x & (self.options.word_size - 1) == 0
215    }
216
217    /// Returns whether a number is page-aligned.
218    fn is_page_aligned(&self, x: usize) -> bool {
219        x & (self.options.page_size - 1) == 0
220    }
221
222    /// Updates the counters as if a page was erased.
223    ///
224    /// The page counter of that page is incremented and the word counters of that page are reset.
225    ///
226    /// # Panics
227    ///
228    /// Panics if the [maximum number of erase cycles per page](BufferOptions::max_page_erases) is
229    /// reached.
230    fn incr_page_erases(&mut self, page: usize) {
231        // Check that pages are not erased too many times.
232        if self.options.strict_mode {
233            assert!(self.page_erases[page] < self.max_page_erases());
234        }
235        self.page_erases[page] += 1;
236        let num_words = self.page_size() / self.word_size();
237        for word in 0 .. num_words {
238            self.word_writes[page * num_words + word] = 0;
239        }
240    }
241
242    /// Updates the word counters as if a partial write occurred.
243    ///
244    /// The partial write is described as if `complete` was supposed to be written to the storage
245    /// starting at byte `index`, but actually only `value` was written. Word counters are
246    /// incremented only if their value would change and they would be completely written.
247    ///
248    /// # Preconditions
249    ///
250    /// - `index` must be word-aligned.
251    /// - `value` and `complete` must have the same word-aligned length.
252    ///
253    /// # Panics
254    ///
255    /// Panics if the [maximum number of writes per word](BufferOptions::max_word_writes) is
256    /// reached.
257    fn incr_word_writes(&mut self, index: usize, value: &[u8], complete: &[u8]) {
258        let word_size = self.word_size();
259        for i in 0 .. value.len() / word_size {
260            let range = core::ops::Range { start: i * word_size, end: (i + 1) * word_size };
261            // Partial word writes do not count.
262            if value[range.clone()] != complete[range.clone()] {
263                continue;
264            }
265            // Words are written only if necessary.
266            if value[range.clone()] == self.storage[index ..][range] {
267                continue;
268            }
269            let word = index / word_size + i;
270            // Check that words are not written too many times.
271            if self.options.strict_mode {
272                assert!(self.word_writes[word] < self.max_word_writes());
273            }
274            self.word_writes[word] += 1;
275        }
276    }
277
278    /// Returns the storage range of an operation.
279    fn operation_range(
280        &self, operation: &BufferOperation<impl Borrow<[u8]>>,
281    ) -> Result<core::ops::Range<usize>, Error> {
282        match *operation {
283            BufferOperation::Write { index, ref value } => index.range(value.borrow().len(), self),
284            BufferOperation::Erase { page } => {
285                StorageIndex { page, byte: 0 }.range(self.page_size(), self)
286            }
287        }
288    }
289}
290
291impl Storage for BufferStorage {
292    fn word_size(&self) -> usize {
293        self.options.word_size
294    }
295
296    fn page_size(&self) -> usize {
297        self.options.page_size
298    }
299
300    fn num_pages(&self) -> usize {
301        self.storage.len() / self.options.page_size
302    }
303
304    fn max_word_writes(&self) -> usize {
305        self.options.max_word_writes
306    }
307
308    fn max_page_erases(&self) -> usize {
309        self.options.max_page_erases
310    }
311
312    fn read_slice(&self, index: StorageIndex, length: usize) -> Result<Cow<'_, [u8]>, Error> {
313        Ok(Cow::Borrowed(&self.storage[index.range(length, self)?]))
314    }
315
316    fn write_slice(&mut self, index: StorageIndex, value: &[u8]) -> Result<(), Error> {
317        if !self.is_word_aligned(index.byte) || !self.is_word_aligned(value.len()) {
318            return Err(Error::user(Code::InvalidAlign));
319        }
320        let operation = BufferOperation::Write { index, value };
321        let range = self.operation_range(&operation)?;
322        // Interrupt operation if armed and delay expired.
323        self.interruption.tick(&operation)?;
324        // Check and update counters.
325        self.incr_word_writes(range.start, value, value);
326        // Check that bits are correctly flipped.
327        if self.options.strict_mode {
328            for (byte, &val) in range.clone().zip(value.iter()) {
329                assert_eq!(self.storage[byte] & val, val);
330            }
331        }
332        // Write to the storage.
333        self.storage[range].copy_from_slice(value);
334        Ok(())
335    }
336
337    fn erase_page(&mut self, page: usize) -> Result<(), Error> {
338        let operation = BufferOperation::Erase { page };
339        let range = self.operation_range(&operation)?;
340        // Interrupt operation if armed and delay expired.
341        self.interruption.tick(&operation)?;
342        // Check and update counters.
343        self.incr_page_erases(page);
344        // Write to the storage.
345        for byte in &mut self.storage[range] {
346            *byte = 0xff;
347        }
348        Ok(())
349    }
350}
351
352impl core::fmt::Display for BufferStorage {
353    fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
354        let num_pages = self.num_pages();
355        let num_words = self.page_size() / self.word_size();
356        let num_bytes = self.word_size();
357        for page in 0 .. num_pages {
358            write!(f, "[{}]", self.page_erases[page])?;
359            for word in 0 .. num_words {
360                write!(f, " [{}]", self.word_writes[page * num_words + word])?;
361                for byte in 0 .. num_bytes {
362                    let index = (page * num_words + word) * num_bytes + byte;
363                    write!(f, "{:02x}", self.storage[index])?;
364                }
365            }
366            writeln!(f)?;
367        }
368        Ok(())
369    }
370}
371
372/// Represents a storage operation.
373///
374/// It is polymorphic over the ownership of the byte slice to avoid unnecessary copies.
375#[derive(Clone, Debug, PartialEq, Eq)]
376enum BufferOperation<ByteSlice: Borrow<[u8]>> {
377    /// Represents a write operation.
378    Write {
379        /// The storage index at which the write should occur.
380        index: StorageIndex,
381
382        /// The slice that should be written.
383        value: ByteSlice,
384    },
385
386    /// Represents an erase operation.
387    Erase {
388        /// The page that should be erased.
389        page: usize,
390    },
391}
392
393/// Represents a storage operation owning its byte slices.
394type OwnedBufferOperation = BufferOperation<Box<[u8]>>;
395
396/// Represents a storage operation sharing its byte slices.
397type SharedBufferOperation<'a> = BufferOperation<&'a [u8]>;
398
399impl SharedBufferOperation<'_> {
400    fn to_owned(&self) -> OwnedBufferOperation {
401        match *self {
402            BufferOperation::Write { index, value } => {
403                BufferOperation::Write { index, value: value.to_vec().into_boxed_slice() }
404            }
405            BufferOperation::Erase { page } => BufferOperation::Erase { page },
406        }
407    }
408}
409
410/// Controls when an operation is interrupted.
411///
412/// This can be used to simulate power-offs while the device is writing to the storage or erasing a
413/// page in the storage.
414#[derive(Clone)]
415enum Interruption {
416    /// Mutable operations have normal behavior.
417    Ready,
418
419    /// If the delay is positive, mutable operations decrement it. If the count is zero, mutable
420    /// operations fail and are saved.
421    Armed { delay: usize },
422
423    /// Mutable operations panic.
424    Saved { operation: OwnedBufferOperation },
425}
426
427impl Interruption {
428    /// Arms an interruption for a given delay.
429    ///
430    /// # Panics
431    ///
432    /// Panics if an interruption is already armed.
433    fn arm(&mut self, delay: usize) {
434        match self {
435            Interruption::Ready => *self = Interruption::Armed { delay },
436            _ => panic!(),
437        }
438    }
439
440    /// Disarms an interruption.
441    ///
442    /// Returns the interrupted operation if any, otherwise the remaining delay.
443    ///
444    /// # Panics
445    ///
446    /// Panics if an interruption was not armed.
447    fn get(&mut self) -> Result<OwnedBufferOperation, usize> {
448        let mut interruption = Interruption::Ready;
449        core::mem::swap(self, &mut interruption);
450        match interruption {
451            Interruption::Armed { delay } => Err(delay),
452            Interruption::Saved { operation } => Ok(operation),
453            _ => panic!(),
454        }
455    }
456
457    /// Interrupts an operation if the delay is over.
458    ///
459    /// Decrements the delay if positive. Otherwise, the operation is stored and the
460    /// [`INTERRUPTION`] error is returned to interrupt the operation.
461    ///
462    /// # Panics
463    ///
464    /// Panics if an operation has already been interrupted and the interruption has not been
465    /// disarmed.
466    fn tick(&mut self, operation: &SharedBufferOperation) -> Result<(), Error> {
467        match self {
468            Interruption::Ready => (),
469            Interruption::Armed { delay } if *delay == 0 => {
470                let operation = operation.to_owned();
471                *self = Interruption::Saved { operation };
472                return Err(INTERRUPT);
473            }
474            Interruption::Armed { delay } => *delay -= 1,
475            Interruption::Saved { .. } => panic!(),
476        }
477        Ok(())
478    }
479}
480
481#[cfg(test)]
482mod tests {
483    use super::*;
484
485    const NUM_PAGES: usize = 2;
486    const OPTIONS: BufferOptions = BufferOptions {
487        word_size: 4,
488        page_size: 16,
489        max_word_writes: 2,
490        max_page_erases: 3,
491        strict_mode: true,
492    };
493    // Those words are decreasing bit patterns. Bits are only changed from 1 to 0 and at least one
494    // bit is changed.
495    const BLANK_WORD: &[u8] = &[0xff, 0xff, 0xff, 0xff];
496    const FIRST_WORD: &[u8] = &[0xee, 0xdd, 0xbb, 0x77];
497    const SECOND_WORD: &[u8] = &[0xca, 0xc9, 0xa9, 0x65];
498    const THIRD_WORD: &[u8] = &[0x88, 0x88, 0x88, 0x44];
499
500    fn new_storage() -> Box<[u8]> {
501        vec![0xff; NUM_PAGES * OPTIONS.page_size].into_boxed_slice()
502    }
503
504    #[test]
505    fn words_are_decreasing() {
506        fn assert_is_decreasing(prev: &[u8], next: &[u8]) {
507            for (&prev, &next) in prev.iter().zip(next.iter()) {
508                assert_eq!(prev & next, next);
509                assert!(prev != next);
510            }
511        }
512        assert_is_decreasing(BLANK_WORD, FIRST_WORD);
513        assert_is_decreasing(FIRST_WORD, SECOND_WORD);
514        assert_is_decreasing(SECOND_WORD, THIRD_WORD);
515    }
516
517    #[test]
518    fn options_ok() {
519        let buffer = BufferStorage::new(new_storage(), OPTIONS);
520        assert_eq!(buffer.word_size(), OPTIONS.word_size);
521        assert_eq!(buffer.page_size(), OPTIONS.page_size);
522        assert_eq!(buffer.num_pages(), NUM_PAGES);
523        assert_eq!(buffer.max_word_writes(), OPTIONS.max_word_writes);
524        assert_eq!(buffer.max_page_erases(), OPTIONS.max_page_erases);
525    }
526
527    #[test]
528    fn read_write_ok() {
529        let mut buffer = BufferStorage::new(new_storage(), OPTIONS);
530        let index = StorageIndex { page: 0, byte: 0 };
531        let next_index = StorageIndex { page: 0, byte: 4 };
532        assert_eq!(buffer.read_slice(index, 4).unwrap(), BLANK_WORD);
533        buffer.write_slice(index, FIRST_WORD).unwrap();
534        assert_eq!(buffer.read_slice(index, 4).unwrap(), FIRST_WORD);
535        assert_eq!(buffer.read_slice(next_index, 4).unwrap(), BLANK_WORD);
536    }
537
538    #[test]
539    fn erase_ok() {
540        let mut buffer = BufferStorage::new(new_storage(), OPTIONS);
541        let index = StorageIndex { page: 0, byte: 0 };
542        let other_index = StorageIndex { page: 1, byte: 0 };
543        buffer.write_slice(index, FIRST_WORD).unwrap();
544        buffer.write_slice(other_index, FIRST_WORD).unwrap();
545        assert_eq!(buffer.read_slice(index, 4).unwrap(), FIRST_WORD);
546        assert_eq!(buffer.read_slice(other_index, 4).unwrap(), FIRST_WORD);
547        buffer.erase_page(0).unwrap();
548        assert_eq!(buffer.read_slice(index, 4).unwrap(), BLANK_WORD);
549        assert_eq!(buffer.read_slice(other_index, 4).unwrap(), FIRST_WORD);
550    }
551
552    #[test]
553    fn invalid_range() {
554        let mut buffer = BufferStorage::new(new_storage(), OPTIONS);
555        let index = StorageIndex { page: 0, byte: 12 };
556        let half_index = StorageIndex { page: 0, byte: 14 };
557        let over_index = StorageIndex { page: 0, byte: 16 };
558        let bad_page = StorageIndex { page: 2, byte: 0 };
559
560        // Reading a word in the storage is ok.
561        assert!(buffer.read_slice(index, 4).is_ok());
562        // Reading a half-word in the storage is ok.
563        assert!(buffer.read_slice(half_index, 2).is_ok());
564        // Reading even a single byte outside a page is not ok.
565        assert!(buffer.read_slice(over_index, 1).is_err());
566        // But reading an empty slice just after a page is ok.
567        assert!(buffer.read_slice(over_index, 0).is_ok());
568        // Reading even an empty slice outside the storage is not ok.
569        assert!(buffer.read_slice(bad_page, 0).is_err());
570
571        // Writing a word in the storage is ok.
572        assert!(buffer.write_slice(index, FIRST_WORD).is_ok());
573        // Writing an unaligned word is not ok.
574        assert!(buffer.write_slice(half_index, FIRST_WORD).is_err());
575        // Writing a word outside a page is not ok.
576        assert!(buffer.write_slice(over_index, FIRST_WORD).is_err());
577        // But writing an empty slice just after a page is ok.
578        assert!(buffer.write_slice(over_index, &[]).is_ok());
579        // Writing even an empty slice outside the storage is not ok.
580        assert!(buffer.write_slice(bad_page, &[]).is_err());
581
582        // Only pages in the storage can be erased.
583        assert!(buffer.erase_page(0).is_ok());
584        assert!(buffer.erase_page(2).is_err());
585    }
586
587    #[test]
588    fn write_twice_ok() {
589        let mut buffer = BufferStorage::new(new_storage(), OPTIONS);
590        let index = StorageIndex { page: 0, byte: 4 };
591        assert!(buffer.write_slice(index, FIRST_WORD).is_ok());
592        assert!(buffer.write_slice(index, SECOND_WORD).is_ok());
593    }
594
595    #[test]
596    fn write_twice_and_once_ok() {
597        let mut buffer = BufferStorage::new(new_storage(), OPTIONS);
598        let index = StorageIndex { page: 0, byte: 0 };
599        let next_index = StorageIndex { page: 0, byte: 4 };
600        assert!(buffer.write_slice(index, FIRST_WORD).is_ok());
601        assert!(buffer.write_slice(index, SECOND_WORD).is_ok());
602        assert!(buffer.write_slice(next_index, THIRD_WORD).is_ok());
603    }
604
605    #[test]
606    #[should_panic]
607    fn write_three_times_panics() {
608        let mut buffer = BufferStorage::new(new_storage(), OPTIONS);
609        let index = StorageIndex { page: 0, byte: 4 };
610        assert!(buffer.write_slice(index, FIRST_WORD).is_ok());
611        assert!(buffer.write_slice(index, SECOND_WORD).is_ok());
612        let _ = buffer.write_slice(index, THIRD_WORD);
613    }
614
615    #[test]
616    fn write_twice_then_once_ok() {
617        let mut buffer = BufferStorage::new(new_storage(), OPTIONS);
618        let index = StorageIndex { page: 0, byte: 0 };
619        assert!(buffer.write_slice(index, FIRST_WORD).is_ok());
620        assert!(buffer.write_slice(index, SECOND_WORD).is_ok());
621        assert!(buffer.erase_page(0).is_ok());
622        assert!(buffer.write_slice(index, FIRST_WORD).is_ok());
623    }
624
625    #[test]
626    fn erase_three_times_ok() {
627        let mut buffer = BufferStorage::new(new_storage(), OPTIONS);
628        assert!(buffer.erase_page(0).is_ok());
629        assert!(buffer.erase_page(0).is_ok());
630        assert!(buffer.erase_page(0).is_ok());
631    }
632
633    #[test]
634    fn erase_three_times_and_once_ok() {
635        let mut buffer = BufferStorage::new(new_storage(), OPTIONS);
636        assert!(buffer.erase_page(0).is_ok());
637        assert!(buffer.erase_page(0).is_ok());
638        assert!(buffer.erase_page(0).is_ok());
639        assert!(buffer.erase_page(1).is_ok());
640    }
641
642    #[test]
643    #[should_panic]
644    fn erase_four_times_panics() {
645        let mut buffer = BufferStorage::new(new_storage(), OPTIONS);
646        assert!(buffer.erase_page(0).is_ok());
647        assert!(buffer.erase_page(0).is_ok());
648        assert!(buffer.erase_page(0).is_ok());
649        let _ = buffer.erase_page(0).is_ok();
650    }
651
652    #[test]
653    #[should_panic]
654    fn switch_zero_to_one_panics() {
655        let mut buffer = BufferStorage::new(new_storage(), OPTIONS);
656        let index = StorageIndex { page: 0, byte: 0 };
657        assert!(buffer.write_slice(index, SECOND_WORD).is_ok());
658        let _ = buffer.write_slice(index, FIRST_WORD);
659    }
660
661    #[test]
662    fn interrupt_delay_ok() {
663        let mut buffer = BufferStorage::new(new_storage(), OPTIONS);
664
665        // Interrupt the second operation.
666        buffer.arm_interruption(1);
667
668        // The first operation should not fail.
669        buffer.write_slice(StorageIndex { page: 0, byte: 0 }, &[0x5c; 8]).unwrap();
670        // The delay should be decremented.
671        assert_eq!(buffer.disarm_interruption(), 0);
672        // The storage should have been modified.
673        assert_eq!(&buffer.storage[.. 8], &[0x5c; 8]);
674        assert!(buffer.storage[8 ..].iter().all(|&x| x == 0xff));
675    }
676
677    #[test]
678    fn interrupt_save_ok() {
679        let mut buffer = BufferStorage::new(new_storage(), OPTIONS);
680
681        // Interrupt the second operation.
682        buffer.arm_interruption(1);
683
684        // The second operation should fail.
685        buffer.write_slice(StorageIndex { page: 0, byte: 0 }, &[0x5c; 8]).unwrap();
686        assert!(buffer.write_slice(StorageIndex { page: 0, byte: 8 }, &[0x93; 8]).is_err());
687        // The operation should represent the change.
688        buffer.corrupt_operation(Box::new(|_, value| assert_eq!(value, &[0x93; 8])));
689        // The storage should not have been modified.
690        assert_eq!(&buffer.storage[.. 8], &[0x5c; 8]);
691        assert!(buffer.storage[8 ..].iter().all(|&x| x == 0xff));
692    }
693}