use rama_core::io::Io;
use rama_core::telemetry::tracing;
use rama_net::address::{HostWithPort, SocketAddress};
use std::fmt;
use super::core::HandshakeError;
use crate::proto::{ReplyKind, server};
pub struct Binder<S> {
stream: S,
requested_bind_address: Option<SocketAddress>,
selected_bind_address: SocketAddress,
}
pub struct BindError<S> {
stream: S,
error: HandshakeError,
}
impl<S> BindError<S> {
#[inline]
pub fn reply(&self) -> ReplyKind {
self.error.reply()
}
pub fn into_stream(self) -> S {
self.stream
}
}
impl<S> fmt::Debug for BindError<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BindError")
.field("stream", &format_args!("{}", std::any::type_name::<S>()))
.field("error", &self.error)
.finish()
}
}
impl<S> fmt::Display for BindError<S> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.error)
}
}
impl<S> std::error::Error for BindError<S> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.error.source()
}
}
pub struct BindOutput<S> {
pub stream: S,
pub server: HostWithPort,
}
impl<S: Io + Unpin> Binder<S> {
pub(crate) fn new(
stream: S,
requested_bind_address: Option<SocketAddress>,
selected_bind_address: SocketAddress,
) -> Self {
Self {
stream,
requested_bind_address,
selected_bind_address,
}
}
pub fn requested_bind_address(&self) -> Option<SocketAddress> {
self.requested_bind_address
}
pub fn selected_bind_address(&self) -> SocketAddress {
self.selected_bind_address
}
pub async fn connect(mut self) -> Result<BindOutput<S>, BindError<S>> {
let server = match server::Reply::read_from(&mut self.stream).await {
Ok(reply) => {
if reply.reply != ReplyKind::Succeeded {
return Err(BindError {
stream: self.stream,
error: HandshakeError::reply_kind(reply.reply)
.with_context("server responded with non-success reply"),
});
}
reply.bind_address
}
Err(err) => {
return Err(BindError {
stream: self.stream,
error: HandshakeError::protocol(err).with_context("read server reply"),
});
}
};
tracing::trace!(
network.local.address = %self.selected_bind_address.ip_addr,
network.local.port = %self.selected_bind_address.port,
server.address = %server.host,
server.port = %server.port,
"socks5: bind handshake complete",
);
Ok(BindOutput {
stream: self.stream,
server,
})
}
pub fn into_stream(self) -> S {
self.stream
}
}