use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use zeroize::Zeroizing;
use crate::decrypt;
use crate::encrypt;
use crate::error::PqfileError;
use crate::format::CHUNK_SIZE;
#[must_use = "encryption result must be checked"]
pub async fn encrypt_stream_async<R, W>(
pubkey_pem: &str,
original_size: u64,
chunk_size: usize,
reader: &mut R,
writer: &mut W,
) -> Result<(), PqfileError>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
if chunk_size == 0 {
return Err(PqfileError::EncryptionFailure);
}
let mut plaintext = Vec::with_capacity(original_size.min(64 * 1024 * 1024) as usize);
reader
.read_to_end(&mut plaintext)
.await
.map_err(PqfileError::Io)?;
let mut ct = Vec::new();
encrypt::encrypt_stream(
pubkey_pem,
plaintext.len() as u64,
chunk_size,
&mut plaintext.as_slice(),
&mut ct,
)?;
writer.write_all(&ct).await.map_err(PqfileError::Io)?;
writer.flush().await.map_err(PqfileError::Io)?;
Ok(())
}
#[must_use = "decryption result must be checked"]
pub async fn decrypt_stream_async<R, W>(
privkey_pem: &str,
reader: R,
writer: &mut W,
passphrase: Option<&str>,
) -> Result<(), PqfileError>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
let mut ct_reader = tokio::io::BufReader::new(reader);
let mut ct = Vec::new();
ct_reader
.read_to_end(&mut ct)
.await
.map_err(PqfileError::Io)?;
let mut plaintext = Zeroizing::new(Vec::new());
decrypt::decrypt_stream(privkey_pem, &mut ct.as_slice(), &mut *plaintext, passphrase)?;
writer
.write_all(&plaintext)
.await
.map_err(PqfileError::Io)?;
writer.flush().await.map_err(PqfileError::Io)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::keygen::keygen_bytes;
#[tokio::test]
async fn async_roundtrip_small() {
let (pub_pem, priv_pem) = keygen_bytes(768, None).unwrap();
let plaintext = b"hello async pqfile world";
let mut ct = Vec::new();
encrypt_stream_async(
&pub_pem,
plaintext.len() as u64,
CHUNK_SIZE,
&mut plaintext.as_slice(),
&mut ct,
)
.await
.unwrap();
let mut out = Vec::new();
decrypt_stream_async(&priv_pem, ct.as_slice(), &mut out, None)
.await
.unwrap();
assert_eq!(out, plaintext);
}
#[tokio::test]
async fn async_roundtrip_multi_chunk() {
let (pub_pem, priv_pem) = keygen_bytes(768, None).unwrap();
let plaintext: Vec<u8> = (0u8..=255).cycle().take(CHUNK_SIZE * 3 + 7).collect();
let mut ct = Vec::new();
encrypt_stream_async(
&pub_pem,
plaintext.len() as u64,
CHUNK_SIZE,
&mut plaintext.as_slice(),
&mut ct,
)
.await
.unwrap();
let mut out = Vec::new();
decrypt_stream_async(&priv_pem, ct.as_slice(), &mut out, None)
.await
.unwrap();
assert_eq!(out, plaintext);
}
#[tokio::test]
async fn async_roundtrip_empty() {
let (pub_pem, priv_pem) = keygen_bytes(768, None).unwrap();
let mut ct = Vec::new();
encrypt_stream_async(&pub_pem, 0, CHUNK_SIZE, &mut [].as_slice(), &mut ct)
.await
.unwrap();
let mut out = Vec::new();
decrypt_stream_async(&priv_pem, ct.as_slice(), &mut out, None)
.await
.unwrap();
assert!(out.is_empty());
}
#[tokio::test]
async fn async_roundtrip_with_passphrase() {
let (pub_pem, priv_pem) = keygen_bytes(768, Some("async-pass")).unwrap();
let plaintext = b"passphrase-protected async roundtrip";
let mut ct = Vec::new();
encrypt_stream_async(
&pub_pem,
plaintext.len() as u64,
CHUNK_SIZE,
&mut plaintext.as_slice(),
&mut ct,
)
.await
.unwrap();
let mut out = Vec::new();
decrypt_stream_async(&priv_pem, ct.as_slice(), &mut out, Some("async-pass"))
.await
.unwrap();
assert_eq!(out, plaintext.as_slice());
}
#[tokio::test]
async fn async_ciphertext_matches_sync() {
use crate::decrypt::decrypt_stream as sync_decrypt;
use crate::encrypt::encrypt_stream as sync_encrypt;
let (pub_pem, priv_pem) = keygen_bytes(768, None).unwrap();
let plaintext = b"async / sync interop check";
let mut ct = Vec::new();
encrypt_stream_async(
&pub_pem,
plaintext.len() as u64,
CHUNK_SIZE,
&mut plaintext.as_slice(),
&mut ct,
)
.await
.unwrap();
let mut out = Vec::new();
sync_decrypt(&priv_pem, &mut ct.as_slice(), &mut out, None).unwrap();
assert_eq!(out, plaintext);
let mut ct2 = Vec::new();
sync_encrypt(
&pub_pem,
plaintext.len() as u64,
CHUNK_SIZE,
&mut plaintext.as_slice(),
&mut ct2,
)
.unwrap();
let mut out2 = Vec::new();
decrypt_stream_async(&priv_pem, ct2.as_slice(), &mut out2, None)
.await
.unwrap();
assert_eq!(out2, plaintext);
}
#[tokio::test]
async fn async_rejects_zero_chunk_size() {
let (pub_pem, _) = keygen_bytes(768, None).unwrap();
let result =
encrypt_stream_async(&pub_pem, 0, 0, &mut [].as_slice(), &mut Vec::new()).await;
assert!(result.is_err());
}
}