//! Asynchronous `tokio::io` handshake adapter (feature `async-io`).
//!
//! The async mirror of [`io_sync`](super::io_sync): a
//! [`tokio::io::AsyncRead`]/[`tokio::io::AsyncWrite`] surface over the
//! type-state Noise handshake. The caller hands
//! the handshake a stream and drives it with a fluent token chain that
//! `.await`s reads/writes on the wire directly, with no caller-sized
//! buffer:
//!
//! ```ignore
//! use hiss::noise::*;
//!
//! // Pin the protocol locally; the `async_initiator`/`async_responder`
//! // constructors fix the role, so there is no
//! // `AsyncHandshake::<…, …, _, _, _, _>` turbofish.
//! type Channel = Noise<pattern::N, P256, ChaChaPoly, Blake2b>;
//!
//! // initiator — `stream: impl tokio::io::AsyncWrite + Unpin`
//! let i = Channel::async_initiator(provider, &[], stream)
//!     .set_rs(recipient_pub);
//! let mut sealed = i.e().await?.es().await?;   // msg1 streamed + flushed; `sealed` owns the stream
//!
//! // responder — `stream: impl tokio::io::AsyncRead + Unpin`
//! let r = Channel::async_responder(provider, &[], stream)
//!     .set_s(recipient_static)?;
//! let (their_e, recv) = r.recv().e().await?;   // each token reads exactly its bytes off the wire
//! let mut opened = recv.es().await?;
//! ```
//!
//! # Design
//!
//! This is the async one of the two handshake drivers; the blocking
//! [`io_sync`](super::io_sync) driver is its mirror. Each token method
//! threads its compile-time-sized bytes through a small fixed stack
//! scratch and streams them to the stream, so the bytes on the wire are
//! byte-identical to the sync path; only the I/O differs. The structure
//! mirrors [`io_sync`](super::io_sync): three `impl` blocks per token per
//! context, generated by macros.
//!
//! The per-token crypto is shared with the sync driver — the `async`
//! token helpers in the internal `process` module are reused directly,
//! so there is a single source of truth for the Noise mix sequence.
//!
//! # Asynchronous crypto
//!
//! The handshake is generic over [`DhProviderAsync`] — the
//! async provider surface — and `.await`s its operations. Both backends
//! implement it: software (`eccoxide`) resolves immediately, and Apple
//! **Secure Enclave** offloads its blocking Security-framework calls to
//! a worker thread (`tokio::task::spawn_blocking`) so the executor never
//! blocks. A sync-only provider that does not implement
//! [`DhProviderAsync`] is a *compile* error here.
//!
//! # Flushing & cancellation
//!
//! Each completed outgoing handshake message is flushed before the call
//! returns, so it reaches the peer even over a buffered stream (the
//! `tokio-rustls` contract). Bytes are streamed per token and crypto
//! state advances as they are produced, so dropping a handshake future
//! mid-message — or a mid-handshake I/O error — is **fatal and
//! unrecoverable**: drop the whole handshake and the connection; there
//! is no per-token retry.
//!
//! # Timeouts
//!
//! The handshake read path has **no clock**: a `recv` token awaits until
//! its bytes arrive, so a stalling or silent peer can keep the future
//! pending indefinitely. Impose a timeout yourself — typically by wrapping
//! the handshake in `tokio::time::timeout`. (Dropping the resulting
//! timed-out future is the fatal-cancellation case above: tear the
//! connection down.)

use std::marker::PhantomData;

use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};

use super::WellFormed;
use super::buffers::{RecvBuffer, SendBuffer};
use super::cipher::Cipher;
use super::error::HandshakeError;
use super::handshake::HandshakeInner;
use super::hash::Hash;
use super::pattern::Pattern;
use super::process::{
    do_ee, do_es_initiator, do_es_responder, do_psk, do_se_initiator, do_se_responder, do_ss,
    recv_e, recv_payload, recv_s, recv_to_transport, send_e, send_payload, send_s,
};
use super::role::{Initiator, Responder, Role};
use super::tokens::*;
use super::transport::Transport;
use super::{Noise, Protocol};
use crate::curve::{Curve, DhCurve};
use crate::provider::{CryptoKeyProvider, DhProviderAsync};

/// Largest single contiguous write/read a token produces: an encrypted
/// static key (`PUBLIC_KEY_SIZE + TAG_SIZE`). 128 bytes covers every
/// supported curve/cipher with margin; the payload tag uses far less.
const TOKEN_SCRATCH: usize = 128;

// ── Streaming token helpers (compute bytes → write/read the wire) ──
//
// The DH/ephemeral mix steps reuse `process.rs`'s async helpers verbatim
// (`send_e`/`do_ee`/`do_es_*`/`do_se_*`/`do_ss`); these wrappers add the
// owned-stream I/O around them.

/// `e` (send): generate + mix the ephemeral and stream its public key.
async fn async_stream_e<Cu, Ci, H, CP, Io>(
    inner: &mut HandshakeInner<Cu, Ci, H, CP>,
    stream: &mut Io,
) -> Result<(), HandshakeError>
where
    Cu: DhCurve,
    Cu::PublicKey: AsRef<[u8]>,
    Ci: Cipher,
    H: Hash,
    CP: DhProviderAsync<Cu>,
    Io: AsyncWrite + Unpin,
{
    let mut scratch = [0u8; TOKEN_SCRATCH];
    let mut buffer = SendBuffer::new(&mut scratch);
    send_e(inner, &mut buffer).await?;
    stream.write_all(buffer.finish()).await?;
    Ok(())
}

/// `s` (send): encrypt + mix the static key and stream it.
async fn async_stream_s<Cu, Ci, H, CP, Io>(
    inner: &mut HandshakeInner<Cu, Ci, H, CP>,
    stream: &mut Io,
    static_key: CP::PrivateKey,
) -> Result<(), HandshakeError>
where
    Cu: Curve,
    Cu::PublicKey: AsRef<[u8]>,
    Ci: Cipher,
    H: Hash,
    CP: CryptoKeyProvider<Cu>,
    Io: AsyncWrite + Unpin,
{
    let mut scratch = [0u8; TOKEN_SCRATCH];
    let mut buffer = SendBuffer::new(&mut scratch);
    send_s(inner, &mut buffer, static_key)?;
    stream.write_all(buffer.finish()).await?;
    Ok(())
}

/// `e` (recv): read the remote ephemeral public key and mix it in.
async fn async_read_e<Cu, Ci, H, CP, Io>(
    inner: &mut HandshakeInner<Cu, Ci, H, CP>,
    stream: &mut Io,
) -> Result<Cu::PublicKey, HandshakeError>
where
    Cu: Curve,
    Cu::PublicKey: AsRef<[u8]>,
    Ci: Cipher,
    H: Hash,
    CP: CryptoKeyProvider<Cu>,
    Io: AsyncRead + Unpin,
{
    let pk_size = Cu::PUBLIC_KEY_SIZE;
    let mut scratch = [0u8; TOKEN_SCRATCH];
    stream.read_exact(&mut scratch[..pk_size]).await?;
    let mut buffer = RecvBuffer::new(&scratch[..pk_size]);
    recv_e(inner, &mut buffer)
}

