//! Blocking `std::io` handshake adapter.
//!
//! A synchronous, [`std::io::Read`]/[`std::io::Write`] surface over the
//! type-state Noise handshake — the caller
//! hands the handshake a stream and drives it with a fluent token chain
//! that reads/writes the wire directly, no `.await`, no caller-sized
//! buffer:
//!
//! ```ignore
//! use hiss::noise::*;
//!
//! // Pin the protocol locally; the `sync_initiator`/`sync_responder`
//! // constructors fix the role, so there is no
//! // `SyncHandshake::<…, …, _, _, _, _>` turbofish.
//! type Channel = Noise<pattern::N, P256, ChaChaPoly, Blake2b>;
//!
//! // initiator — `stream: impl std::io::Write`
//! let i = Channel::sync_initiator(provider, &[], stream)
//!     .set_rs(recipient_pub);
//! let mut sealed = i.e()?.es()?;          // msg1 streamed to the wire; `sealed` owns the stream
//!
//! // responder — `stream: impl std::io::Read`
//! let r = Channel::sync_responder(provider, &[], stream)
//!     .set_s(recipient_static)?;
//! let (their_e, recv) = r.recv().e()?;    // each token reads exactly its bytes off the wire
//! let mut opened = recv.es()?;
//! ```
//!
//! # Design
//!
//! This is one of the two handshake drivers (the other is the async
//! `AsyncHandshake`, feature `async-io`). 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 async path; only the I/O differs. The
//! structure is three `impl` blocks per token per context (more-tokens /
//! last-token-more-messages / last-token-last-message), generated by
//! macros.
//!
//! # Synchronous crypto
//!
//! The blocking handshake is generic over [`DhProvider`] — the
//! synchronous provider surface — and calls its methods directly (no
//! executor, no future-polling). Both backends implement it: software
//! (`eccoxide`) natively, and Apple **Secure Enclave** (whose
//! Security-framework calls are synchronous, blocking C functions) by
//! running them on the calling thread — exactly as Apple's libraries
//! expose them. An async-only provider cannot be used here: it is a
//! *compile* error (it does not implement [`DhProvider`]).
//!
//! The provider-free token helpers (`recv_e`/`recv_s`/`send_s`/
//! `send_payload`/`recv_payload`/`do_psk`/`recv_to_transport`) are shared
//! with the async driver via the internal `process` module; only the
//! DH/ephemeral steps that call the provider have synchronous mirrors
//! here.
//!
//! # Flushing & cancellation
//!
//! Each completed outgoing handshake message is flushed before the call
//! returns, so it reaches the peer even over a buffered writer (and a
//! multi-message handshake does not deadlock waiting on unflushed bytes).
//! Bytes are streamed per token and crypto state advances as they are
//! produced, so a mid-handshake I/O error is **fatal and unrecoverable**
//! — drop the whole handshake and the connection; there is no per-token
//! retry (the same contract as `tokio-rustls`).
//!
//! # Timeouts
//!
//! The handshake read path has **no clock**: a `recv` token blocks until
//! its bytes arrive, so a stalling or silent peer can hang the call
//! indefinitely. Impose a timeout yourself — typically a read timeout on
//! the underlying socket (e.g. [`TcpStream::set_read_timeout`](std::net::TcpStream::set_read_timeout)).

use std::io::{Read, Write};
use std::marker::PhantomData;

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_psk, recv_e, recv_payload, recv_s, recv_to_transport, 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, DhProvider};

/// 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;

// ── Synchronous token crypto ─────────────────────────────────────
//
// Mirror the provider-calling helpers in `process.rs`, but call the
// synchronous `DhProvider` methods instead of awaiting. The
// provider-free helpers are reused from `process.rs` directly.

