use std::{
collections::HashMap,
fmt::{Display, Formatter},
net::{IpAddr, SocketAddr, ToSocketAddrs},
path::PathBuf,
str::FromStr,
sync::Arc,
};
use super::encode_percents;
use crate::{
Address, Error, Result,
connection::socket::{BoxedSplit, WriteHalf},
runtime::{
Runtime,
io::{RegisteredIo, TcpOps},
},
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Tcp {
pub(super) host: String,
pub(super) bind: Option<String>,
pub(super) port: u16,
pub(super) family: Option<TcpTransportFamily>,
pub(super) nonce_file: Option<Vec<u8>>,
}
impl Tcp {
pub fn new(host: &str, port: u16) -> Self {
Self {
host: host.to_owned(),
port,
bind: None,
family: None,
nonce_file: None,
}
}
pub fn set_bind(mut self, bind: Option<String>) -> Self {
self.bind = bind;
self
}
pub fn set_family(mut self, family: Option<TcpTransportFamily>) -> Self {
self.family = family;
self
}
pub fn set_nonce_file(mut self, nonce_file: Option<Vec<u8>>) -> Self {
self.nonce_file = nonce_file;
self
}
pub fn host(&self) -> &str {
&self.host
}
pub fn bind(&self) -> Option<&str> {
self.bind.as_deref()
}
pub fn port(&self) -> u16 {
self.port
}
pub fn family(&self) -> Option<TcpTransportFamily> {
self.family
}
pub fn nonce_file(&self) -> Option<&[u8]> {
self.nonce_file.as_deref()
}
pub fn take_nonce_file(&mut self) -> Option<Vec<u8>> {
self.nonce_file.take()
}
pub(super) fn from_options(
opts: HashMap<&str, &str>,
nonce_tcp_required: bool,
) -> Result<Self> {
let bind = None;
if opts.contains_key("bind") {
return Err(Error::Address("`bind` isn't yet supported".into()));
}
let host = opts
.get("host")
.ok_or_else(|| Error::Address("tcp address is missing `host`".into()))?
.to_string();
let port = opts
.get("port")
.ok_or_else(|| Error::Address("tcp address is missing `port`".into()))?;
let port = port
.parse::<u16>()
.map_err(|_| Error::Address("invalid tcp `port`".into()))?;
let family = opts
.get("family")
.map(|f| TcpTransportFamily::from_str(f))
.transpose()?;
let nonce_file = opts
.get("noncefile")
.map(|f| super::decode_percents(f))
.transpose()?;
if nonce_tcp_required && nonce_file.is_none() {
return Err(Error::Address(
"nonce-tcp address is missing `noncefile`".into(),
));
}
Ok(Self {
host,
bind,
port,
family,
nonce_file,
})
}
pub(super) async fn connect(&self, address: &Address, runtime: &Runtime) -> Result<BoxedSplit> {
let nonce = match self.nonce_file() {
Some(path) => Some(read_nonce(path, runtime).await?),
None => None,
};
let addresses = self.resolve(address, runtime).await?;
let mut error = self.nothing_found(address);
for socket_address in addresses {
match runtime.connect_tcp(socket_address).await {
Ok(source) => {
let mut split = BoxedSplit::from(RegisteredIo::new(runtime, source, TcpOps)?);
if let Some(nonce) = nonce.as_deref() {
write_all(split.write_mut(), nonce).await?;
}
return Ok(split);
}
Err(e) => error = Error::Connection(Arc::new(e), Box::new(address.clone())),
}
}
Err(error)
}
async fn resolve(&self, address: &Address, runtime: &Runtime) -> Result<Vec<SocketAddr>> {
let port = self.port();
let addresses = match self.host().parse::<IpAddr>() {
Ok(ip) => vec![SocketAddr::new(ip, port)],
Err(_) => {
let host = self.host().to_owned();
runtime
.spawn_blocking(move || {
(host.as_str(), port).to_socket_addrs().map(Vec::from_iter)
})
.await
.map_err(|e| Error::Connection(Arc::new(e), Box::new(address.clone())))?
}
};
Ok(match self.family() {
Some(TcpTransportFamily::Ipv4) => {
addresses.into_iter().filter(SocketAddr::is_ipv4).collect()
}
Some(TcpTransportFamily::Ipv6) => {
addresses.into_iter().filter(SocketAddr::is_ipv6).collect()
}
None => addresses,
})
}
fn nothing_found(&self, address: &Address) -> Error {
Error::Address(match self.family() {
Some(family) => format!("no `{family}` addresses found for `{address}`"),
None => format!("no addresses found for `{address}`"),
})
}
}
async fn read_nonce(path: &[u8], runtime: &Runtime) -> Result<Vec<u8>> {
let path = nonce_path(path)?;
runtime
.spawn_blocking(move || std::fs::read(path))
.await
.map_err(Into::into)
}
fn nonce_path(path: &[u8]) -> Result<PathBuf> {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
Ok(PathBuf::from(std::ffi::OsStr::from_bytes(path)))
}
#[cfg(not(unix))]
{
std::str::from_utf8(path)
.map(PathBuf::from)
.map_err(|_| Error::Address("nonce file path is invalid UTF-8".to_owned()))
}
}
async fn write_all(half: &mut impl WriteHalf, bytes: &[u8]) -> Result<()> {
let mut rest = bytes;
while !rest.is_empty() {
let written = half
.sendmsg(
rest,
#[cfg(unix)]
&[],
)
.await?;
rest = &rest[written..];
}
Ok(())
}
impl Display for Tcp {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.nonce_file() {
Some(nonce_file) => {
f.write_str("nonce-tcp:noncefile=")?;
encode_percents(f, nonce_file)?;
f.write_str(",")?;
}
None => f.write_str("tcp:")?,
}
f.write_str("host=")?;
encode_percents(f, self.host().as_bytes())?;
write!(f, ",port={}", self.port())?;
if let Some(bind) = self.bind() {
f.write_str(",bind=")?;
encode_percents(f, bind.as_bytes())?;
}
if let Some(family) = self.family() {
write!(f, ",family={family}")?;
}
Ok(())
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TcpTransportFamily {
Ipv4,
Ipv6,
}
impl FromStr for TcpTransportFamily {
type Err = Error;
fn from_str(family: &str) -> Result<Self> {
match family {
"ipv4" => Ok(Self::Ipv4),
"ipv6" => Ok(Self::Ipv6),
_ => Err(Error::Address(format!(
"invalid tcp address `family`: {family}"
))),
}
}
}
impl Display for TcpTransportFamily {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Ipv4 => write!(f, "ipv4"),
Self::Ipv6 => write!(f, "ipv6"),
}
}
}