alksocks 0.2.0

SOCKS5 (RFC 1928) proxy protocol over alkcall channels: targeted-forked fast-socks5 typestate core plus producer/consumer sessions. Producer/consumer protocol crate.
Documentation
//! Forked from fast-socks5 v1.0.0 (upstream `src/util/stream.rs` —
//! extraction; ownership per ADR-013, see the crate root's `mod.rs`).
//!
//! Upstream carries the `read_exact!`/`ready!` macros, `ConnectError`,
//! and the `tcp_connect*` dialers; only the `read_exact!` macro +
//! `ConnectError` rode the extraction (the dialers and the `ready!`
//! macro are the cut kernel-net region — the crate's local backend is
//! first-party tokio code, ADR-008, and nothing in the extracted
//! region used `ready!`). Extraction-time adjustment: upstream
//! exports the macro at the crate root via `#[macro_export]`; here it
//! is a plain `macro_rules!` scoped by `pub(crate) use` so it stays
//! internal to the RFC 1928 core (implementation detail, not alksocks
//! public API).
//!
//! [`ConnectError`] is currently constructed nowhere in-tree (review
//! 001 C-1 — upstream's sole construction site was the cut
//! `tcp_connect` dialer). The keep-or-cut trigger (cut if the
//! producer layer landed without consuming it) fired and the
//! disposition is **keep** (review 002 N-1): it stays as the
//! hand-port map's companion — the dial-error → reply-code mapping
//! (`to_reply_error`) is what a future kernel-net port (e.g. the
//! dial-callback shape, ADR-004) would reuse, and its mapping is
//! pinned by the module's test.

/// Easy to destructure bytes buffers by naming each fields:
///
/// # Examples (before)
///
/// ```ignore
/// let mut buf = [0u8; 2];
/// stream.read_exact(&mut buf).await?;
/// let [version, method_len] = buf;
///
/// assert_eq!(version, 0x05);
/// ```
///
/// # Examples (after)
///
/// ```ignore
/// let [version, method_len] = read_exact!(stream, [0u8; 2]);
///
/// assert_eq!(version, 0x05);
/// ```
macro_rules! read_exact {
    ($stream: expr, $array: expr) => {{
        let mut x = $array;
        $stream.read_exact(&mut x).await.map(|_| x)
    }};
}

pub(crate) use read_exact;

use crate::rfc1928::ReplyError;
use std::io;

#[derive(thiserror::Error, Debug)]
pub enum ConnectError {
    #[error("Connection timed out")]
    ConnectionTimeout,
    #[error("Connection refused: {0}")]
    ConnectionRefused(#[source] io::Error),
    #[error("Connection aborted: {0}")]
    ConnectionAborted(#[source] io::Error),
    #[error("Connection reset: {0}")]
    ConnectionReset(#[source] io::Error),
    #[error("Not connected: {0}")]
    NotConnected(#[source] io::Error),
    #[error("Other i/o error: {0}")]
    Other(#[source] io::Error),
}

impl ConnectError {
    pub fn to_reply_error(&self) -> ReplyError {
        match self {
            ConnectError::ConnectionTimeout => ReplyError::ConnectionTimeout,
            ConnectError::ConnectionRefused(_) => ReplyError::ConnectionRefused,
            ConnectError::ConnectionAborted(_) | ConnectError::ConnectionReset(_) => {
                ReplyError::ConnectionNotAllowed
            }
            ConnectError::NotConnected(_) => ReplyError::NetworkUnreachable,
            ConnectError::Other(_) => ReplyError::GeneralFailure,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::ConnectError;
    use crate::rfc1928::ReplyError;
    use std::io::ErrorKind;

    #[test]
    fn to_reply_error_mapping() {
        assert!(matches!(
            ConnectError::ConnectionTimeout.to_reply_error(),
            ReplyError::ConnectionTimeout
        ));
        assert!(matches!(
            ConnectError::ConnectionRefused(ErrorKind::ConnectionRefused.into()).to_reply_error(),
            ReplyError::ConnectionRefused
        ));
        assert!(matches!(
            ConnectError::ConnectionAborted(ErrorKind::ConnectionAborted.into()).to_reply_error(),
            ReplyError::ConnectionNotAllowed
        ));
        assert!(matches!(
            ConnectError::ConnectionReset(ErrorKind::ConnectionReset.into()).to_reply_error(),
            ReplyError::ConnectionNotAllowed
        ));
        assert!(matches!(
            ConnectError::NotConnected(ErrorKind::NotConnected.into()).to_reply_error(),
            ReplyError::NetworkUnreachable
        ));
        assert!(matches!(
            ConnectError::Other(ErrorKind::Other.into()).to_reply_error(),
            ReplyError::GeneralFailure
        ));
    }
}