use crate::Hasher;
use rayon::ThreadPoolBuilder;
use tokio::sync::oneshot;
#[derive(Debug, thiserror::Error)]
pub enum PasswordWorkerError<H: Hasher> {
#[error("Hashing error: {0}")]
Hashing(H::Error),
#[error("Channel send error: {0}")]
ChannelSend(#[from] crossbeam_channel::SendError<WorkerCommand<H>>),
#[error("Channel receive error: {0}")]
ChannelRecv(#[from] tokio::sync::oneshot::error::RecvError),
#[error("ThreadPool build error: {0}")]
ThreadPool(#[from] rayon::ThreadPoolBuildError),
}
#[derive(Debug)]
pub enum WorkerCommand<H: Hasher> {
Hash(
String,
H::Config,
oneshot::Sender<Result<String, PasswordWorkerError<H>>>,
),
Verify(
String,
String,
oneshot::Sender<Result<bool, PasswordWorkerError<H>>>,
),
}
#[derive(Debug, Clone)]
pub struct PasswordWorker<H: Hasher> {
sender: crossbeam_channel::Sender<WorkerCommand<H>>,
}
impl<H: Hasher> PasswordWorker<H> {
pub fn new(max_threads: usize) -> Result<Self, PasswordWorkerError<H>> {
let (sender, receiver) = crossbeam_channel::unbounded::<WorkerCommand<H>>();
let thread_pool = ThreadPoolBuilder::new().num_threads(max_threads).build()?;
std::thread::spawn(move || {
while let Ok(command) = receiver.recv() {
match command {
WorkerCommand::Hash(password, cost, result_sender) => {
let result = thread_pool.install(|| H::hash(&password, &cost));
result_sender
.send(result.map_err(PasswordWorkerError::Hashing))
.ok()?;
}
WorkerCommand::Verify(password, hash, result_sender) => {
let result = thread_pool.install(|| H::verify(&password, &hash));
result_sender
.send(result.map_err(PasswordWorkerError::Hashing))
.ok()?;
}
}
}
Some(())
});
Ok(PasswordWorker { sender })
}
pub async fn hash(
&self,
password: impl Into<String>,
cost: H::Config,
) -> Result<String, PasswordWorkerError<H>> {
let (tx, rx) = oneshot::channel();
self.sender
.send(WorkerCommand::Hash(password.into(), cost, tx))?;
rx.await?
}
pub async fn verify(
&self,
password: impl Into<String>,
hash: impl Into<String>,
) -> Result<bool, PasswordWorkerError<H>> {
let (tx, rx) = oneshot::channel();
self.sender
.send(WorkerCommand::Verify(password.into(), hash.into(), tx))?;
rx.await?
}
}