Coverage Report

Created: 2026-01-13 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/OpenSK/libraries/persistent_store/src/driver.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
//! Store wrapper for testing.
16
//!
17
//! [`StoreDriver`] wraps a [`Store`] and compares its behavior with its associated [`StoreModel`].
18
19
use crate::format::{Format, Position};
20
#[cfg(feature = "std")]
21
use crate::StoreUpdate;
22
use crate::{
23
    BufferCorruptFunction, BufferOptions, BufferStorage, Nat, Store, StoreError, StoreHandle,
24
    StoreModel, StoreOperation, StoreResult,
25
};
26
27
/// Tracks the store behavior against its model and its storage.
28
#[derive(Clone)]
29
pub enum StoreDriver {
30
    /// When the store is running.
31
    On(StoreDriverOn),
32
33
    /// When the store is off.
34
    Off(StoreDriverOff),
35
}
36
37
/// Keeps a power-on store and its model in sync.
38
#[derive(Clone)]
39
pub struct StoreDriverOn {
40
    /// The store being tracked.
41
    store: Store<BufferStorage>,
42
43
    /// The model associated to the store.
44
    model: StoreModel,
45
}
46
47
/// Keeps a power-off store and its potential models in sync.
48
#[derive(Clone)]
49
pub struct StoreDriverOff {
50
    /// The storage of the store being tracked.
51
    storage: BufferStorage,
52
53
    /// The last valid model before power off.
54
    model: StoreModel,
55
56
    /// In case of interrupted operation, the invariant after completion.
57
    complete: Option<Complete>,
58
}
59
60
/// The invariant a store must satisfy if an interrupted operation completes.
61
#[derive(Clone)]
62
struct Complete {
63
    /// The model after the operation completes.
64
    model: StoreModel,
65
66
    /// The entries that should be deleted after the operation completes.
67
    deleted: Vec<StoreHandle>,
68
}
69
70
/// Specifies an interruption.
71
pub struct StoreInterruption<'a> {
72
    /// After how many storage operations the interruption should happen.
73
    pub delay: usize,
74
75
    /// How the interrupted operation should be corrupted.
76
    pub corrupt: BufferCorruptFunction<'a>,
