use async_trait::async_trait;
use log::debug;
use std::{cell::RefCell, sync::Arc, time::Duration};
use tokio::sync::Mutex;
use ledger_lib::Device;
use ledger_proto::{ApduBase, ApduReq};
use ledger_mob_apdu::{
state::{Digest, TxState},
tx::{TxComplete, TxInfo, TxInfoReq, TxInit, TxSetMessage},
};
use crate::Error;
mod key_image;
mod memo;
mod ring;
mod subaddress;
mod summary;
#[derive(Clone, Debug, PartialEq)]
pub struct TxConfig {
pub account_index: u32,
pub num_memos: usize,
pub num_rings: usize,
pub request_timeout: Duration,
pub user_timeout: Duration,
}
pub struct TransactionHandle<T: Device> {
t: Arc<Mutex<T>>,
info: TxConfig,
state: RefCell<TransactionState>,
}
struct TransactionState {
digest: Digest,
memo_count: usize,
ring_count: usize,
}
impl<T: Device + Send> TransactionHandle<T> {
pub async fn new(info: TxConfig, transport: Arc<Mutex<T>>) -> Result<Self, Error> {
let mut buff = [0u8; 256];
let tx_init = TxInit::new(info.account_index, info.num_rings as u8);
let mut t = transport.lock().await;
let r = t
.request::<TxInfo>(tx_init, &mut buff, info.request_timeout)
.await?;
drop(t);
Ok(Self {
info,
t: transport,
state: RefCell::new(TransactionState {
digest: r.digest,
memo_count: 0,
ring_count: 0,
}),
})
}
pub async fn set_message(&mut self, m: &[u8]) -> Result<(), Error> {
let mut buff = [0u8; 256];
let req = TxSetMessage::new(m);
let digest = {
let mut state = self.state.borrow_mut();
Digest::update(&mut state.digest, &req.hash()).clone()
};
let mut t = self.t.lock().await;
let resp = t
.request::<TxInfo>(req, &mut buff, self.info.request_timeout)
.await?;
check_state(resp.state, TxState::Pending)?;
check_digest(&resp.digest, &digest)?;
Ok(())
}
pub async fn await_approval(&mut self, timeout_s: u32) -> Result<(), Error> {
let mut buff = [0u8; 256];
for _i in 0..timeout_s {
let r = self
.request::<TxInfo>(TxInfoReq {}, &mut buff, self.info.request_timeout)
.await;
debug!("awaiting tx approval (state: {:?})", r);
match r {
Ok(v) if v.state == TxState::Pending => (),
Ok(v) if v.state == TxState::Ready => return Ok(()),
Ok(v) if v.state == TxState::TxDenied => return Err(Error::UserDenied),
Ok(v) if v.state == TxState::Error => return Err(Error::Engine(0)),
Ok(v) => return Err(Error::InvalidState(v.state, TxState::Pending)),
Err(_) => (),
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
Err(Error::UserTimeout)
}
pub async fn complete(mut self) -> Result<(), Error> {
let mut buff = [0u8; 256];
let _r = self
.request::<TxInfo>(TxComplete, &mut buff, self.info.request_timeout)
.await?;
Ok(())
}
}
pub(crate) fn check_state(actual: TxState, expected: TxState) -> Result<(), Error> {
if actual != expected {
Err(Error::InvalidState(actual, expected))
} else {
Ok(())
}
}
pub(crate) fn check_digest(actual: &Digest, expected: &Digest) -> Result<(), Error> {
if expected != actual {
Err(Error::DigestMismatch)
} else {
Ok(())
}
}
#[async_trait]
impl<T: Device + Send> Device for TransactionHandle<T> {
async fn request<'a, 'b, RESP: ApduBase<'b>>(
&mut self,
request: impl ApduReq<'a> + Send,
buff: &'b mut [u8],
timeout: Duration,
) -> Result<RESP, ledger_lib::Error> {
self.t.lock().await.request(request, buff, timeout).await
}
}