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