/// Generate our ephemeral, write its public key, mix it in.
fn sync_send_e<Cu, Ci, H, CP>(
    inner: &mut HandshakeInner<Cu, Ci, H, CP>,
    buffer: &mut SendBuffer<'_>,
) -> Result<(), HandshakeError>
where
    Cu: DhCurve,
    Cu::PublicKey: AsRef<[u8]>,
    Ci: Cipher,
    H: Hash,
    CP: DhProvider<Cu>,
{
    let e = inner
        .provider
        .generate_ephemeral_key()
        .map_err(|e| HandshakeError::Crypto(Box::new(e)))?;
    let e_pub = inner
        .provider
        .public_key(&e)
        .map_err(|e| HandshakeError::Crypto(Box::new(e)))?;
    buffer.write(e_pub.as_ref());
    inner.symmetric.mix_hash(e_pub.as_ref());
    if inner.has_psk {
        inner.symmetric.mix_key(e_pub.as_ref());
    }
    inner.e = Some(e);
    inner.e_pub = Some(e_pub);
    Ok(())
}

/// Initiator `es`: DH(e, rs).
fn sync_do_es_initiator<Cu, Ci, H, CP>(
    inner: &mut HandshakeInner<Cu, Ci, H, CP>,
) -> Result<(), HandshakeError>
where
    Cu: DhCurve,
    Cu::SharedSecret: AsRef<[u8]>,
    Ci: Cipher,
    H: Hash,
    CP: DhProvider<Cu>,
{
    let e = inner
        .e
        .as_ref()
        .ok_or(HandshakeError::MissingEphemeralKey)?;
    let rs = inner
        .rs
        .as_ref()
        .ok_or(HandshakeError::MissingRemoteStatic)?;
    let ss = inner
        .provider
        .dh(e, rs)
        .map_err(|e| HandshakeError::Crypto(Box::new(e)))?;
    inner.symmetric.mix_key(ss.as_ref());
    Ok(())
}

/// Responder `es`: DH(s, re).
fn sync_do_es_responder<Cu, Ci, H, CP>(
    inner: &mut HandshakeInner<Cu, Ci, H, CP>,
) -> Result<(), HandshakeError>
where
    Cu: DhCurve,
    Cu::SharedSecret: AsRef<[u8]>,
    Ci: Cipher,
    H: Hash,
    CP: DhProvider<Cu>,
{
    let s = inner.s.as_ref().ok_or(HandshakeError::MissingStaticKey)?;
    let re = inner
        .re
        .as_ref()
        .ok_or(HandshakeError::MissingRemoteEphemeral)?;
    let ss = inner
        .provider
        .dh(s, re)
        .map_err(|e| HandshakeError::Crypto(Box::new(e)))?;
    inner.symmetric.mix_key(ss.as_ref());
    Ok(())
}

/// `ee`: DH(e, re) (role-independent).
fn sync_do_ee<Cu, Ci, H, CP>(
    inner: &mut HandshakeInner<Cu, Ci, H, CP>,
) -> Result<(), HandshakeError>
where
    Cu: DhCurve,
    Cu::SharedSecret: AsRef<[u8]>,
    Ci: Cipher,
    H: Hash,
    CP: DhProvider<Cu>,
{
    let e = inner
        .e
        .as_ref()
        .ok_or(HandshakeError::MissingEphemeralKey)?;
    let re = inner
        .re
        .as_ref()
        .ok_or(HandshakeError::MissingRemoteEphemeral)?;
    let ss = inner
        .provider
        .dh(e, re)
        .map_err(|e| HandshakeError::Crypto(Box::new(e)))?;
    inner.symmetric.mix_key(ss.as_ref());
    Ok(())
}

/// Initiator `se`: DH(s, re).
fn sync_do_se_initiator<Cu, Ci, H, CP>(
    inner: &mut HandshakeInner<Cu, Ci, H, CP>,
) -> Result<(), HandshakeError>
where
    Cu: DhCurve,
    Cu::SharedSecret: AsRef<[u8]>,
    Ci: Cipher,
    H: Hash,
    CP: DhProvider<Cu>,
{
    let s = inner.s.as_ref().ok_or(HandshakeError::MissingStaticKey)?;
    let re = inner
        .re
        .as_ref()
        .ok_or(HandshakeError::MissingRemoteEphemeral)?;
    let ss = inner
        .provider
        .dh(s, re)
        .map_err(|e| HandshakeError::Crypto(Box::new(e)))?;
    inner.symmetric.mix_key(ss.as_ref());
    Ok(())
}

