Skip to content

Commit 553c700

Browse files
committed
rust: port ListBackups api call to Rust
- wrap sd_list_subdir - use it to list and return backups - remove C code that becomes unused
1 parent 881eb99 commit 553c700

File tree

16 files changed

+213
-202
lines changed

16 files changed

+213
-202
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
77
## Firmware
88

99
### [Unreleased]
10+
- ListBackups: ported to Rust
11+
12+
### 9.8.0 [released 2021-10-21]
1013
- Multi edition: add Cardano support.
1114
- Allow recovery words that convert to a zero seed, such as the 12 words `abandon abandon .... about`.
1215
- RestoreBackup: ported to Rust. Will now return UserAbortError on user abort instead of GenericError.

messages/backup_commands.options

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,4 @@
1515
* mangle_names:M_STRIP_PACKAGE
1616

1717
CheckBackupResponse.id fixed_length:true max_size:65
18-
ListBackupsResponse.info max_count:50
19-
// One backup per seed -> 100 seeds may be backed up per SD card. Adapt this number if it is too small.
20-
BackupInfo.id fixed_length:true max_size:65
21-
BackupInfo.name fixed_length:true max_size:64
2218
RestoreBackupRequest.id fixed_length:true max_size:65

src/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ set(DBB-FIRMWARE-SOURCES
4444
${CMAKE_SOURCE_DIR}/src/workflow/blocking.c
4545
${CMAKE_SOURCE_DIR}/src/workflow/confirm.c
4646
${CMAKE_SOURCE_DIR}/src/workflow/idle_workflow.c
47-
${CMAKE_SOURCE_DIR}/src/workflow/restore.c
4847
${CMAKE_SOURCE_DIR}/src/workflow/orientation_screen.c
4948
${CMAKE_SOURCE_DIR}/src/workflow/status.c
5049
${CMAKE_SOURCE_DIR}/src/workflow/verify_recipient.c
@@ -400,6 +399,8 @@ add_custom_target(rust-bindgen
400399
--whitelist-function reset_reset
401400
--whitelist-function sd_card_inserted
402401
--whitelist-function sd_format
402+
--whitelist-function sd_list_subdir
403+
--whitelist-function sd_free_list
403404
--whitelist-var BIP39_WORDLIST_LEN
404405
--whitelist-function app_eth_params_get
405406
--whitelist-function app_eth_erc20_params_get

src/backup/restore.c

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -247,41 +247,3 @@ restore_error_t restore_from_directory(const char* dir, Backup* backup, BackupDa
247247
// the bits that are diverging.
248248
return RESTORE_OK;
249249
}
250-
251-
restore_error_t restore_list_backups(ListBackupsResponse* backups)
252-
{
253-
sd_list_t list_subdirs __attribute__((__cleanup__(sd_free_list)));
254-
if (!sd_list(&list_subdirs)) {
255-
return RESTORE_ERR_SD_LIST;
256-
}
257-
backups->info_count = 0;
258-
for (size_t i = 0; i < list_subdirs.num_files; i++) {
259-
if (backups->info_count >= sizeof(backups->info) / sizeof(backups->info[0])) {
260-
return RESTORE_TOO_MANY;
261-
}
262-
char* dir = list_subdirs.files[i];
263-
Backup __attribute__((__cleanup__(backup_cleanup_backup))) backup;
264-
BackupData __attribute__((__cleanup__(backup_cleanup_backup_data))) backup_data;
265-
if (restore_from_directory(dir, &backup, &backup_data) != RESTORE_OK) {
266-
continue;
267-
}
268-
backups->info[backups->info_count].timestamp = backup.backup_v1.content.metadata.timestamp;
269-
size_t id_size = sizeof(backups->info[backups->info_count].id);
270-
int snprintf_result = snprintf(backups->info[backups->info_count].id, id_size, "%s", dir);
271-
if (snprintf_result < 0 || snprintf_result >= (int)id_size) {
272-
return RESTORE_ERR_SD_LIST;
273-
}
274-
size_t name_size = sizeof(backups->info[backups->info_count].name);
275-
snprintf_result = snprintf(
276-
backups->info[backups->info_count].name,
277-
name_size,
278-
"%s",
279-
backup.backup_v1.content.metadata.name);
280-
if (snprintf_result < 0 || snprintf_result >= (int)name_size) {
281-
return RESTORE_ERR_SD_LIST;
282-
}
283-
backups->info_count++;
284-
}
285-
286-
return RESTORE_OK;
287-
}

src/backup/restore.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,6 @@ restore_error_t restore_from_buffer(
3535
Backup* backup,
3636
BackupData* backup_data);
3737

38-
/**
39-
* Returns a list of backup information to the caller.
40-
*/
41-
restore_error_t restore_list_backups(ListBackupsResponse* backups);
42-
4338
/**
4439
* Restore a backup from directory.
4540
* @param[in] dir The directory from which we want to restore the backup.

src/commander/commander.c

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929
#include <util.h>
3030
#include <version.h>
3131

32-
#include <workflow/restore.h>
33-
3432
#include "hww.pb.h"
3533

3634
#include <apps/btc/btc.h>
@@ -51,16 +49,6 @@ static void _report_error(Response* response, commander_error_t error_code)
5149
response->which_response = Response_error_tag;
5250
}
5351

54-
// ------------------------------------ API ------------------------------------- //
55-
56-
static commander_error_t _api_list_backups(ListBackupsResponse* response)
57-
{
58-
if (!workflow_list_backups(response)) {
59-
return COMMANDER_ERR_GENERIC;
60-
}
61-
return COMMANDER_OK;
62-
}
63-
6452
// ------------------------------------ Process ------------------------------------- //
6553

6654
/**
@@ -85,11 +73,9 @@ static commander_error_t _api_process(const Request* request, Response* response
8573
case Request_btc_sign_init_tag:
8674
case Request_btc_sign_input_tag:
8775
case Request_btc_sign_output_tag:
76+
(void)response;
8877
return COMMANDER_ERR_DISABLED;
8978
#endif
90-
case Request_list_backups_tag:
91-
response->which_response = Response_list_backups_tag;
92-
return _api_list_backups(&(response->response.list_backups));
9379
default:
9480
screen_print_debug("command unknown", 1000);
9581
return COMMANDER_ERR_INVALID_INPUT;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ async fn process_api(request: &Request) -> Option<Result<Response, Error>> {
132132
Some(set_mnemonic_passphrase_enabled::process(request).await)
133133
}
134134
Request::InsertRemoveSdcard(ref request) => Some(sdcard::process(request).await),
135+
Request::ListBackups(_) => Some(backup::list()),
135136
Request::CheckSdcard(_) => Some(Ok(Response::CheckSdcard(pb::CheckSdCardResponse {
136137
inserted: bitbox02::sdcard_inserted(),
137138
}))),

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

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use super::Error;
1616
use crate::pb;
1717

18+
use alloc::vec::Vec;
19+
1820
use pb::response::Response;
1921

2022
use crate::workflow::{confirm, status, unlock};
@@ -138,13 +140,31 @@ pub async fn create(
138140
}
139141
}
140142

143+
pub fn list() -> Result<Response, Error> {
144+
let mut info: Vec<pb::BackupInfo> = Vec::new();
145+
for dir in bitbox02::sd::list_subdir(None)? {
146+
let data = match bitbox02::backup::restore_from_directory(&dir) {
147+
Ok(data) => data,
148+
Err(_) => continue,
149+
};
150+
info.push(pb::BackupInfo {
151+
id: dir,
152+
timestamp: data.timestamp,
153+
name: data.name,
154+
})
155+
}
156+
Ok(Response::ListBackups(pb::ListBackupsResponse { info }))
157+
}
158+
141159
#[cfg(test)]
142160
mod tests {
143161
extern crate std;
144162
use super::*;
145163

146164
use crate::bb02_async::block_on;
147-
use bitbox02::testing::{mock, mock_memory, mock_sd, mock_unlocked, Data, MUTEX};
165+
use bitbox02::testing::{
166+
mock, mock_memory, mock_sd, mock_unlocked, mock_unlocked_using_mnemonic, Data, MUTEX,
167+
};
148168
use std::boxed::Box;
149169

150170
/// Test backup creation on a uninitialized keystore.
@@ -174,4 +194,84 @@ mod tests {
174194
);
175195
assert_eq!(EXPECTED_TIMESTMAP, bitbox02::memory::get_seed_birthdate());
176196
}
197+
198+
#[test]
199+
pub fn test_list() {
200+
let _guard = MUTEX.lock().unwrap();
201+
const EXPECTED_TIMESTMAP: u32 = 1601281809;
202+
203+
const DEVICE_NAME_1: &str = "test device name";
204+
const DEVICE_NAME_2: &str = "another test device name";
205+
206+
mock_sd();
207+
208+
// No backups yet.
209+
assert_eq!(
210+
list(),
211+
Ok(Response::ListBackups(pb::ListBackupsResponse {
212+
info: vec![]
213+
}))
214+
);
215+
216+
// Create one backup.
217+
mock(Data {
218+
sdcard_inserted: Some(true),
219+
ui_confirm_create: Some(Box::new(|_params| true)),
220+
..Default::default()
221+
});
222+
mock_unlocked_using_mnemonic("purity concert above invest pigeon category peace tuition hazard vivid latin since legal speak nation session onion library travel spell region blast estate stay");
223+
mock_memory();
224+
bitbox02::memory::set_device_name(DEVICE_NAME_1).unwrap();
225+
assert!(block_on(create(&pb::CreateBackupRequest {
226+
timestamp: EXPECTED_TIMESTMAP,
227+
timezone_offset: 18000,
228+
}))
229+
.is_ok());
230+
231+
assert_eq!(
232+
list(),
233+
Ok(Response::ListBackups(pb::ListBackupsResponse {
234+
info: vec![pb::BackupInfo {
235+
id: "41233dfbad010723dbbb93514b7b81016b73f8aa35c5148e1b478f60d5750dce".into(),
236+
timestamp: EXPECTED_TIMESTMAP,
237+
name: DEVICE_NAME_1.into(),
238+
}]
239+
}))
240+
);
241+
242+
// Create another backup.
243+
mock(Data {
244+
sdcard_inserted: Some(true),
245+
ui_confirm_create: Some(Box::new(|_params| true)),
246+
..Default::default()
247+
});
248+
mock_unlocked_using_mnemonic("goddess item rack improve shaft occur actress rib emerge salad rich blame model glare lounge stable electric height scrub scrub oyster now dinner oven");
249+
mock_memory();
250+
bitbox02::memory::set_device_name(DEVICE_NAME_2).unwrap();
251+
assert!(block_on(create(&pb::CreateBackupRequest {
252+
timestamp: EXPECTED_TIMESTMAP,
253+
timezone_offset: 18000,
254+
}))
255+
.is_ok());
256+
257+
assert_eq!(
258+
list(),
259+
Ok(Response::ListBackups(pb::ListBackupsResponse {
260+
info: vec![
261+
pb::BackupInfo {
262+
id: "41233dfbad010723dbbb93514b7b81016b73f8aa35c5148e1b478f60d5750dce"
263+
.into(),
264+
timestamp: EXPECTED_TIMESTMAP,
265+
name: DEVICE_NAME_1.into(),
266+
},
267+
pb::BackupInfo {
268+
id: "4c7005846ffc09f31850201a6fdfff084191164eb318db2c6fe5a39df4a97ba0"
269+
.into(),
270+
timestamp: EXPECTED_TIMESTMAP,
271+
name: DEVICE_NAME_2.into(),
272+
}
273+
]
274+
}))
275+
)
276+
}
177277
}

src/rust/bitbox02/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub mod input;
4040
pub mod keystore;
4141
pub mod memory;
4242
pub mod random;
43+
pub mod sd;
4344
pub mod securechip;
4445
pub mod ui;
4546

src/rust/bitbox02/src/memory.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use alloc::string::String;
1919
// deduct one for the null terminator.
2020
pub const DEVICE_NAME_MAX_LEN: usize = bitbox02_sys::MEMORY_DEVICE_NAME_MAX_LEN as usize - 1;
2121

22+
#[derive(Debug)]
2223
pub struct Error;
2324

2425
pub fn get_device_name() -> String {

src/rust/bitbox02/src/sd.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2021 Shift Crypto AG
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+
extern crate alloc;
16+
use alloc::string::String;
17+
use alloc::vec::Vec;
18+
19+
struct SdList(bitbox02_sys::sd_list_t);
20+
21+
impl Drop for SdList {
22+
fn drop(&mut self) {
23+
unsafe { bitbox02_sys::sd_free_list(&mut self.0) }
24+
}
25+
}
26+
27+
pub fn list_subdir(subdir: Option<&str>) -> Result<Vec<String>, ()> {
28+
let mut list = SdList(bitbox02_sys::sd_list_t {
29+
num_files: 0,
30+
files: core::ptr::null_mut(),
31+
});
32+
match unsafe {
33+
bitbox02_sys::sd_list_subdir(
34+
&mut list.0,
35+
match subdir {
36+
Some(subdir) => crate::util::str_to_cstr_vec(subdir).unwrap().as_ptr(),
37+
None => core::ptr::null(),
38+
},
39+
)
40+
} {
41+
true => (0..list.0.num_files)
42+
.map(|i| unsafe {
43+
let ptr = *list.0.files.offset(i as _) as *const u8;
44+
crate::util::str_from_null_terminated_ptr(ptr).map(String::from)
45+
})
46+
.collect(),
47+
false => Err(()),
48+
}
49+
}

src/rust/bitbox02/src/testing.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,9 @@ pub fn mock(data: Data) {
4949
keystore::lock();
5050
}
5151

52-
pub const TEST_MNEMONIC: &str = "purity concert above invest pigeon category peace tuition hazard vivid latin since legal speak nation session onion library travel spell region blast estate stay";
53-
54-
/// This mocks an unlocked keystore with a fixed bip39 seed based on these bip39 recovery words:
55-
/// `purity concert above invest pigeon category peace tuition hazard vivid latin since legal speak nation session onion library travel spell region blast estate stay`
56-
pub fn mock_unlocked() {
57-
let seed: [u8; 32] = keystore::bip39_mnemonic_to_seed(TEST_MNEMONIC)
52+
/// This mocks an unlocked keystore with the given bip39 recovery words.
53+
pub fn mock_unlocked_using_mnemonic(mnemonic: &str) {
54+
let seed: [u8; 32] = keystore::bip39_mnemonic_to_seed(mnemonic)
5855
.unwrap()
5956
.as_slice()
6057
.try_into()
@@ -63,6 +60,14 @@ pub fn mock_unlocked() {
6360
keystore::unlock_bip39(&crate::input::SafeInputString::new()).unwrap();
6461
}
6562

63+
pub const TEST_MNEMONIC: &str = "purity concert above invest pigeon category peace tuition hazard vivid latin since legal speak nation session onion library travel spell region blast estate stay";
64+
65+
/// This mocks an unlocked keystore with a fixed bip39 seed based on these bip39 recovery words:
66+
/// `purity concert above invest pigeon category peace tuition hazard vivid latin since legal speak nation session onion library travel spell region blast estate stay`
67+
pub fn mock_unlocked() {
68+
mock_unlocked_using_mnemonic(TEST_MNEMONIC)
69+
}
70+
6671
/// This mounts a new FAT32 volume in RAM for use in unit tests. As there is only one volume, access only when holding `MUTEX`.
6772
pub fn mock_sd() {
6873
unsafe {

0 commit comments

Comments
 (0)