/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 | | } |