Compare commits
No commits in common. "4886396044356d2676a77c3900af796fe7641f42" and "7c2577d196c059ab6e2d5b0efe5e036bdad75be7" have entirely different histories.
4886396044
...
7c2577d196
|
@ -19,7 +19,6 @@ tracing = "0.1.40"
|
||||||
trust-dns-resolver = "0.22.0"
|
trust-dns-resolver = "0.22.0"
|
||||||
try_map = "0.3.1"
|
try_map = "0.3.1"
|
||||||
peanuts = { version = "0.1.0", path = "../peanuts" }
|
peanuts = { version = "0.1.0", path = "../peanuts" }
|
||||||
futures = "0.3.31"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
test-log = { version = "0.2", features = ["trace"] }
|
test-log = { version = "0.2", features = ["trace"] }
|
||||||
|
|
266
src/client.rs
266
src/client.rs
|
@ -1,266 +0,0 @@
|
||||||
use std::{pin::pin, sync::Arc, task::Poll};
|
|
||||||
|
|
||||||
use futures::{Sink, Stream, StreamExt};
|
|
||||||
use rsasl::config::SASLConfig;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
connection::{Tls, Unencrypted},
|
|
||||||
jid::ParseError,
|
|
||||||
stanza::{
|
|
||||||
client::Stanza,
|
|
||||||
sasl::Mechanisms,
|
|
||||||
stream::{Feature, Features},
|
|
||||||
},
|
|
||||||
Connection, Error, JabberStream, Result, JID,
|
|
||||||
};
|
|
||||||
|
|
||||||
// feed it client stanzas, receive client stanzas
|
|
||||||
pub struct JabberClient {
|
|
||||||
connection: ConnectionState,
|
|
||||||
jid: JID,
|
|
||||||
password: Arc<SASLConfig>,
|
|
||||||
server: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JabberClient {
|
|
||||||
pub fn new(
|
|
||||||
jid: impl TryInto<JID, Error = ParseError>,
|
|
||||||
password: impl ToString,
|
|
||||||
) -> Result<JabberClient> {
|
|
||||||
let jid = jid.try_into()?;
|
|
||||||
let sasl_config = SASLConfig::with_credentials(
|
|
||||||
None,
|
|
||||||
jid.localpart.clone().ok_or(Error::NoLocalpart)?,
|
|
||||||
password.to_string(),
|
|
||||||
)?;
|
|
||||||
Ok(JabberClient {
|
|
||||||
connection: ConnectionState::Disconnected,
|
|
||||||
jid: jid.clone(),
|
|
||||||
password: sasl_config,
|
|
||||||
server: jid.domainpart,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn connect(&mut self) -> Result<()> {
|
|
||||||
match &self.connection {
|
|
||||||
ConnectionState::Disconnected => {
|
|
||||||
self.connection = ConnectionState::Disconnected
|
|
||||||
.connect(&mut self.jid, self.password.clone(), &mut self.server)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
ConnectionState::Connecting(_connecting) => Err(Error::AlreadyConnecting),
|
|
||||||
ConnectionState::Connected(_jabber_stream) => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Stream for JabberClient {
|
|
||||||
type Item = Result<Stanza>;
|
|
||||||
|
|
||||||
fn poll_next(
|
|
||||||
self: std::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
) -> std::task::Poll<Option<Self::Item>> {
|
|
||||||
let mut client = pin!(self);
|
|
||||||
match &mut client.connection {
|
|
||||||
ConnectionState::Disconnected => Poll::Pending,
|
|
||||||
ConnectionState::Connecting(_connecting) => Poll::Pending,
|
|
||||||
ConnectionState::Connected(jabber_stream) => jabber_stream.poll_next_unpin(cx),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ConnectionState {
|
|
||||||
Disconnected,
|
|
||||||
Connecting(Connecting),
|
|
||||||
Connected(JabberStream<Tls>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConnectionState {
|
|
||||||
pub async fn connect(
|
|
||||||
mut self,
|
|
||||||
jid: &mut JID,
|
|
||||||
auth: Arc<SASLConfig>,
|
|
||||||
server: &mut String,
|
|
||||||
) -> Result<Self> {
|
|
||||||
loop {
|
|
||||||
match self {
|
|
||||||
ConnectionState::Disconnected => {
|
|
||||||
self = ConnectionState::Connecting(Connecting::start(&server).await?);
|
|
||||||
}
|
|
||||||
ConnectionState::Connecting(connecting) => match connecting {
|
|
||||||
Connecting::InsecureConnectionEstablised(tcp_stream) => {
|
|
||||||
self = ConnectionState::Connecting(Connecting::InsecureStreamStarted(
|
|
||||||
JabberStream::start_stream(tcp_stream, server).await?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Connecting::InsecureStreamStarted(jabber_stream) => {
|
|
||||||
self = ConnectionState::Connecting(Connecting::InsecureGotFeatures(
|
|
||||||
jabber_stream.get_features().await?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Connecting::InsecureGotFeatures((features, jabber_stream)) => {
|
|
||||||
match features.negotiate()? {
|
|
||||||
Feature::StartTls(_start_tls) => {
|
|
||||||
self =
|
|
||||||
ConnectionState::Connecting(Connecting::StartTls(jabber_stream))
|
|
||||||
}
|
|
||||||
// TODO: better error
|
|
||||||
_ => return Err(Error::TlsRequired),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Connecting::StartTls(jabber_stream) => {
|
|
||||||
self = ConnectionState::Connecting(Connecting::ConnectionEstablished(
|
|
||||||
jabber_stream.starttls(&server).await?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Connecting::ConnectionEstablished(tls_stream) => {
|
|
||||||
self = ConnectionState::Connecting(Connecting::StreamStarted(
|
|
||||||
JabberStream::start_stream(tls_stream, server).await?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Connecting::StreamStarted(jabber_stream) => {
|
|
||||||
self = ConnectionState::Connecting(Connecting::GotFeatures(
|
|
||||||
jabber_stream.get_features().await?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Connecting::GotFeatures((features, jabber_stream)) => {
|
|
||||||
match features.negotiate()? {
|
|
||||||
Feature::StartTls(_start_tls) => return Err(Error::AlreadyTls),
|
|
||||||
Feature::Sasl(mechanisms) => {
|
|
||||||
self = ConnectionState::Connecting(Connecting::Sasl(
|
|
||||||
mechanisms,
|
|
||||||
jabber_stream,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Feature::Bind => {
|
|
||||||
self = ConnectionState::Connecting(Connecting::Bind(jabber_stream))
|
|
||||||
}
|
|
||||||
Feature::Unknown => return Err(Error::Unsupported),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Connecting::Sasl(mechanisms, jabber_stream) => {
|
|
||||||
self = ConnectionState::Connecting(Connecting::ConnectionEstablished(
|
|
||||||
jabber_stream.sasl(mechanisms, auth.clone()).await?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
Connecting::Bind(jabber_stream) => {
|
|
||||||
self = ConnectionState::Connected(jabber_stream.bind(jid).await?)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
connected => return Ok(connected),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Connecting {
|
|
||||||
InsecureConnectionEstablised(Unencrypted),
|
|
||||||
InsecureStreamStarted(JabberStream<Unencrypted>),
|
|
||||||
InsecureGotFeatures((Features, JabberStream<Unencrypted>)),
|
|
||||||
StartTls(JabberStream<Unencrypted>),
|
|
||||||
ConnectionEstablished(Tls),
|
|
||||||
StreamStarted(JabberStream<Tls>),
|
|
||||||
GotFeatures((Features, JabberStream<Tls>)),
|
|
||||||
Sasl(Mechanisms, JabberStream<Tls>),
|
|
||||||
Bind(JabberStream<Tls>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connecting {
|
|
||||||
pub async fn start(server: &str) -> Result<Self> {
|
|
||||||
match Connection::connect(server).await? {
|
|
||||||
Connection::Encrypted(tls_stream) => Ok(Connecting::ConnectionEstablished(tls_stream)),
|
|
||||||
Connection::Unencrypted(tcp_stream) => {
|
|
||||||
Ok(Connecting::InsecureConnectionEstablised(tcp_stream))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Features {
|
|
||||||
pub fn negotiate(self) -> Result<Feature> {
|
|
||||||
if let Some(Feature::StartTls(s)) = self
|
|
||||||
.features
|
|
||||||
.iter()
|
|
||||||
.find(|feature| matches!(feature, Feature::StartTls(_s)))
|
|
||||||
{
|
|
||||||
// TODO: avoid clone
|
|
||||||
return Ok(Feature::StartTls(s.clone()));
|
|
||||||
} else if let Some(Feature::Sasl(mechanisms)) = self
|
|
||||||
.features
|
|
||||||
.iter()
|
|
||||||
.find(|feature| matches!(feature, Feature::Sasl(_)))
|
|
||||||
{
|
|
||||||
// TODO: avoid clone
|
|
||||||
return Ok(Feature::Sasl(mechanisms.clone()));
|
|
||||||
} else if let Some(Feature::Bind) = self
|
|
||||||
.features
|
|
||||||
.into_iter()
|
|
||||||
.find(|feature| matches!(feature, Feature::Bind))
|
|
||||||
{
|
|
||||||
Ok(Feature::Bind)
|
|
||||||
} else {
|
|
||||||
// TODO: better error
|
|
||||||
return Err(Error::Negotiation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum InsecureConnecting {
|
|
||||||
Disconnected,
|
|
||||||
ConnectionEstablished(Connection),
|
|
||||||
PreStarttls(JabberStream<Unencrypted>),
|
|
||||||
PreAuthenticated(JabberStream<Tls>),
|
|
||||||
Authenticated(Tls),
|
|
||||||
PreBound(JabberStream<Tls>),
|
|
||||||
Bound(JabberStream<Tls>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sink<Stanza> for JabberClient {
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn poll_ready(
|
|
||||||
self: std::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
) -> std::task::Poll<std::result::Result<(), Self::Error>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_send(
|
|
||||||
self: std::pin::Pin<&mut Self>,
|
|
||||||
item: Stanza,
|
|
||||||
) -> std::result::Result<(), Self::Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_flush(
|
|
||||||
self: std::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
) -> std::task::Poll<std::result::Result<(), Self::Error>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_close(
|
|
||||||
self: std::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
) -> std::task::Poll<std::result::Result<(), Self::Error>> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use super::JabberClient;
|
|
||||||
use test_log::test;
|
|
||||||
use tokio::time::sleep;
|
|
||||||
|
|
||||||
#[test(tokio::test)]
|
|
||||||
async fn login() {
|
|
||||||
let mut client = JabberClient::new("test@blos.sm", "slayed").unwrap();
|
|
||||||
client.connect().await.unwrap();
|
|
||||||
sleep(Duration::from_secs(5)).await
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,6 +10,7 @@ use tokio_native_tls::native_tls::TlsConnector;
|
||||||
use tokio_native_tls::TlsStream;
|
use tokio_native_tls::TlsStream;
|
||||||
use tracing::{debug, info, instrument, trace};
|
use tracing::{debug, info, instrument, trace};
|
||||||
|
|
||||||
|
use crate::Jabber;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::{Error, JID};
|
use crate::{Error, JID};
|
||||||
|
|
||||||
|
@ -18,51 +19,69 @@ pub type Unencrypted = TcpStream;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Connection {
|
pub enum Connection {
|
||||||
Encrypted(Tls),
|
Encrypted(Jabber<Tls>),
|
||||||
Unencrypted(Unencrypted),
|
Unencrypted(Jabber<Unencrypted>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
// #[instrument]
|
#[instrument]
|
||||||
/// stream not started
|
/// stream not started
|
||||||
// pub async fn ensure_tls(self) -> Result<Jabber<Tls>> {
|
pub async fn ensure_tls(self) -> Result<Jabber<Tls>> {
|
||||||
// match self {
|
match self {
|
||||||
// Connection::Encrypted(j) => Ok(j),
|
Connection::Encrypted(j) => Ok(j),
|
||||||
// Connection::Unencrypted(mut j) => {
|
Connection::Unencrypted(mut j) => {
|
||||||
// j.start_stream().await?;
|
j.start_stream().await?;
|
||||||
// info!("upgrading connection to tls");
|
info!("upgrading connection to tls");
|
||||||
// j.get_features().await?;
|
j.get_features().await?;
|
||||||
// let j = j.starttls().await?;
|
let j = j.starttls().await?;
|
||||||
// Ok(j)
|
Ok(j)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
pub async fn connect_user(jid: impl AsRef<str>) -> Result<Self> {
|
pub async fn connect_user(jid: impl AsRef<str>, password: String) -> Result<Self> {
|
||||||
let jid: JID = JID::from_str(jid.as_ref())?;
|
let jid: JID = JID::from_str(jid.as_ref())?;
|
||||||
let server = jid.domainpart.clone();
|
let server = jid.domainpart.clone();
|
||||||
Self::connect(&server).await
|
let auth = SASLConfig::with_credentials(None, jid.localpart.clone().unwrap(), password)?;
|
||||||
|
println!("auth: {:?}", auth);
|
||||||
|
Self::connect(&server, Some(jid), Some(auth)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
pub async fn connect(server: impl AsRef<str> + std::fmt::Debug) -> Result<Self> {
|
pub async fn connect(
|
||||||
info!("connecting to {}", server.as_ref());
|
server: &str,
|
||||||
let sockets = Self::get_sockets(server.as_ref()).await;
|
jid: Option<JID>,
|
||||||
|
auth: Option<Arc<SASLConfig>>,
|
||||||
|
) -> Result<Self> {
|
||||||
|
info!("connecting to {}", server);
|
||||||
|
let sockets = Self::get_sockets(&server).await;
|
||||||
debug!("discovered sockets: {:?}", sockets);
|
debug!("discovered sockets: {:?}", sockets);
|
||||||
for (socket_addr, tls) in sockets {
|
for (socket_addr, tls) in sockets {
|
||||||
match tls {
|
match tls {
|
||||||
true => {
|
true => {
|
||||||
if let Ok(connection) = Self::connect_tls(socket_addr, server.as_ref()).await {
|
if let Ok(connection) = Self::connect_tls(socket_addr, &server).await {
|
||||||
info!("connected via encrypted stream to {}", socket_addr);
|
info!("connected via encrypted stream to {}", socket_addr);
|
||||||
// let (readhalf, writehalf) = tokio::io::split(connection);
|
let (readhalf, writehalf) = tokio::io::split(connection);
|
||||||
return Ok(Self::Encrypted(connection));
|
return Ok(Self::Encrypted(Jabber::new(
|
||||||
|
readhalf,
|
||||||
|
writehalf,
|
||||||
|
jid,
|
||||||
|
auth,
|
||||||
|
server.to_owned(),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false => {
|
false => {
|
||||||
if let Ok(connection) = Self::connect_unencrypted(socket_addr).await {
|
if let Ok(connection) = Self::connect_unencrypted(socket_addr).await {
|
||||||
info!("connected via unencrypted stream to {}", socket_addr);
|
info!("connected via unencrypted stream to {}", socket_addr);
|
||||||
// let (readhalf, writehalf) = tokio::io::split(connection);
|
let (readhalf, writehalf) = tokio::io::split(connection);
|
||||||
return Ok(Self::Unencrypted(connection));
|
return Ok(Self::Unencrypted(Jabber::new(
|
||||||
|
readhalf,
|
||||||
|
writehalf,
|
||||||
|
jid,
|
||||||
|
auth,
|
||||||
|
server.to_owned(),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,16 +188,16 @@ mod tests {
|
||||||
|
|
||||||
#[test(tokio::test)]
|
#[test(tokio::test)]
|
||||||
async fn connect() {
|
async fn connect() {
|
||||||
Connection::connect("blos.sm").await.unwrap();
|
Connection::connect("blos.sm", None, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test(tokio::test)]
|
#[test(tokio::test)]
|
||||||
// async fn test_tls() {
|
async fn test_tls() {
|
||||||
// Connection::connect("blos.sm", None, None)
|
Connection::connect("blos.sm", None, None)
|
||||||
// .await
|
.await
|
||||||
// .unwrap()
|
.unwrap()
|
||||||
// .ensure_tls()
|
.ensure_tls()
|
||||||
// .await
|
.await
|
||||||
// .unwrap();
|
.unwrap();
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
26
src/error.rs
26
src/error.rs
|
@ -8,16 +8,24 @@ use crate::{jid::ParseError, stanza::sasl::Failure};
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Connection,
|
Connection,
|
||||||
|
BadStream,
|
||||||
|
StartTlsUnavailable,
|
||||||
|
TlsNegotiation,
|
||||||
Utf8Decode,
|
Utf8Decode,
|
||||||
|
NoFeatures,
|
||||||
|
UnknownNamespace,
|
||||||
|
UnknownAttribute,
|
||||||
|
NoID,
|
||||||
|
NoType,
|
||||||
|
IDMismatch,
|
||||||
|
BindError,
|
||||||
|
ParseError,
|
||||||
Negotiation,
|
Negotiation,
|
||||||
TlsRequired,
|
TlsRequired,
|
||||||
AlreadyTls,
|
UnexpectedEnd,
|
||||||
Unsupported,
|
|
||||||
NoLocalpart,
|
|
||||||
AlreadyConnecting,
|
|
||||||
UnexpectedElement(peanuts::Element),
|
UnexpectedElement(peanuts::Element),
|
||||||
|
UnexpectedText,
|
||||||
XML(peanuts::Error),
|
XML(peanuts::Error),
|
||||||
Deserialization(peanuts::DeserializeError),
|
|
||||||
SASL(SASLError),
|
SASL(SASLError),
|
||||||
JID(ParseError),
|
JID(ParseError),
|
||||||
Authentication(Failure),
|
Authentication(Failure),
|
||||||
|
@ -29,6 +37,8 @@ pub enum Error {
|
||||||
pub enum SASLError {
|
pub enum SASLError {
|
||||||
SASL(rsasl::prelude::SASLError),
|
SASL(rsasl::prelude::SASLError),
|
||||||
MechanismName(MechanismNameError),
|
MechanismName(MechanismNameError),
|
||||||
|
NoChallenge,
|
||||||
|
NoSuccess,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<rsasl::prelude::SASLError> for Error {
|
impl From<rsasl::prelude::SASLError> for Error {
|
||||||
|
@ -37,12 +47,6 @@ impl From<rsasl::prelude::SASLError> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<peanuts::DeserializeError> for Error {
|
|
||||||
fn from(e: peanuts::DeserializeError) -> Self {
|
|
||||||
Error::Deserialization(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<MechanismNameError> for Error {
|
impl From<MechanismNameError> for Error {
|
||||||
fn from(e: MechanismNameError) -> Self {
|
fn from(e: MechanismNameError) -> Self {
|
||||||
Self::SASL(SASLError::MechanismName(e))
|
Self::SASL(SASLError::MechanismName(e))
|
||||||
|
|
394
src/jabber.rs
394
src/jabber.rs
|
@ -1,10 +1,8 @@
|
||||||
use std::pin::pin;
|
use std::str;
|
||||||
use std::str::{self, FromStr};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_recursion::async_recursion;
|
use async_recursion::async_recursion;
|
||||||
use futures::StreamExt;
|
use peanuts::element::IntoElement;
|
||||||
use peanuts::element::{FromContent, IntoElement};
|
|
||||||
use peanuts::{Reader, Writer};
|
use peanuts::{Reader, Writer};
|
||||||
use rsasl::prelude::{Mechname, SASLClient, SASLConfig};
|
use rsasl::prelude::{Mechname, SASLClient, SASLConfig};
|
||||||
use tokio::io::{AsyncRead, AsyncWrite, ReadHalf, WriteHalf};
|
use tokio::io::{AsyncRead, AsyncWrite, ReadHalf, WriteHalf};
|
||||||
|
@ -15,7 +13,6 @@ use crate::connection::{Tls, Unencrypted};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::stanza::bind::{Bind, BindType, FullJidType, ResourceType};
|
use crate::stanza::bind::{Bind, BindType, FullJidType, ResourceType};
|
||||||
use crate::stanza::client::iq::{Iq, IqType, Query};
|
use crate::stanza::client::iq::{Iq, IqType, Query};
|
||||||
use crate::stanza::client::Stanza;
|
|
||||||
use crate::stanza::sasl::{Auth, Challenge, Mechanisms, Response, ServerResponse};
|
use crate::stanza::sasl::{Auth, Challenge, Mechanisms, Response, ServerResponse};
|
||||||
use crate::stanza::starttls::{Proceed, StartTls};
|
use crate::stanza::starttls::{Proceed, StartTls};
|
||||||
use crate::stanza::stream::{Feature, Features, Stream};
|
use crate::stanza::stream::{Feature, Features, Stream};
|
||||||
|
@ -23,34 +20,47 @@ use crate::stanza::XML_VERSION;
|
||||||
use crate::JID;
|
use crate::JID;
|
||||||
use crate::{Connection, Result};
|
use crate::{Connection, Result};
|
||||||
|
|
||||||
// open stream (streams started)
|
pub struct Jabber<S> {
|
||||||
pub struct JabberStream<S> {
|
|
||||||
reader: Reader<ReadHalf<S>>,
|
reader: Reader<ReadHalf<S>>,
|
||||||
writer: Writer<WriteHalf<S>>,
|
writer: Writer<WriteHalf<S>>,
|
||||||
|
jid: Option<JID>,
|
||||||
|
auth: Option<Arc<SASLConfig>>,
|
||||||
|
server: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: AsyncRead> futures::Stream for JabberStream<S> {
|
impl<S> Jabber<S>
|
||||||
type Item = Result<Stanza>;
|
|
||||||
|
|
||||||
fn poll_next(
|
|
||||||
self: std::pin::Pin<&mut Self>,
|
|
||||||
cx: &mut std::task::Context<'_>,
|
|
||||||
) -> std::task::Poll<Option<Self::Item>> {
|
|
||||||
pin!(self).reader.poll_next_unpin(cx).map(|content| {
|
|
||||||
content.map(|content| -> Result<Stanza> {
|
|
||||||
let stanza = content.map(|content| Stanza::from_content(content))?;
|
|
||||||
Ok(stanza?)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> JabberStream<S>
|
|
||||||
where
|
where
|
||||||
S: AsyncRead + AsyncWrite + Unpin + Send + std::fmt::Debug,
|
S: AsyncRead + AsyncWrite + Unpin,
|
||||||
JabberStream<S>: std::fmt::Debug,
|
|
||||||
{
|
{
|
||||||
pub async fn sasl(mut self, mechanisms: Mechanisms, sasl_config: Arc<SASLConfig>) -> Result<S> {
|
pub fn new(
|
||||||
|
reader: ReadHalf<S>,
|
||||||
|
writer: WriteHalf<S>,
|
||||||
|
jid: Option<JID>,
|
||||||
|
auth: Option<Arc<SASLConfig>>,
|
||||||
|
server: String,
|
||||||
|
) -> Self {
|
||||||
|
let reader = Reader::new(reader);
|
||||||
|
let writer = Writer::new(writer);
|
||||||
|
Self {
|
||||||
|
reader,
|
||||||
|
writer,
|
||||||
|
jid,
|
||||||
|
auth,
|
||||||
|
server,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Jabber<S>
|
||||||
|
where
|
||||||
|
S: AsyncRead + AsyncWrite + Unpin + Send,
|
||||||
|
Jabber<S>: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
pub async fn sasl(
|
||||||
|
&mut self,
|
||||||
|
mechanisms: Mechanisms,
|
||||||
|
sasl_config: Arc<SASLConfig>,
|
||||||
|
) -> Result<()> {
|
||||||
let sasl = SASLClient::new(sasl_config);
|
let sasl = SASLClient::new(sasl_config);
|
||||||
let mut offered_mechs: Vec<&Mechname> = Vec::new();
|
let mut offered_mechs: Vec<&Mechname> = Vec::new();
|
||||||
for mechanism in &mechanisms.mechanisms {
|
for mechanism in &mechanisms.mechanisms {
|
||||||
|
@ -133,15 +143,12 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let writer = self.writer.into_inner();
|
Ok(())
|
||||||
let reader = self.reader.into_inner();
|
|
||||||
let stream = reader.unsplit(writer);
|
|
||||||
Ok(stream)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn bind(mut self, jid: &mut JID) -> Result<Self> {
|
pub async fn bind(&mut self) -> Result<()> {
|
||||||
let iq_id = nanoid::nanoid!();
|
let iq_id = nanoid::nanoid!();
|
||||||
if let Some(resource) = &jid.resourcepart {
|
if let Some(resource) = self.jid.clone().unwrap().resourcepart {
|
||||||
let iq = Iq {
|
let iq = Iq {
|
||||||
from: None,
|
from: None,
|
||||||
id: iq_id.clone(),
|
id: iq_id.clone(),
|
||||||
|
@ -149,7 +156,7 @@ where
|
||||||
r#type: IqType::Set,
|
r#type: IqType::Set,
|
||||||
lang: None,
|
lang: None,
|
||||||
query: Some(Query::Bind(Bind {
|
query: Some(Query::Bind(Bind {
|
||||||
r#type: Some(BindType::Resource(ResourceType(resource.to_string()))),
|
r#type: Some(BindType::Resource(ResourceType(resource))),
|
||||||
})),
|
})),
|
||||||
errors: Vec::new(),
|
errors: Vec::new(),
|
||||||
};
|
};
|
||||||
|
@ -164,12 +171,12 @@ where
|
||||||
lang: _,
|
lang: _,
|
||||||
query:
|
query:
|
||||||
Some(Query::Bind(Bind {
|
Some(Query::Bind(Bind {
|
||||||
r#type: Some(BindType::Jid(FullJidType(new_jid))),
|
r#type: Some(BindType::Jid(FullJidType(jid))),
|
||||||
})),
|
})),
|
||||||
errors: _,
|
errors: _,
|
||||||
} if id == iq_id => {
|
} if id == iq_id => {
|
||||||
*jid = new_jid;
|
self.jid = Some(jid);
|
||||||
return Ok(self);
|
return Ok(());
|
||||||
}
|
}
|
||||||
Iq {
|
Iq {
|
||||||
from: _,
|
from: _,
|
||||||
|
@ -207,12 +214,12 @@ where
|
||||||
lang: _,
|
lang: _,
|
||||||
query:
|
query:
|
||||||
Some(Query::Bind(Bind {
|
Some(Query::Bind(Bind {
|
||||||
r#type: Some(BindType::Jid(FullJidType(new_jid))),
|
r#type: Some(BindType::Jid(FullJidType(jid))),
|
||||||
})),
|
})),
|
||||||
errors: _,
|
errors: _,
|
||||||
} if id == iq_id => {
|
} if id == iq_id => {
|
||||||
*jid = new_jid;
|
self.jid = Some(jid);
|
||||||
return Ok(self);
|
return Ok(());
|
||||||
}
|
}
|
||||||
Iq {
|
Iq {
|
||||||
from: _,
|
from: _,
|
||||||
|
@ -233,44 +240,39 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
pub async fn start_stream(connection: S, server: &mut String) -> Result<Self> {
|
pub async fn start_stream(&mut self) -> Result<()> {
|
||||||
// client to server
|
// client to server
|
||||||
let (reader, writer) = tokio::io::split(connection);
|
|
||||||
let mut reader = Reader::new(reader);
|
|
||||||
let mut writer = Writer::new(writer);
|
|
||||||
|
|
||||||
// declaration
|
// declaration
|
||||||
writer.write_declaration(XML_VERSION).await?;
|
self.writer.write_declaration(XML_VERSION).await?;
|
||||||
|
|
||||||
// opening stream element
|
// opening stream element
|
||||||
let stream = Stream::new_client(
|
let server = self.server.clone().try_into()?;
|
||||||
None,
|
let stream = Stream::new_client(None, server, None, "en".to_string());
|
||||||
JID::from_str(server.as_ref())?,
|
self.writer.write_start(&stream).await?;
|
||||||
None,
|
|
||||||
"en".to_string(),
|
|
||||||
);
|
|
||||||
writer.write_start(&stream).await?;
|
|
||||||
|
|
||||||
// server to client
|
// server to client
|
||||||
|
|
||||||
// may or may not send a declaration
|
// may or may not send a declaration
|
||||||
let _decl = reader.read_prolog().await?;
|
let _decl = self.reader.read_prolog().await?;
|
||||||
|
|
||||||
// receive stream element and validate
|
// receive stream element and validate
|
||||||
let stream: Stream = reader.read_start().await?;
|
let text = str::from_utf8(self.reader.buffer.data()).unwrap();
|
||||||
|
debug!("data: {}", text);
|
||||||
|
let stream: Stream = self.reader.read_start().await?;
|
||||||
debug!("got stream: {:?}", stream);
|
debug!("got stream: {:?}", stream);
|
||||||
if let Some(from) = stream.from {
|
if let Some(from) = stream.from {
|
||||||
*server = from.to_string();
|
self.server = from.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self { reader, writer })
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_features(mut self) -> Result<(Features, Self)> {
|
pub async fn get_features(&mut self) -> Result<Features> {
|
||||||
debug!("getting features");
|
debug!("getting features");
|
||||||
let features: Features = self.reader.read().await?;
|
let features: Features = self.reader.read().await?;
|
||||||
debug!("got features: {:?}", features);
|
debug!("got features: {:?}", features);
|
||||||
Ok((features, self))
|
Ok(features)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_inner(self) -> S {
|
pub fn into_inner(self) -> S {
|
||||||
|
@ -278,89 +280,89 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JabberStream<Unencrypted> {
|
impl Jabber<Unencrypted> {
|
||||||
// pub async fn negotiate<S: AsyncRead + AsyncWrite + Unpin>(
|
pub async fn negotiate<S: AsyncRead + AsyncWrite + Unpin>(mut self) -> Result<Jabber<Tls>> {
|
||||||
// mut self,
|
self.start_stream().await?;
|
||||||
// features: Features,
|
// TODO: timeout
|
||||||
// ) -> Result<Feature> {
|
let features = self.get_features().await?.features;
|
||||||
// // TODO: timeout
|
if let Some(Feature::StartTls(_)) = features
|
||||||
// if let Some(Feature::StartTls(_)) = features
|
.iter()
|
||||||
// .features
|
.find(|feature| matches!(feature, Feature::StartTls(_s)))
|
||||||
// .iter()
|
{
|
||||||
// .find(|feature| matches!(feature, Feature::StartTls(_s)))
|
let jabber = self.starttls().await?;
|
||||||
// {
|
let jabber = jabber.negotiate().await?;
|
||||||
// return Ok(self);
|
return Ok(jabber);
|
||||||
// } else {
|
} else {
|
||||||
// // TODO: better error
|
// TODO: better error
|
||||||
// return Err(Error::TlsRequired);
|
return Err(Error::TlsRequired);
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// #[async_recursion]
|
|
||||||
// pub async fn negotiate_tls_optional(mut self) -> Result<Connection> {
|
|
||||||
// self.start_stream().await?;
|
|
||||||
// // TODO: timeout
|
|
||||||
// let features = self.get_features().await?.features;
|
|
||||||
// if let Some(Feature::StartTls(_)) = features
|
|
||||||
// .iter()
|
|
||||||
// .find(|feature| matches!(feature, Feature::StartTls(_s)))
|
|
||||||
// {
|
|
||||||
// let jabber = self.starttls().await?;
|
|
||||||
// let jabber = jabber.negotiate().await?;
|
|
||||||
// return Ok(Connection::Encrypted(jabber));
|
|
||||||
// } else if let (Some(sasl_config), Some(Feature::Sasl(mechanisms))) = (
|
|
||||||
// self.auth.clone(),
|
|
||||||
// features
|
|
||||||
// .iter()
|
|
||||||
// .find(|feature| matches!(feature, Feature::Sasl(_))),
|
|
||||||
// ) {
|
|
||||||
// self.sasl(mechanisms.clone(), sasl_config).await?;
|
|
||||||
// let jabber = self.negotiate_tls_optional().await?;
|
|
||||||
// Ok(jabber)
|
|
||||||
// } else if let Some(Feature::Bind) = features
|
|
||||||
// .iter()
|
|
||||||
// .find(|feature| matches!(feature, Feature::Bind))
|
|
||||||
// {
|
|
||||||
// self.bind().await?;
|
|
||||||
// Ok(Connection::Unencrypted(self))
|
|
||||||
// } else {
|
|
||||||
// // TODO: better error
|
|
||||||
// return Err(Error::Negotiation);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JabberStream<Tls> {
|
#[async_recursion]
|
||||||
// #[async_recursion]
|
pub async fn negotiate_tls_optional(mut self) -> Result<Connection> {
|
||||||
// pub async fn negotiate(mut self) -> Result<JabberStream<Tls>> {
|
self.start_stream().await?;
|
||||||
// self.start_stream().await?;
|
// TODO: timeout
|
||||||
// let features = self.get_features().await?.features;
|
let features = self.get_features().await?.features;
|
||||||
|
if let Some(Feature::StartTls(_)) = features
|
||||||
// if let (Some(sasl_config), Some(Feature::Sasl(mechanisms))) = (
|
.iter()
|
||||||
// self.auth.clone(),
|
.find(|feature| matches!(feature, Feature::StartTls(_s)))
|
||||||
// features
|
{
|
||||||
// .iter()
|
let jabber = self.starttls().await?;
|
||||||
// .find(|feature| matches!(feature, Feature::Sasl(_))),
|
let jabber = jabber.negotiate().await?;
|
||||||
// ) {
|
return Ok(Connection::Encrypted(jabber));
|
||||||
// // TODO: avoid clone
|
} else if let (Some(sasl_config), Some(Feature::Sasl(mechanisms))) = (
|
||||||
// self.sasl(mechanisms.clone(), sasl_config).await?;
|
self.auth.clone(),
|
||||||
// let jabber = self.negotiate().await?;
|
features
|
||||||
// Ok(jabber)
|
.iter()
|
||||||
// } else if let Some(Feature::Bind) = features
|
.find(|feature| matches!(feature, Feature::Sasl(_))),
|
||||||
// .iter()
|
) {
|
||||||
// .find(|feature| matches!(feature, Feature::Bind))
|
self.sasl(mechanisms.clone(), sasl_config).await?;
|
||||||
// {
|
let jabber = self.negotiate_tls_optional().await?;
|
||||||
// self.bind().await?;
|
Ok(jabber)
|
||||||
// Ok(self)
|
} else if let Some(Feature::Bind) = features
|
||||||
// } else {
|
.iter()
|
||||||
// // TODO: better error
|
.find(|feature| matches!(feature, Feature::Bind))
|
||||||
// return Err(Error::Negotiation);
|
{
|
||||||
// }
|
self.bind().await?;
|
||||||
// }
|
Ok(Connection::Unencrypted(self))
|
||||||
|
} else {
|
||||||
|
// TODO: better error
|
||||||
|
return Err(Error::Negotiation);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JabberStream<Unencrypted> {
|
impl Jabber<Tls> {
|
||||||
pub async fn starttls(mut self, domain: impl AsRef<str>) -> Result<Tls> {
|
#[async_recursion]
|
||||||
|
pub async fn negotiate(mut self) -> Result<Jabber<Tls>> {
|
||||||
|
self.start_stream().await?;
|
||||||
|
let features = self.get_features().await?.features;
|
||||||
|
|
||||||
|
if let (Some(sasl_config), Some(Feature::Sasl(mechanisms))) = (
|
||||||
|
self.auth.clone(),
|
||||||
|
features
|
||||||
|
.iter()
|
||||||
|
.find(|feature| matches!(feature, Feature::Sasl(_))),
|
||||||
|
) {
|
||||||
|
// TODO: avoid clone
|
||||||
|
self.sasl(mechanisms.clone(), sasl_config).await?;
|
||||||
|
let jabber = self.negotiate().await?;
|
||||||
|
Ok(jabber)
|
||||||
|
} else if let Some(Feature::Bind) = features
|
||||||
|
.iter()
|
||||||
|
.find(|feature| matches!(feature, Feature::Bind))
|
||||||
|
{
|
||||||
|
self.bind().await?;
|
||||||
|
Ok(self)
|
||||||
|
} else {
|
||||||
|
// TODO: better error
|
||||||
|
return Err(Error::Negotiation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Jabber<Unencrypted> {
|
||||||
|
pub async fn starttls(mut self) -> Result<Jabber<Tls>> {
|
||||||
self.writer
|
self.writer
|
||||||
.write_full(&StartTls { required: false })
|
.write_full(&StartTls { required: false })
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -368,31 +370,43 @@ impl JabberStream<Unencrypted> {
|
||||||
debug!("got proceed: {:?}", proceed);
|
debug!("got proceed: {:?}", proceed);
|
||||||
let connector = TlsConnector::new().unwrap();
|
let connector = TlsConnector::new().unwrap();
|
||||||
let stream = self.reader.into_inner().unsplit(self.writer.into_inner());
|
let stream = self.reader.into_inner().unsplit(self.writer.into_inner());
|
||||||
if let Ok(tls_stream) = tokio_native_tls::TlsConnector::from(connector)
|
if let Ok(tlsstream) = tokio_native_tls::TlsConnector::from(connector)
|
||||||
.connect(domain.as_ref(), stream)
|
.connect(&self.server, stream)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
// let (read, write) = tokio::io::split(tlsstream);
|
let (read, write) = tokio::io::split(tlsstream);
|
||||||
// let client = JabberStream::new(read, write);
|
let client = Jabber::new(
|
||||||
return Ok(tls_stream);
|
read,
|
||||||
|
write,
|
||||||
|
self.jid.to_owned(),
|
||||||
|
self.auth.to_owned(),
|
||||||
|
self.server.to_owned(),
|
||||||
|
);
|
||||||
|
return Ok(client);
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::Connection);
|
return Err(Error::Connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for JabberStream<Tls> {
|
impl std::fmt::Debug for Jabber<Tls> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Jabber")
|
f.debug_struct("Jabber")
|
||||||
.field("connection", &"tls")
|
.field("connection", &"tls")
|
||||||
|
.field("jid", &self.jid)
|
||||||
|
.field("auth", &self.auth)
|
||||||
|
.field("server", &self.server)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for JabberStream<Unencrypted> {
|
impl std::fmt::Debug for Jabber<Unencrypted> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("Jabber")
|
f.debug_struct("Jabber")
|
||||||
.field("connection", &"unencrypted")
|
.field("connection", &"unencrypted")
|
||||||
|
.field("jid", &self.jid)
|
||||||
|
.field("auth", &self.auth)
|
||||||
|
.field("server", &self.server)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -408,61 +422,61 @@ mod tests {
|
||||||
|
|
||||||
#[test(tokio::test)]
|
#[test(tokio::test)]
|
||||||
async fn start_stream() {
|
async fn start_stream() {
|
||||||
// let connection = Connection::connect("blos.sm", None, None).await.unwrap();
|
let connection = Connection::connect("blos.sm", None, None).await.unwrap();
|
||||||
// match connection {
|
match connection {
|
||||||
// Connection::Encrypted(mut c) => c.start_stream().await.unwrap(),
|
Connection::Encrypted(mut c) => c.start_stream().await.unwrap(),
|
||||||
// Connection::Unencrypted(mut c) => c.start_stream().await.unwrap(),
|
Connection::Unencrypted(mut c) => c.start_stream().await.unwrap(),
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test(tokio::test)]
|
#[test(tokio::test)]
|
||||||
async fn sasl() {
|
async fn sasl() {
|
||||||
// let mut jabber = Connection::connect_user("test@blos.sm", "slayed".to_string())
|
let mut jabber = Connection::connect_user("test@blos.sm", "slayed".to_string())
|
||||||
// .await
|
.await
|
||||||
// .unwrap()
|
.unwrap()
|
||||||
// .ensure_tls()
|
.ensure_tls()
|
||||||
// .await
|
.await
|
||||||
// .unwrap();
|
.unwrap();
|
||||||
// let text = str::from_utf8(jabber.reader.buffer.data()).unwrap();
|
let text = str::from_utf8(jabber.reader.buffer.data()).unwrap();
|
||||||
// println!("data: {}", text);
|
println!("data: {}", text);
|
||||||
// jabber.start_stream().await.unwrap();
|
jabber.start_stream().await.unwrap();
|
||||||
|
|
||||||
// let text = str::from_utf8(jabber.reader.buffer.data()).unwrap();
|
let text = str::from_utf8(jabber.reader.buffer.data()).unwrap();
|
||||||
// println!("data: {}", text);
|
println!("data: {}", text);
|
||||||
// jabber.reader.read_buf().await.unwrap();
|
jabber.reader.read_buf().await.unwrap();
|
||||||
// let text = str::from_utf8(jabber.reader.buffer.data()).unwrap();
|
let text = str::from_utf8(jabber.reader.buffer.data()).unwrap();
|
||||||
// println!("data: {}", text);
|
println!("data: {}", text);
|
||||||
|
|
||||||
// let features = jabber.get_features().await.unwrap();
|
let features = jabber.get_features().await.unwrap();
|
||||||
// let (sasl_config, feature) = (
|
let (sasl_config, feature) = (
|
||||||
// jabber.auth.clone().unwrap(),
|
jabber.auth.clone().unwrap(),
|
||||||
// features
|
features
|
||||||
// .features
|
.features
|
||||||
// .iter()
|
.iter()
|
||||||
// .find(|feature| matches!(feature, Feature::Sasl(_)))
|
.find(|feature| matches!(feature, Feature::Sasl(_)))
|
||||||
// .unwrap(),
|
.unwrap(),
|
||||||
// );
|
);
|
||||||
// match feature {
|
match feature {
|
||||||
// Feature::StartTls(_start_tls) => todo!(),
|
Feature::StartTls(_start_tls) => todo!(),
|
||||||
// Feature::Sasl(mechanisms) => {
|
Feature::Sasl(mechanisms) => {
|
||||||
// jabber.sasl(mechanisms.clone(), sasl_config).await.unwrap();
|
jabber.sasl(mechanisms.clone(), sasl_config).await.unwrap();
|
||||||
// }
|
}
|
||||||
// Feature::Bind => todo!(),
|
Feature::Bind => todo!(),
|
||||||
// Feature::Unknown => todo!(),
|
Feature::Unknown => todo!(),
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn negotiate() {
|
async fn negotiate() {
|
||||||
// let _jabber = Connection::connect_user("test@blos.sm", "slayed".to_string())
|
let _jabber = Connection::connect_user("test@blos.sm", "slayed".to_string())
|
||||||
// .await
|
.await
|
||||||
// .unwrap()
|
.unwrap()
|
||||||
// .ensure_tls()
|
.ensure_tls()
|
||||||
// .await
|
.await
|
||||||
// .unwrap()
|
.unwrap()
|
||||||
// .negotiate()
|
.negotiate()
|
||||||
// .await
|
.await
|
||||||
// .unwrap();
|
.unwrap();
|
||||||
// sleep(Duration::from_secs(5)).await
|
sleep(Duration::from_secs(5)).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
26
src/lib.rs
26
src/lib.rs
|
@ -2,7 +2,6 @@
|
||||||
// #![feature(let_chains)]
|
// #![feature(let_chains)]
|
||||||
|
|
||||||
// TODO: logging (dropped errors)
|
// TODO: logging (dropped errors)
|
||||||
pub mod client;
|
|
||||||
pub mod connection;
|
pub mod connection;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod jabber;
|
pub mod jabber;
|
||||||
|
@ -12,25 +11,24 @@ pub mod stanza;
|
||||||
pub use connection::Connection;
|
pub use connection::Connection;
|
||||||
use connection::Tls;
|
use connection::Tls;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use jabber::JabberStream;
|
pub use jabber::Jabber;
|
||||||
pub use jid::JID;
|
pub use jid::JID;
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
pub async fn login<J: AsRef<str>, P: AsRef<str>>(jid: J, password: P) -> Result<JabberStream<Tls>> {
|
pub async fn login<J: AsRef<str>, P: AsRef<str>>(jid: J, password: P) -> Result<Jabber<Tls>> {
|
||||||
todo!()
|
Ok(Connection::connect_user(jid, password.as_ref().to_string())
|
||||||
// Ok(Connection::connect_user(jid, password.as_ref().to_string())
|
.await?
|
||||||
// .await?
|
.ensure_tls()
|
||||||
// .ensure_tls()
|
.await?
|
||||||
// .await?
|
.negotiate()
|
||||||
// .negotiate()
|
.await?)
|
||||||
// .await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
// #[tokio::test]
|
#[tokio::test]
|
||||||
// async fn test_login() {
|
async fn test_login() {
|
||||||
// crate::login("test@blos.sm/clown", "slayed").await.unwrap();
|
crate::login("test@blos.sm/clown", "slayed").await.unwrap();
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use iq::Iq;
|
use iq::Iq;
|
||||||
use message::Message;
|
use message::Message;
|
||||||
use peanuts::{
|
use peanuts::{
|
||||||
element::{Content, ContentBuilder, FromContent, FromElement, IntoContent, IntoElement},
|
element::{FromElement, IntoElement},
|
||||||
DeserializeError,
|
DeserializeError,
|
||||||
};
|
};
|
||||||
use presence::Presence;
|
use presence::Presence;
|
||||||
|
@ -20,18 +20,6 @@ pub enum Stanza {
|
||||||
Presence(Presence),
|
Presence(Presence),
|
||||||
Iq(Iq),
|
Iq(Iq),
|
||||||
Error(StreamError),
|
Error(StreamError),
|
||||||
OtherContent(Content),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromContent for Stanza {
|
|
||||||
fn from_content(content: Content) -> peanuts::element::DeserializeResult<Self> {
|
|
||||||
match content {
|
|
||||||
Content::Element(element) => Ok(Stanza::from_element(element)?),
|
|
||||||
Content::Text(_) => Ok(Stanza::OtherContent(content)),
|
|
||||||
Content::PI => Ok(Stanza::OtherContent(content)),
|
|
||||||
Content::Comment(_) => Ok(Stanza::OtherContent(content)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromElement for Stanza {
|
impl FromElement for Stanza {
|
||||||
|
@ -48,14 +36,13 @@ impl FromElement for Stanza {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoContent for Stanza {
|
impl IntoElement for Stanza {
|
||||||
fn builder(&self) -> peanuts::element::ContentBuilder {
|
fn builder(&self) -> peanuts::element::ElementBuilder {
|
||||||
match self {
|
match self {
|
||||||
Stanza::Message(message) => <Message as IntoContent>::builder(message),
|
Stanza::Message(message) => message.builder(),
|
||||||
Stanza::Presence(presence) => <Presence as IntoContent>::builder(presence),
|
Stanza::Presence(presence) => presence.builder(),
|
||||||
Stanza::Iq(iq) => <Iq as IntoContent>::builder(iq),
|
Stanza::Iq(iq) => iq.builder(),
|
||||||
Stanza::Error(error) => <StreamError as IntoContent>::builder(error),
|
Stanza::Error(error) => error.builder(),
|
||||||
Stanza::OtherContent(_content) => ContentBuilder::Comment("other-content".to_string()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue