Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sim/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ tokio = { version = "1.20", features = [ "full" ] }
lazy_static = "1.4"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
bollard = "0.18"
bollard = "0.19.4"
async-trait = "0.1"
futures = "0.3"
tar = "0.4"
Expand Down
110 changes: 59 additions & 51 deletions sim/src/drivers/docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ use std::{
};

use async_trait::async_trait;
use bollard::body_full;
use bollard::models::ContainerCreateBody;
use bollard::models::HostConfig;
use bollard::query_parameters::CreateContainerOptionsBuilder;
use bollard::query_parameters::{
InspectContainerOptionsBuilder, LogsOptionsBuilder, RemoveContainerOptionsBuilder,
StartContainerOptionsBuilder, StopContainerOptionsBuilder, UploadToContainerOptionsBuilder,
};
use bollard::{
container::{
Config, CreateContainerOptions, LogsOptions, RemoveContainerOptions, StartContainerOptions,
StopContainerOptions, UploadToContainerOptions,
},
service::{ContainerStateStatusEnum, HostConfig, PortBinding},
models::{ContainerStateStatusEnum, PortBinding},
Docker,
};
use bytes::{BufMut, BytesMut};
Expand Down Expand Up @@ -49,7 +53,7 @@ impl DockerDriver {
}
}

const DEFAULT_IMAGE: &str = "ghcr.io/ledgerhq/speculos";
const DEFAULT_IMAGE: &str = "ghcr.io/ledgerhq/ledger-app-builder/ledger-app-dev-tools:latest";

