mirror of
https://github.com/trussed-dev/pc-usbip-runner.git
synced 2026-10-28 09:49:43 -07:00
165 lines
4.6 KiB
Rust
165 lines
4.6 KiB
Rust
//! USB/IP simulation of a Trussed device.
|
|
//!
|
|
//! This example contains a dummy app that responds with random data to the CTAPHID vendor command
|
|
//! 0x60. It can be tested with `nitropy nk3 list` and `nitropy nk3 rng`.
|
|
//!
|
|
//! For a more complete example, see the [usbip runner][] for the Nitrokey 3.
|
|
//!
|
|
//! [usbip runner]: https://github.com/Nitrokey/nitrokey-3-firmware/tree/main/runners/usbip
|
|
|
|
use std::path::PathBuf;
|
|
|
|
#[cfg(feature = "ctaphid")]
|
|
use ctaphid_dispatch::app::{Command, Error, VendorCommand};
|
|
|
|
use clap::Parser;
|
|
use clap_num::maybe_hex;
|
|
use littlefs2::{
|
|
const_ram_storage,
|
|
fs::{Allocation, Filesystem},
|
|
};
|
|
use littlefs2_core::{path, DynFilesystem};
|
|
use trussed::{
|
|
backend::{CoreOnly, NoId},
|
|
pipe::{ServiceEndpoint, TrussedChannel},
|
|
service::Service,
|
|
types::{CoreContext, NoData},
|
|
Client,
|
|
};
|
|
use trussed_core::syscall;
|
|
use trussed_usbip::{Platform, Store, Syscall};
|
|
|
|
/// USP/IP based virtualization a Trussed device.
|
|
#[derive(Parser, Debug)]
|
|
#[clap(about, version, author)]
|
|
struct Args {
|
|
/// USB Name string
|
|
#[clap(short, long, default_value = "Trussed")]
|
|
name: String,
|
|
|
|
/// USB Manufacturer string
|
|
#[clap(short, long, default_value = "Trussed")]
|
|
manufacturer: String,
|
|
|
|
/// Trussed state file
|
|
#[clap(long, default_value = "trussed-state.bin")]
|
|
state_file: PathBuf,
|
|
|
|
/// USB VID id
|
|
#[clap(short, long, parse(try_from_str=maybe_hex), default_value_t = 0x20a0)]
|
|
vid: u16,
|
|
|
|
/// USB PID id
|
|
#[clap(short, long, parse(try_from_str=maybe_hex), default_value_t = 0x42b2)]
|
|
pid: u16,
|
|
}
|
|
|
|
struct DummyApp<C: Client> {
|
|
client: C,
|
|
}
|
|
|
|
impl<C: Client> DummyApp<C> {
|
|
fn rng(&mut self, response: &mut heapless_bytes::BytesView) {
|
|
let bytes = syscall!(self.client.random_bytes(57)).bytes;
|
|
response.extend_from_slice(&bytes).unwrap();
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "ctaphid")]
|
|
const CTAPHID_COMMAND_RNG: Command = Command::Vendor(VendorCommand::H60);
|
|
|
|
#[cfg(feature = "ctaphid")]
|
|
impl<C: Client> ctaphid_dispatch::app::App<'_> for DummyApp<C> {
|
|
fn commands(&self) -> &'static [Command] {
|
|
&[CTAPHID_COMMAND_RNG]
|
|
}
|
|
|
|
fn call(
|
|
&mut self,
|
|
command: Command,
|
|
_request: &[u8],
|
|
response: &mut heapless_bytes::BytesView,
|
|
) -> Result<(), Error> {
|
|
match command {
|
|
CTAPHID_COMMAND_RNG => self.rng(response),
|
|
_ => return Err(Error::InvalidCommand),
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
struct Apps<C: Client> {
|
|
dummy: DummyApp<C>,
|
|
}
|
|
|
|
impl<'a> trussed_usbip::Apps<'a, CoreOnly> for Apps<trussed_usbip::Client<CoreOnly>> {
|
|
type Data = ();
|
|
|
|
fn new(
|
|
_service: &mut Service<Platform, CoreOnly>,
|
|
endpoints: &mut Vec<ServiceEndpoint<'static, NoId, NoData>>,
|
|
syscall: Syscall,
|
|
_data: (),
|
|
) -> Self {
|
|
static CHANNEL: TrussedChannel = TrussedChannel::new();
|
|
let (requester, responder) = CHANNEL.split().unwrap();
|
|
let context = CoreContext::new(path!("dummy").into());
|
|
endpoints.push(ServiceEndpoint::new(responder, context, &[]));
|
|
let client = trussed_usbip::Client::new(requester, syscall, None);
|
|
let dummy = DummyApp { client };
|
|
Self { dummy }
|
|
}
|
|
|
|
#[cfg(feature = "ctaphid")]
|
|
fn with_ctaphid_apps<T>(
|
|
&mut self,
|
|
f: impl FnOnce(&mut [&mut dyn ctaphid_dispatch::app::App<'a>]) -> T,
|
|
) -> T {
|
|
f(&mut [&mut self.dummy])
|
|
}
|
|
|
|
#[cfg(feature = "ccid")]
|
|
fn with_ccid_apps<T>(
|
|
&mut self,
|
|
f: impl FnOnce(&mut [&mut dyn apdu_dispatch::app::App]) -> T,
|
|
) -> T {
|
|
f(&mut [])
|
|
}
|
|
}
|
|
|
|
const_ram_storage!(RamStorage, 512 * 128);
|
|
|
|
fn ram_filesystem() -> &'static dyn DynFilesystem {
|
|
let storage = Box::leak(Box::new(RamStorage::new()));
|
|
Filesystem::format(storage).expect("failed to format RAM filesystem");
|
|
let alloc = Box::leak(Box::new(Allocation::new()));
|
|
let fs = Filesystem::mount(alloc, storage).expect("failed to mount RAM filesystem");
|
|
Box::leak(Box::new(fs))
|
|
}
|
|
|
|
fn main() {
|
|
pretty_env_logger::init();
|
|
|
|
let args = Args::parse();
|
|
|
|
// TODO: use filesystem storage for IFS
|
|
let store = Store {
|
|
ifs: ram_filesystem(),
|
|
efs: ram_filesystem(),
|
|
vfs: ram_filesystem(),
|
|
};
|
|
let options = trussed_usbip::usb::Options {
|
|
manufacturer: Some(args.manufacturer),
|
|
product: Some(args.name),
|
|
serial_number: None,
|
|
vid: args.vid,
|
|
pid: args.pid,
|
|
};
|
|
|
|
log::info!("Initializing Trussed");
|
|
let platform = Platform::new(store);
|
|
trussed_usbip::Builder::with_default_usb_classes(options)
|
|
.build::<Apps<_>>()
|
|
.exec(platform, ());
|
|
}
|