Coverage Report

Created: 2026-02-14 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/wasefire-store-0.2.4/src/file.rs
Line
Count
Source
1
// Copyright 2022 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
//! File-backed persistent flash storage for virtual authenticator.
16
//!
17
//! [`FileStorage`] implements the flash [`Storage`] interface but doesn't interface with an
18
//! actual flash storage. Instead it uses a host-based file to persist the storage state.
19
20
use alloc::borrow::Cow;
21
use core::cell::RefCell;
22
use std::fs::{File, OpenOptions};
23
use std::io::{Read, Seek, SeekFrom, Write};
24
use std::path::Path;
25
26
use crate::{Storage, StorageIndex, StorageResult};
27
28
/// Simulates a flash storage using a host-based file.
29
///
30
/// This is usable for emulating authenticator hardware on VM hypervisor's host OS.
31
pub struct FileStorage {
32
    // Options of the storage.
33
    options: FileOptions,
34
35
    /// File for persisting contents of the storage.
36
    ///
37
    /// Reading data from File requires mutable reference, as seeking and reading data
38
    /// changes file's current position.
39
    ///
40
    /// All operations on backing file internally always first seek to needed position,
41
    /// so it's safe to borrow mutable reference to backing file for the time of operation.
42
    file: RefCell<File>,
43
}
44
45
/// Options for file-backed storage.
46
pub struct FileOptions {
47
    /// Size of a word in bytes.
48
    pub word_size: usize,
49
50
    /// Size of a page in bytes.
51
    pub page_size: usize,
52
53
    /// Number of pages in storage.
54
    pub num_pages: usize,
55
}
56
57
impl FileStorage {
58
0
    pub fn new(path: &Path, options: FileOptions) -> StorageResult<FileStorage> {
59
0
        let mut file_ref = RefCell::new(
60
0
            OpenOptions::new().read(true).write(true).create(true).truncate(false).open(path)?,
61
        );
62
0
        let file = file_ref.get_mut();
63
0
        let file_len = file.metadata()?.len();
64
0
        let store_len: u64 = (options.page_size * options.num_pages) as u64;
65
66
0
        if file_len == 0 {
67
0
            file.seek(SeekFrom::Start(0))?;
68
0
            let buf = vec![0xff; options.page_size];
69
0
            for _ in 0 .. options.num_pages {
70
0
                file.write_all(&buf)?;
71
            }
72
0
        } else if file_len != store_len {
73
            // FileStorage buffer should be of fixed size, opening previously saved file
74
            // from storage of different size is not supported
75
0
            panic!("Invalid file size {file_len}, should be {store_len}");
76
0
        }
77
0
        Ok(FileStorage { options, file: file_ref })
78
0
    }
79
}
80
81
impl Storage for FileStorage {
82
0
    fn word_size(&self) -> usize {
83
0
        self.options.word_size
84
0
    }
85
86
0
    fn page_size(&self) -> usize {
87
0
        self.options.page_size
88
0
    }
89
90
0
    fn num_pages(&self) -> usize {
91
0
        self.options.num_pages
92
0
    }
93
94
0
    fn max_word_writes(&self) -> usize {
95
        // We can write an unlimited amount of times in a file, but the store arithmetic
96
        // uses `Nat` so the value should fit in a `Nat`.
97
0
        u32::MAX as usize
98
0
    }
99
100
0
    fn max_page_erases(&self) -> usize {
101
        // We can "erase" an unlimited amount of times in a file, but the store format
102
        // encodes the number of erase cycles on 16 bits.
103
0
        u16::MAX as usize
104
0
    }
105
106
0
    fn read_slice(&self, index: StorageIndex, length: usize) -> StorageResult<Cow<[u8]>> {
107
0
        let mut file = self.file.borrow_mut();
108
0
        file.seek(SeekFrom::Start(index.range(length, self)?.start as u64))?;
109
0
        let mut buf = vec![0u8; length];
110
0
        file.read_exact(&mut buf)?;
111
0
        Ok(Cow::Owned(buf))
112
0
    }
113
114
0
    fn write_slice(&mut self, index: StorageIndex, value: &[u8]) -> StorageResult<()> {
115
0
        let mut file = self.file.borrow_mut();
116
0
        file.seek(SeekFrom::Start(index.range(value.len(), self)?.start as u64))?;
117
0
        file.write_all(value)?;
118
0
        Ok(())
119
0
    }
120
121
0
    fn erase_page(&mut self, page: usize) -> StorageResult<()> {
122
0
        let mut file = self.file.borrow_mut();
123
0
        let index = StorageIndex { page, byte: 0 };
124
0
        file.seek(SeekFrom::Start(index.range(self.page_size(), self)?.start as u64))?;
125
0
        file.write_all(&vec![0xff; self.page_size()][..])?;
126
0
        Ok(())
127
0
    }
128
}
129
130
#[cfg(test)]
131
mod tests {
132
    use std::path::PathBuf;
133
134
    use tempfile::TempDir;
135
136
    use super::*;
137
138
    const BLANK_WORD: &[u8] = &[0xff, 0xff, 0xff, 0xff];
139
    const DATA_WORD: &[u8] = &[0xee, 0xdd, 0xbb, 0x77];
140
141
    const FILE_NAME: &str = "opensk_storage.bin";
142
143
    const OPTIONS: FileOptions = FileOptions { word_size: 4, page_size: 0x1000, num_pages: 20 };
144
145
    fn make_tmp_dir() -> PathBuf {
146
        let tmp_dir = TempDir::new().unwrap();
147
        tmp_dir.into_path()
148
    }
149
150
    fn remove_tmp_dir(tmp_dir: &Path) {
151
        std::fs::remove_dir_all(tmp_dir).unwrap();
152
    }
153
154
    fn temp_storage(tmp_dir: &Path) -> FileStorage {
155
        FileStorage::new(&tmp_dir.join(FILE_NAME), OPTIONS).unwrap()
156
    }
157
158
    #[test]
159
    fn read_write_persist_ok() {
160
        let index = StorageIndex { page: 0, byte: 0 };
161
        let next_index = StorageIndex { page: 0, byte: 4 };
162
163
        let tmp_dir = make_tmp_dir();
164
        {
165
            let mut file_storage = temp_storage(&tmp_dir);
166
            assert_eq!(file_storage.read_slice(index, 4).unwrap(), BLANK_WORD);
167
            file_storage.write_slice(index, DATA_WORD).unwrap();
168
            assert_eq!(file_storage.read_slice(index, 4).unwrap(), DATA_WORD);
169
            assert_eq!(file_storage.read_slice(next_index, 4).unwrap(), BLANK_WORD);
170
        }
171
        // Reload and check the data from previously persisted storage
172
        {
173
            let file_storage = temp_storage(&tmp_dir);
174
            assert_eq!(file_storage.read_slice(index, 4).unwrap(), DATA_WORD);
175
            assert_eq!(file_storage.read_slice(next_index, 4).unwrap(), BLANK_WORD);
176
        }
177
        remove_tmp_dir(&tmp_dir);
178
    }
179
}