extern crate provider_archive;
use crate::keys::extract_keypair;
use nkeys::KeyPairType;
use provider_archive::*;
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use structopt::clap::AppSettings;
use structopt::StructOpt;
type Result<T> = ::std::result::Result<T, Box<dyn ::std::error::Error>>;
const GZIP_MAGIC: [u8; 2] = [0x1f, 0x8b];
#[derive(Debug, StructOpt, Clone)]
#[structopt(
global_settings(&[AppSettings::ColoredHelp, AppSettings::VersionlessSubcommands]),
name = "par")]
pub struct ParCli {
#[structopt(flatten)]
command: ParCliCommand,
}
#[derive(Debug, Clone, StructOpt)]
enum ParCliCommand {
#[structopt(name = "create")]
Create(CreateCommand),
#[structopt(name = "inspect")]
Inspect(InspectCommand),
#[structopt(name = "insert")]
Insert(InsertCommand),
}
#[derive(StructOpt, Debug, Clone)]
struct CreateCommand {
#[structopt(short = "c", long = "capid")]
capid: String,
#[structopt(short = "v", long = "vendor")]
vendor: String,
#[structopt(short = "r", long = "revision")]
revision: Option<i32>,
#[structopt(name = "version")]
version: Option<String>,
#[structopt(
short = "d",
long = "directory",
env = "WASH_KEYS",
hide_env_values = true
)]
directory: Option<String>,
#[structopt(short = "i", long = "issuer")]
issuer: Option<String>,
#[structopt(short = "s", long = "subject")]
subject: Option<String>,
#[structopt(short = "n", long = "name")]
name: String,
#[structopt(short = "a", long = "arch")]
arch: String,
#[structopt(short = "b", long = "binary")]
binary: String,
#[structopt(short = "o", long = "output")]
output: Option<String>,
#[structopt(long = "compress")]
compress: bool,
#[structopt(long = "disable-keygen")]
disable_keygen: bool,
}
#[derive(StructOpt, Debug, Clone)]
struct InspectCommand {
#[structopt(name = "archive")]
archive: String,
}
#[derive(StructOpt, Debug, Clone)]
struct InsertCommand {
#[structopt(name = "archive")]
archive: String,
#[structopt(short = "a", long = "arch")]
arch: String,
#[structopt(short = "b", long = "binary")]
binary: String,
#[structopt(
short = "d",
long = "directory",
env = "WASH_KEYS",
hide_env_values = true
)]
directory: Option<String>,
#[structopt(short = "i", long = "issuer")]
issuer: Option<String>,
#[structopt(short = "s", long = "subject")]
subject: Option<String>,
#[structopt(long = "disable-keygen")]
disable_keygen: bool,
}
pub fn handle_command(cli: ParCli) -> Result<()> {
match cli.command {
ParCliCommand::Create(cmd) => handle_create(cmd),
ParCliCommand::Inspect(cmd) => handle_inspect(cmd),
ParCliCommand::Insert(cmd) => handle_insert(cmd),
}
}
fn handle_create(cmd: CreateCommand) -> Result<()> {
let mut par = ProviderArchive::new(
&cmd.capid,
&cmd.name,
&cmd.vendor,
cmd.revision,
cmd.version,
);
let mut f = File::open(cmd.binary.clone())?;
let mut lib = Vec::new();
f.read_to_end(&mut lib)?;
let issuer = extract_keypair(
cmd.issuer,
Some(cmd.binary.clone()),
cmd.directory.clone(),
KeyPairType::Account,
cmd.disable_keygen,
)?;
let subject = extract_keypair(
cmd.subject,
Some(cmd.binary.clone()),
cmd.directory,
KeyPairType::Service,
cmd.disable_keygen,
)?;
par.add_library(&cmd.arch, &lib)
.map_err(|e| format!("{}", e))?;
let output = match cmd.output {
Some(path) => path,
None => format!(
"{}.par",
PathBuf::from(cmd.binary.clone())
.file_stem()
.unwrap()
.to_str()
.unwrap()
.to_string()
),
};
if par.write(&output, &issuer, &subject, cmd.compress).is_err() {
println!(
"Error writing PAR. Please ensure directory {:?} exists",
PathBuf::from(output).parent().unwrap(),
);
}
Ok(())
}
fn handle_inspect(cmd: InspectCommand) -> Result<()> {
let mut buf = Vec::new();
let mut f = File::open(&cmd.archive)?;
f.read_to_end(&mut buf)?;
let archive = ProviderArchive::try_load(&buf).map_err(|e| format!("{}", e))?;
let claims = archive.claims().unwrap();
let metadata = claims.metadata.unwrap();
use term_table::row::Row;
use term_table::table_cell::*;
use term_table::{Table, TableStyle};
let mut table = Table::new();
table.max_column_width = 68;
table.style = TableStyle::extended();
table.add_row(Row::new(vec![TableCell::new_with_alignment(
format!("{} - Provider Archive", metadata.name.unwrap()),
2,
Alignment::Center,
)]));
table.add_row(Row::new(vec![
TableCell::new("Public Key"),
TableCell::new_with_alignment(claims.subject, 1, Alignment::Right),
]));
table.add_row(Row::new(vec![
TableCell::new("Capability Contract ID"),
TableCell::new_with_alignment(metadata.capid, 1, Alignment::Right),
]));
table.add_row(Row::new(vec![
TableCell::new("Vendor"),
TableCell::new_with_alignment(metadata.vendor, 1, Alignment::Right),
]));
table.add_row(Row::new(vec![TableCell::new_with_alignment(
"Supported Architecture Targets",
2,
Alignment::Center,
)]));
table.add_row(Row::new(vec![TableCell::new_with_alignment(
archive.targets().join("\n"),
2,
Alignment::Left,
)]));
println!("{}", table.render());
Ok(())
}
fn handle_insert(cmd: InsertCommand) -> Result<()> {
let mut buf = Vec::new();
let mut f = File::open(cmd.archive.clone())?;
f.read_to_end(&mut buf)?;
let mut par = ProviderArchive::try_load(&buf).map_err(|e| format!("{}", e))?;
let issuer = extract_keypair(
cmd.issuer,
Some(cmd.binary.clone()),
cmd.directory.clone(),
KeyPairType::Account,
cmd.disable_keygen,
)?;
let subject = extract_keypair(
cmd.subject,
Some(cmd.binary.clone()),
cmd.directory,
KeyPairType::Service,
cmd.disable_keygen,
)?;
let mut f = File::open(cmd.binary.clone())?;
let mut lib = Vec::new();
f.read_to_end(&mut lib)?;
par.add_library(&cmd.arch, &lib)
.map_err(|e| format!("{}", e))?;
par.write(
&cmd.archive,
&issuer,
&subject,
is_compressed(&buf).map_err(|e| format!("{}", e))?,
)
.map_err(|e| format!("{}", e))?;
Ok(())
}
fn is_compressed(input: &[u8]) -> Result<bool> {
if input.len() < 2 {
return Err("Not enough bytes to be a valid PAR file".into());
}
Ok(input[0..2] == GZIP_MAGIC)
}