/// `s` (recv): read + decrypt the remote static key and mix it in.
async fn async_read_s<Cu, Ci, H, CP, Io>(
    inner: &mut HandshakeInner<Cu, Ci, H, CP>,
    stream: &mut Io,
) -> Result<Cu::PublicKey, HandshakeError>
where
    Cu: Curve,
    Cu::PublicKey: AsRef<[u8]>,
    Ci: Cipher,
    H: Hash,
    CP: CryptoKeyProvider<Cu>,
    Io: AsyncRead + Unpin,
{
    let wire_len = if inner.symmetric.has_key() {
        Cu::PUBLIC_KEY_SIZE + Ci::TAG_SIZE
    } else {
        Cu::PUBLIC_KEY_SIZE
    };
    const {
        assert!(
            Cu::PUBLIC_KEY_SIZE + Ci::TAG_SIZE <= TOKEN_SCRATCH,
            "curve public key + AEAD tag exceeds the 128-byte scratch buffer"
        )
    };
    let mut scratch = [0u8; TOKEN_SCRATCH];
    stream.read_exact(&mut scratch[..wire_len]).await?;
    let mut buffer = RecvBuffer::new(&scratch[..wire_len]);
    recv_s(inner, &mut buffer)
}

/// Close an outgoing message: encrypt-and-hash the empty payload, stream
/// the resulting tag, and flush so a buffered stream releases the whole
/// message (and a multi-message handshake does not deadlock).
async fn send_message_tail_async<Cu, Ci, H, CP, Io>(
    inner: &mut HandshakeInner<Cu, Ci, H, CP>,
    stream: &mut Io,
) -> Result<(), HandshakeError>
where
    Cu: Curve,
    Ci: Cipher,
    H: Hash,
    CP: CryptoKeyProvider<Cu>,
    Io: AsyncWrite + Unpin,
{
    let tag_len = if inner.symmetric.has_key() {
        Ci::TAG_SIZE
    } else {
        0
    };
    let mut scratch = [0u8; TOKEN_SCRATCH];
    let mut buffer = SendBuffer::new(&mut scratch[..tag_len]);
    send_payload(inner, &mut buffer)?;
    stream.write_all(buffer.finish()).await?;
    stream.flush().await?;
    Ok(())
}

/// Close an incoming message: read and verify the trailing empty-payload
/// tag (`TAG_SIZE` bytes when keyed, nothing otherwise).
async fn recv_message_tail_async<Cu, Ci, H, CP, Io>(
    inner: &mut HandshakeInner<Cu, Ci, H, CP>,
    stream: &mut Io,
) -> Result<(), HandshakeError>
where
    Cu: Curve,
    Ci: Cipher,
    H: Hash,
    CP: CryptoKeyProvider<Cu>,
    Io: AsyncRead + Unpin,
{
    let tag_len = if inner.symmetric.has_key() {
        Ci::TAG_SIZE
    } else {
        0
    };
    let mut scratch = [0u8; TOKEN_SCRATCH];
    stream.read_exact(&mut scratch[..tag_len]).await?;
    let mut buffer = RecvBuffer::new(&scratch[..tag_len]);
    recv_payload(inner, &mut buffer)
}

// ═══════════════════════════════════════════════════════════════
//  State types — own the stream `Io` and thread the runtime
//  `HandshakeInner` through the type-state token chain.
// ═══════════════════════════════════════════════════════════════

/// Asynchronous handshake state between messages — the resting point
/// from which the next message is sent or received. See the module docs.
///
/// The type parameters encode the handshake position at compile time:
/// `N` is the protocol (suite + pattern), `R` the role
/// (`Initiator`/`Responder`), `CP` the DH provider and `Io` the owned
/// stream. `Stage` is the list of pre-messages still to process (each
/// `set_s`/`set_rs` consumes one and is unavailable once it is `Nil`),
/// and `Msgs` is the list of handshake messages not yet sent or
/// received; only the token method that matches the head of `Msgs` is in
/// scope, so an out-of-order step is a compile error.
pub struct AsyncHandshake<Proto, R, Stage, Msgs, CP, Io>
where
    Proto: Protocol,
    CP: DhProviderAsync<Proto::Curve>,
{
    inner: HandshakeInner<Proto::Curve, Proto::Cipher, Proto::Hash, CP>,
    stream: Io,
    _marker: PhantomData<fn() -> (Proto, R, Stage, Msgs)>,
}

/// Asynchronous state part-way through an outgoing message.
///
/// Reached from [`AsyncHandshake`] once a send message has begun.
/// `Tokens` is the list of tokens still to write in the current message
/// and `MsgRest` the messages that follow it; the other parameters carry
/// the same meaning as on [`AsyncHandshake`]. Each token method consumes
/// the head of `Tokens`; emptying it closes the message (flushing its
/// tail) and yields either the next [`AsyncHandshake`] or, on the final
/// message, the [`AsyncTransport`].
pub struct AsyncSending<Proto, R, Tokens, MsgRest, CP, Io>
where
    Proto: Protocol,
    CP: DhProviderAsync<Proto::Curve>,
{
    inner: HandshakeInner<Proto::Curve, Proto::Cipher, Proto::Hash, CP>,
    stream: Io,
    _marker: PhantomData<fn() -> (Proto, R, Tokens, MsgRest)>,
}

/// Asynchronous state part-way through an incoming message.
///
/// Reached from [`AsyncHandshake::recv`]. `Tokens` is the list of tokens
/// still to read in the current message and `MsgRest` the messages that
/// follow it; the other parameters carry the same meaning as on
/// [`AsyncHandshake`]. Each token method consumes the head of `Tokens`;
/// emptying it verifies the message tail and yields either the next
/// [`AsyncHandshake`] or, on the final message, the [`AsyncTransport`].
pub struct AsyncReceiving<Proto, R, Tokens, MsgRest, CP, Io>
where
    Proto: Protocol,
    CP: DhProviderAsync<Proto::Curve>,
{
    inner: HandshakeInner<Proto::Curve, Proto::Cipher, Proto::Hash, CP>,
    stream: Io,
    _marker: PhantomData<fn() -> (Proto, R, Tokens, MsgRest)>,
}

/// Completed handshake plus the stream it ran over.
///
/// Like a TLS stream, the post-handshake [`Transport`] and the `Io` it
/// rides on are bundled together; [`into_parts`](Self::into_parts)
/// recovers them separately.
pub struct AsyncTransport<Proto: Protocol, Io> {
    transport: Transport<Proto>,
    stream: Io,
}

impl<Proto: Protocol, Io> AsyncTransport<Proto, Io> {
    /// The post-handshake transport cipher pair.
    pub fn transport(&mut self) -> &mut Transport<Proto> {
        &mut self.transport
    }

    /// The underlying stream.
    pub fn stream(&mut self) -> &mut Io {
        &mut self.stream
    }

    /// Split into the transport and the stream.
    pub fn into_parts(self) -> (Transport<Proto>, Io) {
        (self.transport, self.stream)
    }
}

// ═══════════════════════════════════════════════════════════════
//  Construction
// ═══════════════════════════════════════════════════════════════

impl<Proto, CP, Io>
    AsyncHandshake<
        Proto,
        Initiator,
        <Proto::Pattern as Pattern>::PreMessages,
        <Proto::Pattern as Pattern>::Messages,
        CP,
        Io,
    >
where
    Proto: Protocol,
    CP: DhProviderAsync<Proto::Curve>,
{
    /// Begin an async handshake as the **initiator** over `stream`.
    pub fn initiate(provider: CP, prologue: &[u8], stream: Io) -> Self {
        AsyncHandshake {
            inner: HandshakeInner::new::<Proto>(provider, prologue),
            stream,
            _marker: PhantomData,
        }
    }
}

