external-command-rs 0.1.3

External tool in Rust
Documentation
use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use rust_util::{XResult, debugging, opt_result, simple_error};
use serde::{Deserialize, Serialize, de};
use serde_json::Value;
use std::process::{Command, Output};

#[derive(Debug, Serialize, Deserialize)]
struct ErrorResult {
    pub success: bool,
    pub error: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ExternalSpecResult {
    pub success: bool,
    pub agent: String,
    pub specification: String,
    pub commands: Vec<String>,
}

#[derive(Debug, Serialize, Deserialize)]
struct ExternalPublicKeyResult {
    pub success: bool,
    pub public_key_base64: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct ExternalSignResult {
    pub success: bool,
    pub signature_base64: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct ExternalDhResult {
    pub success: bool,
    pub shared_secret_hex: String,
}

pub struct ExternalCommand {
    pub external_command: String,
    pub parameter: String,
}

impl ExternalCommand {
    pub fn new(external_command: String, parameter: String) -> ExternalCommand {
        ExternalCommand {
            external_command,
            parameter,
        }
    }

    pub fn spec(&self) -> XResult<ExternalSpecResult> {
        external_spec(&self.external_command)
    }

    pub fn public_key(&self) -> XResult<Vec<u8>> {
        external_public_key(&self.external_command, &self.parameter)
    }

    pub fn sign(&self, alg: &str, content: &[u8]) -> XResult<Vec<u8>> {
        external_sign(&self.external_command, &self.parameter, alg, content)
    }

    pub fn ecdh(&self, ephemera_public_key: &[u8]) -> XResult<Vec<u8>> {
        external_ecdh(&self.external_command, &self.parameter, ephemera_public_key)
    }
}

pub fn external_spec(external_command: &str) -> XResult<ExternalSpecResult> {
    let mut cmd = Command::new(external_command);
    cmd.arg("external_spec");

    let cmd_stdout = run_command_stdout(cmd)?;
    if is_success(&cmd_stdout)? {
        let external_spec: ExternalSpecResult = from_str(&cmd_stdout)?;
        Ok(external_spec)
    } else {
        let error_result: ErrorResult = from_str(&cmd_stdout)?;
        simple_error!("{}", error_result.error)
    }
}

pub fn external_public_key(external_command: &str, parameter: &str) -> XResult<Vec<u8>> {
    let mut cmd = Command::new(external_command);
    cmd.arg("external_public_key");
    cmd.arg("--parameter");
    cmd.arg(parameter);

    let cmd_stdout = run_command_stdout(cmd)?;
    if is_success(&cmd_stdout)? {
        let external_public_key: ExternalPublicKeyResult = from_str(&cmd_stdout)?;
        Ok(STANDARD.decode(&external_public_key.public_key_base64)?)
    } else {
        let error_result: ErrorResult = from_str(&cmd_stdout)?;
        simple_error!("{}", error_result.error)
    }
}

pub fn external_sign(
    external_command: &str,
    parameter: &str,
    alg: &str,
    content: &[u8],
) -> XResult<Vec<u8>> {
    external_sign_digested(external_command, parameter, alg, content, "")
}

pub fn external_sign_digested(
    external_command: &str,
    parameter: &str,
    alg: &str,
    content: &[u8],
    digest_type: &str,
) -> XResult<Vec<u8>> {
    let mut cmd = Command::new(external_command);
    cmd.arg("external_sign");
    cmd.arg("--parameter");
    cmd.arg(parameter);
    cmd.arg("--alg");
    cmd.arg(alg);
    cmd.arg("--message-base64");
    cmd.arg(STANDARD.encode(content));
    if !digest_type.is_empty() && digest_type != "raw" {
        cmd.arg("--message-type");
        cmd.arg(digest_type);
    }

    let cmd_stdout = run_command_stdout(cmd)?;
    parse_sign_result(&cmd_stdout)
}

pub fn external_ecdh(
    external_command: &str,
    parameter: &str,
    ephemera_public_key: &[u8],
) -> XResult<Vec<u8>> {
    let mut cmd = Command::new(external_command);
    cmd.arg("external_ecdh");
    cmd.arg("--parameter");
    cmd.arg(parameter);
    cmd.arg("--epk");
    cmd.arg(STANDARD.encode(ephemera_public_key));

    let cmd_stdout = run_command_stdout(cmd)?;
    parse_ecdh_result(&cmd_stdout)
}

fn run_command_stdout(cmd: Command) -> XResult<String> {
    let output = run_command(cmd)?;
    let stdout_text = opt_result!(String::from_utf8(output.stdout), "Parse stdout failed:{}");
    Ok(stdout_text.trim().to_string())
}

fn run_command(mut cmd: Command) -> XResult<Output> {
    debugging!("Run command: {:?}", cmd);
    let output = cmd.output();
    match output {
        Err(e) => simple_error!("Run command failed: {:?}", e),
        Ok(output) => {
            debugging!("Output: {:?}", output);
            if !output.status.success() {
                let stderr = String::from_utf8_lossy(&output.stderr);
                let stdout = String::from_utf8_lossy(&output.stdout);
                simple_error!(
                    "Run command not success: {:?}\n - stdout: {}\n - stderr: {}",
                    output.status.code(),
                    stdout,
                    stderr
                )
            } else {
                Ok(output)
            }
        }
    }
}

fn parse_sign_result(stdout: &str) -> XResult<Vec<u8>> {
    if is_success(stdout)? {
        let sign_result: ExternalSignResult = from_str(stdout)?;
        Ok(STANDARD.decode(&sign_result.signature_base64)?)
    } else {
        let error_result: ErrorResult = from_str(stdout)?;
        simple_error!("{}", error_result.error)
    }
}

fn parse_ecdh_result(stdout: &str) -> XResult<Vec<u8>> {
    if is_success(stdout)? {
        let dh_result: ExternalDhResult = from_str(stdout)?;
        Ok(hex::decode(&dh_result.shared_secret_hex)?)
    } else {
        let error_result: ErrorResult = from_str(stdout)?;
        simple_error!("{}", error_result.error)
    }
}

pub fn from_str<'a, T>(s: &'a str) -> XResult<T>
where
    T: de::Deserialize<'a>,
{
    match serde_json::from_str(s) {
        Ok(result) => Ok(result),
        Err(e) => simple_error!("Parse JSON: {}, error: {}", s, e),
    }
}

fn is_success(cmd_stdout: &str) -> XResult<bool> {
    let val = opt_result!(
        serde_json::from_str::<Value>(cmd_stdout),
        "Parse result: {}, failed: {}",
        cmd_stdout
    );
    if let Value::Object(map) = val {
        if let Some(success_value) = map.get("success") {
            if let Value::Bool(result) = success_value {
                return Ok(*result);
            }
        }
    }
    simple_error!("Bad result: {}", cmd_stdout)
}