77
}
78
79
/// Possible ways a driver operation may fail.
80
#[derive(Debug)]
81
pub enum StoreInvariant {
82
    /// The store reached its lifetime.
83
    ///
84
    /// This is not simulated by the model. So the operation should be ignored.
85
    NoLifetime,
86
87
    /// The store returned an unexpected error.
88
    StoreError(StoreError),
89
90
    /// The store did not recover an interrupted operation.
91
    Interrupted {
92
        /// The reason why the store didn't rollback the operation.
93
        rollback: Box<StoreInvariant>,
94
95
        /// The reason why the store didn't complete the operation.
96
        complete: Box<StoreInvariant>,
97
    },
98
99
    /// The store returned a different result than the model.
100
    DifferentResult {
101
        /// The result of the store.
102
        store: StoreResult<()>,
103
104
        /// The result of the model.
105
        model: StoreResult<()>,
106
    },
107
108
    /// The store did not wipe an entry.
109
    NotWiped {
110
        /// The key of the entry that has not been wiped.
111
        key: usize,
112
113
        /// The value of the entry in the storage.
114
        value: Vec<u8>,
115
    },
116
117
    /// The store has an entry not present in the model.
118
    OnlyInStore {
119
        /// The key of the additional entry.
120
        key: usize,
121
    },
122
123
    /// The store has a different value than the model for an entry.
124
    DifferentValue {
125
        /// The key of the entry with a different value.
126
        key: usize,
127
128
        /// The value of the entry in the store.
129
        store: Box<[u8]>,
130
131
        /// The value of the entry in the model.
132
        model: Box<[u8]>,
133
    },
134
135
    /// The store is missing an entry from the model.
136
    OnlyInModel {
137
        /// The key of the missing entry.
138
        key: usize,
139
    },
140
141
    /// The store reports a different capacity than the model.
142
    DifferentCapacity {
143
        /// The capacity according to the store.
144
        store: usize,
145
146
        /// The capacity according to the model.
147
        model: usize,
148
    },
149
150
    /// The store failed to track the number of erase cycles correctly.
151
    DifferentErase {
152
        /// The first page in physical storage order with a wrong value.
153
        page: usize,
154
155
        /// How many times the page has been erased according to the store.
156
        store: usize,
157
158
        /// How many times the page has been erased according to the model.
159
        model: usize,
160
    },
161
162
    /// The store failed to track the number of word writes correctly.
163
    DifferentWrite {
164
        /// The first page in physical storage order with a wrong value.
165
        page: usize,
166
167
        /// The first word in the page with a wrong value.
168
        word: usize,
169
170
        /// How many times the word has been written according to the store.
171
        ///
172
        /// This value is exact only for the metadata of the page. For the content of the page, it
173
        /// is set to:
174
        /// - 0 if the word is after the tail. Such word should not have been written.
175
        /// - 1 if the word is before the tail. Such word may or may not have been written.
176
        store: usize,
177
178
        /// How many times the word has been written according to the model.
179
        ///
180
        /// This value is exact only for the metadata of the page. For the content of the page, it
181
        /// is set to:
182
        /// - 0 if the word was not written.
183
        /// - 1 if the word was written.
184
        model: usize,
185
    },
186
}
187
188
impl From<StoreError> for StoreInvariant {
189
4
    fn from(error: StoreError) -> StoreInvariant {
190
4
        StoreInvariant::StoreError(error)
191
4
    }
192
}
193
194
impl StoreDriver {
195
    /// Provides read-only access to the storage.
196
0
    pub fn storage(&self) -> &BufferStorage {
197
0
        match self {
198
0
            StoreDriver::On(x) => x.store().storage(),
199
0
            StoreDriver::Off(x) => x.storage(),
200
        }
201
0
    }
202
203
    /// Provides read-only access to the model.
204
0
    pub fn model(&self) -> &StoreModel {
205
0
        match self {
206
0
            StoreDriver::On(x) => x.model(),
207
0
            StoreDriver::Off(x) => x.model(),
208
        }
209
0
    }
210
211
    /// Extracts the power-on version of the driver.
212
2.50k
    pub fn on(self) -> Option<StoreDriverOn> {
213
2.50k
        match self {
214
2.50k
            StoreDriver::On(x) => Some(x),
215
0
            StoreDriver::Off(_) => None,
216
        }
217
2.50k
    }
218
219
    /// Powers on the store if not already on.
220
3.39k
    pub fn power_on(self) -> Result<StoreDriverOn, StoreInvariant> {
221
3.39k
        match self {
222
883
            StoreDriver::On(x) => Ok(x),
223
2.50k
            StoreDriver::Off(x) => x.power_on(),
224
        }
225
3.39k
    }
226
227
    /// Extracts the power-off version of the driver.
228
0
    pub fn off(self) -> Option<StoreDriverOff> {
229
0
        match self {
230
0
            StoreDriver::On(_) => None,
231
0
            StoreDriver::Off(x) => Some(x),
232
        }
233
0
    }
234
}
235
236
impl StoreDriverOff {
237
    /// Starts a simulation with a clean storage given its configuration.
238
1.90k
    pub fn new(options: BufferOptions, num_pages: usize) -> StoreDriverOff {
239
1.90k
        let storage = vec![0xff; num_pages * options.page_size].into_boxed_slice();
240
1.90k
        let storage = BufferStorage::new(storage, options);
241
1.90k
        StoreDriverOff::new_dirty(storage)
242
1.90k
    }
243
244
    /// Starts a simulation from an existing storage.
245
4.38k
    pub fn new_dirty(storage: BufferStorage) -> StoreDriverOff {
246
4.38k
        let format = Format::new(&storage).unwrap();
247
4.38k
        StoreDriverOff {
248
4.38k
            storage,
249
4.38k
            model: StoreModel::new(format),
250
4.38k
            complete: None,
251
4.38k
        }
252
4.38k
    }
253
254
    /// Provides read-only access to the storage.
255
0
    pub fn storage(&self) -> &BufferStorage {
256
0
        &self.storage
257
0
    }
258
259
    /// Provides mutable access to the storage.
260
0
    pub fn storage_mut(&mut self) -> &mut BufferStorage {
261
0
        &mut self.storage
262
0
    }
263
264
    /// Provides read-only access to the model.
265
0
    pub fn model(&self) -> &StoreModel {
266
0
        &self.model
267
0
    }
268
269
    /// Powers on the store without interruption.
270
    ///
271
    /// # Panics
272
    ///
273
    /// Panics if the store cannot be powered on.
274
2.50k
    pub fn power_on(self) -> Result<StoreDriverOn, StoreInvariant> {
275
2.50k
        Ok(self
276
2.50k
            .partial_power_on(StoreInterruption::none())
277
2.50k
            .map_err(|x| x.1)?
278
2.50k
            .on()
279
2.50k
            .unwrap())
280
2.50k
    }
281
282
    /// Powers on the store with a possible interruption.
283
171k
    pub fn partial_power_on(
284
171k
        mut self,
285
171k
        interruption: StoreInterruption,
286
171k
    ) -> Result<StoreDriver, (BufferStorage, StoreInvariant)> {
287
171k
        self.storage.arm_interruption(interruption.delay);
288
171k
        Ok(match Store::new(self.storage) {
289
45.4k
            Ok(mut store) => {
290
45.4k
                store.storage_mut().disarm_interruption();
291
45.4k
                let mut error = None;
292
45.4k
                if let Some(complete) = self.complete {
293
41.5k
                    match StoreDriverOn::new(store, complete.model, &complete.deleted) {
294
30.8k
                        Ok(driver) => return Ok(StoreDriver::On(driver)),
295
10.7k
                        Err((e, x)) => {
296
10.7k
                            error = Some(e);
297
10.7k
                            store = x;
298
10.7k
                        }
299
                    }
300
3.94k
                };
301
14.6k
                StoreDriver::On(StoreDriverOn::new(store, self.model, &[]).map_err(
302
30
                    |(rollback, store)| {
303
30
                        let storage = store.extract_storage();
304
30
                        match error {
305
30
                            None => (storage, rollback),
306
0
                            Some(complete) => {
307
0
                                let rollback = Box::new(rollback);
308
0
                                let complete = Box::new(complete);
309
0
                                (storage, StoreInvariant::Interrupted { rollback, complete })
310
                            }
311
                        }
312
30
                    },
313
30
                )?)
314
            }
315
125k
            Err((StoreError::StorageError, mut storage)) => {
316
125k
                storage.corrupt_operation(interruption.corrupt);
317
125k
                StoreDriver::Off(StoreDriverOff { storage, ..self })
318
            }
319
155
            Err((error, mut storage)) => {
320
155
                storage.reset_interruption();
321
155
                return Err((storage, StoreInvariant::StoreError(error)));
322
            }
323
        })
324
171k
    }
325
326
    /// Returns the number of storage operations to power on.
327
    ///
328
    /// Returns `None` if the store cannot power on successfully.
329
168k
    pub fn count_operations(&self) -> Option<usize> {
330
168k
        let initial_delay = usize::MAX;
331
168k
        let mut storage = self.storage.clone();
332
168k
        storage.arm_interruption(initial_delay);
333
168k
        let mut store = Store::new(storage).ok()?;
334
168k
        Some(initial_delay - store.storage_mut().disarm_interruption())
335
168k
    }
336
}
337
338
impl StoreDriverOn {
339
    /// Provides read-only access to the store.
340
0
    pub fn store(&self) -> &Store<BufferStorage> {
341
0
        &self.store
342
0
    }
343
344
    /// Extracts the store.
345
3.39k
    pub fn extract_store(self) -> Store<BufferStorage> {
346
3.39k
        self.store
347
3.39k
    }
348
349
    /// Provides mutable access to the store.
350
0
    pub fn store_mut(&mut self) -> &mut Store<BufferStorage> {
351
0
        &mut self.store
352
0
    }
353
354
    /// Provides read-only access to the model.
355
199k
    pub fn model(&self) -> &StoreModel {
356
199k
        &self.model
357
199k
    }
358
359
    /// Applies a store operation to the store and model without interruption.
360
0
    pub fn apply(&mut self, operation: StoreOperation) -> Result<(), StoreInvariant> {
361
0
        let (deleted, store_result) = self.store.apply(&operation);
362
0
        let model_result = self.model.apply(operation);
363
0
        if store_result != model_result {
364
0
            return Err(StoreInvariant::DifferentResult {
365
0
                store: store_result,
366
0
                model: model_result,
367
0
            });
368
0
        }
369
0
        self.check_deleted(&deleted)?;
370
0
        Ok(())
371
0
    }
372
373
    /// Applies a store operation to the store and model with a possible interruption.
374
199k
    pub fn partial_apply(
375
199k
        mut self,
376
199k
        operation: StoreOperation,
377
199k
        interruption: StoreInterruption,
378
199k
    ) -> Result<(Option<StoreError>, StoreDriver), (Store<BufferStorage>, StoreInvariant)> {
379
199k
        self.store
380
199k
            .storage_mut()
381
199k
            .arm_interruption(interruption.delay);
382
199k
        let (deleted, store_result) = self.store.apply(&operation);
383
77.1k
        Ok(match store_result {
384
38
            Err(StoreError::NoLifetime) => return Err((self.store, StoreInvariant::NoLifetime)),
385
            Ok(()) | Err(StoreError::NoCapacity) | Err(StoreError::InvalidArgument) => {
386
158k
                self.store.storage_mut().disarm_interruption();
387
158k
                let model_result = self.model.apply(operation);
388
158k
                if store_result != model_result {
389
0
                    return Err((
390
0
                        self.store,
391
0
                        StoreInvariant::DifferentResult {
392
0
                            store: store_result,
393
0
                            model: model_result,
394
0
                        },
395
0
                    ));
396
158k
                }
397
158k
                if store_result.is_ok() {
398
122k
                    if let Err(invariant) = self.check_deleted(&deleted) {
399
0
                        return Err((self.store, invariant));
400
122k
                    }
401
35.5k
                }
402
158k
                (store_result.err(), StoreDriver::On(self))
403
            }
404
            Err(StoreError::StorageError) => {
405
41.5k
                let mut driver = StoreDriverOff {
406
41.5k
                    storage: self.store.extract_storage(),
407
41.5k
                    model: self.model,
408
41.5k
                    complete: None,
409
41.5k
                };
410
41.5k
                driver.storage.corrupt_operation(interruption.corrupt);
411
41.5k
                let mut model = driver.model.clone();
412
41.5k
                if model.apply(operation).is_ok() {
413
41.5k
                    driver.complete = Some(Complete { model, deleted });
414
41.5k
                }
415
41.5k
                (None, StoreDriver::Off(driver))
416
            }
417
1
            Err(error) => return Err((self.store, StoreInvariant::StoreError(error))),
418
        })
419
199k
    }
420
421
    /// Returns the number of storage operations to apply a store operation.
422
    ///
423
    /// Returns `None` if the store cannot apply the operation successfully.
424
199k
    pub fn count_operations(&self, operation: &StoreOperation) -> Option<usize> {
425
199k
        let initial_delay = usize::MAX;
426
199k
        let mut store = self.store.clone();
427
199k
        store.storage_mut().arm_interruption(initial_delay);
428
199k
        store.apply(operation).1.ok()?;
429
164k
        Some(initial_delay - store.storage_mut().disarm_interruption())
430
199k
    }
431
432
    /// Powers off the store.
433
0
    pub fn power_off(self) -> StoreDriverOff {
434
0
        StoreDriverOff {
435
0
            storage: self.store.extract_storage(),
436
0
            model: self.model,
437
0
            complete: None,
438
0
        }
439
0
    }
440
441
    /// Applies an insertion to the store and model without interruption.
442
    #[cfg(feature = "std")]
443
0
    pub fn insert(&mut self, key: usize, value: &[u8]) -> Result<(), StoreInvariant> {
444
0
        let value = value.to_vec();
445
0
        let updates = vec![StoreUpdate::Insert { key, value }];
446
0
        self.apply(StoreOperation::Transaction { updates })
447
0
    }
448
449
    /// Applies a deletion to the store and model without interruption.
450
    #[cfg(feature = "std")]
451
0
    pub fn remove(&mut self, key: usize) -> Result<(), StoreInvariant> {
452
0
        let updates = vec![StoreUpdate::Remove { key }];
453
0
        self.apply(StoreOperation::Transaction { updates })
454
0
    }
455
456
    /// Applies a clear operation to the store and model without interruption.
457
    #[cfg(feature = "std")]
458
0
    pub fn clear(&mut self, min_key: usize) -> Result<(), StoreInvariant> {
459
0
        self.apply(StoreOperation::Clear { min_key })
460
0
    }
461
462
    /// Checks that the store and model are in sync.
463
179k
    pub fn check(&self) -> Result<(), StoreInvariant> {
464
179k
        self.recover_check(&[])
465
179k
    }
466
467
    /// Starts a simulation from a power-off store.
468
    ///
469
    /// Checks that the store and model are in sync and that the given deleted entries are wiped.
470
56.1k
    fn new(
471
56.1k
        store: Store<BufferStorage>,
472
56.1k
        model: StoreModel,
473
56.1k
        deleted: &[StoreHandle],
474
56.1k
    ) -> Result<StoreDriverOn, (StoreInvariant, Store<BufferStorage>)> {
475
56.1k
        let driver = StoreDriverOn { store, model };
476
56.1k
        match driver.recover_check(deleted) {
477
45.4k
            Ok(()) => Ok(driver),
478
10.7k
            Err(error) => Err((error, driver.store)),
479
        }
480
56.1k
    }
481
482
    /// Checks that the store and model are in sync and that the given entries are wiped.
483
235k
    fn recover_check(&self, deleted: &[StoreHandle]) -> Result<(), StoreInvariant> {
484
235k
        self.check_deleted(deleted)?;
485
232k
        self.check_model()?;
486
224k
        self.check_storage()?;
487
224k
        Ok(())
488
235k
    }
489
490
    /// Checks that the given entries are wiped from the storage.
491
357k
    fn check_deleted(&self, deleted: &[StoreHandle]) -> Result<(), StoreInvariant> {
492
409k
        for handle in deleted {
493
54.4k
            let value = self.store.inspect_value(handle);
494
19.2M
            if !value.iter().all(|&x| x == 0x00) {
495
2.82k
                return Err(StoreInvariant::NotWiped {
496
2.82k
                    key: handle.get_key(),
497
2.82k
                    value,
498
2.82k
                });
499
51.6k
            }
500
        }
501
355k
        Ok(())
502
357k
    }
503
504
    /// Checks that the store and model are in sync.
505
232k
    fn check_model(&self) -> Result<(), StoreInvariant> {
506
232k
        let mut model_content = self.model.content().clone();
507
485k
        for handle in self.store.iter()? {
508
485k
            let handle = handle?;
509
485k
            let model_value = match model_content.remove(&handle.get_key()) {
510
                None => {
511
987
                    return Err(StoreInvariant::OnlyInStore {
512
987
                        key: handle.get_key(),
513
987
                    })
514
                }
515
484k
                Some(x) => x,
516
            };
517
484k
            let store_value = handle.get_value(&self.store)?.into_boxed_slice();
518
484k
            if store_value != model_value {
519
819
                return Err(StoreInvariant::DifferentValue {
520
819
                    key: handle.get_key(),
521
819
                    store: store_value,
522
819
                    model: model_value,
523
819
                });
524
484k
            }
525
        }
526
230k
        if let Some(&key) = model_content.keys().next() {
527
6.07k
            return Err(StoreInvariant::OnlyInModel { key });
528
224k
        }
529
224k
        let store_capacity = self.store.capacity()?.remaining();
530
224k
        let model_capacity = self.model.capacity().remaining();
531
224k
        if store_capacity != model_capacity {
532
0
            return Err(StoreInvariant::DifferentCapacity {
533
0
                store: store_capacity,
534
0
                model: model_capacity,
535
0
            });
536
224k
        }
537
224k
        Ok(())
538
232k
    }
539
540
    /// Checks that the store is tracking lifetime correctly.
541
224k
    fn check_storage(&self) -> Result<(), StoreInvariant> {
542
224k
        let format = self.model.format();
543
224k
        let storage = self.store.storage();
544
224k
        let num_words = format.page_size() / format.word_size();
545
224k
        let head = self.store.head()?;
546
224k
        let tail = self.store.tail()?;
547
5.39M
        for page in 0..format.num_pages() {
548
            // Check the erase cycle of the page.
549
5.39M
            let store_erase = head.cycle(format) + (page < head.page(format)) as Nat;
550
5.39M
            let model_erase = storage.get_page_erases(page as usize);
551
5.39M
            if store_erase as usize != model_erase {
552
24
                return Err(StoreInvariant::DifferentErase {
553
24
                    page: page as usize,
554
24
                    store: store_erase as usize,
555
24
                    model: model_erase,
556
24
                });
557
5.39M
            }
558
5.39M
            let page_pos = Position::new(format, store_erase, page, 0);
559
560
            // Check the init word of the page.
561
5.39M
            let mut store_write = (page_pos < tail) as usize;
562
5.39M
            if page == 0 && tail == Position::new(format, 0, 0, 0) {
563
18.7k
                // When the store is initialized and nothing written yet, the first page is still
564
18.7k
                // initialized.
565
18.7k
                store_write = 1;
566
5.37M
            }
567
5.39M
            let model_write = storage.get_word_writes((page * num_words) as usize);
568
5.39M
            if store_write != model_write {
569
1
                return Err(StoreInvariant::DifferentWrite {
570
1
                    page: page as usize,
571
1
                    word: 0,
572
1
                    store: store_write,
573
1
                    model: model_write,
574
1
                });
575
5.39M
            }
576
577
            // Check the compact info of the page.
578
5.39M
            let model_write = storage.get_word_writes((page * num_words + 1) as usize);
579
5.39M
            let store_write = 0;
580
5.39M
            if store_write != model_write {
581
0
                return Err(StoreInvariant::DifferentWrite {
582
0
                    page: page as usize,
583
0
                    word: 1,
584
0
                    store: store_write,
585
0
                    model: model_write,
586
0
                });
587
5.39M
            }
588
589
            // Check the content of the page. We only check cases where the model says a word was
590
            // written while the store doesn't think it should be the case. This is because the
591
            // model doesn't count writes to the same value. Also we only check whether a word is
592
            // written and not how many times. This is because this is hard to rebuild in the store.
593
1.48G
            for word in 2..num_words {
594
1.48G
                let store_write = (page_pos + (word - 2) < tail) as usize;
595
1.48G
                let model_write =
596
1.48G
                    (storage.get_word_writes((page * num_words + word) as usize) > 0) as usize;
597
1.48G
                if store_write < model_write {
598
0
                    return Err(StoreInvariant::DifferentWrite {
599
0
                        page: page as usize,
600
0
                        word: word as usize,
601
0
                        store: store_write,
602
0
                        model: model_write,
603
0
                    });
604
1.48G
                }
605
            }
606
        }
607
224k
        Ok(())
608
224k
    }
609
}
610
611
impl<'a> StoreInterruption<'a> {
612
    /// Builds an interruption that never triggers.
613
58.4k
    pub fn none() -> StoreInterruption<'a> {
614
        StoreInterruption {
615
58.4k
            delay: usize::max_value(),
616
58.4k
            corrupt: Box::new(|_, _| {}),
617
        }
618
58.4k
    }
619
620
    /// Builds an interruption without corruption.
621
0
    pub fn pure(delay: usize) -> StoreInterruption<'a> {
622
        StoreInterruption {
623
0
            delay,
624
0
            corrupt: Box::new(|_, _| {}),
625
        }
626
0
    }
627
}