impl<Proto, CP, Io>
    AsyncHandshake<
        Proto,
        Responder,
        <Proto::Pattern as Pattern>::PreMessages,
        <Proto::Pattern as Pattern>::Messages,
        CP,
        Io,
    >
where
    Proto: Protocol,
    CP: DhProviderAsync<Proto::Curve>,
{
    /// Begin an async handshake as the **responder** over `stream`.
    pub fn respond(provider: CP, prologue: &[u8], stream: Io) -> Self {
        AsyncHandshake {
            inner: HandshakeInner::new::<Proto>(provider, prologue),
            stream,
            _marker: PhantomData,
        }
    }
}

// ═══════════════════════════════════════════════════════════════
//  Ergonomic protocol-level constructors (no turbofish)
// ═══════════════════════════════════════════════════════════════

impl<P: WellFormed, Cu: DhCurve, Ci: Cipher, H: Hash> Noise<P, Cu, Ci, H> {
    /// Begin an async handshake as the **initiator** over `stream`,
    /// without naming the six [`AsyncHandshake`] type parameters.
    ///
    /// Paired with a local protocol alias (e.g.
    /// `type Channel = Noise<pattern::IKpsk1, P256, ChaChaPoly, Blake2b>`)
    /// this removes the turbofish entirely.
    pub fn async_initiator<CP, Io>(
        provider: CP,
        prologue: &[u8],
        stream: Io,
    ) -> AsyncHandshake<Self, Initiator, P::PreMessages, P::Messages, CP, Io>
    where
        CP: DhProviderAsync<Cu>,
    {
        AsyncHandshake::initiate(provider, prologue, stream)
    }

    /// Begin an async handshake as the **responder** over `stream`,
    /// without naming the six [`AsyncHandshake`] type parameters.
    pub fn async_responder<CP, Io>(
        provider: CP,
        prologue: &[u8],
        stream: Io,
    ) -> AsyncHandshake<Self, Responder, P::PreMessages, P::Messages, CP, Io>
    where
        CP: DhProviderAsync<Cu>,
    {
        AsyncHandshake::respond(provider, prologue, stream)
    }
}

// ═══════════════════════════════════════════════════════════════
//  Pre-message keys (the `<- s` / `-> s` directions)
// ═══════════════════════════════════════════════════════════════

// `<- s` + Initiator: the responder's static is the remote static.
impl<Proto, Tokens, Rest, Msgs, CP, Io>
    AsyncHandshake<Proto, Initiator, Cons<Message<ToInitiator, Tokens>, Rest>, Msgs, CP, Io>
where
    Proto: Protocol,
    CP: DhProviderAsync<Proto::Curve>,
    <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
{
    /// Provide the remote party's static public key (`<- s`).
    pub fn set_rs(
        mut self,
        remote_static: <Proto::Curve as Curve>::PublicKey,
    ) -> AsyncHandshake<Proto, Initiator, Rest, Msgs, CP, Io> {
        self.inner.symmetric.mix_hash(remote_static.as_ref());
        self.inner.rs = Some(remote_static);
        AsyncHandshake {
            inner: self.inner,
            stream: self.stream,
            _marker: PhantomData,
        }
    }
}

// `<- s` + Responder: this is our own static key.
impl<Proto, Tokens, Rest, Msgs, CP, Io>
    AsyncHandshake<Proto, Responder, Cons<Message<ToInitiator, Tokens>, Rest>, Msgs, CP, Io>
where
    Proto: Protocol,
    CP: DhProviderAsync<Proto::Curve>,
    <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
{
    /// Provide our local static key (`<- s`).
    pub fn set_s(
        mut self,
        static_key: CP::PrivateKey,
    ) -> Result<AsyncHandshake<Proto, Responder, Rest, Msgs, CP, Io>, HandshakeError> {
        let s_pub = self
            .inner
            .provider
            .public_key(&static_key)
            .map_err(|e| HandshakeError::Crypto(Box::new(e)))?;
        self.inner.symmetric.mix_hash(s_pub.as_ref());
        self.inner.s_pub = Some(s_pub);
        self.inner.s = Some(static_key);
        Ok(AsyncHandshake {
            inner: self.inner,
            stream: self.stream,
            _marker: PhantomData,
        })
    }
}

// `-> s` + Initiator: this is our own static key.
impl<Proto, Tokens, Rest, Msgs, CP, Io>
    AsyncHandshake<Proto, Initiator, Cons<Message<ToResponder, Tokens>, Rest>, Msgs, CP, Io>
where
    Proto: Protocol,
    CP: DhProviderAsync<Proto::Curve>,
    <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
{
    /// Provide our local static key (`-> s`).
    pub fn set_s(
        mut self,
        static_key: CP::PrivateKey,
    ) -> Result<AsyncHandshake<Proto, Initiator, Rest, Msgs, CP, Io>, HandshakeError> {
        let s_pub = self
            .inner
            .provider
            .public_key(&static_key)
            .map_err(|e| HandshakeError::Crypto(Box::new(e)))?;
        self.inner.symmetric.mix_hash(s_pub.as_ref());
        self.inner.s_pub = Some(s_pub);
        self.inner.s = Some(static_key);
        Ok(AsyncHandshake {
            inner: self.inner,
            stream: self.stream,
            _marker: PhantomData,
        })
    }
}

// `-> s` + Responder: the initiator's static is the remote static.
impl<Proto, Tokens, Rest, Msgs, CP, Io>
    AsyncHandshake<Proto, Responder, Cons<Message<ToResponder, Tokens>, Rest>, Msgs, CP, Io>
where
    Proto: Protocol,
    CP: DhProviderAsync<Proto::Curve>,
    <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
{
    /// Provide the remote party's static public key (`-> s`).
    pub fn set_rs(
        mut self,
        remote_static: <Proto::Curve as Curve>::PublicKey,
    ) -> AsyncHandshake<Proto, Responder, Rest, Msgs, CP, Io> {
        self.inner.symmetric.mix_hash(remote_static.as_ref());
        self.inner.rs = Some(remote_static);
        AsyncHandshake {
            inner: self.inner,
            stream: self.stream,
            _marker: PhantomData,
        }
    }
}

// ═══════════════════════════════════════════════════════════════
//  Entry points: begin a send (Sending) or receive (Receiving) message
// ═══════════════════════════════════════════════════════════════

// Start a send message whose first token is E.
//
// Split into two mutually-exclusive impls mirroring the `async_send_token!`
// finalizer variants, so a bare single-`E` send message (`-> e`) followed by
// more messages can advance — the generic entry would leave such a message in
// `AsyncSending<…, Nil, …>` with no method to close it. (A single `-> e` as the
// *last* message never keys the cipher and is rejected by the `WellFormed`
// keyed-cipher guard, so there is no finalize-to-transport variant.)

// Variant 1: more tokens follow `E` in this message.
impl<Proto, R, Next, More, MsgRest, Dir, CP, Io>
    AsyncHandshake<Proto, R, Nil, Cons<Message<Dir, Cons<E, Cons<Next, More>>>, MsgRest>, CP, Io>
where
    Proto: Protocol,
    R: Role<SendDir = Dir>,
    CP: DhProviderAsync<Proto::Curve>,
    <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
    Io: AsyncWrite + Unpin,
{
    /// Generate and stream our ephemeral public key (`e`).
    pub async fn e(
        mut self,
    ) -> Result<AsyncSending<Proto, R, Cons<Next, More>, MsgRest, CP, Io>, HandshakeError> {
        async_stream_e(&mut self.inner, &mut self.stream).await?;
        Ok(AsyncSending {
            inner: self.inner,
            stream: self.stream,
            _marker: PhantomData,
        })
    }
}

