// TODO: logging (dropped errors) #![allow(unused_must_use)] use std::{ net::{IpAddr, SocketAddr}, str::FromStr, }; use jid::JID; use quick_xml::{Reader, Writer}; use tokio::net::{ tcp::{OwnedReadHalf, OwnedWriteHalf}, TcpStream, }; pub mod jid; pub struct JabberData { jid: jid::JID, password: String, } impl JabberData { pub fn new(jid: JID, password: String) -> Self { Self { jid, password } } async fn get_sockets(&self) -> Vec { let mut socket_addrs = Vec::new(); // if it's a socket/ip then just return that // socket if let Ok(socket_addr) = SocketAddr::from_str(&self.jid.domainpart) { socket_addrs.push(socket_addr); return socket_addrs; } // ip if let Ok(ip) = IpAddr::from_str(&self.jid.domainpart) { socket_addrs.push(SocketAddr::new(ip, 5222)); socket_addrs.push(SocketAddr::new(ip, 5223)); return socket_addrs; } // if port specified return name resolutions with specified port // otherwise resolve if let Ok(resolver) = trust_dns_resolver::AsyncResolver::tokio_from_system_conf() { if let Ok(lookup) = resolver .srv_lookup(format!("_xmpp-client._tcp.{}", self.jid.domainpart)) .await { for srv in lookup { resolver .lookup_ip(srv.target().to_owned()) .await .map(|ips| { for ip in ips { socket_addrs.push(SocketAddr::new(ip, srv.port())) } }); } } if let Ok(lookup) = resolver .srv_lookup(format!("_xmpps-client._tcp.{}", self.jid.domainpart)) .await { for srv in lookup { resolver .lookup_ip(srv.target().to_owned()) .await .map(|ips| { for ip in ips { socket_addrs.push(SocketAddr::new(ip, srv.port())) } }); } } // in case cannot connect through SRV records resolver.lookup_ip(&self.jid.domainpart).await.map(|ips| { for ip in ips { socket_addrs.push(SocketAddr::new(ip, 5222)); socket_addrs.push(SocketAddr::new(ip, 5223)); } }); } socket_addrs } } pub struct Jabber { reader: Reader, writer: Writer, data: JabberData, } #[derive(Debug)] pub enum JabberError { NotConnected, } impl Jabber { pub async fn connect(data: JabberData) -> Result { for socket_addr in data.get_sockets().await { println!("trying {}", socket_addr); if let Ok(stream) = TcpStream::connect(socket_addr).await { println!("connected to {}", socket_addr); let (read, write) = stream.into_split(); return Ok(Self { reader: Reader::from_reader(read), writer: Writer::new(write), data, }); } } Err(JabberError::NotConnected) } async fn reconnect(&mut self) { for socket_addr in self.data.get_sockets().await { println!("trying {}", socket_addr); if let Ok(stream) = TcpStream::connect(socket_addr).await { println!("connected to {}", socket_addr); let (read, write) = stream.into_split(); self.reader = Reader::from_reader(read); self.writer = Writer::new(write); return; } } println!("could not connect") } async fn begin_stream(&mut self) -> Result<(), JabberError> { todo!() } async fn starttls() -> Result<(), JabberError> { todo!() } async fn directtls() -> Result<(), JabberError> { todo!() } async fn auth(&mut self) -> Result<(), JabberError> { todo!() } async fn close(&mut self) {} } #[cfg(test)] mod tests { use crate::jid::JID; use super::*; #[tokio::test] async fn get_sockets() { let data = JabberData::new(JID::from_str("cel@blos.sm").unwrap(), "password".to_owned()); println!("{:?}", data.get_sockets().await) } #[tokio::test] async fn connect() { Jabber::connect(JabberData::new( JID::from_str("cel@blos.sm").unwrap(), "password".to_owned(), )) .await .unwrap(); } }