use std::{borrow::Cow, collections::HashMap, path::PathBuf, str::FromStr};
use testcontainers::{
core::{Mount, WaitFor},
Image,
};
const NAME: &str = "docker.io/zondax/builder-zemu";
const TAG: &str = "speculos-3a3439f6b45eca7f56395673caaf434c202e7005";
const TEST_SEED_PHRASE: &str =
"\"other base behind follow wet put glad muscle unlock sell income october\"";
#[allow(dead_code)]
static ENV: &Map = &Map(phf::phf_map! {
"BOLOS_SDK"=> "/project/deps/nanos-secure-sdk",
"BOLOS_ENV" => "/opt/bolos",
"DISPLAY" => "host.docker.internal:0",
});
struct Map(phf::Map<&'static str, &'static str>);
#[allow(clippy::implicit_hasher)]
impl From<&Map> for HashMap<String, String> {
fn from(Map(map): &Map) -> Self {
map.into_iter()
.map(|(a, b)| ((*a).to_string(), (*b).to_string()))
.collect()
}
}
#[derive(Debug, Default)]
pub struct Speculos {
env: HashMap<String, String>,
volumes: Vec<Mount>,
cmd: String,
}
const DEFAULT_APP_PATH: &str = "/project/app/bin";
impl Speculos {
#[allow(dead_code)]
pub fn new(ledger_device_model: String) -> Self {
#[allow(unused_mut)]
let apps_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("test_fixtures")
.join("apps");
let volumes = vec![Mount::bind_mount(
apps_dir.to_str().unwrap(),
DEFAULT_APP_PATH,
)];
let cmd = Self::get_cmd(ledger_device_model);
Speculos {
env: ENV.into(),
volumes,
cmd,
}
}
fn get_cmd(ledger_device_model: String) -> String {
let device_model: DeviceModel = ledger_device_model.parse().unwrap();
let container_elf_path = format!("{DEFAULT_APP_PATH}/{}", device_model.as_file());
format!(
"/home/zondax/speculos/speculos.py --log-level speculos:DEBUG --color JADE_GREEN \
--display headless \
-s {TEST_SEED_PHRASE} \
-m {device_model} {container_elf_path}"
)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum DeviceModel {
NanoS,
NanoSP,
NanoX,
}
impl FromStr for DeviceModel {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"nanos" => Ok(DeviceModel::NanoS),
"nanosp" => Ok(DeviceModel::NanoSP),
"nanox" => Ok(DeviceModel::NanoX),
_ => Err(format!("Unsupported device model: {}", s)),
}
}
}
impl std::fmt::Display for DeviceModel {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
DeviceModel::NanoS => write!(f, "nanos"),
DeviceModel::NanoSP => write!(f, "nanosp"),
DeviceModel::NanoX => write!(f, "nanox"),
}
}
}
impl DeviceModel {
pub fn as_file(&self) -> &str {
match self {
DeviceModel::NanoS => "stellarNanoSApp.elf",
DeviceModel::NanoSP => "stellarNanoSPApp.elf",
DeviceModel::NanoX => "stellarNanoXApp.elf",
}
}
}
impl Image for Speculos {
fn name(&self) -> &str {
NAME
}
fn tag(&self) -> &str {
TAG
}
fn ready_conditions(&self) -> Vec<WaitFor> {
vec![WaitFor::message_on_stdout("HTTP proxy started...")]
}
fn env_vars(
&self,
) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
self.env.clone().into_iter().collect::<Vec<_>>()
}
fn mounts(&self) -> impl IntoIterator<Item = &Mount> {
self.volumes.iter()
}
fn cmd(&self) -> impl IntoIterator<Item = impl Into<std::borrow::Cow<'_, str>>> {
vec![self.cmd.clone()].into_iter()
}
}