// Variant 2: `E` is the only token and more messages remain.
impl<Proto, R, NextMsg, MoreMsgs, Dir, CP, Io>
    AsyncHandshake<Proto, R, Nil, Cons<Message<Dir, Cons<E, Nil>>, Cons<NextMsg, MoreMsgs>>, CP, Io>
where
    Proto: Protocol,
    R: Role<SendDir = Dir>,
    CP: DhProviderAsync<Proto::Curve>,
    <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
    Io: AsyncWrite + Unpin,
{
    /// Stream a single-`E` message (`-> e`) when more messages follow.
    pub async fn e(
        mut self,
    ) -> Result<AsyncHandshake<Proto, R, Nil, Cons<NextMsg, MoreMsgs>, CP, Io>, HandshakeError>
    {
        async_stream_e(&mut self.inner, &mut self.stream).await?;
        send_message_tail_async(&mut self.inner, &mut self.stream).await?;
        Ok(AsyncHandshake {
            inner: self.inner,
            stream: self.stream,
            _marker: PhantomData,
        })
    }
}

// Start a send message whose first token is Psk.
impl<Proto, R, Tokens, MsgRest, Dir, CP, Io>
    AsyncHandshake<Proto, R, Nil, Cons<Message<Dir, Cons<Psk, Tokens>>, MsgRest>, CP, Io>
where
    Proto: Protocol,
    R: Role<SendDir = Dir>,
    CP: DhProviderAsync<Proto::Curve>,
    <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
{
    /// Mix the pre-shared key (`psk`) — writes nothing to the wire.
    pub async fn psk(
        mut self,
        psk_key: &crate::psk::Psk,
    ) -> Result<AsyncSending<Proto, R, Tokens, MsgRest, CP, Io>, HandshakeError> {
        do_psk(&mut self.inner, psk_key)?;
        Ok(AsyncSending {
            inner: self.inner,
            stream: self.stream,
            _marker: PhantomData,
        })
    }
}

// Start a send message whose first token is S.
impl<Proto, R, Tokens, MsgRest, Dir, CP, Io>
    AsyncHandshake<Proto, R, Nil, Cons<Message<Dir, Cons<S, Tokens>>, MsgRest>, CP, Io>
where
    Proto: Protocol,
    R: Role<SendDir = Dir>,
    CP: DhProviderAsync<Proto::Curve>,
    <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
    Io: AsyncWrite + Unpin,
{
    /// Encrypt and stream our static public key (`s`).
    pub async fn s(
        mut self,
        static_key: CP::PrivateKey,
    ) -> Result<AsyncSending<Proto, R, Tokens, MsgRest, CP, Io>, HandshakeError> {
        async_stream_s(&mut self.inner, &mut self.stream, static_key).await?;
        Ok(AsyncSending {
            inner: self.inner,
            stream: self.stream,
            _marker: PhantomData,
        })
    }
}

// Start receiving the next message (PreMsgs = Nil, message is ours to
// read). No bytes are read here — each token pulls exactly what it needs.
impl<Proto, R, Tokens, Rest, Dir, CP, Io>
    AsyncHandshake<Proto, R, Nil, Cons<Message<Dir, Tokens>, Rest>, CP, Io>
where
    Proto: Protocol,
    R: Role<RecvDir = Dir>,
    CP: DhProviderAsync<Proto::Curve>,
{
    /// Begin reading the next incoming handshake message.
    pub fn recv(self) -> AsyncReceiving<Proto, R, Tokens, Rest, CP, Io> {
        AsyncReceiving {
            inner: self.inner,
            stream: self.stream,
            _marker: PhantomData,
        }
    }
}

// ═══════════════════════════════════════════════════════════════
//  Macro: three-variant impls for a send token
// ═══════════════════════════════════════════════════════════════

/// Generate three `impl` blocks for a send-side token method. The body
/// has access to `inner: &mut HandshakeInner` and `stream: &mut Io`
/// (name it `_stream` for DH/psk tokens that write nothing) plus any
/// declared args.
macro_rules! async_send_token {
    (
        role: $R:ty,
        token: $Token:ty,
        method: $method:ident ($($arg:ident : $arg_ty:ty),*),
        bounds: [$($extra:tt)*],
        doc: $doc:expr,
        body: |$inner:ident, $stream:ident| { $($logic:tt)* }
    ) => {
        // Variant 1: more tokens after this one.
        impl<Proto, Next, More, MsgRest, CP, Io>
            AsyncSending<Proto, $R, Cons<$Token, Cons<Next, More>>, MsgRest, CP, Io>
        where
            Proto: Protocol,
            <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
            CP: DhProviderAsync<Proto::Curve>,
            Io: AsyncWrite + Unpin,
            $($extra)*
        {
            #[doc = $doc]
            pub async fn $method(
                mut self, $($arg: $arg_ty,)*
            ) -> Result<AsyncSending<Proto, $R, Cons<Next, More>, MsgRest, CP, Io>, HandshakeError> {
                {
                    let $inner = &mut self.inner;
                    let $stream = &mut self.stream;
                    $($logic)*
                }
                Ok(AsyncSending { inner: self.inner, stream: self.stream, _marker: PhantomData })
            }
        }

        // Variant 2: last token, more messages.
        impl<Proto, NextMsg, MoreMsgs, CP, Io>
            AsyncSending<Proto, $R, Cons<$Token, Nil>, Cons<NextMsg, MoreMsgs>, CP, Io>
        where
            Proto: Protocol,
            <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
            CP: DhProviderAsync<Proto::Curve>,
            Io: AsyncWrite + Unpin,
            $($extra)*
        {
            #[doc = $doc]
            pub async fn $method(
                mut self, $($arg: $arg_ty,)*
            ) -> Result<AsyncHandshake<Proto, $R, Nil, Cons<NextMsg, MoreMsgs>, CP, Io>, HandshakeError> {
                {
                    let $inner = &mut self.inner;
                    let $stream = &mut self.stream;
                    $($logic)*
                }
                send_message_tail_async(&mut self.inner, &mut self.stream).await?;
                Ok(AsyncHandshake { inner: self.inner, stream: self.stream, _marker: PhantomData })
            }
        }

        // Variant 3: last token, last message.
        impl<Proto, CP, Io>
            AsyncSending<Proto, $R, Cons<$Token, Nil>, Nil, CP, Io>
        where
            Proto: Protocol,
            <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
            CP: DhProviderAsync<Proto::Curve>,
            Io: AsyncWrite + Unpin,
            $($extra)*
        {
            #[doc = $doc]
            pub async fn $method(
                mut self, $($arg: $arg_ty,)*
            ) -> Result<AsyncTransport<Proto, Io>, HandshakeError> {
                {
                    let $inner = &mut self.inner;
                    let $stream = &mut self.stream;
                    $($logic)*
                }
                send_message_tail_async(&mut self.inner, &mut self.stream).await?;
                let transport = recv_to_transport::<Proto, $R, CP>(self.inner);
                Ok(AsyncTransport { transport, stream: self.stream })
            }
        }
    };
}

// ═══════════════════════════════════════════════════════════════
//  Macro: three-variant impls for a recv token (no revealed key)
// ═══════════════════════════════════════════════════════════════