/// Responder `se`: DH(e, rs).
fn sync_do_se_responder<Cu, Ci, H, CP>(
    inner: &mut HandshakeInner<Cu, Ci, H, CP>,
) -> Result<(), HandshakeError>
where
    Cu: DhCurve,
    Cu::SharedSecret: AsRef<[u8]>,
    Ci: Cipher,
    H: Hash,
    CP: DhProvider<Cu>,
{
    let e = inner
        .e
        .as_ref()
        .ok_or(HandshakeError::MissingEphemeralKey)?;
    let rs = inner
        .rs
        .as_ref()
        .ok_or(HandshakeError::MissingRemoteStatic)?;
    let ss = inner
        .provider
        .dh(e, rs)
        .map_err(|e| HandshakeError::Crypto(Box::new(e)))?;
    inner.symmetric.mix_key(ss.as_ref());
    Ok(())
}

/// `ss`: DH(s, rs) (role-independent).
fn sync_do_ss<Cu, Ci, H, CP>(
    inner: &mut HandshakeInner<Cu, Ci, H, CP>,
) -> Result<(), HandshakeError>
where
    Cu: DhCurve,
    Cu::SharedSecret: AsRef<[u8]>,
    Ci: Cipher,
    H: Hash,
    CP: DhProvider<Cu>,
{
    let s = inner.s.as_ref().ok_or(HandshakeError::MissingStaticKey)?;
    let rs = inner
        .rs
        .as_ref()
        .ok_or(HandshakeError::MissingRemoteStatic)?;
    let ss = inner
        .provider
        .dh(s, rs)
        .map_err(|e| HandshakeError::Crypto(Box::new(e)))?;
    inner.symmetric.mix_key(ss.as_ref());
    Ok(())
}

// ── Streaming token helpers (compute bytes → write/read the wire) ──

/// `e` (send): generate + mix the ephemeral and stream its public key.
fn sync_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: DhProvider<Cu>,
    Io: Write,
{
    let mut scratch = [0u8; TOKEN_SCRATCH];
    let mut buffer = SendBuffer::new(&mut scratch);
    sync_send_e(inner, &mut buffer)?;
    stream.write_all(buffer.finish())?;
    Ok(())
}

/// `s` (send): encrypt + mix the static key and stream it.
fn sync_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: Write,
{
    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())?;
    Ok(())
}

/// `e` (recv): read the remote ephemeral public key and mix it in.
fn sync_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: Read,
{
    let pk_size = Cu::PUBLIC_KEY_SIZE;
    let mut scratch = [0u8; TOKEN_SCRATCH];
    stream.read_exact(&mut scratch[..pk_size])?;
    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.
fn sync_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: Read,
{
    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])?;
    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 (`TAG_SIZE` bytes when keyed, nothing otherwise),
/// and flush so a buffered writer releases the whole message.
fn send_message_tail<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: Write,
{
    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())?;
    stream.flush()?;
    Ok(())
}

/// Close an incoming message: read and verify the trailing empty-payload
/// tag (`TAG_SIZE` bytes when keyed, nothing otherwise).
fn recv_message_tail<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: Read,
{
    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])?;
    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.
// ═══════════════════════════════════════════════════════════════

/// Synchronous 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 SyncHandshake<Proto, R, Stage, Msgs, CP, Io>
where
    Proto: Protocol,
    CP: DhProvider<Proto::Curve>,
{
    inner: HandshakeInner<Proto::Curve, Proto::Cipher, Proto::Hash, CP>,
    stream: Io,
    _marker: PhantomData<fn() -> (Proto, R, Stage, Msgs)>,
}

