use aurora_core::{AuroraResult, Pipeline, Value, AuroraError};
use std::process::Command;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::io::Write;
fn find_tool(name: &str) -> bool {
Command::new("which")
.arg(name)
.output()
.ok()
.is_some_and(|o| o.status.success())
}
pub fn crypto_hash(input: &str, algo: Option<&str>) -> AuroraResult<Pipeline> {
let algo_name = algo.unwrap_or("sha256");
let is_file = std::path::Path::new(input).exists() && std::path::Path::new(input).is_file();
match algo_name {
"sha256" => {
if is_file && find_tool("sha256sum") {
hash_file_cmd("sha256sum", input, "sha256")
} else if is_file && find_tool("shasum") {
hash_file_cmd("shasum", input, "sha256")
} else {
hash_string_cmd("sha256sum", input, "sha256")
}
}
"md5" => {
if is_file && find_tool("md5sum") {
hash_file_cmd("md5sum", input, "md5")
} else if find_tool("md5sum") {
hash_string_cmd("md5sum", input, "md5")
} else if find_tool("shasum") {
hash_string_cmd("shasum", input, "md5")
} else {
return Err(AuroraError::ModuleError(
"md5 requires md5sum or shasum command".into()
));
}
}
_ => {
if find_tool(algo_name) {
hash_string_cmd(algo_name, input, algo_name)
} else {
return Err(AuroraError::InvalidInput(
format!("unknown algorithm: {algo_name}")
));
}
}
}
}
fn hash_file_cmd(tool: &str, path: &str, algo: &str) -> AuroraResult<Pipeline> {
let output = Command::new(tool)
.arg(path)
.output()
.map_err(|e| AuroraError::ModuleError(format!("{tool} failed: {e}")))?;
let stdout = String::from_utf8_lossy(&output.stdout);
let hash = stdout.split_whitespace().next().unwrap_or("").to_string();
Ok(Pipeline::table(
vec!["algorithm".into(), "hash".into()],
vec![vec![Value::String(algo.into()), Value::String(hash)]],
))
}
fn hash_string_cmd(tool: &str, input: &str, algo: &str) -> AuroraResult<Pipeline> {
let mut child = Command::new(tool)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.spawn()
.map_err(|e| AuroraError::ModuleError(format!("{tool} failed: {e}")))?;
if let Some(ref mut stdin) = child.stdin {
stdin.write_all(input.as_bytes())
.map_err(|e| AuroraError::ModuleError(format!("Write failed: {e}")))?;
}
drop(child.stdin.take());
let output = child.wait_with_output()
.map_err(|e| AuroraError::ModuleError(format!("{tool} failed: {e}")))?;
let hash = String::from_utf8_lossy(&output.stdout)
.split_whitespace()
.next()
.unwrap_or("")
.to_string();
Ok(Pipeline::table(
vec!["algorithm".into(), "hash".into()],
vec![vec![Value::String(algo.into()), Value::String(hash)]],
))
}
pub fn crypto_genkey(algo: Option<&str>) -> AuroraResult<Pipeline> {
let algo_name = algo.unwrap_or("aes-256");
use std::time::{SystemTime, UNIX_EPOCH};
let seed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let mut hasher = DefaultHasher::new();
seed.hash(&mut hasher);
let hash = hasher.finish();
let key = format!("{:016x}{:016x}", hash, hash);
Ok(Pipeline::table(
vec!["algorithm".into(), "key".into()],
vec![vec![Value::String(algo_name.into()), Value::String(key)]],
))
}