macro_rules! async_recv_token {
    (
        role: $R:ty,
        token: $Token:ty,
        method: $method:ident ($($arg:ident : $arg_ty:ty),*),
        bounds: [$($extra:tt)*],
        doc: $doc:expr,
        body: |$inner:ident, $stream:ident| { $($logic:tt)* }
    ) => {
        // Variant 1: more tokens.
        impl<Proto, Next, More, MsgRest, CP, Io>
            AsyncReceiving<Proto, $R, Cons<$Token, Cons<Next, More>>, MsgRest, CP, Io>
        where
            Proto: Protocol,
            <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
            CP: DhProviderAsync<Proto::Curve>,
            Io: AsyncRead + Unpin,
            $($extra)*
        {
            #[doc = $doc]
            pub async fn $method(
                mut self, $($arg: $arg_ty,)*
            ) -> Result<AsyncReceiving<Proto, $R, Cons<Next, More>, MsgRest, CP, Io>, HandshakeError> {
                {
                    let $inner = &mut self.inner;
                    let $stream = &mut self.stream;
                    $($logic)*
                }
                Ok(AsyncReceiving { inner: self.inner, stream: self.stream, _marker: PhantomData })
            }
        }

        // Variant 2: last token, more messages.
        impl<Proto, NextMsg, MoreMsgs, CP, Io>
            AsyncReceiving<Proto, $R, Cons<$Token, Nil>, Cons<NextMsg, MoreMsgs>, CP, Io>
        where
            Proto: Protocol,
            <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
            CP: DhProviderAsync<Proto::Curve>,
            Io: AsyncRead + Unpin,
            $($extra)*
        {
            #[doc = $doc]
            pub async fn $method(
                mut self, $($arg: $arg_ty,)*
            ) -> Result<AsyncHandshake<Proto, $R, Nil, Cons<NextMsg, MoreMsgs>, CP, Io>, HandshakeError> {
                {
                    let $inner = &mut self.inner;
                    let $stream = &mut self.stream;
                    $($logic)*
                }
                recv_message_tail_async(&mut self.inner, &mut self.stream).await?;
                Ok(AsyncHandshake { inner: self.inner, stream: self.stream, _marker: PhantomData })
            }
        }

        // Variant 3: last token, last message.
        impl<Proto, CP, Io>
            AsyncReceiving<Proto, $R, Cons<$Token, Nil>, Nil, CP, Io>
        where
            Proto: Protocol,
            <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
            CP: DhProviderAsync<Proto::Curve>,
            Io: AsyncRead + Unpin,
            $($extra)*
        {
            #[doc = $doc]
            pub async fn $method(
                mut self, $($arg: $arg_ty,)*
            ) -> Result<AsyncTransport<Proto, Io>, HandshakeError> {
                {
                    let $inner = &mut self.inner;
                    let $stream = &mut self.stream;
                    $($logic)*
                }
                recv_message_tail_async(&mut self.inner, &mut self.stream).await?;
                let transport = recv_to_transport::<Proto, $R, CP>(self.inner);
                Ok(AsyncTransport { transport, stream: self.stream })
            }
        }
    };
}

// ═══════════════════════════════════════════════════════════════
//  Macro: recv token that reveals a public key (E or S)
// ═══════════════════════════════════════════════════════════════

macro_rules! async_recv_reveal_token {
    (
        role: $R:ty,
        token: $Token:ty,
        method: $method:ident ($($arg:ident : $arg_ty:ty),*),
        bounds: [$($extra:tt)*],
        doc: $doc:expr,
        body: |$inner:ident, $stream:ident| { $($logic:tt)* }
    ) => {
        // Variant 1: more tokens after this one.
        impl<Proto, Next, More, MsgRest, CP, Io>
            AsyncReceiving<Proto, $R, Cons<$Token, Cons<Next, More>>, MsgRest, CP, Io>
        where
            Proto: Protocol,
            <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
            CP: DhProviderAsync<Proto::Curve>,
            Io: AsyncRead + Unpin,
            $($extra)*
        {
            #[doc = $doc]
            #[allow(clippy::type_complexity)]
            pub async fn $method(
                mut self, $($arg: $arg_ty,)*
            ) -> Result<(<Proto::Curve as Curve>::PublicKey, AsyncReceiving<Proto, $R, Cons<Next, More>, MsgRest, CP, Io>), HandshakeError> {
                let revealed = {
                    let $inner = &mut self.inner;
                    let $stream = &mut self.stream;
                    $($logic)*
                };
                Ok((revealed, AsyncReceiving { inner: self.inner, stream: self.stream, _marker: PhantomData }))
            }
        }

        // Variant 2: last token, more messages.
        impl<Proto, NextMsg, MoreMsgs, CP, Io>
            AsyncReceiving<Proto, $R, Cons<$Token, Nil>, Cons<NextMsg, MoreMsgs>, CP, Io>
        where
            Proto: Protocol,
            <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
            CP: DhProviderAsync<Proto::Curve>,
            Io: AsyncRead + Unpin,
            $($extra)*
        {
            #[doc = $doc]
            #[allow(clippy::type_complexity)]
            pub async fn $method(
                mut self, $($arg: $arg_ty,)*
            ) -> Result<(<Proto::Curve as Curve>::PublicKey, AsyncHandshake<Proto, $R, Nil, Cons<NextMsg, MoreMsgs>, CP, Io>), HandshakeError> {
                let revealed = {
                    let $inner = &mut self.inner;
                    let $stream = &mut self.stream;
                    $($logic)*
                };
                recv_message_tail_async(&mut self.inner, &mut self.stream).await?;
                Ok((revealed, AsyncHandshake { inner: self.inner, stream: self.stream, _marker: PhantomData }))
            }
        }

        // Variant 3: last token, last message.
        impl<Proto, CP, Io>
            AsyncReceiving<Proto, $R, Cons<$Token, Nil>, Nil, CP, Io>
        where
            Proto: Protocol,
            <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
            CP: DhProviderAsync<Proto::Curve>,
            Io: AsyncRead + Unpin,
            $($extra)*
        {
            #[doc = $doc]
            #[allow(clippy::type_complexity)]
            pub async fn $method(
                mut self, $($arg: $arg_ty,)*
            ) -> Result<(<Proto::Curve as Curve>::PublicKey, AsyncTransport<Proto, Io>), HandshakeError> {
                let revealed = {
                    let $inner = &mut self.inner;
                    let $stream = &mut self.stream;
                    $($logic)*
                };
                recv_message_tail_async(&mut self.inner, &mut self.stream).await?;
                let transport = recv_to_transport::<Proto, $R, CP>(self.inner);
                Ok((revealed, AsyncTransport { transport, stream: self.stream }))
            }
        }
    };
}

// ═══════════════════════════════════════════════════════════════
//  Token: E (role-independent)
// ═══════════════════════════════════════════════════════════════

