Base opentitantool application.
1. Establish the basic `opentitantool` framework.
2. Create a procedural macro for deriving `CommandDispatch` for interior
nodes in the command hierarchy.
3. Create some sample `hello` commands in `hello.rs`.
Signed-off-by: Chris Frantz <cfrantz@google.com>
diff --git a/sw/host/opentitanlib/Cargo.toml b/sw/host/opentitanlib/Cargo.toml
index 3309988..85b7870 100644
--- a/sw/host/opentitanlib/Cargo.toml
+++ b/sw/host/opentitanlib/Cargo.toml
@@ -15,3 +15,6 @@
nix = "0.17.0"
bitflags = "1.0"
log = "0.4"
+
+erased-serde = "0.3.12"
+opentitantool_derive = {path = "opentitantool_derive"}
diff --git a/sw/host/opentitanlib/opentitantool_derive/Cargo.toml b/sw/host/opentitanlib/opentitantool_derive/Cargo.toml
new file mode 100644
index 0000000..39f5f9d
--- /dev/null
+++ b/sw/host/opentitanlib/opentitantool_derive/Cargo.toml
@@ -0,0 +1,18 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+[package]
+name = "opentitantool_derive"
+version = "0.1.0"
+authors = ["lowRISC contributors"]
+edition = "2018"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "1.0"
+proc-macro-error = "1.0"
+quote = "1.0"
+syn = "1.0"
diff --git a/sw/host/opentitanlib/opentitantool_derive/src/lib.rs b/sw/host/opentitanlib/opentitantool_derive/src/lib.rs
new file mode 100644
index 0000000..3c000c6
--- /dev/null
+++ b/sw/host/opentitanlib/opentitantool_derive/src/lib.rs
@@ -0,0 +1,105 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+use proc_macro2::TokenStream;
+use proc_macro_error::{abort, proc_macro_error};
+use quote::quote;
+use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Fields, Ident, Variant};
+
+/// Derives the `CommandDispatch` trait for a NewType enum.
+///
+/// Derive this on purely interior nodes in the opentitantool command hierarchy
+/// (that is: nodes whose only purpose is to dispatch down to the next layer
+/// in the command heirarchy). The automatic derivation simply calls the
+/// `run` method on the next layer in the command hierarchy.
+///
+/// Imagine that you have structs `World` and `People` which implement
+/// "Hello World" and "Hello People" commands. You would create Newtype
+/// variants inside of a `Hello` enum and derive `CommandDispatch` to
+/// generate the dispatch layer for this interior node in the command hierarchy:
+///
+/// ```
+/// #[derive(StructOpt, CommandDispatch)]
+/// pub enum Hello {
+/// World(World),
+/// People(People),
+/// }
+///
+/// // The derived code is as follows:
+/// impl opentitanlib::app::command::CommandDispatch for Hello {
+/// fn run(
+/// &self,
+/// backend: &mut dyn opentitanlib::transport::Transport
+/// ) -> anyhow::Result<Option<Box<dyn erased_serde::Serialize>>> {
+/// match self {
+/// Hello::World(ref __field) => __field.run(backend),
+/// Hello::People(ref __field) => __field.run(backend),
+/// }
+/// }
+/// }
+/// ```
+#[proc_macro_derive(CommandDispatch)]
+#[proc_macro_error]
+pub fn derive_command_dispatch(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+
+ match &input.data {
+ Data::Enum(e) => dispatch_enum(&input.ident, e).into(),
+ _ => abort!(
+ input.ident.span(),
+ "CommandDispatch is only implemented for enums"
+ ),
+ }
+}
+
+fn dispatch_enum(name: &Ident, e: &DataEnum) -> TokenStream {
+ let arms = e
+ .variants
+ .iter()
+ .map(|variant| dispatch_variant(name, variant))
+ .collect::<Vec<_>>();
+
+ // We wrap the derived code inside an anonymous const block to give the
+ // `extern crate` references a local namespace that wont pollute the
+ // global namespace.
+ quote! {
+ const _: () = {
+ extern crate opentitanlib;
+ extern crate anyhow;
+ extern crate erased_serde;
+
+ impl opentitanlib::app::command::CommandDispatch for #name {
+ fn run(
+ &self,
+ backend: &mut dyn opentitanlib::transport::Transport
+ ) -> anyhow::Result<Option<Box<dyn erased_serde::Serialize>>> {
+ match self {
+ #(#arms),*
+ }
+ }
+ }
+ };
+ }
+}
+
+fn dispatch_variant(name: &Ident, variant: &Variant) -> TokenStream {
+ let ident = &variant.ident;
+ let unnamed_len = match &variant.fields {
+ Fields::Unnamed(u) => u.unnamed.len(),
+ _ => abort!(
+ variant.ident.span(),
+ "CommandDispatch is only implemented for Newtype variants"
+ ),
+ };
+ if unnamed_len != 1 {
+ abort!(
+ variant.ident.span(),
+ "CommandDispatch is only implemented for Newtype variants"
+ );
+ }
+ quote! {
+ #name::#ident(ref __field) =>
+ opentitanlib::app::command::CommandDispatch::run(__field, backend)
+ }
+}
diff --git a/sw/host/opentitanlib/src/app/command.rs b/sw/host/opentitanlib/src/app/command.rs
new file mode 100644
index 0000000..b275cce
--- /dev/null
+++ b/sw/host/opentitanlib/src/app/command.rs
@@ -0,0 +1,16 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::transport::Transport;
+use anyhow::Result;
+use erased_serde::Serialize;
+pub use opentitantool_derive::*;
+
+/// The `CommandDispatch` trait should be implemented for all leaf structures
+/// in the application's command hierarchy. It can be automatically derived
+/// on internal nodes in the heirarchy. See the documentation for
+/// [`opentitantool_derive`].
+pub trait CommandDispatch {
+ fn run(&self, transport: &mut dyn Transport) -> Result<Option<Box<dyn Serialize>>>;
+}
diff --git a/sw/host/opentitanlib/src/app/mod.rs b/sw/host/opentitanlib/src/app/mod.rs
new file mode 100644
index 0000000..a371aaa
--- /dev/null
+++ b/sw/host/opentitanlib/src/app/mod.rs
@@ -0,0 +1,6 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+//! Useful modules for OpenTitanTool application development.
+pub mod command;
diff --git a/sw/host/opentitanlib/src/lib.rs b/sw/host/opentitanlib/src/lib.rs
index 3825007..ab50890 100644
--- a/sw/host/opentitanlib/src/lib.rs
+++ b/sw/host/opentitanlib/src/lib.rs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
+pub mod app;
pub mod io;
pub mod transport;
pub mod util;
diff --git a/sw/host/opentitantool/Cargo.toml b/sw/host/opentitantool/Cargo.toml
new file mode 100644
index 0000000..7345c24
--- /dev/null
+++ b/sw/host/opentitantool/Cargo.toml
@@ -0,0 +1,24 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+[package]
+name = "opentitantool"
+version = "0.1.0"
+authors = ["lowRISC contributors"]
+edition = "2018"
+
+[dependencies]
+anyhow = "1.0"
+thiserror = "1.0"
+opentitanlib = {path="../opentitanlib"}
+structopt = "0.3"
+log = "0.4"
+env_logger = "0.8.3"
+raw_tty = "0.1.0"
+regex = "1"
+nix = "0.17.0"
+
+serde = {version="1", features=["serde_derive"]}
+serde_json = "1"
+erased-serde = "0.3.12"
diff --git a/sw/host/opentitantool/src/backend/mod.rs b/sw/host/opentitantool/src/backend/mod.rs
new file mode 100644
index 0000000..544be3c
--- /dev/null
+++ b/sw/host/opentitantool/src/backend/mod.rs
@@ -0,0 +1,29 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+use anyhow::Result;
+use structopt::StructOpt;
+use thiserror::Error;
+
+use opentitanlib::transport::{EmptyTransport, Transport};
+
+#[derive(Debug, StructOpt)]
+pub struct BackendOpts {
+ #[structopt(long, default_value)]
+ interface: String,
+}
+
+#[derive(Error, Debug)]
+pub enum Error {
+ #[error("unknown interface {0}")]
+ UnknownInterface(String),
+}
+
+/// Creates the requested backend interface according to [`BackendOpts`].
+pub fn create(args: &BackendOpts) -> Result<Box<dyn Transport>> {
+ match args.interface.as_str() {
+ "" => Ok(Box::new(EmptyTransport)),
+ _ => Err(Error::UnknownInterface(args.interface.clone()).into()),
+ }
+}
diff --git a/sw/host/opentitantool/src/command/hello.rs b/sw/host/opentitantool/src/command/hello.rs
new file mode 100644
index 0000000..4df9860
--- /dev/null
+++ b/sw/host/opentitantool/src/command/hello.rs
@@ -0,0 +1,92 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+//! Sample command heirarchy.
+//!
+//! This module implements a sample command heirarchy to demonstrate
+//! the command framework for opentitantool.
+
+use anyhow::Result;
+use erased_serde::Serialize;
+use structopt::StructOpt;
+
+use opentitanlib::app::command::CommandDispatch;
+use opentitanlib::transport::Transport;
+
+#[derive(Debug, StructOpt)]
+/// The `hello world` command accepts an command option of `--cruel`.
+pub struct HelloWorld {
+ #[structopt(short, long)]
+ cruel: bool,
+}
+
+#[derive(serde::Serialize)]
+/// The [`HelloMessage`] is the result of the `hello world` command.
+pub struct HelloMessage {
+ pub greeting: String,
+}
+
+impl CommandDispatch for HelloWorld {
+ fn run(&self, _transport: &mut dyn Transport) -> Result<Option<Box<dyn Serialize>>> {
+ // Is the world cruel or not?
+ let msg = if self.cruel {
+ "Hello cruel World!"
+ } else {
+ "Hello World!"
+ };
+ Ok(Some(Box::new(HelloMessage {
+ greeting: msg.to_owned(),
+ })))
+ }
+}
+
+#[derive(Debug, StructOpt)]
+/// The `hello people` command takes no additional switches or arguments.
+pub struct HelloPeople {
+ // Unit structs not allowed by StructOpt
+}
+
+impl CommandDispatch for HelloPeople {
+ fn run(&self, _transport: &mut dyn Transport) -> Result<Option<Box<dyn Serialize>>> {
+ // The `hello people` command produces no result.
+ Ok(None)
+ }
+}
+
+#[derive(Debug, StructOpt, CommandDispatch)]
+/// There are two types of `hello` subcommand; this enum binds them together
+/// so they can both be under the `hello` command. This enum also derives
+/// `CommandDispatch` which automatically builds a `run` method for this
+/// type which dispatches to the subcommands.
+pub enum HelloTypes {
+ World(HelloWorld),
+ People(HelloPeople),
+}
+
+#[derive(Debug, StructOpt)]
+/// The `goodbye` command takes no options or arguments.
+pub struct GoodbyeCommand {}
+
+#[derive(serde::Serialize)]
+/// The [`GoodbyeMessage`] is the result of the `goodbye` command.
+pub struct GoodbyeMessage {
+ pub message: String,
+}
+
+impl CommandDispatch for GoodbyeCommand {
+ fn run(&self, _transport: &mut dyn Transport) -> Result<Option<Box<dyn Serialize>>> {
+ Ok(Some(Box::new(GoodbyeMessage {
+ message: "Goodbye!".to_owned(),
+ })))
+ }
+}
+
+#[derive(Debug, StructOpt, CommandDispatch)]
+/// The [`Greetings`] enum binds the `hello` and `goodbye` command hirarchies
+/// in this module together into a single type which can be included into
+/// the root of the command heirarchy in `main.rs`.
+pub enum Greetings {
+ Hello(HelloTypes),
+ Goodbye(GoodbyeCommand),
+}
diff --git a/sw/host/opentitantool/src/command/mod.rs b/sw/host/opentitantool/src/command/mod.rs
new file mode 100644
index 0000000..950ba35
--- /dev/null
+++ b/sw/host/opentitantool/src/command/mod.rs
@@ -0,0 +1,5 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+pub mod hello;
diff --git a/sw/host/opentitantool/src/main.rs b/sw/host/opentitantool/src/main.rs
new file mode 100644
index 0000000..3dbbb2b
--- /dev/null
+++ b/sw/host/opentitantool/src/main.rs
@@ -0,0 +1,44 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+use anyhow::Result;
+use log::LevelFilter;
+use structopt::StructOpt;
+
+mod backend;
+mod command;
+use opentitanlib::app::command::CommandDispatch;
+
+#[derive(Debug, StructOpt, CommandDispatch)]
+enum RootCommandHierarchy {
+ // Flattened because `Greetings` is a subcommand hierarchy.
+ #[structopt(flatten)]
+ Greetings(command::hello::Greetings),
+}
+
+#[derive(Debug, StructOpt)]
+struct Opts {
+ #[structopt(long, default_value = "off")]
+ logging: LevelFilter,
+
+ #[structopt(flatten)]
+ backend_opts: backend::BackendOpts,
+
+ #[structopt(subcommand)]
+ command: RootCommandHierarchy,
+}
+
+fn main() -> Result<()> {
+ let opts = Opts::from_args();
+ env_logger::Builder::from_default_env()
+ .filter(None, opts.logging)
+ .init();
+
+ let mut interface = backend::create(&opts.backend_opts)?;
+
+ if let Some(value) = opts.command.run(&mut *interface)? {
+ println!("{}", serde_json::to_string_pretty(&value)?);
+ }
+ Ok(())
+}