/// Synchronous state part-way through an outgoing message.
///
/// Reached from [`SyncHandshake`] 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 [`SyncHandshake`]. Each token method consumes the
/// head of `Tokens`; emptying it closes the message (flushing its tail)
/// and yields either the next [`SyncHandshake`] or, on the final message,
/// the [`SyncTransport`].
pub struct SyncSending<Proto, R, Tokens, MsgRest, CP, Io>
where
    Proto: Protocol,
    CP: DhProvider<Proto::Curve>,
{
    inner: HandshakeInner<Proto::Curve, Proto::Cipher, Proto::Hash, CP>,
    stream: Io,
    _marker: PhantomData<fn() -> (Proto, R, Tokens, MsgRest)>,
}

/// Synchronous state part-way through an incoming message.
///
/// Reached from [`SyncHandshake::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
/// [`SyncHandshake`]. Each token method consumes the head of `Tokens`;
/// emptying it verifies the message tail and yields either the next
/// [`SyncHandshake`] or, on the final message, the [`SyncTransport`].
pub struct SyncReceiving<Proto, R, Tokens, MsgRest, CP, Io>
where
    Proto: Protocol,
    CP: DhProvider<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 SyncTransport<Proto: Protocol, Io> {
    transport: Transport<Proto>,
    stream: Io,
}

impl<Proto: Protocol, Io> SyncTransport<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>
    SyncHandshake<
        Proto,
        Initiator,
        <Proto::Pattern as Pattern>::PreMessages,
        <Proto::Pattern as Pattern>::Messages,
        CP,
        Io,
    >
where
    Proto: Protocol,
    CP: DhProvider<Proto::Curve>,
{
    /// Begin a blocking handshake as the **initiator** over `stream`.
    pub fn initiate(provider: CP, prologue: &[u8], stream: Io) -> Self {
        SyncHandshake {
            inner: HandshakeInner::new::<Proto>(provider, prologue),
            stream,
            _marker: PhantomData,
        }
    }
}

impl<Proto, CP, Io>
    SyncHandshake<
        Proto,
        Responder,
        <Proto::Pattern as Pattern>::PreMessages,
        <Proto::Pattern as Pattern>::Messages,
        CP,
        Io,
    >
where
    Proto: Protocol,
    CP: DhProvider<Proto::Curve>,
{
    /// Begin a blocking handshake as the **responder** over `stream`.
    pub fn respond(provider: CP, prologue: &[u8], stream: Io) -> Self {
        SyncHandshake {
            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 a blocking handshake as the **initiator** over `stream`,
    /// without naming the six [`SyncHandshake`] type parameters.
    ///
    /// Paired with a local protocol alias (e.g.
    /// `type Channel = Noise<pattern::IKpsk1, P256, ChaChaPoly, Blake2b>`)
    /// this removes the turbofish entirely:
    ///
    /// ```ignore
    /// let hs = Channel::sync_initiator(provider, &[], stream).set_rs(peer);
    /// ```
    pub fn sync_initiator<CP, Io>(
        provider: CP,
        prologue: &[u8],
        stream: Io,
    ) -> SyncHandshake<Self, Initiator, P::PreMessages, P::Messages, CP, Io>
    where
        CP: DhProvider<Cu>,
    {
        SyncHandshake::initiate(provider, prologue, stream)
    }

    /// Begin a blocking handshake as the **responder** over `stream`,
    /// without naming the six [`SyncHandshake`] type parameters.
    pub fn sync_responder<CP, Io>(
        provider: CP,
        prologue: &[u8],
        stream: Io,
    ) -> SyncHandshake<Self, Responder, P::PreMessages, P::Messages, CP, Io>
    where
        CP: DhProvider<Cu>,
    {
        SyncHandshake::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>
    SyncHandshake<Proto, Initiator, Cons<Message<ToInitiator, Tokens>, Rest>, Msgs, CP, Io>
where
    Proto: Protocol,
    CP: DhProvider<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,
    ) -> SyncHandshake<Proto, Initiator, Rest, Msgs, CP, Io> {
        self.inner.symmetric.mix_hash(remote_static.as_ref());
        self.inner.rs = Some(remote_static);
        SyncHandshake {
            inner: self.inner,
            stream: self.stream,
            _marker: PhantomData,
        }
    }
}

// `<- s` + Responder: this is our own static key.
impl<Proto, Tokens, Rest, Msgs, CP, Io>
    SyncHandshake<Proto, Responder, Cons<Message<ToInitiator, Tokens>, Rest>, Msgs, CP, Io>
where
    Proto: Protocol,
    CP: DhProvider<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<SyncHandshake<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(SyncHandshake {
            inner: self.inner,
            stream: self.stream,
            _marker: PhantomData,
        })
    }
}

