use super::SeaError;
use base64::{engine::general_purpose, Engine as _};
use pbkdf2::pbkdf2_hmac;
use rand::RngCore;
use sha2::{Digest, Sha256};
use std::sync::Arc;
use tokio::task;
#[derive(Clone, Debug)]
pub struct WorkOptions {
pub name: Option<String>,
pub iterations: Option<u32>,
pub salt: Option<Vec<u8>>,
pub hash: Option<String>,
pub length: Option<usize>,
pub encode: Option<String>,
}
impl Default for WorkOptions {
fn default() -> Self {
Self {
name: Some("PBKDF2".to_string()),
iterations: Some(100_000),
salt: None,
hash: Some("SHA-256".to_string()),
length: Some(512), encode: Some("base64".to_string()),
}
}
}
pub async fn work(
data: &[u8],
salt_or_pair: Option<Vec<u8>>,
opt: WorkOptions,
) -> Result<String, SeaError> {
let opt = Arc::new(opt);
let data = data.to_vec();
let name_lower = opt
.name
.as_ref()
.map(|n| n.to_lowercase())
.unwrap_or_else(|| "pbkdf2".to_string());
if name_lower.starts_with("sha") {
return task::spawn_blocking(move || {
let mut hasher = Sha256::new();
hasher.update(&data);
let hash = hasher.finalize();
let encoded = match opt.encode.as_deref().unwrap_or("base64") {
"base64" => general_purpose::STANDARD_NO_PAD.encode(hash),
"hex" => hex::encode(hash),
_ => general_purpose::STANDARD_NO_PAD.encode(hash),
};
Ok(encoded)
})
.await
.map_err(|e| SeaError::Crypto(format!("Task join error: {}", e)))?
.map_err(|e: SeaError| e);
}
let salt = if let Some(ref salt) = salt_or_pair {
salt.clone()
} else if let Some(ref opt_salt) = opt.salt {
opt_salt.clone()
} else {
let mut salt_bytes = vec![0u8; 9];
rand::thread_rng().fill_bytes(&mut salt_bytes);
salt_bytes
};
let iterations = opt.iterations.unwrap_or(100_000);
let length_bits = opt.length.unwrap_or(512);
let length_bytes = length_bits / 8;
let result = task::spawn_blocking(move || {
let mut output = vec![0u8; length_bytes];
pbkdf2_hmac::<Sha256>(&data, &salt, iterations, &mut output);
output
})
.await
.map_err(|e| SeaError::Crypto(format!("Task join error: {}", e)))?;
let encoded = match opt.encode.as_deref().unwrap_or("base64") {
"base64" => general_purpose::STANDARD_NO_PAD.encode(&result),
"hex" => hex::encode(&result),
_ => general_purpose::STANDARD_NO_PAD.encode(&result),
};
Ok(encoded)
}
pub async fn work_string(
data: &str,
salt_or_pair: Option<Vec<u8>>,
opt: WorkOptions,
) -> Result<String, SeaError> {
work(data.as_bytes(), salt_or_pair, opt).await
}
pub async fn work_json<T: serde::Serialize>(
data: &T,
salt_or_pair: Option<Vec<u8>>,
opt: WorkOptions,
) -> Result<String, SeaError> {
let json_str = serde_json::to_string(data)
.map_err(|e| SeaError::Crypto(format!("JSON serialization error: {}", e)))?;
work(json_str.as_bytes(), salt_or_pair, opt).await
}