use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE};
use reqwest::{Client as HttpClient, Response};
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use std::time::Duration;
use ledger_transport::{async_trait, APDUAnswer, APDUCommand, Exchange};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum LedgerZemuError {
#[error("Zemu response error")]
ResponseError,
#[error("Ledger inner error")]
InnerError,
}
pub struct Emulator {
url: String,
}
#[derive(Serialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
struct ZemuRequest {
apdu_hex: String,
}
#[derive(Deserialize, Debug, Clone)]
struct ZemuResponse {
data: String,
error: Option<String>,
}
impl Emulator {
#[allow(dead_code)] #[must_use]
pub fn new(host: &str, port: u16) -> Self {
Self {
url: format!("http://{host}:{port}"),
}
}
}
#[async_trait]
impl Exchange for Emulator {
type Error = LedgerZemuError;
type AnswerType = Vec<u8>;
async fn exchange<I>(
&self,
command: &APDUCommand<I>,
) -> Result<APDUAnswer<Self::AnswerType>, Self::Error>
where
I: Deref<Target = [u8]> + Send + Sync,
{
let raw_command = hex::encode(command.serialize());
let request = ZemuRequest {
apdu_hex: raw_command,
};
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
let resp: Response = HttpClient::new()
.post(&self.url)
.headers(headers)
.timeout(Duration::from_secs(60))
.json(&request)
.send()
.await
.map_err(|e| {
tracing::error!("create http client error: {:?}", e);
LedgerZemuError::InnerError
})?;
tracing::debug!("http response: {:?}", resp);
if resp.status().is_success() {
let result: ZemuResponse = resp.json().await.map_err(|e| {
tracing::error!("error response: {:?}", e);
LedgerZemuError::ResponseError
})?;
if result.error.is_none() {
APDUAnswer::from_answer(hex::decode(result.data).expect("decode error"))
.map_err(|_| LedgerZemuError::ResponseError)
} else {
Err(LedgerZemuError::ResponseError)
}
} else {
tracing::error!("error response: {:?}", resp.status());
Err(LedgerZemuError::ResponseError)
}
}
}