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