use async_trait::async_trait;
use bhwi::{
Interpreter,
bitbox::{
BitBoxCommand, BitBoxInterpreter, BitBoxResponse, BitBoxTransmit,
error::BitBoxError,
noise::{NoiseConfigData, NoiseState, PairingCodeHook},
},
bitcoin::Network,
common,
};
use crate::{HttpClient, Transport};
pub const HOST_PASSPHRASE_REJECTED: &str = "The BitBox02 does not accept a passphrase from the host. Please enable the passphrase option and enter the passphrase on the device during unlock.";
pub struct BitBox<T> {
pub transport: T,
pub network: Network,
noise: NoiseState,
}
impl<T> BitBox<T> {
pub fn new(transport: T, pairing_data: Option<NoiseConfigData>) -> Self {
Self {
transport,
network: Network::Bitcoin,
noise: NoiseState::new(pairing_data),
}
}
pub fn with_network(mut self, network: Network) -> Self {
self.network = network;
self
}
pub fn pairing_code(&self) -> Option<&str> {
self.noise.pairing_code()
}
pub fn set_pairing_code_hook(&mut self, hook: PairingCodeHook) {
self.noise.set_pairing_code_hook(hook);
}
pub fn noise_config_data(&self) -> NoiseConfigData {
self.noise.data().clone()
}
pub fn is_paired(&self) -> bool {
self.noise.is_paired()
}
}
struct RawCommand(BitBoxCommand);
impl TryFrom<RawCommand> for BitBoxCommand {
type Error = BitBoxError;
fn try_from(cmd: RawCommand) -> Result<Self, Self::Error> {
Ok(cmd.0)
}
}
impl<T: Transport> BitBox<T> {
pub async fn restore_from_mnemonic(
&mut self,
timestamp: u32,
timezone_offset: i32,
) -> Result<(), BitBoxError> {
self.run_bitbox(BitBoxCommand::RestoreFromMnemonic {
timestamp,
timezone_offset,
})
.await
.map(|_| ())
}
async fn run_bitbox(&mut self, command: BitBoxCommand) -> Result<BitBoxResponse, BitBoxError> {
use crate::CommonInterface;
let (transport, _http, _host, mut interpreter) = <BitBox<T> as CommonInterface<
RawCommand,
BitBoxTransmit,
BitBoxResponse,
BitBoxError,
>>::components(self);
let transmit = interpreter.start(RawCommand(command))?;
let exchange = transport
.exchange(&transmit.payload, transmit.encrypted)
.await
.map_err(|e| transport_error(transport.error_kind(&e), format!("{e:?}")))?;
let mut next = interpreter.exchange(exchange)?;
while let Some(t) = next {
let exchange = transport
.exchange(&t.payload, t.encrypted)
.await
.map_err(|e| transport_error(transport.error_kind(&e), format!("{e:?}")))?;
next = interpreter.exchange(exchange)?;
}
interpreter.end()
}
}
fn transport_error(kind: common::ErrorKind, message: String) -> BitBoxError {
match kind {
common::ErrorKind::Disconnected => BitBoxError::Disconnected(message),
_ => BitBoxError::Transport(message),
}
}
impl<C, T, R, E, F> crate::CommonInterface<C, T, R, E> for BitBox<F>
where
C: TryInto<BitBoxCommand, Error = BitBoxError>,
T: From<BitBoxTransmit>,
R: From<BitBoxResponse>,
E: From<BitBoxError>,
F: Transport,
{
type TransportError = F::Error;
type HttpClientError = BitBoxError;
fn components(
&mut self,
) -> (
&mut (dyn Transport<Error = Self::TransportError> + '_),
&(dyn HttpClient<Error = Self::HttpClientError> + '_),
Option<&mut (dyn crate::HostInteraction + 'static)>,
impl Interpreter<Command = C, Transmit = T, Response = R, Error = E>,
) {
let network = self.network;
(
&mut self.transport,
&DummyClient,
None,
BitBoxInterpreter::new(&mut self.noise).with_network(network),
)
}
}
impl<T> crate::OnUnlock for BitBox<T> {
fn on_unlock(&mut self, _response: common::Response) -> Result<(), common::Error> {
Ok(())
}
}
pub struct DummyClient;
#[async_trait(?Send)]
impl HttpClient for DummyClient {
type Error = BitBoxError;
async fn request(&self, _url: &str, _req: &[u8]) -> Result<Vec<u8>, Self::Error> {
unreachable!("BitBox02 does not use an HTTP client")
}
}