Skip to content

Commit 6d0e8e5

Browse files
committed
rust: add better error messages
The protobuf Error response has a message field, but it is hardcoded to generic strings depending on the error type, e.g. "generic", "invalid input", etc. When a user/developer reports this error, better information is useful. This patch adds error messages based on the error condition. Unfortunately, for now we still have to stick to static strings with no runtime information, e.g. "invalid keypath" over "keypath invalid: {}" cotaining the actual keypath. I experimented with dynamic strings, but this immediatelly added another ~7kB in binary bloat due the usage of String and formatting. Only changing the type from `&'static str` to `String` adds a few kB. Alternatives considered: - use String+format!() to be able to return runtime info (e.g. actual keypaths used), as well as chain context strings together, but that resulted in too many kilobytes of additional binary bloat. - error deps like anyhow, snafu, etc. They all add a lot of binary and code bloat. - using only numeric error codes instead of static strings to save binary space. Can still do in the future if needed. - using `ufmt` crate hoping it produces smaller binaries than `format!()` for dynamic strings. I tried it and it was about the same.
1 parent b5019ca commit 6d0e8e5

21 files changed

+699
-215
lines changed

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

+9-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ pub mod noise;
1818
extern crate alloc;
1919
use alloc::vec::Vec;
2020

21+
use api::error::{Error, ErrorKind};
22+
2123
const OP_UNLOCK: u8 = b'u';
2224
const OP_ATTESTATION: u8 = b'a';
2325

@@ -30,17 +32,20 @@ const OP_STATUS_FAILURE_UNINITIALIZED: u8 = 2;
3032
/// message, `Err(Error::InvalidInput)` is returned.
3133
pub async fn next_request(
3234
response: crate::pb::response::Response,
33-
) -> Result<crate::pb::request::Request, api::error::Error> {
35+
) -> Result<crate::pb::request::Request, Error> {
3436
let mut out = [OP_STATUS_SUCCESS].to_vec();
35-
noise::encrypt(&api::encode(response), &mut out).or(Err(api::error::Error::NoiseEncrypt))?;
37+
noise::encrypt(&api::encode(response), &mut out).map_err(Error::err_noise_encrypt)?;
3638
let request = crate::async_usb::next_request(out).await;
3739
match request.split_first() {
3840
Some((&noise::OP_NOISE_MSG, encrypted_request)) => {
3941
let decrypted_request =
40-
noise::decrypt(&encrypted_request).or(Err(api::error::Error::NoiseDecrypt))?;
42+
noise::decrypt(&encrypted_request).map_err(Error::err_noise_decrypt)?;
4143
api::decode(&decrypted_request[..])
4244
}
43-
_ => Err(api::error::Error::InvalidInput),
45+
_ => Err(Error {
46+
msg: None,
47+
kind: ErrorKind::InvalidInput,
48+
}),
4449
}
4550
}
4651

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

+17-4
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ mod system;
3737

3838
use alloc::vec::Vec;
3939

40-
use error::{make_error, Error};
40+
use error::{make_error, Error, ErrorKind};
4141
use pb::request::Request;
4242
use pb::response::Response;
4343
use prost::Message;
@@ -58,7 +58,14 @@ pub fn decode(input: &[u8]) -> Result<Request, Error> {
5858
Ok(pb::Request {
5959
request: Some(request),
6060
}) => Ok(request),
61-
_ => Err(Error::InvalidInput),
61+
Ok(pb::Request { request: None }) => Err(Error {
62+
msg: Some("request missing".into()),
63+
kind: ErrorKind::InvalidInput,
64+
}),
65+
Err(_) => Err(Error {
66+
msg: Some("protobuf decode error".into()),
67+
kind: ErrorKind::InvalidInput,
68+
}),
6269
}
6370
}
6471

