Skip to content

Commit bfbfe39

Browse files
committed
bhwi-cli: command to enumerate devices
The main goal of this change is to add a command to bhwi-cli that enumerates all supported devices and displays information (ex. fingerprint) about them so that the user can interact with a device. Lots of tangential changes have also been made as I was trying to build out a decent foundation for adding device enumeration. Large refactors were done in order to make it possible to have heterogeneous vectors of devices. It's not a major benefit but it makes the cli code nicer. This includes the creation of the HWIDevice trait and making error types use thiserror to implement std::error::Error. This also required the bhwi-wasm errors to be changed from JsValue to a wrapper error, WasmError, which converts the errors to strings. Not ideal, but could not find a better solution right now. Lots of the Transports from the e2e crates were moved around and changed for re-usability between the cli crate e2e tests. The rest of the changes were simply writing the Transports (HID and serial backends) used for the currently supported devices.
1 parent ea9b12b commit bfbfe39

File tree

40 files changed

+1559
-386
lines changed

40 files changed

+1559
-386
lines changed

Cargo.lock

Lines changed: 479 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ reqwest = "0.13.2"
2121
serde = "1.0.228"
2222
serde_json = "1.0.149"
2323
serde_cbor = "0.11.2"
24+
thiserror = "1"
2425
tokio = "1.0"

bhwi-async/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ hex = { workspace = true, optional = true}
2323
serde = { workspace = true, optional = true}
2424
serde_cbor = { workspace = true, optional = true}
2525
serde_json = { workspace = true, optional = true}
26+
thiserror.workspace = true
2627

2728
byteorder = "1.5"

bhwi-async/src/lib.rs

Lines changed: 89 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pub mod jade;
33
pub mod ledger;
44
pub mod transport;
55

6-
use std::fmt::Debug;
6+
use std::{error::Error as StdError, fmt::Debug};
77

