use sha2::{Digest, Sha256, Sha512};
use crate::{
error::{EvalError, EvalResult, InterpreterError},
value::Value,
};
pub fn has_function(name: &str) -> bool {
matches!(name, "sha256" | "sha512")
}
pub fn call(func: &str, args: &[Value]) -> EvalResult {
match func {
"sha256" => {
let input = arg_bytes(func, args)?;
let bytes = Sha256::digest(&input).to_vec();
Ok(Value::HashDigest { algo: "sha256".to_string(), bytes })
}
"sha512" => {
let input = arg_bytes(func, args)?;
let bytes = Sha512::digest(&input).to_vec();
Ok(Value::HashDigest { algo: "sha512".to_string(), bytes })
}
_ => Err(InterpreterError::AttributeError(format!(
"module 'hashlib' has no attribute '{func}'"
))
.into()),
}
}
pub fn dispatch_hash_method(
algo: &str,
bytes: &[u8],
method: &str,
_args: &[Value],
kwargs: &indexmap::IndexMap<String, Value>,
) -> EvalResult {
crate::eval::functions::reject_kwargs(method, kwargs)?;
match method {
"hexdigest" => Ok(Value::String(hex::encode(bytes).into())),
"digest" => Ok(Value::Bytes(bytes.to_vec())),
"digest_size" | "block_size" | "name" => match method {
"name" => Ok(Value::String(algo.into())),
"digest_size" => {
#[expect(
clippy::cast_possible_wrap,
reason = "hash output lengths are 32 (sha256) or 64 (sha512) bytes — bounded by the sha2 crate's fixed-size GenericArray, well below i64::MAX"
)]
let size = bytes.len() as i64;
Ok(Value::Int(size))
}
"block_size" => Ok(Value::Int(match algo {
"sha256" => 64,
"sha512" => 128,
_ => 0,
})),
_ => unreachable!(),
},
_ => Err(InterpreterError::AttributeError(format!(
"'{algo} HASH' object has no attribute '{method}'"
))
.into()),
}
}
pub fn hash_attribute(algo: &str, bytes: &[u8], attr: &str) -> EvalResult {
match attr {
"name" => Ok(Value::String(algo.into())),
"digest_size" => {
#[expect(
clippy::cast_possible_wrap,
reason = "hash output lengths are 32 (sha256) or 64 (sha512) bytes — bounded by the sha2 crate's fixed-size GenericArray, well below i64::MAX"
)]
let size = bytes.len() as i64;
Ok(Value::Int(size))
}
"block_size" => Ok(Value::Int(match algo {
"sha256" => 64,
"sha512" => 128,
_ => 0,
})),
_ => Err(InterpreterError::AttributeError(format!(
"'{algo} HASH' object has no attribute '{attr}'"
))
.into()),
}
}
fn arg_bytes(func: &str, args: &[Value]) -> Result<Vec<u8>, EvalError> {
let value = args.first().ok_or_else(|| {
EvalError::from(InterpreterError::TypeError(format!("{func}() missing required argument")))
})?;
match value {
Value::Bytes(b) => Ok(b.clone()),
other => Err(InterpreterError::TypeError(format!(
"Strings must be encoded before hashing (got '{}')",
other.type_name()
))
.into()),
}
}
pub struct HashlibModule;
#[async_trait::async_trait]
impl crate::eval::modules::Module for HashlibModule {
fn name(&self) -> &'static str {
"hashlib"
}
fn has_function(&self, name: &str) -> bool {
has_function(name)
}
async fn call(
&self,
_state: &mut crate::state::InterpreterState,
func: &str,
args: &[Value],
_kwargs: &indexmap::IndexMap<String, Value>,
_tools: &crate::tools::Tools,
) -> EvalResult {
call(func, args)
}
}