From 91b19a803ec6278629e36c6e3fd0d36b0ac76508 Mon Sep 17 00:00:00 2001 From: javalsai Date: Wed, 25 Feb 2026 18:36:06 +0100 Subject: [PATCH] feat: add async support --- CHANGELOG.md | 3 +++ Cargo.lock | 18 ++++++++++++++- Cargo.toml | 7 +++++- src/lib.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..049d08e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# v0.2.0 + +* Added async client support behind a non-default cargo feature flag using tokio's `AsyncRead` and `AsyncWrite`. diff --git a/Cargo.lock b/Cargo.lock index 69e09c6..871546a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -273,13 +273,20 @@ dependencies = [ [[package]] name = "pamsock" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anstyle", "clap", "pam", + "tokio", ] +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + [[package]] name = "proc-macro2" version = "1.0.106" @@ -367,6 +374,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "pin-project-lite", +] + [[package]] name = "unicode-ident" version = "1.0.24" diff --git a/Cargo.toml b/Cargo.toml index 31fe3ff..fd1899b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,18 @@ [package] name = "pamsock" -version = "0.1.0" +version = "0.2.0" edition = "2024" +[features] +async = ["dep:tokio"] + [dependencies] anstyle = "1.0.13" clap = { version = "4.5.60", features = ["derive"] } pam = "0.8.0" +tokio = { version = "1.49.0", optional = true } + [lints.clippy] pedantic = { level = "deny", priority = -1 } nursery = { level = "deny", priority = -1 } diff --git a/src/lib.rs b/src/lib.rs index 7eebbb7..96f02e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,19 @@ pub mod prot { ServerResponse::from_u8(ucom::read_u8(&mut stream)?) } + #[must_use] + #[cfg(feature = "async")] + pub async fn attempt_login_async( + mut stream: impl tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin, + user: &str, + passwd: &str, + ) -> Option { + ucom_async::write_str(&mut stream, user).await?; + ucom_async::write_str(&mut stream, passwd).await?; + + ServerResponse::from_u8(ucom_async::read_u8(&mut stream).await?) + } + pub mod ucom { //! # µcom //! @@ -123,4 +136,53 @@ pub mod prot { into.write_all(&size_buf).ok() } } + + #[cfg(feature = "async")] + pub mod ucom_async { + //! # µcom_async + //! + //! Analogous to [`super::ucom`] but asynchronous. + + use tokio::io::{AsyncRead, AsyncWrite, AsyncReadExt, AsyncWriteExt}; + + pub async fn read_duple(from: &mut (impl AsyncRead + Unpin)) -> Option<(String, String)> { + let user = read_str(from).await?; + let pass = read_str(from).await?; + Some((user, pass)) + } + + pub async fn read_str(from: &mut (impl AsyncRead + Unpin)) -> Option { + let size = read_u8(from).await? as usize; + let mut buf = vec![0; size]; + from.read_exact(&mut buf).await.ok()?; + String::from_utf8(buf).ok() + } + + pub async fn read_u8(from: &mut (impl AsyncRead + Unpin)) -> Option { + let mut size_buf = [0; 1]; + from.read_exact(&mut size_buf).await.ok()?; + Some(u8::from_be_bytes(size_buf)) + } + + pub async fn write_duple( + into: &mut (impl AsyncWrite + Unpin), + user: &str, + pass: &str, + ) -> Option<()> { + write_str(into, user).await?; + write_str(into, pass).await?; + Some(()) + } + + pub async fn write_str(into: &mut (impl AsyncWrite + Unpin), str: &str) -> Option<()> { + let buf = str.as_bytes(); + write_u8(into, buf.len().try_into().ok()?).await?; + into.write_all(buf).await.ok() + } + + pub async fn write_u8(into: &mut (impl AsyncWrite + Unpin), u8: u8) -> Option<()> { + let size_buf = u8.to_be_bytes(); + into.write_all(&size_buf).await.ok() + } + } }