async_send_token! {
    role: Initiator, token: E, method: e(), bounds: [],
    doc: "Process the `e` token: generate a fresh ephemeral key, write its \
          public key to the wire, and mix it into the handshake hash (also \
          mixing it into the chaining key in a PSK pattern).",
    body: |inner, stream| { async_stream_e(inner, stream).await?; }
}
async_send_token! {
    role: Responder, token: E, method: e(), bounds: [],
    doc: "Process the `e` token: generate a fresh ephemeral key, write its \
          public key to the wire, and mix it into the handshake hash (also \
          mixing it into the chaining key in a PSK pattern).",
    body: |inner, stream| { async_stream_e(inner, stream).await?; }
}
async_recv_reveal_token! {
    role: Initiator, token: E, method: e(), bounds: [],
    doc: "Process the `e` token: read the peer's ephemeral public key from \
          the wire and mix it into the handshake hash (also mixing it into \
          the chaining key in a PSK pattern). Returns the revealed key.",
    body: |inner, stream| { async_read_e(inner, stream).await? }
}
async_recv_reveal_token! {
    role: Responder, token: E, method: e(), bounds: [],
    doc: "Process the `e` token: read the peer's ephemeral public key from \
          the wire and mix it into the handshake hash (also mixing it into \
          the chaining key in a PSK pattern). Returns the revealed key.",
    body: |inner, stream| { async_read_e(inner, stream).await? }
}

// ═══════════════════════════════════════════════════════════════
//  Token: S
// ═══════════════════════════════════════════════════════════════

async_send_token! {
    role: Initiator, token: S, method: s(static_key: CP::PrivateKey), bounds: [],
    doc: "Process the `s` token: write our local static public key to the \
          wire — encrypted once a key has been established by a prior DH \
          token — and mix it into the handshake hash.",
    body: |inner, stream| { async_stream_s(inner, stream, static_key).await?; }
}
async_send_token! {
    role: Responder, token: S, method: s(static_key: CP::PrivateKey), bounds: [],
    doc: "Process the `s` token: write our local static public key to the \
          wire — encrypted once a key has been established by a prior DH \
          token — and mix it into the handshake hash.",
    body: |inner, stream| { async_stream_s(inner, stream, static_key).await?; }
}
async_recv_reveal_token! {
    role: Initiator, token: S, method: s(), bounds: [],
    doc: "Process the `s` token: read the peer's static public key from the \
          wire — decrypting it once a key has been established by a prior DH \
          token — and mix it into the handshake hash. Returns the revealed key.",
    body: |inner, stream| { async_read_s(inner, stream).await? }
}
async_recv_reveal_token! {
    role: Responder, token: S, method: s(), bounds: [],
    doc: "Process the `s` token: read the peer's static public key from the \
          wire — decrypting it once a key has been established by a prior DH \
          token — and mix it into the handshake hash. Returns the revealed key.",
    body: |inner, stream| { async_read_s(inner, stream).await? }
}

// ═══════════════════════════════════════════════════════════════
//  Token: Ee (role-independent)
// ═══════════════════════════════════════════════════════════════