@@ -110,7 +117,10 @@ async fn process_api_btc(request: &Request) -> Option<Result<Response, Error>> {
110117

111118
#[cfg(not(any(feature = "app-bitcoin", feature = "app-litecoin")))]
112119
async fn process_api_btc(_request: &Request) -> Option<Result<Response, Error>> {
113-
Some(Err(Error::Disabled))
120+
Some(Err(Error {
121+
msg: None,
122+
kind: ErrorKind::Disabled,
123+
}))
114124
}
115125

116126
/// Handle a protobuf api call.
@@ -164,7 +174,10 @@ pub async fn process(input: Vec<u8>) -> Vec<u8> {
164174
Err(err) => return encode(make_error(err)),
165175
};
166176
if !bitbox02::commander::states_can_call(request_tag(&request) as u16) {
167-
return encode(make_error(Error::InvalidState));
177+
return encode(make_error(Error {
178+
msg: None,
179+
kind: ErrorKind::InvalidState,
180+
}));
168181
}
169182

170183
// Since we will process the call now, so can clear the 'force next' info.

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

+26-10
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
use super::Error;
15+
use super::error::{Context, Error, ErrorKind};
1616
use crate::pb;
1717

1818
use pb::response::Response;
@@ -24,7 +24,10 @@ pub async fn check(
2424
&pb::CheckBackupRequest { silent }: &pb::CheckBackupRequest,
2525
) -> Result<Response, Error> {
2626
if !bitbox02::sdcard_inserted() {
27-
return Err(Error::InvalidInput);
27+
return Err(Error {
28+
msg: Some("sdcard not inserted".into()),
29+
kind: ErrorKind::InvalidInput,
30+
});
2831
}
2932
match backup::check() {
3033
Ok(backup::CheckData { id, name, .. }) => {
@@ -55,12 +58,18 @@ pub async fn check(
5558
if !silent {
5659
status::status("Backup missing\nor invalid", false).await;
5760
}
58-
Err(Error::Generic)
61+
Err(Error {
62+
msg: Some("backup missing or invalid".into()),
63+
kind: ErrorKind::Generic,
64+
})
5965
}
6066
Err(err) => {
6167
let msg = format!("Could not check\nbackup\n{:?}", err).replace("BACKUP_ERR_", "");
6268
status::status(&msg, false).await;
63-
Err(Error::Generic)
69+
Err(Error {
70+
msg: None,
71+
kind: ErrorKind::Generic,
72+
})
6473
}
6574
}
6675
}
@@ -83,7 +92,10 @@ pub async fn create(
8392
const MAX_WEST_UTC_OFFSET: i32 = -43200; // 12 hours in seconds
8493

8594
if timezone_offset < MAX_WEST_UTC_OFFSET || timezone_offset > MAX_EAST_UTC_OFFSET {
86-
return Err(Error::InvalidInput);
95+
return Err(Error {
96+
msg: Some("invalid timezone_offset".into()),
97+
kind: ErrorKind::InvalidInput,
98+
});
8799
}
88100

89101
// Wait for sd card
@@ -95,7 +107,10 @@ pub async fn create(
95107
let is_initialized = bitbox02::memory::is_initialized();
96108

97109
if is_initialized {
98-
unlock::unlock_keystore("Unlock device", unlock::CanCancel::Yes).await?;
110+
unlock::unlock_keystore("Unlock device", unlock::CanCancel::Yes)
111+
.await
112+
.map_err(Error::err)
113+
.context("unlock_keystore failed")?;
99114
}
100115

101116
let seed_birthdate = if !is_initialized {
@@ -106,9 +121,7 @@ pub async fn create(
106121
..Default::default()
107122
};
108123
confirm::confirm(&params).await?;
109-
if bitbox02::memory::set_seed_birthdate(timestamp).is_err() {
110-
return Err(Error::Memory);
111-
}
124+
bitbox02::memory::set_seed_birthdate(timestamp).map_err(Error::err_memory)?;
112125
timestamp
113126
} else if let Ok(backup::CheckData { birthdate, .. }) = backup::check() {
114127
// If adding new backup after initialized, we do not know the seed birthdate.
@@ -133,7 +146,10 @@ pub async fn create(
133146
let msg = format!("Backup not created\nPlease contact\nsupport ({:?})", err)
134147
.replace("BACKUP_ERR_", "");
135148
status::status(&msg, false).await;
136-
Err(Error::Generic)
149+
Err(Error {
150+
msg: None,
151+
kind: ErrorKind::Generic,
152+
})
137153
}
138154
}
139155
}

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

+46-12
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ mod params;
1919
mod script;
2020
pub mod signmsg;
2121

22+
use super::error::{Context, Error, ErrorKind};
2223
use super::pb;
23-
use super::Error;
2424

2525
use crate::apps::bitcoin;
2626
use crate::workflow::confirm;
@@ -48,7 +48,10 @@ pub async fn next_request(response: pb::btc_response::Response) -> Result<Reques
4848
pb::request::Request::Btc(pb::BtcRequest {
4949
request: Some(request),
5050
}) => Ok(request),
51-
_ => Err(Error::InvalidState),
51+
_ => Err(Error {
52+
msg: Some("expected Btc request, got".into()),
53+
kind: ErrorKind::InvalidState,
54+
}),
5255
}
5356
}
5457

@@ -68,9 +71,13 @@ pub async fn antiklepto_get_host_nonce(
6871
Ok(host_nonce
6972
.as_slice()
7073
.try_into()
71-
.or(Err(Error::InvalidInput))?)
74+
.map_err(Error::err_invalid_input)
75+
.context("could not parse host nonce")?)
7276
}
73-
_ => Err(Error::InvalidState),
77+
_ => Err(Error {
78+
msg: Some("expected AntikleptoSignature".into()),
79+
kind: ErrorKind::InvalidState,
80+
}),
7481
}
7582
}
7683

