stellar_ledger/emulator_test_support/
http_transport.rs1use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE};
7use reqwest::{Client as HttpClient, Response};
8use serde::{Deserialize, Serialize};
9use std::ops::Deref;
10use std::time::Duration;
11
12use ledger_transport::{async_trait, APDUAnswer, APDUCommand, Exchange};
13
14use thiserror::Error;
15
16#[derive(Error, Debug)]
17pub enum LedgerZemuError {
18 #[error("Zemu response error")]
20 ResponseError,
21 #[error("Ledger inner error")]
23 InnerError,
24}
25
26pub struct Emulator {
27 url: String,
28}
29
30#[derive(Serialize, Debug, Clone)]
31#[serde(rename_all = "camelCase")]
32struct ZemuRequest {
33 apdu_hex: String,
34}
35
36#[derive(Deserialize, Debug, Clone)]
37struct ZemuResponse {
38 data: String,
39 error: Option<String>,
40}
41
42impl Emulator {
43 #[allow(dead_code)] #[must_use]
45 pub fn new(host: &str, port: u16) -> Self {
46 Self {
47 url: format!("http://{host}:{port}"),
48 }
49 }
50}
51
52#[async_trait]
53impl Exchange for Emulator {
54 type Error = LedgerZemuError;
55 type AnswerType = Vec<u8>;
56
57 async fn exchange<I>(
58 &self,
59 command: &APDUCommand<I>,
60 ) -> Result<APDUAnswer<Self::AnswerType>, Self::Error>
61 where
62 I: Deref<Target = [u8]> + Send + Sync,
63 {
64 let raw_command = hex::encode(command.serialize());
65 let request = ZemuRequest {
66 apdu_hex: raw_command,
67 };
68
69 let mut headers = HeaderMap::new();
70 headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
71 headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
72
73 let resp: Response = HttpClient::new()
74 .post(&self.url)
75 .headers(headers)
76 .timeout(Duration::from_secs(60))
77 .json(&request)
78 .send()
79 .await
80 .map_err(|e| {
81 tracing::error!("create http client error: {:?}", e);
82 LedgerZemuError::InnerError
83 })?;
84 tracing::debug!("http response: {:?}", resp);
85
86 if resp.status().is_success() {
87 let result: ZemuResponse = resp.json().await.map_err(|e| {
88 tracing::error!("error response: {:?}", e);
89 LedgerZemuError::ResponseError
90 })?;
91 if result.error.is_none() {
92 APDUAnswer::from_answer(hex::decode(result.data).expect("decode error"))
93 .map_err(|_| LedgerZemuError::ResponseError)
94 } else {
95 Err(LedgerZemuError::ResponseError)
96 }
97 } else {
98 tracing::error!("error response: {:?}", resp.status());
99 Err(LedgerZemuError::ResponseError)
100 }
101 }
102}