// `-> s` + Initiator: this is our own static key.
impl<Proto, Tokens, Rest, Msgs, CP, Io>
    SyncHandshake<Proto, Initiator, Cons<Message<ToResponder, Tokens>, Rest>, Msgs, CP, Io>
where
    Proto: Protocol,
    CP: DhProvider<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<SyncHandshake<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(SyncHandshake {
            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>
    SyncHandshake<Proto, Responder, Cons<Message<ToResponder, Tokens>, Rest>, Msgs, CP, Io>
where
    Proto: Protocol,
    CP: DhProvider<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,
    ) -> SyncHandshake<Proto, Responder, Rest, Msgs, CP, Io> {
        self.inner.symmetric.mix_hash(remote_static.as_ref());
        self.inner.rs = Some(remote_static);
        SyncHandshake {
            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 `sync_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
// `SyncSending<…, 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>
    SyncHandshake<Proto, R, Nil, Cons<Message<Dir, Cons<E, Cons<Next, More>>>, MsgRest>, CP, Io>
where
    Proto: Protocol,
    R: Role<SendDir = Dir>,
    CP: DhProvider<Proto::Curve>,
    <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
    Io: Write,
{
    /// Generate and stream our ephemeral public key (`e`).
    pub fn e(
        mut self,
    ) -> Result<SyncSending<Proto, R, Cons<Next, More>, MsgRest, CP, Io>, HandshakeError> {
        sync_stream_e(&mut self.inner, &mut self.stream)?;
        Ok(SyncSending {
            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>
    SyncHandshake<Proto, R, Nil, Cons<Message<Dir, Cons<E, Nil>>, Cons<NextMsg, MoreMsgs>>, CP, Io>
where
    Proto: Protocol,
    R: Role<SendDir = Dir>,
    CP: DhProvider<Proto::Curve>,
    <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
    Io: Write,
{
    /// Stream a single-`E` message (`-> e`) when more messages follow.
    pub fn e(
        mut self,
    ) -> Result<SyncHandshake<Proto, R, Nil, Cons<NextMsg, MoreMsgs>, CP, Io>, HandshakeError> {
        sync_stream_e(&mut self.inner, &mut self.stream)?;
        send_message_tail(&mut self.inner, &mut self.stream)?;
        Ok(SyncHandshake {
            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>
    SyncHandshake<Proto, R, Nil, Cons<Message<Dir, Cons<Psk, Tokens>>, MsgRest>, CP, Io>
where
    Proto: Protocol,
    R: Role<SendDir = Dir>,
    CP: DhProvider<Proto::Curve>,
    <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
{
    /// Mix the pre-shared key (`psk`) — writes nothing to the wire.
    pub fn psk(
        mut self,
        psk_key: &crate::psk::Psk,
    ) -> Result<SyncSending<Proto, R, Tokens, MsgRest, CP, Io>, HandshakeError> {
        do_psk(&mut self.inner, psk_key)?;
        Ok(SyncSending {
            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>
    SyncHandshake<Proto, R, Nil, Cons<Message<Dir, Cons<S, Tokens>>, MsgRest>, CP, Io>
where
    Proto: Protocol,
    R: Role<SendDir = Dir>,
    CP: DhProvider<Proto::Curve>,
    <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
    Io: Write,
{
    /// Encrypt and stream our static public key (`s`).
    pub fn s(
        mut self,
        static_key: CP::PrivateKey,
    ) -> Result<SyncSending<Proto, R, Tokens, MsgRest, CP, Io>, HandshakeError> {
        sync_stream_s(&mut self.inner, &mut self.stream, static_key)?;
        Ok(SyncSending {
            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>
    SyncHandshake<Proto, R, Nil, Cons<Message<Dir, Tokens>, Rest>, CP, Io>
where
    Proto: Protocol,
    R: Role<RecvDir = Dir>,
    CP: DhProvider<Proto::Curve>,
{
    /// Begin reading the next incoming handshake message.
    pub fn recv(self) -> SyncReceiving<Proto, R, Tokens, Rest, CP, Io> {
        SyncReceiving {
            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! sync_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>
            SyncSending<Proto, $R, Cons<$Token, Cons<Next, More>>, MsgRest, CP, Io>
        where
            Proto: Protocol,
            <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
            CP: DhProvider<Proto::Curve>,
            Io: Write,
            $($extra)*
        {
            #[doc = $doc]
            pub fn $method(
                mut self, $($arg: $arg_ty,)*
            ) -> Result<SyncSending<Proto, $R, Cons<Next, More>, MsgRest, CP, Io>, HandshakeError> {
                {
                    let $inner = &mut self.inner;
                    let $stream = &mut self.stream;
                    $($logic)*
                }
                Ok(SyncSending { inner: self.inner, stream: self.stream, _marker: PhantomData })
            }
        }

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

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

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

macro_rules! sync_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>
            SyncReceiving<Proto, $R, Cons<$Token, Cons<Next, More>>, MsgRest, CP, Io>
        where
            Proto: Protocol,
            <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
            CP: DhProvider<Proto::Curve>,
            Io: Read,
            $($extra)*
        {
            #[doc = $doc]
            pub fn $method(
                mut self, $($arg: $arg_ty,)*
            ) -> Result<SyncReceiving<Proto, $R, Cons<Next, More>, MsgRest, CP, Io>, HandshakeError> {
                {
                    let $inner = &mut self.inner;
                    let $stream = &mut self.stream;
                    $($logic)*
                }
                Ok(SyncReceiving { inner: self.inner, stream: self.stream, _marker: PhantomData })
            }
        }

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

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

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

macro_rules! sync_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>
            SyncReceiving<Proto, $R, Cons<$Token, Cons<Next, More>>, MsgRest, CP, Io>
        where
            Proto: Protocol,
            <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
            CP: DhProvider<Proto::Curve>,
            Io: Read,
            $($extra)*
        {
            #[doc = $doc]
            #[allow(clippy::type_complexity)]
            pub fn $method(
                mut self, $($arg: $arg_ty,)*
            ) -> Result<(<Proto::Curve as Curve>::PublicKey, SyncReceiving<Proto, $R, Cons<Next, More>, MsgRest, CP, Io>), HandshakeError> {
                let revealed = {
                    let $inner = &mut self.inner;
                    let $stream = &mut self.stream;
                    $($logic)*
                };
                Ok((revealed, SyncReceiving { inner: self.inner, stream: self.stream, _marker: PhantomData }))
            }
        }

        // Variant 2: last token, more messages.
        impl<Proto, NextMsg, MoreMsgs, CP, Io>
            SyncReceiving<Proto, $R, Cons<$Token, Nil>, Cons<NextMsg, MoreMsgs>, CP, Io>
        where
            Proto: Protocol,
            <Proto::Curve as Curve>::PublicKey: AsRef<[u8]>,
            CP: DhProvider<Proto::Curve>,
            Io: Read,
            $($extra)*
        {
            #[doc = $doc]
            #[allow(clippy::type_complexity)]
            pub fn $method(
                mut self, $($arg: $arg_ty,)*
            ) -> Result<(<Proto::Curve as Curve>::PublicKey, SyncHandshake<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(&mut self.inner, &mut self.stream)?;
                Ok((revealed, SyncHandshake { inner: self.inner, stream: self.stream, _marker: PhantomData }))
            }
        }

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

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

sync_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| { sync_stream_e(inner, stream)?; }
}
sync_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| { sync_stream_e(inner, stream)?; }
}
sync_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| { sync_read_e(inner, stream)? }
}
sync_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| { sync_read_e(inner, stream)? }
}

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

sync_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| { sync_stream_s(inner, stream, static_key)?; }
}
sync_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| { sync_stream_s(inner, stream, static_key)?; }
}
sync_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| { sync_read_s(inner, stream)? }
}
sync_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| { sync_read_s(inner, stream)? }
}

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

sync_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| { sync_do_ee(inner)?; }
}
sync_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| { sync_do_ee(inner)?; }
}
sync_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| { sync_do_ee(inner)?; }
}
sync_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| { sync_do_ee(inner)?; }
}

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

sync_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| { sync_do_es_initiator(inner)?; }
}
sync_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| { sync_do_es_initiator(inner)?; }
}
sync_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| { sync_do_es_responder(inner)?; }
}
sync_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| { sync_do_es_responder(inner)?; }
}

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

sync_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| { sync_do_se_initiator(inner)?; }
}
sync_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| { sync_do_se_initiator(inner)?; }
}
sync_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| { sync_do_se_responder(inner)?; }
}
sync_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| { sync_do_se_responder(inner)?; }
}

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

sync_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| { sync_do_ss(inner)?; }
}
sync_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| { sync_do_ss(inner)?; }
}
sync_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| { sync_do_ss(inner)?; }
}
sync_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| { sync_do_ss(inner)?; }
}

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