@@ -85,7 +92,10 @@ fn coin_enabled(coin: pb::BtcCoin) -> Result<(), Error> {
8592
if let Ltc | Tltc = coin {
8693
return Ok(());
8794
}
88-
Err(Error::Disabled)
95+
Err(Error {
96+
msg: None,
97+
kind: ErrorKind::Disabled,
98+
})
8999
}
90100

91101
/// Processes an xpub api call.
@@ -96,7 +106,9 @@ async fn xpub(
96106
display: bool,
97107
) -> Result<Response, Error> {
98108
let params = params::get(coin);
99-
bitcoin::keypath::validate_xpub(keypath, params.bip44_coin)?;
109+
bitcoin::keypath::validate_xpub(keypath, params.bip44_coin)
110+
.map_err(Error::err_generic)
111+
.context("invalid keypath")?;
100112
let xpub_type = match xpub_type {
101113
XPubType::Tpub => xpub_type_t::TPUB,
102114
XPubType::Xpub => xpub_type_t::XPUB,
@@ -109,7 +121,9 @@ async fn xpub(
109121
XPubType::CapitalUpub => xpub_type_t::CAPITAL_UPUB,
110122
XPubType::CapitalYpub => xpub_type_t::CAPITAL_YPUB,
111123
};
112-
let xpub = encode_xpub_at_keypath(keypath, xpub_type).or(Err(Error::InvalidInput))?;
124+
let xpub = encode_xpub_at_keypath(keypath, xpub_type)
125+
.map_err(Error::err_invalid_input)
126+
.context("encode_xpub_at_keypath failed")?;
113127
if display {
114128
let title = format!("{}\naccount #{}", params.name, keypath[2] - HARDENED + 1,);
115129
let confirm_params = confirm::Params {
@@ -130,7 +144,9 @@ async fn address_simple(
130144
keypath: &[u32],
131145
display: bool,
132146
) -> Result<Response, Error> {
133-
let address = bitbox02::app_btc::address_simple(coin as _, simple_type as _, keypath)?;
147+
let address = bitbox02::app_btc::address_simple(coin as _, simple_type as _, keypath)
148+
.map_err(Error::err)
149+
.context("address_simple failed")?;
134150
if display {
135151
let confirm_params = confirm::Params {
136152
title: params::get(coin).name,
@@ -150,17 +166,30 @@ async fn address_simple(
150166
pub async fn process_pub(request: &pb::BtcPubRequest) -> Option<Result<Response, Error>> {
151167
let coin = match BtcCoin::from_i32(request.coin) {
152168
Some(coin) => coin,
153-
None => return Some(Err(Error::InvalidInput)),
169+
None => {
170+
return Some(Err(Error {
171+
msg: Some("invalid coin".into()),
172+
kind: ErrorKind::InvalidInput,
173+
}))
174+
}
154175
};
155176
if let Err(err) = coin_enabled(coin) {
156177
return Some(Err(err));
157178
}
158179
match request.output {
159-
None => Some(Err(Error::InvalidInput)),
180+
None => Some(Err(Error {
181+
msg: Some("request.output missing".into()),
182+
kind: ErrorKind::InvalidInput,
183+
})),
160184
Some(Output::XpubType(xpub_type)) => {
161185
let xpub_type = match XPubType::from_i32(xpub_type) {
162186
Some(xpub_type) => xpub_type,
163-
None => return Some(Err(Error::InvalidInput)),
187+
None => {
188+
return Some(Err(Error {
189+
msg: Some("invalid xpub_type".into()),
190+
kind: ErrorKind::InvalidInput,
191+
}))
192+
}
164193
};
165194
Some(xpub(coin, xpub_type, &request.keypath, request.display).await)
166195
}
@@ -169,7 +198,12 @@ pub async fn process_pub(request: &pb::BtcPubRequest) -> Option<Result<Response,
169198
})) => {
170199
let simple_type = match SimpleType::from_i32(simple_type) {
171200
Some(simple_type) => simple_type,
172-
None => return Some(Err(Error::InvalidInput)),
201+
None => {
202+
return Some(Err(Error {
203+
msg: Some("invalid simple_type".into()),
204+
kind: ErrorKind::InvalidInput,
205+
}))
206+
}
173207
};
174208
Some(address_simple(coin, simple_type, &request.keypath, request.display).await)
175209
}

0 commit comments

Comments
 (0)