88
use async_trait::async_trait;
99
use bhwi::{
@@ -20,19 +20,19 @@ pub use ledger::Ledger;
2020

2121
#[async_trait(?Send)]
2222
pub trait Transport {
23-
type Error: Debug;
23+
type Error: StdError + Send + Sync + Debug + 'static;
2424
async fn exchange(&mut self, command: &[u8], encrypted: bool) -> Result<Vec<u8>, Self::Error>;
2525
}
2626

2727
#[async_trait(?Send)]
2828
pub trait HttpClient {
29-
type Error: Debug;
29+
type Error: StdError + Send + Sync + Debug + 'static;
3030
async fn request(&self, url: &str, request: &[u8]) -> Result<Vec<u8>, Self::Error>;
3131
}
3232

3333
#[async_trait(?Send)]
3434
pub trait HWI {
35-
type Error: Debug;
35+
type Error: StdError + Send + Sync + Debug + 'static;
3636
async fn unlock(&mut self, network: Network) -> Result<(), Self::Error>;
3737
async fn get_master_fingerprint(&mut self) -> Result<Fingerprint, Self::Error>;
3838
async fn get_extended_pubkey(
@@ -47,19 +47,51 @@ pub trait HWI {
4747
) -> Result<(u8, Signature), Self::Error>;
4848
}
4949

50-
#[derive(Debug)]
51-
pub enum Error<E, F> {
52-
Transport(E),
53-
HttpClient(F),
54-
Interpreter(common::Error),
50+
// TODO: this will become a pain to maintain, but we can have a proc-macro
51+
// generate this trait by putting it over HWI's definition and then also
52+
// generate the blanket impl which will map the errors to HWIDeviceError
53+
#[async_trait(?Send)]
54+
pub trait HWIDevice {
55+
async fn unlock(&mut self, network: Network) -> Result<(), HWIDeviceError>;
56+
async fn get_master_fingerprint(&mut self) -> Result<Fingerprint, HWIDeviceError>;
57+
async fn get_extended_pubkey(
58+
&mut self,
59+
path: DerivationPath,
60+
display: bool,
61+
) -> Result<Xpub, HWIDeviceError>;
62+
async fn sign_message(
63+
&mut self,
64+
message: &[u8],
65+
path: DerivationPath,
66+
) -> Result<(u8, Signature), HWIDeviceError>;
5567
}
5668

57-
impl<E, F> From<common::Error> for Error<E, F> {
58-
fn from(value: common::Error) -> Self {
59-
Self::Interpreter(value)
69+
#[derive(Debug, thiserror::Error)]
70+
#[error("hwi device error: {0}")]
71+
pub struct HWIDeviceError(#[from] Box<dyn StdError + Send + Sync + 'static>);
72+
73+
impl HWIDeviceError {
74+
pub fn new(error: impl StdError + Send + Sync + 'static) -> Self {
75+
Self(Box::new(error))
6076
}
6177
}
6278

79+
#[derive(Debug, thiserror::Error)]
80+
pub enum Error<E, F>
81+
where
82+
E: StdError + Send + Sync + 'static,
83+
F: StdError + Send + Sync + 'static,
84+
{
85+
#[error("transport error: {0}")]
86+
Transport(E),
87+
88+
#[error("http client error: {0}")]
89+
HttpClient(F),
90+
91+
#[error("interpreter error: {0}")]
92+
Interpreter(#[from] common::Error),
93+
}
94+
6395
#[async_trait(?Send)]
6496
impl<D> HWI for D
6597
where
@@ -126,13 +158,52 @@ where
126158
}
127159
}
128160

161+
#[async_trait(?Send)]
162+
impl<T> HWIDevice for T
163+
where
164+
T: HWI,
165+
T::Error: StdError + Send + Sync + 'static,
166+
{
167+
async fn unlock(&mut self, network: Network) -> Result<(), HWIDeviceError> {
168+
HWI::unlock(self, network)
169+
.await
170+
.map_err(HWIDeviceError::new)
171+
}
172+
173+
async fn get_master_fingerprint(&mut self) -> Result<Fingerprint, HWIDeviceError> {
174+
HWI::get_master_fingerprint(self)
175+
.await
176+
.map_err(HWIDeviceError::new)
177+
}
178+
179+
async fn get_extended_pubkey(
180+
&mut self,
181+
path: DerivationPath,
182+
display: bool,
183+
) -> Result<Xpub, HWIDeviceError> {
184+
HWI::get_extended_pubkey(self, path, display)
185+
.await
186+
.map_err(HWIDeviceError::new)
187+
}
188+
189+
async fn sign_message(
190+
&mut self,
191+
message: &[u8],
192+
path: DerivationPath,
193+
) -> Result<(u8, Signature), HWIDeviceError> {
194+
HWI::sign_message(self, message, path)
195+
.await
196+
.map_err(HWIDeviceError::new)
197+
}
198+
}
199+
129200
pub trait OnUnlock {
130201
fn on_unlock(&mut self, _response: common::Response) -> Result<(), common::Error>;
131202
}
132203

133204
pub trait CommonInterface<C, T, R, E> {
134-
type TransportError: Debug;
135-
type HttpClientError: Debug;
205+
type TransportError: StdError + Send + Sync + Debug + 'static;
206+
type HttpClientError: StdError + Send + Sync + Debug + 'static;
136207

137208
#[allow(clippy::type_complexity)]
138209
fn components(
@@ -144,13 +215,13 @@ pub trait CommonInterface<C, T, R, E> {
144215
);
145216
}
146217

147-
async fn run_command<'a, D, C, E, F>(
148-
device: &'a mut D,
218+
async fn run_command<D, C, E, F>(
219+
device: &mut D,
149220
command: C,
150221
) -> Result<common::Response, Error<E, F>>
151222
where
152-
E: std::fmt::Debug + 'a,
153-
F: std::fmt::Debug + 'a,
223+
E: StdError + Send + Sync + std::fmt::Debug + 'static,
224+
F: StdError + Send + Sync + std::fmt::Debug + 'static,
154225
D: CommonInterface<
155226
common::Command,
156227
common::Transmit,

bhwi-async/src/transport/coldcard/hid.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
use crate::{Transport, transport::Channel};
22
use async_trait::async_trait;
33

4-
pub const COLDCARD_VID: u16 = 0xd13e;
4+
pub use bhwi::coldcard::COLDCARD_DEVICE_ID;
5+
56
const COLDCARD_PACKET_WRITE_SIZE: usize = 63;
67
const COLDCARD_PACKET_READ_SIZE: usize = 64;
78

8-
#[derive(Debug)]
9+
#[derive(Debug, thiserror::Error)]
910
pub enum ColdcardHIDError {
11+
#[error("communication error: {0}")]
1012
Comm(&'static str),
11-
Hid(std::io::Error),
12-
}
1313

14-
impl From<std::io::Error> for ColdcardHIDError {
15-
fn from(value: std::io::Error) -> Self {
16-
ColdcardHIDError::Hid(value)
17-
}
14+
#[error("HID IO error")]
15+
Hid(#[from] std::io::Error),
1816
}
1917

2018
pub struct ColdcardTransportHID<C> {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub use bhwi::coldcard::DEFAULT_CKCC_SOCKET;
12
pub mod hid;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
pub use bhwi::jade::JADE_DEVICE_IDS;
2+
13
#[cfg(feature = "emulators")]
24
pub mod tcp;

bhwi-async/src/transport/jade/tcp.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::fmt::Debug;
2-
31
use async_trait::async_trait;
42
use serde_cbor::Value;
53

@@ -17,15 +15,13 @@ impl<C> TcpTransport<C> {
1715

1816
#[async_trait(?Send)]
1917
pub trait TcpClient {
20-
type Error: Debug + From<serde_cbor::Error>;
21-
22-
async fn write_all(&mut self, command: &[u8]) -> Result<(), Self::Error>;
23-
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error>;
18+
async fn write_all(&mut self, command: &[u8]) -> Result<(), std::io::Error>;
19+
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error>;
2420
}
2521

2622
#[async_trait(?Send)]
2723
impl<C: TcpClient> Transport for TcpTransport<C> {
28-
type Error = <C as TcpClient>::Error;
24+
type Error = std::io::Error;
2925

3026
async fn exchange(&mut self, command: &[u8], _encrypted: bool) -> Result<Vec<u8>, Self::Error> {
3127
self.client.write_all(command).await?;
@@ -47,7 +43,7 @@ impl<C: TcpClient> Transport for TcpTransport<C> {
4743
continue; // read more bytes
4844
}
4945
Err(e) => {
50-
return Err(e.into());
46+
return Err(std::io::Error::other(e));
5147
}
5248
}
5349
}

bhwi-async/src/transport/ledger/hid.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,36 +14,32 @@
1414
* limitations under the License.
1515
********************************************************************************/
1616
use async_trait::async_trait;
17+
pub use bhwi::ledger::LEDGER_DEVICE_ID;
1718
use byteorder::{BigEndian, ReadBytesExt};
1819
use std::io::Cursor;
1920

2021
use crate::{Transport, transport::Channel};
2122

22-
pub const LEDGER_VID: u16 = 0x2c97;
23-
pub const LEDGER_USAGE_PAGE: u16 = 0xFFA0;
2423
pub const LEDGER_CHANNEL: u16 = 0x0101;
2524
// for Windows compatability, we prepend the buffer with a 0x00
2625
// so the actual buffer is 64 bytes
2726
const LEDGER_PACKET_WRITE_SIZE: u8 = 64;
2827
const LEDGER_PACKET_READ_SIZE: u8 = 64;
2928

30-
#[derive(Debug)]
29+
#[derive(Debug, thiserror::Error)]
3130
pub enum LedgerHIDError {
31+
#[error("communication error: {0}")]
3232
Comm(&'static str),
33-
Hid(std::io::Error),
34-
}
3533

36-
impl From<std::io::Error> for LedgerHIDError {
37-
fn from(value: std::io::Error) -> Self {
38-
LedgerHIDError::Hid(value)
39-
}
34+
#[error("HID IO error")]
35+
Hid(#[from] std::io::Error),
4036
}
4137

42-
pub struct LedgerTransportHID<C> {
38+
pub struct LedgerTransportHID<C: Channel> {
4339
channel: C,
4440
}
4541

46-
impl<C> LedgerTransportHID<C> {
42+
impl<C: Channel> LedgerTransportHID<C> {
4743
pub fn new(channel: C) -> Self {
4844
Self { channel }
4945
}

0 commit comments

Comments
 (0)