/// [Driver] implementation for [DockerDriver]
#[async_trait]
Expand All @@ -58,11 +62,13 @@ impl Driver for DockerDriver {

async fn run(&self, app: &str, opts: Options) -> anyhow::Result<Self::Handle> {
// Set container name
let name = format!("speculos-{}", opts.http_port);
let create_options = Some(CreateContainerOptions {
name: &name,
platform: None,
});
let name = format!(
"speculos-{}-{}",
opts.model.to_string().to_lowercase(),
opts.http_port
);

let create_options = CreateContainerOptionsBuilder::default().name(&name).build();

// Setup ports
let mut ports = vec![opts.http_port];
Expand All @@ -82,14 +88,15 @@ impl Driver for DockerDriver {
let app_file = app_path.file_name().and_then(|n| n.to_str()).unwrap();

// Setup speculos command
let mut cmd = vec![];

let mut cmd = vec!["speculos".to_string()];
cmd.append(&mut opts.args());
cmd.push(format!("/app/{app_file}"));

debug!("command: {}", cmd.join(" "));

// Setup container
let create_config = Config {
let create_config = ContainerCreateBody {
image: Some(DEFAULT_IMAGE.to_string()),
cmd: Some(cmd),
attach_stdout: Some(true),
Expand All @@ -99,29 +106,33 @@ impl Driver for DockerDriver {
exposed_ports.clone().map(|p| (p.0, p.2)),
)),
host_config: Some(HostConfig {
#[cfg(target_os = "linux")]
binds: Some(vec![String::from("/tmp/.X11-unix:/tmp/.X11-unix")]),
port_bindings: Some(HashMap::from_iter(exposed_ports.map(|p| (p.0, Some(p.1))))),
..Default::default()
}),

env: {
#[cfg(any(target_os = "windows", target_os = "macos"))]
let display = Some(vec![String::from("DISPLAY=host.docker.internal:0")]);
#[cfg(target_os = "linux")]
let display = std::env::var("DISPLAY")
.ok()
.map(|d| vec![format!("DISPLAY={}", d)]);
display
},
..Default::default()
};

// Remove existing container if there is one
let _ = self
.d
.remove_container(
&name,
Some(RemoveContainerOptions {
force: true,
..Default::default()
}),
)
.await;
let remove_options = RemoveContainerOptionsBuilder::new().force(true).build();
let _ = self.d.remove_container(&name, Some(remove_options)).await;

// Create container
debug!("Creating container {}", name);
let _create_info = self
.d
.create_container(create_options, create_config)
.create_container(Some(create_options), create_config)
.await?;

// Generate application archive
Expand All @@ -134,35 +145,29 @@ impl Driver for DockerDriver {
drop(tar);

// Write app archive to container
let upload_options = UploadToContainerOptions {
path: "/",
..Default::default()
};
let upload_options = UploadToContainerOptionsBuilder::new().path("/").build();

self.d
.upload_to_container(&name, Some(upload_options), buff.to_vec().into())
.upload_to_container(&name, Some(upload_options), body_full(buff.to_vec().into()))
.await?;

// Start container
debug!("Starting container {}", name);
let _start_info = self
.d
.start_container(&name, None::<StartContainerOptions<String>>)
.await?;
let start_options = StartContainerOptionsBuilder::new().build();
let _start_info = self.d.start_container(&name, Some(start_options)).await?;

debug!("Container started");

let (exit_tx, mut exit_rx) = channel();

// Setup log streaming task
let mut logs = self.d.logs::<String>(
&name,
Some(LogsOptions {
stderr: true,
stdout: true,
follow: true,
..Default::default()
}),
);
let log_options = LogsOptionsBuilder::new()
.stderr(true)
.stdout(true)
.follow(true)
.build();

let mut logs = self.d.logs(&name, Some(log_options));

tokio::task::spawn(async move {
debug!("start log task");
Expand Down Expand Up @@ -204,10 +209,13 @@ impl Driver for DockerDriver {

// Poll container info periodically
loop {
// Fetch container info
let info = self.d.inspect_container(&handle.name, None).await?;
let inspect_options = InspectContainerOptionsBuilder::new().build();

debug!("info: {:?}", info);
// Fetch container info
let info = self
.d
.inspect_container(&handle.name, Some(inspect_options))
.await?;

// Return when container exits
match info.state.and_then(|s| s.status) {
Expand All @@ -229,16 +237,16 @@ impl Driver for DockerDriver {
let _ = handle.exit_tx.send(());

// Send container stop signal
let options = Some(StopContainerOptions { t: 0 });
let stop_options = StopContainerOptionsBuilder::new().t(0).build();
let options = Some(stop_options);
let _ = self.d.stop_container(&handle.name, options).await;

// Remove container
debug!("Removing container");
let options = Some(RemoveContainerOptions {
force: true,
..Default::default()
});
self.d.remove_container(&handle.name, options).await?;
let remove_options = RemoveContainerOptionsBuilder::new().force(true).build();
self.d
.remove_container(&handle.name, Some(remove_options))
.await?;

debug!("Container removed");

Expand Down
25 changes: 20 additions & 5 deletions sim/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ pub enum Model {
NanoSP,
/// Nano X
NanoX,
/// Stax
Stax,
/// Flex
Flex,
/// Nano Gen 5
NanoGen5,
}

impl Model {
Expand All @@ -83,6 +89,9 @@ impl Model {
Model::NanoS => "nanos",
Model::NanoSP => "nanosplus",
Model::NanoX => "nanox",
Model::Stax => "stax",
Model::Flex => "flex",
Model::NanoGen5 => "nanogen5",
}
}
}
Expand All @@ -107,8 +116,8 @@ pub struct Options {
pub model: Model,

/// Display mode
#[clap(long, value_enum, default_value_t = Options::default().display)]
pub display: Display,
#[clap(long, value_enum)]
pub display: Option<Display>,

/// SDK version override (defaults based on --model)
#[clap(long)]
Expand Down Expand Up @@ -147,11 +156,11 @@ impl Default for Options {
fn default() -> Self {
Self {
model: Model::NanoSP,
display: Display::Headless,
display: None,
sdk: None,
api_level: None,
seed: None,
http_port: 5000,
http_port: 5001,
apdu_port: None,
debug: false,
root: None,
Expand All @@ -166,10 +175,13 @@ impl Options {
// Basic args
let mut args = vec![
format!("--model={}", self.model),
format!("--display={}", self.display),
format!("--api-port={}", self.http_port),
];

if let Some(display) = &self.display {
args.push(format!("--display={display}"));
}

if let Some(seed) = &self.seed {
args.push(format!("--seed={seed}"));
}
Expand Down Expand Up @@ -222,6 +234,9 @@ mod tests {
(Model::NanoSP, "nanosp", "nanosp"),
(Model::NanoSP, "nanosp", "nanosplus"),
(Model::NanoX, "nanox", "nanox"),
(Model::Stax, "stax", "stax"),
(Model::Flex, "flex", "flex"),
(Model::NanoGen5, "nanogen5", "nanogen5"),
];

for (model, enc, dec) in t {
Expand Down
Loading