Skip to content

Commit 2773605

Browse files
committed
tests: test against bitbox02 simulators
The simulators are downloaded base on the simulators.json file if the file does not exist (or the hash is wrong). This allows us to exercise the library much more easily without needing to mock everything, as the simulator behaves like the real BitBox02 for the most part. We could automatically download all releases by using the GitHub releases API (https://api.github.com/repos/BitBoxSwiss/bitbox02-firmware/releases), but the downside is that there would be network requests each time you run the unit tests. For now we simply keep the list manually. The simulators are only released for linux-amd64 for now, so we skip tests if we are not running in this environment.
1 parent e015094 commit 2773605

File tree

14 files changed

+1455
-7
lines changed

14 files changed

+1455
-7
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ wasm-bindgen-futures = { version ="0.4.42", optional = true }
4343
web-sys = { version = "0.3.64", features = ["Storage", "Window"], optional = true }
4444

4545
[dev-dependencies]
46+
async-trait = "0.1.68"
4647
wasm-bindgen-test = "0.3.42"
48+
tokio = { version = "1", features = ["time", "macros", "rt", "fs"] }
49+
reqwest = "0.12"
50+
url = "2.5"
51+
bitcoinconsensus = { version = "0.106.0", default-features = false }
4752

4853
[build-dependencies]
4954
prost-build = { version = "0.11" }
@@ -91,6 +96,7 @@ lto = true
9196
# This may or may not cause trouble on macOS, see: https://github.com/libusb/hidapi/issues/503
9297
multithreaded = []
9398
usb = ["dep:hidapi"]
99+
simulator = []
94100
wasm = [
95101
"dep:enum-assoc",
96102
"dep:js-sys",

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright 2023 Shift Crypto AG, Switzerland. All rights reserved.
1+
Copyright 2023-2024 Shift Crypto AG, Switzerland. All rights reserved.
22

33
Apache License
44
Version 2.0, January 2004

ci.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
set -e
44

55
features=(
6+
"simulator,tokio"
67
"usb"
78
"wasm"
89
"multithreaded,usb"
@@ -23,8 +24,8 @@ cargo fmt --check
2324

2425
for feature_set in "${features[@]}"; do
2526
echo $feature_set
26-
cargo test --locked --features="$feature_set" --all-targets
27-
cargo clippy --locked --features="$feature_set" --all-targets -- -D warnings -A clippy::empty-docs
27+
cargo test --tests --locked --features="$feature_set" -- --nocapture
28+
cargo clippy --tests --locked --features="$feature_set" -- -D warnings -A clippy::empty-docs
2829
done
2930

3031
for example in "${examples[@]}"; do

src/communication.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::util::Threading;
44
use async_trait::async_trait;
55
use thiserror::Error;
66

7-
#[cfg(any(feature = "wasm", feature = "usb"))]
7+
#[cfg(any(feature = "wasm", feature = "usb", feature = "simulator"))]
88
pub const FIRMWARE_CMD: u8 = 0x80 + 0x40 + 0x01;
99

1010
#[derive(Error, Debug)]
@@ -146,7 +146,7 @@ const HWW_RSP_BUSY: u8 = 0x02;
146146
// Bad request.
147147
const HWW_RSP_NACK: u8 = 0x03;
148148

149-
#[derive(Debug, Copy, Clone)]
149+
#[derive(PartialEq, Debug, Copy, Clone)]
150150
pub enum Product {
151151
Unknown,
152152
BitBox02Multi,

src/constants.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
#[cfg(any(feature = "wasm", feature = "usb"))]
12
pub const VENDOR_ID: u16 = 0x03eb;
3+
#[cfg(any(feature = "wasm", feature = "usb"))]
24
pub const PRODUCT_ID: u16 = 0x2403;

src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ pub enum Error {
5252
#[cfg(feature = "usb")]
5353
#[error("hid error: {0}")]
5454
Hid(#[from] hidapi::HidError),
55+
#[cfg(feature = "simulator")]
56+
#[error("simulator error: {0}")]
57+
Simulator(#[from] crate::simulator::Error),
5558
#[error("communication error: {0}")]
5659
#[cfg_attr(feature = "wasm", assoc(js_code = "communication".into()))]
5760
Communication(communication::Error),

src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub mod error;
99
pub mod eth;
1010
mod noise;
1111
pub mod runtime;
12+
#[cfg(feature = "simulator")]
13+
pub mod simulator;
1214
#[cfg(feature = "usb")]
1315
pub mod usb;
1416
#[cfg(feature = "wasm")]
@@ -97,6 +99,18 @@ impl<R: Runtime> BitBox<R> {
9799
Self::from(comm, noise_config).await
98100
}
99101

102+
#[cfg(feature = "simulator")]
103+
pub async fn from_simulator(
104+
endpoint: Option<&str>,
105+
noise_config: Box<dyn NoiseConfig>,
106+
) -> Result<BitBox<R>, Error> {
107+
let comm = Box::new(communication::U2fHidCommunication::from(
108+
crate::simulator::try_connect::<R>(endpoint).await?,
109+
communication::FIRMWARE_CMD,
110+
));
111+
Self::from(comm, noise_config).await
112+
}
113+
100114
/// Invokes the device unlock and pairing.
101115
pub async fn unlock_and_pair(self) -> Result<PairingBitBox<R>, Error> {
102116
self.communication

src/simulator.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use async_trait::async_trait;
2+
use std::io::{Read, Write};
3+
use std::net::TcpStream;
4+
5+
use std::sync::Mutex;
6+
7+
use super::communication::{Error as CommunicationError, ReadWrite};
8+
use super::runtime::Runtime;
9+
10+
const DEFAULT_ENDPOINT: &str = "127.0.0.1:15423";
11+
12+
pub struct TcpClient {
13+
stream: Mutex<TcpStream>,
14+
}
15+
16+
impl TcpClient {
17+
fn new(address: &str) -> Result<Self, std::io::Error> {
18+
let stream = TcpStream::connect(address)?;
19+
Ok(Self {
20+
stream: Mutex::new(stream),
21+
})
22+
}
23+
}
24+
25+
#[derive(thiserror::Error, Debug)]
26+
pub enum Error {
27+
#[error("connection error")]
28+
Connect,
29+
}
30+
31+
/// Connect to a running simulator at this endpoint. Endpoint defaults to `127.0.0.1:15423`.
32+
/// This tries to connect repeatedly for up to about 2 seconds.
33+
pub async fn try_connect<R: Runtime>(endpoint: Option<&str>) -> Result<Box<TcpClient>, Error> {
34+
for _ in 0..200 {
35+
match TcpClient::new(endpoint.unwrap_or(DEFAULT_ENDPOINT)) {
36+
Ok(client) => return Ok(Box::new(client)),
37+
Err(_) => R::sleep(std::time::Duration::from_millis(10)).await,
38+
}
39+
}
40+
Err(Error::Connect)
41+
}
42+
43+
impl crate::util::Threading for TcpClient {}
44+
45+
#[async_trait(?Send)]
46+
impl ReadWrite for TcpClient {
47+
fn write(&self, msg: &[u8]) -> Result<usize, CommunicationError> {
48+
let mut stream = self.stream.lock().unwrap();
49+
stream.write(msg).map_err(|_| CommunicationError::Write)
50+
}
51+
52+
async fn read(&self) -> Result<Vec<u8>, CommunicationError> {
53+
let mut stream = self.stream.lock().unwrap();
54+
55+
let mut buffer = vec![0; 64];
56+
let n = stream
57+
.read(&mut buffer)
58+
.map_err(|_| CommunicationError::Read)?;
59+
buffer.truncate(n);
60+
Ok(buffer)
61+
}
62+
}

tests/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/simulators/

0 commit comments

Comments
 (0)