use crate::apdu::{AppletSelect, CommandApdu, StatusCommand};
use crate::error::StatusError;
use crate::shared::{CkTransport, to_cktap};
use crate::{CkTapCard, CkTapError};
use async_trait::async_trait;
use std::io::{Read, Write};
use std::os::unix::net::UnixStream;
use std::path::Path;
use std::string::ToString;
use std::sync::Arc;
pub const CVC: &str = "123456";
pub async fn find_emulator(pipe_path: &Path) -> Result<CkTapCard, StatusError> {
if !pipe_path.exists() {
return Err(StatusError::CkTap(CkTapError::Transport(
"Emulator pipe doesn't exist.".to_string(),
)));
}
let stream = UnixStream::connect(pipe_path).expect("unix stream");
let card_emulator = CardEmulator { stream };
to_cktap(Arc::new(card_emulator)).await
}
#[derive(Debug)]
pub struct CardEmulator {
stream: UnixStream,
}
#[async_trait]
impl CkTransport for CardEmulator {
async fn transmit_apdu(&self, command_apdu: Vec<u8>) -> Result<Vec<u8>, CkTapError> {
let select_apdu: Vec<u8> = AppletSelect::default().apdu_bytes();
let command_apdu = if command_apdu.eq(&select_apdu) {
StatusCommand::default().apdu_bytes()
} else {
command_apdu
};
let mut stream = self.stream.try_clone().expect("clone unix stream");
stream
.write_all(&command_apdu.as_slice()[5..])
.map_err(|e| CkTapError::Transport(e.to_string()))?;
let mut buffer = [0; 4096];
stream
.read(&mut buffer[..])
.map_err(|e| CkTapError::Transport(e.to_string()))?;
Ok(buffer.to_vec())
}
}
#[cfg(test)]
pub mod test {
use crate::emulator::find_emulator;
use std::fmt::Display;
use std::path::Path;
use std::process::{Child, Command, Stdio};
use std::thread::sleep;
use std::time::Duration;
pub struct EcardSubprocess {
pub child: Child,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum CardTypeOption {
SatsCard,
TapSigner,
SatsChip,
}
impl CardTypeOption {
pub fn values() -> [CardTypeOption; 3] {
[
CardTypeOption::SatsCard,
CardTypeOption::TapSigner,
CardTypeOption::SatsChip,
]
}
}
impl Display for CardTypeOption {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
CardTypeOption::SatsCard => String::from("--satscard"),
CardTypeOption::SatsChip => String::from("--satschip"),
CardTypeOption::TapSigner => String::from("--tapsigner"),
};
write!(f, "{str}")
}
}
impl EcardSubprocess {
pub fn new(
pipe_path: &Path,
card_type: &CardTypeOption,
) -> Result<Self, Box<dyn std::error::Error>> {
let no_init = *card_type != CardTypeOption::SatsCard;
let mut command = Command::new("python3");
command
.arg("../coinkite/coinkite-tap-proto/emulator/ecard.py")
.arg("--rng-seed")
.arg("1337")
.arg("emulate")
.arg(card_type.to_string())
.arg("--pipe")
.arg(pipe_path.to_str().unwrap())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
if no_init {
command.arg("--no-init");
}
let child = command.spawn()?;
sleep(Duration::from_millis(500));
Ok(EcardSubprocess { child })
}
}
impl Drop for EcardSubprocess {
fn drop(&mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
}
}
#[tokio::test]
pub async fn test_transmit() {
let pipe_path = Path::new("/tmp/test-transmit-pipe");
let _python = EcardSubprocess::new(pipe_path, &CardTypeOption::SatsCard).unwrap();
let emulator = find_emulator(pipe_path).await;
assert!(emulator.is_ok());
}
}