sync_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)?; }
}
sync_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)?; }
}
sync_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)?; }
}
sync_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::cell::RefCell;
    use std::collections::VecDeque;
    use std::io::Cursor;
    use std::rc::Rc;

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

    /// A single-threaded in-memory bidirectional byte pipe. Reads pull
    /// from one shared queue, writes push to the other; the peer endpoint
    /// has them swapped. Safe to drive ping-pong on one thread (each side
    /// finishes a message's I/O before yielding), so no blocking/deadlock.
    #[derive(Clone)]
    struct Pipe {
        inbound: Rc<RefCell<VecDeque<u8>>>,
        outbound: Rc<RefCell<VecDeque<u8>>>,
    }

    impl std::io::Read for Pipe {
        fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
            let mut q = self.inbound.borrow_mut();
            let n = q.len().min(buf.len());
            for slot in buf.iter_mut().take(n) {
                *slot = q.pop_front().unwrap();
            }
            Ok(n)
        }
    }

    impl std::io::Write for Pipe {
        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
            self.outbound.borrow_mut().extend(buf.iter().copied());
            Ok(buf.len())
        }
        fn flush(&mut self) -> std::io::Result<()> {
            Ok(())
        }
    }

    /// Full Noise N seal/open over blocking `std::io`, with a real
    /// transport round-trip — proves wire correctness end-to-end (any
    /// wrong byte would break the AEAD tag).
    #[test]
    fn n_sync_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();

        // ── Seal (initiator) — stream into an owned Vec ──────────────
        let sealer = SyncHandshake::<Seal, Initiator, _, _, _, _>::initiate(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            Vec::<u8>::new(),
        )
        .set_rs(recipient_pub);
        let sealer_done = sealer.e().unwrap().es().unwrap();
        let (mut send_transport, wire) = sealer_done.into_parts();
        // msg1 = ephemeral (65) + empty-payload tag (16) = 81 bytes.
        assert_eq!(wire.len(), 81);

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

        // ── Open (responder) — read from a Cursor over the wire ──────
        let opener = SyncHandshake::<Seal, Responder, _, _, _, _>::respond(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            Cursor::new(wire),
        )
        .set_s(recipient_static)
        .unwrap();
        let (_revealed_e, recv) = opener.recv().e().unwrap();
        let mut recv_transport = recv.es().unwrap();

        // Both sides derived the same session.
        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 same N seal/open round-trip as `n_sync_seal_open_roundtrip`,
    /// but built through a local alias + role-fixing constructors
    /// (`N::sync_initiator` / `sync_responder`) — no turbofish, no
    /// `SyncHandshake::<…, …, _, _, _, _>` spelling.
    #[test]
    fn n_sync_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::sync_initiator(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            Vec::<u8>::new(),
        )
        .set_rs(recipient_pub);
        let (mut send_transport, wire) = sealer.e().unwrap().es().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::sync_responder(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            Cursor::new(wire),
        )
        .set_s(recipient_static)
        .unwrap();
        let (_e, recv) = opener.recv().e().unwrap();
        let mut recv_transport = recv.es().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).
    #[test]
    fn n_sync_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 = SyncHandshake::<Seal, Initiator, _, _, _, _>::initiate(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            Vec::<u8>::new(),
        )
        .set_rs(recipient_pub);
        let (_t, mut wire) = sealer.e().unwrap().es().unwrap().into_parts();
        wire[1] ^= 0xFF; // flip a byte in the ephemeral public key

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

        // The `e` read may already reject an invalid point; otherwise the
        // DH mismatch surfaces at `es` (payload-tag failure).
        match opener.recv().e() {
            Err(_) => {}
            Ok((_e, recv)) => assert!(recv.es().is_err()),
        }
    }

    /// The same blocking handshake works with the **Secure Enclave**
    /// provider — proving Apple keys are usable synchronously (using
    /// software-backed `SecKey`s so the test needs no SE hardware).
    #[cfg(any(target_os = "macos", target_os = "ios"))]
    #[test]
    fn n_sync_seal_open_with_secure_enclave_provider() {
        use crate::provider::apple::AppleSecureEnclave;

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

        let sealer = SyncHandshake::<Seal, Initiator, _, _, _, _>::initiate(
            provider.clone(),
            &[],
            Vec::<u8>::new(),
        )
        .set_rs(recipient_pub);
        let (mut send_transport, wire) = sealer.e().unwrap().es().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 = SyncHandshake::<Seal, Responder, _, _, _, _>::respond(
            provider.clone(),
            &[],
            Cursor::new(wire),
        )
        .set_s(recipient_static)
        .unwrap();
        let (_revealed_e, recv) = opener.recv().e().unwrap();
        let mut recv_transport = recv.es().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 blocking driver.** Every token
    /// of both messages (`-> e,es,s,ss,psk` / `<- e,ee,se`) driven
    /// hiss↔hiss over a shared in-memory [`Pipe`], exercising the
    /// `io_sync` initiator-role helpers end-to-end. Cross-implementation
    /// agreement (vs `snow`) is covered by `tests/snow_interop.rs`; this
    /// test pins the matching session and revealed initiator static.
    #[tokio::test]
    async fn ikpsk1_sync_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 (i2r, r2i) = (
            Rc::new(RefCell::new(VecDeque::<u8>::new())),
            Rc::new(RefCell::new(VecDeque::<u8>::new())),
        );
        let init_stream = Pipe {
            inbound: r2i.clone(),
            outbound: i2r.clone(),
        };
        let resp_stream = Pipe {
            inbound: i2r.clone(),
            outbound: r2i.clone(),
        };

        // io_sync initiator drives msg1 (-> e, es, s, ss, psk).
        let i_hs = SyncHandshake::<Channel, Initiator, _, _, _, _>::initiate(
            EphemeralOnly::new(StdRng::from_os_rng()),
            &[],
            init_stream,
        )
        .set_rs(responder_pub);
        let i_hs = i_hs
            .e()
            .unwrap()
            .es()
            .unwrap()
            .s(initiator_static)
            .unwrap()
            .ss()
            .unwrap()
            .psk(&psk)
            .unwrap();
        assert_eq!(i2r.borrow().len(), 162);

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

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

        // Both ends derived the same session.
        assert_eq!(
            i_transport.transport.session_id(),
            r_transport.transport.session_id()
        );
    }

    /// Noise K (`-> e, es, ss`, pre `-> s, <- s`) round-trip over blocking
    /// `std::io` — single-message, exercises the `-> s` pre-message
    /// setters and the `ss` token end-to-end.
    #[test]
    fn k_sync_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 = SyncHandshake::<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().unwrap().es().unwrap().ss().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 = SyncHandshake::<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().unwrap();
        let mut recv_transport = recv.es().unwrap().ss().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);
    }
}
