Skip to content

Commit eac6984

Browse files
committed
hal: add rest of the functions to the Sd trait
And use the hal layer to access sdcard. This allows local mocking in unit tests and other BitBox products to plug their own impl.
1 parent 95a3b48 commit eac6984

File tree

8 files changed

+185
-77
lines changed

8 files changed

+185
-77
lines changed

src/rust/bitbox02-rust/src/backup.rs

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ use prost::Message;
1616

1717
use crate::pb_backup;
1818

19+
use crate::hal::Sd;
20+
1921
use alloc::boxed::Box;
2022
use alloc::string::String;
2123
use alloc::vec::Vec;
@@ -156,15 +158,18 @@ fn bitwise_recovery(buf1: &[u8], buf2: &[u8], buf3: &[u8]) -> Result<Zeroizing<V
156158
Ok(recovered_contents)
157159
}
158160

159-
pub fn load(dir: &str) -> Result<(Zeroizing<BackupData>, pb_backup::BackupMetaData), ()> {
160-
let files = bitbox02::sd::list_subdir(Some(dir))?;
161+
pub fn load(
162+
hal: &mut impl crate::hal::Hal,
163+
dir: &str,
164+
) -> Result<(Zeroizing<BackupData>, pb_backup::BackupMetaData), ()> {
165+
let files = hal.sd().list_subdir(Some(dir))?;
161166
if files.len() != 3 {
162167
return Err(());
163168
}
164169
let file_contents: [Zeroizing<Vec<u8>>; 3] = [
165-
bitbox02::sd::load_bin(&files[0], dir)?,
166-
bitbox02::sd::load_bin(&files[1], dir)?,
167-
bitbox02::sd::load_bin(&files[2], dir)?,
170+
hal.sd().load_bin(&files[0], dir)?,
171+
hal.sd().load_bin(&files[1], dir)?,
172+
hal.sd().load_bin(&files[2], dir)?,
168173
];
169174
for contents in file_contents.iter() {
170175
if let o @ Ok(_) = load_from_buffer(contents) {
@@ -182,6 +187,7 @@ pub fn load(dir: &str) -> Result<(Zeroizing<BackupData>, pb_backup::BackupMetaDa
182187
}
183188

184189
pub fn create(
190+
hal: &mut impl crate::hal::Hal,
185191
seed: &[u8],
186192
name: &str,
187193
backup_create_timestamp: u32,
@@ -226,7 +232,7 @@ pub fn create(
226232
};
227233
let backup_encoded = backup.encode_to_vec();
228234
let dir = id(seed);
229-
let files = bitbox02::sd::list_subdir(Some(&dir)).or(Err(Error::SdList))?;
235+
let files = hal.sd().list_subdir(Some(&dir)).or(Err(Error::SdList))?;
230236

231237
let filename_datetime = {
232238
let tm = bitbox02::get_datetime(backup_create_timestamp).map_err(|_| Error::Generic)?;
@@ -247,8 +253,12 @@ pub fn create(
247253
if files.contains(&filename) {
248254
return Err(Error::Generic);
249255
}
250-
bitbox02::sd::write_bin(&filename, &dir, &backup_encoded).or(Err(Error::SdWrite))?;
251-
if bitbox02::sd::load_bin(&filename, &dir)
256+
hal.sd()
257+
.write_bin(&filename, &dir, &backup_encoded)
258+
.or(Err(Error::SdWrite))?;
259+
if hal
260+
.sd()
261+
.load_bin(&filename, &dir)
252262
.or(Err(Error::SdRead))?
253263
.as_slice()
254264
!= backup_encoded.as_slice()
@@ -258,7 +268,7 @@ pub fn create(
258268
}
259269
let mut stale = false;
260270
for file in files {
261-
if bitbox02::sd::erase_file_in_subdir(&file, &dir).is_err() {
271+
if hal.sd().erase_file_in_subdir(&file, &dir).is_err() {
262272
stale = true
263273
}
264274
}
@@ -272,10 +282,9 @@ pub fn create(
272282
mod tests {
273283
use super::*;
274284

285+
use crate::hal::testing::TestingHal;
275286
use core::convert::TryInto;
276287

277-
use bitbox02::testing::mock_sd;
278-
279288
#[test]
280289
fn test_id() {
281290
// Seeds of different lengths (16, 24, 32 bytes)
@@ -295,14 +304,14 @@ mod tests {
295304
}
296305

297306
fn _test_create_load(seed: &[u8]) {
298-
mock_sd();
307+
let mut mock_hal = TestingHal::new();
299308
let timestamp = 1601281809;
300309
let birthdate = timestamp - 32400;
301-
assert!(create(seed, "test name", timestamp, birthdate).is_ok());
310+
assert!(create(&mut mock_hal, seed, "test name", timestamp, birthdate).is_ok());
302311
let dir = id(seed);
303-
assert_eq!(bitbox02::sd::list_subdir(None), Ok(vec![dir.clone()]));
312+
assert_eq!(mock_hal.sd.list_subdir(None), Ok(vec![dir.clone()]));
304313
assert_eq!(
305-
bitbox02::sd::list_subdir(Some(&dir)),
314+
mock_hal.sd.list_subdir(Some(&dir)),
306315
Ok(vec![
307316
"backup_Mon_2020-09-28T08-30-09Z_0.bin".into(),
308317
"backup_Mon_2020-09-28T08-30-09Z_1.bin".into(),
@@ -311,20 +320,22 @@ mod tests {
311320
);
312321

313322
// Recreating using same timestamp is not allowed and doesn't change the backups.
314-
assert!(create(seed, "new name", timestamp, birthdate).is_err());
323+
assert!(create(&mut mock_hal, seed, "new name", timestamp, birthdate).is_err());
315324
assert_eq!(
316-
bitbox02::sd::list_subdir(Some(&dir)),
325+
mock_hal.sd.list_subdir(Some(&dir)),
317326
Ok(vec![
318327
"backup_Mon_2020-09-28T08-30-09Z_0.bin".into(),
319328
"backup_Mon_2020-09-28T08-30-09Z_1.bin".into(),
320329
"backup_Mon_2020-09-28T08-30-09Z_2.bin".into()
321330
])
322331
);
323332

324-
let contents: [zeroize::Zeroizing<Vec<u8>>; 3] = bitbox02::sd::list_subdir(Some(&dir))
333+
let contents: [zeroize::Zeroizing<Vec<u8>>; 3] = mock_hal
334+
.sd
335+
.list_subdir(Some(&dir))
325336
.unwrap()
326337
.iter()
327-
.map(|file| bitbox02::sd::load_bin(file, &dir).unwrap())
338+
.map(|file| mock_hal.sd.load_bin(file, &dir).unwrap())
328339
.collect::<Vec<_>>()
329340
.try_into()
330341
.unwrap();
@@ -334,17 +345,17 @@ mod tests {
334345
);
335346

336347
// Recreating the backup removes the previous files.
337-
assert!(create(seed, "new name", timestamp + 1, birthdate).is_ok());
348+
assert!(create(&mut mock_hal, seed, "new name", timestamp + 1, birthdate).is_ok());
338349
assert_eq!(
339-
bitbox02::sd::list_subdir(Some(&dir)),
350+
mock_hal.sd.list_subdir(Some(&dir)),
340351
Ok(vec![
341352
"backup_Mon_2020-09-28T08-30-10Z_0.bin".into(),
342353
"backup_Mon_2020-09-28T08-30-10Z_1.bin".into(),
343354
"backup_Mon_2020-09-28T08-30-10Z_2.bin".into()
344355
])
345356
);
346357

347-
let (backup_data, metadata) = load(&dir).unwrap();
358+
let (backup_data, metadata) = load(&mut mock_hal, &dir).unwrap();
348359
assert_eq!(backup_data.get_seed(), seed);
349360
assert_eq!(backup_data.0.birthdate, birthdate);
350361
assert_eq!(metadata.name.as_str(), "new name");

src/rust/bitbox02-rust/src/hal.rs

Lines changed: 111 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,15 @@
1515
use crate::workflow::RealWorkflows;
1616
pub use crate::workflow::Workflows as Ui;
1717

18+
use alloc::string::String;
19+
use alloc::vec::Vec;
20+
1821
pub trait Sd {
1922
fn sdcard_inserted(&mut self) -> bool;
23+
fn list_subdir(&mut self, subdir: Option<&str>) -> Result<Vec<String>, ()>;
24+
fn erase_file_in_subdir(&mut self, filename: &str, dir: &str) -> Result<(), ()>;
25+
fn load_bin(&mut self, filename: &str, dir: &str) -> Result<zeroize::Zeroizing<Vec<u8>>, ()>;
26+
fn write_bin(&mut self, filename: &str, dir: &str, data: &[u8]) -> Result<(), ()>;
2027
}
2128

2229
/// Hardware abstraction layer for BitBox devices.
@@ -28,9 +35,30 @@ pub trait Hal {
2835
pub struct BitBox02Sd;
2936

3037
impl Sd for BitBox02Sd {
38+
#[inline(always)]
3139
fn sdcard_inserted(&mut self) -> bool {
3240
bitbox02::sd::sdcard_inserted()
3341
}
42+
43+
#[inline(always)]
44+
fn list_subdir(&mut self, subdir: Option<&str>) -> Result<Vec<String>, ()> {
45+
bitbox02::sd::list_subdir(subdir)
46+
}
47+
48+
#[inline(always)]
49+
fn erase_file_in_subdir(&mut self, filename: &str, dir: &str) -> Result<(), ()> {
50+
bitbox02::sd::erase_file_in_subdir(filename, dir)
51+
}
52+
53+
#[inline(always)]
54+
fn load_bin(&mut self, filename: &str, dir: &str) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
55+
bitbox02::sd::load_bin(filename, dir)
56+
}
57+
58+
#[inline(always)]
59+
fn write_bin(&mut self, filename: &str, dir: &str, data: &[u8]) -> Result<(), ()> {
60+
bitbox02::sd::write_bin(filename, dir, data)
61+
}
3462
}
3563

3664
pub struct BitBox02Hal {
@@ -58,24 +86,71 @@ impl Hal for BitBox02Hal {
5886

5987
#[cfg(feature = "testing")]
6088
pub mod testing {
89+
use alloc::collections::BTreeMap;
90+
use alloc::string::String;
91+
use alloc::vec::Vec;
92+
6193
pub struct TestingSd {
6294
pub inserted: Option<bool>,
95+
files: BTreeMap<String, BTreeMap<String, Vec<u8>>>,
6396
}
6497

6598
impl TestingSd {
6699
pub fn new() -> Self {
67-
Self { inserted: None }
100+
Self {
101+
inserted: None,
102+
files: BTreeMap::new(),
103+
}
68104
}
69105
}
70-
pub struct TestingHal<'a> {
71-
pub ui: crate::workflow::testing::TestingWorkflows<'a>,
72-
pub sd: TestingSd,
73-
}
74106

75107
impl super::Sd for TestingSd {
76108
fn sdcard_inserted(&mut self) -> bool {
77109
self.inserted.unwrap()
78110
}
111+
112+
fn list_subdir(&mut self, subdir: Option<&str>) -> Result<Vec<String>, ()> {
113+
match subdir {
114+
Some(key) => Ok(self
115+
.files
116+
.get(key)
117+
.map(|files| files.keys().cloned().collect())
118+
.unwrap_or_default()),
119+
None => Ok(self.files.keys().cloned().collect()),
120+
}
121+
}
122+
123+
fn erase_file_in_subdir(&mut self, filename: &str, dir: &str) -> Result<(), ()> {
124+
self.files
125+
.get_mut(dir)
126+
.and_then(|files| files.remove(filename).map(|_| ()))
127+
.ok_or(())
128+
}
129+
130+
fn load_bin(
131+
&mut self,
132+
filename: &str,
133+
dir: &str,
134+
) -> Result<zeroize::Zeroizing<Vec<u8>>, ()> {
135+
self.files
136+
.get(dir)
137+
.and_then(|files| files.get(filename))
138+
.map(|data| zeroize::Zeroizing::new(data.clone()))
139+
.ok_or(())
140+
}
141+
142+
fn write_bin(&mut self, filename: &str, dir: &str, data: &[u8]) -> Result<(), ()> {
143+
self.files
144+
.entry(dir.into())
145+
.or_default()
146+
.insert(filename.into(), data.to_vec());
147+
Ok(())
148+
}
149+
}
150+
151+
pub struct TestingHal<'a> {
152+
pub ui: crate::workflow::testing::TestingWorkflows<'a>,
153+
pub sd: TestingSd,
79154
}
80155

81156
impl TestingHal<'_> {
@@ -95,4 +170,35 @@ pub mod testing {
95170
&mut self.sd
96171
}
97172
}
173+
174+
#[cfg(test)]
175+
mod tests {
176+
use super::*;
177+
use crate::hal::Sd;
178+
179+
// Quick check if our mock TestingSd implementation makes sense.
180+
#[test]
181+
fn test_sd_list_write_read_erase() {
182+
let mut sd = TestingSd::new();
183+
assert_eq!(sd.list_subdir(None), Ok(vec![]));
184+
assert_eq!(sd.list_subdir(Some("dir1")), Ok(vec![]));
185+
186+
assert!(sd.load_bin("file1.txt", "dir1").is_err());
187+
assert!(sd.write_bin("file1.txt", "dir1", b"data").is_ok());
188+
assert_eq!(sd.list_subdir(None), Ok(vec!["dir1".into()]));
189+
assert_eq!(sd.list_subdir(Some("dir1")), Ok(vec!["file1.txt".into()]));
190+
assert_eq!(
191+
sd.load_bin("file1.txt", "dir1").unwrap().as_slice(),
192+
b"data"
193+
);
194+
assert!(sd.write_bin("file1.txt", "dir1", b"replaced data").is_ok());
195+
assert_eq!(
196+
sd.load_bin("file1.txt", "dir1").unwrap().as_slice(),
197+
b"replaced data"
198+
);
199+
assert!(sd.erase_file_in_subdir("doesnt-exist.txt", "dir1").is_err());
200+
assert!(sd.erase_file_in_subdir("file1.txt", "dir1").is_ok());
201+
assert_eq!(sd.list_subdir(Some("dir1")), Ok(vec![]));
202+
}
203+
}
98204
}

src/rust/bitbox02-rust/src/hww.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ mod tests {
139139
use crate::bb02_async::block_on;
140140
use crate::hal::testing::TestingHal;
141141
use crate::workflow::testing::Screen;
142-
use bitbox02::testing::{mock_memory, mock_sd};
142+
use bitbox02::testing::mock_memory;
143143

144144
use prost::Message;
145145

@@ -236,7 +236,6 @@ mod tests {
236236
#[test]
237237
fn test_noise() {
238238
mock_memory();
239-
mock_sd();
240239
let mut make_request = init_noise();
241240
let request = crate::pb::Request {
242241
request: Some(crate::pb::request::Request::ListBackups(
@@ -477,7 +476,6 @@ mod tests {
477476
] {
478477
bitbox02::keystore::lock();
479478
mock_memory();
480-
mock_sd();
481479

482480
bitbox02::memory::set_device_name("test device name").unwrap();
483481

@@ -508,8 +506,7 @@ mod tests {
508506
}]
509507
);
510508

511-
let mut mock_hal = TestingHal::new();
512-
mock_hal.sd.inserted = Some(true);
509+
mock_hal.ui = crate::workflow::testing::TestingWorkflows::new();
513510
make_request(
514511
&mut mock_hal,
515512
(crate::pb::Request {
@@ -541,7 +538,7 @@ mod tests {
541538

542539
let seed = bitbox02::keystore::copy_seed().unwrap();
543540
assert_eq!(seed.len(), host_entropy.len());
544-
let mut mock_hal = TestingHal::new();
541+
mock_hal.ui = crate::workflow::testing::TestingWorkflows::new();
545542
assert!(matches!(
546543
crate::pb::Response::decode(
547544
make_request(
@@ -566,7 +563,7 @@ mod tests {
566563
));
567564
assert_eq!(mock_hal.ui.screens, vec![]);
568565

569-
let mut mock_hal = TestingHal::new();
566+
mock_hal.ui = crate::workflow::testing::TestingWorkflows::new();
570567
make_request(
571568
&mut mock_hal,
572569
(crate::pb::Request {
@@ -587,7 +584,7 @@ mod tests {
587584
}]
588585
);
589586

590-
let mut mock_hal = TestingHal::new();
587+
mock_hal.ui = crate::workflow::testing::TestingWorkflows::new();
591588
assert!(matches!(
592589
crate::pb::Response::decode(
593590
make_request(
@@ -612,7 +609,7 @@ mod tests {
612609
));
613610
assert_eq!(mock_hal.ui.screens, vec![]);
614611

615-
let mut mock_hal = TestingHal::new();
612+
mock_hal.ui = crate::workflow::testing::TestingWorkflows::new();
616613
let backup_id = match crate::pb::Response::decode(
617614
make_request(
618615
&mut mock_hal,
@@ -647,7 +644,7 @@ mod tests {
647644
};
648645
assert_eq!(mock_hal.ui.screens, vec![]);
649646

650-
let mut mock_hal = TestingHal::new();
647+
mock_hal.ui = crate::workflow::testing::TestingWorkflows::new();
651648
mock_hal
652649
.ui
653650
.set_enter_string(Box::new(|_params| Ok("password".into())));

0 commit comments

Comments
 (0)