async_send_token! {
    role: Initiator, token: Ee, method: ee(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `ee` token: perform the Diffie–Hellman between our \
          local ephemeral and the remote ephemeral, and mix the shared \
          secret into the chaining key. Writes nothing to the wire; \
          subsequent payloads are encrypted under the advanced key.",
    body: |inner, _stream| { do_ee(inner).await?; }
}
async_send_token! {
    role: Responder, token: Ee, method: ee(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `ee` token: perform the Diffie–Hellman between our \
          local ephemeral and the remote ephemeral, and mix the shared \
          secret into the chaining key. Writes nothing to the wire; \
          subsequent payloads are encrypted under the advanced key.",
    body: |inner, _stream| { do_ee(inner).await?; }
}
async_recv_token! {
    role: Initiator, token: Ee, method: ee(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `ee` token: perform the Diffie–Hellman between our \
          local ephemeral and the remote ephemeral, and mix the shared \
          secret into the chaining key. Reads nothing from the wire; \
          subsequent payloads are decrypted under the advanced key.",
    body: |inner, _stream| { do_ee(inner).await?; }
}
async_recv_token! {
    role: Responder, token: Ee, method: ee(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `ee` token: perform the Diffie–Hellman between our \
          local ephemeral and the remote ephemeral, and mix the shared \
          secret into the chaining key. Reads nothing from the wire; \
          subsequent payloads are decrypted under the advanced key.",
    body: |inner, _stream| { do_ee(inner).await?; }
}

// ═══════════════════════════════════════════════════════════════
//  Token: Es (role-dependent)
// ═══════════════════════════════════════════════════════════════

async_send_token! {
    role: Initiator, token: Es, method: es(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `es` token (initiator): perform the Diffie–Hellman \
          between our local ephemeral and the remote static, and mix the \
          shared secret into the chaining key. Writes nothing to the wire; \
          subsequent payloads are encrypted under the advanced key.",
    body: |inner, _stream| { do_es_initiator(inner).await?; }
}
async_recv_token! {
    role: Initiator, token: Es, method: es(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `es` token (initiator): perform the Diffie–Hellman \
          between our local ephemeral and the remote static, and mix the \
          shared secret into the chaining key. Reads nothing from the wire; \
          subsequent payloads are decrypted under the advanced key.",
    body: |inner, _stream| { do_es_initiator(inner).await?; }
}
async_send_token! {
    role: Responder, token: Es, method: es(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `es` token (responder): perform the Diffie–Hellman \
          between our local static and the remote ephemeral, and mix the \
          shared secret into the chaining key. Writes nothing to the wire; \
          subsequent payloads are encrypted under the advanced key.",
    body: |inner, _stream| { do_es_responder(inner).await?; }
}
async_recv_token! {
    role: Responder, token: Es, method: es(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `es` token (responder): perform the Diffie–Hellman \
          between our local static and the remote ephemeral, and mix the \
          shared secret into the chaining key. Reads nothing from the wire; \
          subsequent payloads are decrypted under the advanced key.",
    body: |inner, _stream| { do_es_responder(inner).await?; }
}

// ═══════════════════════════════════════════════════════════════
//  Token: Se (role-dependent)
// ═══════════════════════════════════════════════════════════════

async_send_token! {
    role: Initiator, token: Se, method: se(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `se` token (initiator): perform the Diffie–Hellman \
          between our local static and the remote ephemeral, and mix the \
          shared secret into the chaining key. Writes nothing to the wire; \
          subsequent payloads are encrypted under the advanced key.",
    body: |inner, _stream| { do_se_initiator(inner).await?; }
}
async_recv_token! {
    role: Initiator, token: Se, method: se(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `se` token (initiator): perform the Diffie–Hellman \
          between our local static and the remote ephemeral, and mix the \
          shared secret into the chaining key. Reads nothing from the wire; \
          subsequent payloads are decrypted under the advanced key.",
    body: |inner, _stream| { do_se_initiator(inner).await?; }
}
async_send_token! {
    role: Responder, token: Se, method: se(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `se` token (responder): perform the Diffie–Hellman \
          between our local ephemeral and the remote static, and mix the \
          shared secret into the chaining key. Writes nothing to the wire; \
          subsequent payloads are encrypted under the advanced key.",
    body: |inner, _stream| { do_se_responder(inner).await?; }
}
async_recv_token! {
    role: Responder, token: Se, method: se(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `se` token (responder): perform the Diffie–Hellman \
          between our local ephemeral and the remote static, and mix the \
          shared secret into the chaining key. Reads nothing from the wire; \
          subsequent payloads are decrypted under the advanced key.",
    body: |inner, _stream| { do_se_responder(inner).await?; }
}

// ═══════════════════════════════════════════════════════════════
//  Token: Ss (role-independent)
// ═══════════════════════════════════════════════════════════════

async_send_token! {
    role: Initiator, token: Ss, method: ss(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `ss` token: perform the Diffie–Hellman between our \
          local static and the remote static, and mix the shared secret \
          into the chaining key. Writes nothing to the wire; subsequent \
          payloads are encrypted under the advanced key.",
    body: |inner, _stream| { do_ss(inner).await?; }
}
async_send_token! {
    role: Responder, token: Ss, method: ss(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `ss` token: perform the Diffie–Hellman between our \
          local static and the remote static, and mix the shared secret \
          into the chaining key. Writes nothing to the wire; subsequent \
          payloads are encrypted under the advanced key.",
    body: |inner, _stream| { do_ss(inner).await?; }
}
async_recv_token! {
    role: Initiator, token: Ss, method: ss(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `ss` token: perform the Diffie–Hellman between our \
          local static and the remote static, and mix the shared secret \
          into the chaining key. Reads nothing from the wire; subsequent \
          payloads are decrypted under the advanced key.",
    body: |inner, _stream| { do_ss(inner).await?; }
}
async_recv_token! {
    role: Responder, token: Ss, method: ss(), bounds: [<Proto::Curve as DhCurve>::SharedSecret: AsRef<[u8]>,],
    doc: "Process the `ss` token: perform the Diffie–Hellman between our \
          local static and the remote static, and mix the shared secret \
          into the chaining key. Reads nothing from the wire; subsequent \
          payloads are decrypted under the advanced key.",
    body: |inner, _stream| { do_ss(inner).await?; }
}

// ═══════════════════════════════════════════════════════════════
//  Token: Psk (role-independent)
// ═══════════════════════════════════════════════════════════════

async_send_token! {
    role: Initiator, token: Psk, method: psk(psk_key: &crate::psk::Psk), bounds: [],
    doc: "Process the `psk` token: mix the 32-byte pre-shared key into both \
          the chaining key and the handshake hash (Noise's `MixKeyAndHash`). \
          Writes nothing to the wire; subsequent payloads are encrypted \
          under the advanced key.",
    body: |inner, _stream| { do_psk(inner, psk_key)?; }
}
async_send_token! {
    role: Responder, token: Psk, method: psk(psk_key: &crate::psk::Psk), bounds: [],
    doc: "Process the `psk` token: mix the 32-byte pre-shared key into both \
          the chaining key and the handshake hash (Noise's `MixKeyAndHash`). \
          Writes nothing to the wire; subsequent payloads are encrypted \
          under the advanced key.",
    body: |inner, _stream| { do_psk(inner, psk_key)?; }
}
async_recv_token! {
    role: Initiator, token: Psk, method: psk(psk_key: &crate::psk::Psk), bounds: [],
    doc: "Process the `psk` token: mix the 32-byte pre-shared key into both \
          the chaining key and the handshake hash (Noise's `MixKeyAndHash`). \
          Reads nothing from the wire; subsequent payloads are decrypted \
          under the advanced key.",
    body: |inner, _stream| { do_psk(inner, psk_key)?; }
}
async_recv_token! {
    role: Responder, token: Psk, method: psk(psk_key: &crate::psk::Psk), bounds: [],
    doc: "Process the `psk` token: mix the 32-byte pre-shared key into both \
          the chaining key and the handshake hash (Noise's `MixKeyAndHash`). \
          Reads nothing from the wire; subsequent payloads are decrypted \
          under the advanced key.",
    body: |inner, _stream| { do_psk(inner, psk_key)?; }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::noise::{Blake2b, ChaChaPoly, Initiator, Noise, P256, Responder, pattern};
    use crate::provider::EphemeralOnly;
    use crate::provider::ProviderExt;
    use crate::psk::Psk;
    use rand::{SeedableRng, rngs::StdRng};
    use std::io::Cursor;

    type Seal = Noise<pattern::N, P256, ChaChaPoly, Blake2b>;
    type Channel = Noise<pattern::IKpsk1, P256, ChaChaPoly, Blake2b>;
    type NoiseK = Noise<pattern::K, P256, ChaChaPoly, Blake2b>;

    /// Full Noise N seal/open over `tokio::io`, with a real transport
    /// round-trip — proves wire correctness end-to-end (any wrong byte
    /// would break the AEAD tag). `Vec<u8>` is a tokio `AsyncWrite`;
    /// `Cursor<Vec<u8>>` is a tokio `AsyncRead`.
    #[tokio::test]
    async fn n_async_seal_open_roundtrip() {
        let mut provider = EphemeralOnly::new(StdRng::from_os_rng());
        let recipient_static = provider.generate::<P256>().unwrap();
        let recipient_pub = provider.public(&recipient_static).unwrap();

        let sealer = AsyncHandshake::<Seal, Initiator, _, _, _, _>::initiate(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            Vec::<u8>::new(),
        )
        .set_rs(recipient_pub);
        let sealer_done = sealer.e().await.unwrap().es().await.unwrap();
        let (mut send_transport, wire) = sealer_done.into_parts();
        assert_eq!(wire.len(), 81);

        let payload = [0x42u8; 32];
        let mut sealed = [0u8; 48];
        let sealed_len = send_transport.send(&payload, &mut sealed).unwrap();

        let opener = AsyncHandshake::<Seal, Responder, _, _, _, _>::respond(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            Cursor::new(wire),
        )
        .set_s(recipient_static)
        .unwrap();
        let (_revealed_e, recv) = opener.recv().e().await.unwrap();
        let mut recv_transport = recv.es().await.unwrap();

        assert_eq!(
            send_transport.session_id(),
            recv_transport.transport().session_id()
        );

        let mut opened = [0u8; 32];
        let opened_len = recv_transport
            .transport()
            .receive(&sealed[..sealed_len], &mut opened)
            .unwrap();
        assert_eq!(opened_len, 32);
        assert_eq!(opened, payload);
    }

    /// The N round-trip built through a local alias + role-fixing async
    /// constructors (`N::async_initiator` / `async_responder`) —
    /// no turbofish.
    #[tokio::test]
    async fn n_async_constructors_roundtrip() {
        type N = Seal;

        let mut provider = EphemeralOnly::new(StdRng::from_os_rng());
        let recipient_static = provider.generate::<P256>().unwrap();
        let recipient_pub = provider.public(&recipient_static).unwrap();

        let sealer = N::async_initiator(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            Vec::<u8>::new(),
        )
        .set_rs(recipient_pub);
        let (mut send_transport, wire) = sealer.e().await.unwrap().es().await.unwrap().into_parts();
        assert_eq!(wire.len(), 81);

        let payload = [0x42u8; 32];
        let mut sealed = [0u8; 48];
        let sealed_len = send_transport.send(&payload, &mut sealed).unwrap();

        let opener = N::async_responder(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            Cursor::new(wire),
        )
        .set_s(recipient_static)
        .unwrap();
        let (_e, recv) = opener.recv().e().await.unwrap();
        let mut recv_transport = recv.es().await.unwrap();

        let mut opened = [0u8; 32];
        let opened_len = recv_transport
            .transport()
            .receive(&sealed[..sealed_len], &mut opened)
            .unwrap();
        assert_eq!(opened_len, 32);
        assert_eq!(opened, payload);
    }

    /// A corrupted ephemeral on the wire must make the responder's `es`
    /// fail (DH mismatch → payload-tag verification fails).
    #[tokio::test]
    async fn n_async_tampered_ephemeral_rejected() {
        let mut provider = EphemeralOnly::new(StdRng::from_os_rng());
        let recipient_static = provider.generate::<P256>().unwrap();
        let recipient_pub = provider.public(&recipient_static).unwrap();

        let sealer = AsyncHandshake::<Seal, Initiator, _, _, _, _>::initiate(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            Vec::<u8>::new(),
        )
        .set_rs(recipient_pub);
        let (_t, mut wire) = sealer.e().await.unwrap().es().await.unwrap().into_parts();
        wire[1] ^= 0xFF; // flip a byte in the ephemeral public key

        let opener = AsyncHandshake::<Seal, Responder, _, _, _, _>::respond(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            Cursor::new(wire),
        )
        .set_s(recipient_static)
        .unwrap();

        match opener.recv().e().await {
            Err(_) => {}
            Ok((_e, recv)) => assert!(recv.es().await.is_err()),
        }
    }

    /// The same async handshake works with the **Secure Enclave**
    /// provider — its blocking calls are offloaded via `spawn_blocking`,
    /// which the `#[tokio::test]` runtime supports (using software-backed
    /// `SecKey`s so the test needs no SE hardware).
    #[cfg(any(target_os = "macos", target_os = "ios"))]
    #[tokio::test]
    async fn n_async_seal_open_with_secure_enclave_provider() {
        use crate::provider::CryptoKeyProviderAsync;
        use crate::provider::apple::AppleSecureEnclave;

        let mut provider = AppleSecureEnclave::new("uk.co.example.hiss-test");
        let recipient_static =
            CryptoKeyProviderAsync::<P256>::generate_ephemeral_key_async(&mut provider)
                .await
                .unwrap();
        let recipient_pub = provider.public(&recipient_static).unwrap();

        let sealer = AsyncHandshake::<Seal, Initiator, _, _, _, _>::initiate(
            provider.clone(),
            &[],
            Vec::<u8>::new(),
        )
        .set_rs(recipient_pub);
        let (mut send_transport, wire) = sealer.e().await.unwrap().es().await.unwrap().into_parts();
        assert_eq!(wire.len(), 81);

        let payload = [0x42u8; 32];
        let mut sealed = [0u8; 48];
        let sealed_len = send_transport.send(&payload, &mut sealed).unwrap();

        let opener = AsyncHandshake::<Seal, Responder, _, _, _, _>::respond(
            provider.clone(),
            &[],
            Cursor::new(wire),
        )
        .set_s(recipient_static)
        .unwrap();
        let (_revealed_e, recv) = opener.recv().e().await.unwrap();
        let mut recv_transport = recv.es().await.unwrap();

        assert_eq!(
            send_transport.session_id(),
            recv_transport.transport().session_id()
        );
        let mut opened = [0u8; 32];
        let opened_len = recv_transport
            .transport()
            .receive(&sealed[..sealed_len], &mut opened)
            .unwrap();
        assert_eq!(&opened[..opened_len], &payload);
    }

    /// **Full IKpsk1 round-trip over the async driver.** Every token of
    /// both messages (`-> e,es,s,ss,psk` / `<- e,ee,se`) driven hiss↔hiss
    /// over a `tokio::io::duplex` pipe, exercising both role paths of the
    /// `io_async` driver end-to-end. Cross-implementation agreement (vs
    /// `snow`) is covered by `tests/snow_interop.rs`; this pins the
    /// matching session and revealed initiator static. The 4 KiB duplex
    /// buffer holds each message, so the ping-pong runs sequentially on a
    /// single task without deadlock.
    #[tokio::test]
    async fn ikpsk1_async_round_trip() {
        let mut provider = EphemeralOnly::new(StdRng::from_os_rng());
        let initiator_static = provider.generate::<P256>().unwrap();
        let initiator_pub = provider.public(&initiator_static).unwrap();
        let responder_static = provider.generate::<P256>().unwrap();
        let responder_pub = provider.public(&responder_static).unwrap();
        let psk = Psk::from_bytes([0xAA; 32]);

        let (init_stream, resp_stream) = tokio::io::duplex(4096);

        // io_async initiator drives msg1 (-> e, es, s, ss, psk).
        let i_hs = AsyncHandshake::<Channel, Initiator, _, _, _, _>::initiate(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            init_stream,
        )
        .set_rs(responder_pub);
        let i_hs = i_hs
            .e()
            .await
            .unwrap()
            .es()
            .await
            .unwrap()
            .s(initiator_static)
            .await
            .unwrap()
            .ss()
            .await
            .unwrap()
            .psk(&psk)
            .await
            .unwrap();

        // io_async responder consumes msg1 and produces msg2 (<- e,ee,se).
        let r_hs = AsyncHandshake::<Channel, Responder, _, _, _, _>::respond(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            resp_stream,
        )
        .set_s(responder_static)
        .unwrap();
        let (_e, recv) = r_hs.recv().e().await.unwrap();
        let recv = recv.es().await.unwrap();
        let (revealed_i_pub, recv) = recv.s().await.unwrap();
        assert_eq!(revealed_i_pub, initiator_pub);
        let recv = recv.ss().await.unwrap();
        let r_hs = recv.psk(&psk).await.unwrap();
        let r_transport = r_hs
            .e()
            .await
            .unwrap()
            .ee()
            .await
            .unwrap()
            .se()
            .await
            .unwrap();

        // io_async initiator consumes msg2 (<- e, ee, se).
        let (_revealed_r_e, recv) = i_hs.recv().e().await.unwrap();
        let i_transport = recv.ee().await.unwrap().se().await.unwrap();

        assert_eq!(
            i_transport.transport.session_id(),
            r_transport.transport.session_id()
        );
    }

    /// Noise K (`-> e, es, ss`, pre `-> s, <- s`) round-trip over
    /// `tokio::io` — single-message, exercises the `-> s` pre-message
    /// setters and the `ss` token end-to-end.
    #[tokio::test]
    async fn k_async_round_trip() {
        let mut provider = EphemeralOnly::new(StdRng::from_os_rng());
        let alice_static = provider.generate::<P256>().unwrap();
        let alice_pub = provider.public(&alice_static).unwrap();
        let bob_static = provider.generate::<P256>().unwrap();
        let bob_pub = provider.public(&bob_static).unwrap();
        let payload = [0x42u8; 32];

        // initiator: pre -> s (alice), <- s (bob); msg1 -> e, es, ss.
        let sealer = AsyncHandshake::<NoiseK, Initiator, _, _, _, _>::initiate(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            Vec::<u8>::new(),
        )
        .set_s(alice_static)
        .unwrap()
        .set_rs(bob_pub);
        let (mut send_transport, wire) = sealer
            .e()
            .await
            .unwrap()
            .es()
            .await
            .unwrap()
            .ss()
            .await
            .unwrap()
            .into_parts();
        assert_eq!(wire.len(), 81);
        let mut sealed = [0u8; 48];
        let n = send_transport.send(&payload, &mut sealed).unwrap();

        // responder: pre -> s (alice via set_rs), <- s (bob via set_s).
        let opener = AsyncHandshake::<NoiseK, Responder, _, _, _, _>::respond(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            Cursor::new(wire),
        )
        .set_rs(alice_pub)
        .set_s(bob_static)
        .unwrap();
        let (_e, recv) = opener.recv().e().await.unwrap();
        let mut recv_transport = recv.es().await.unwrap().ss().await.unwrap();
        assert_eq!(
            send_transport.session_id(),
            recv_transport.transport().session_id()
        );
        let mut opened = [0u8; 32];
        let on = recv_transport
            .transport()
            .receive(&sealed[..n], &mut opened)
            .unwrap();
        assert_eq!(&opened[..on], &payload);
    }
}
