implement sink and stream with tokio::spawn
This commit is contained in:
parent
595d165479
commit
6385e43e8c
|
@ -1,6 +1,12 @@
|
||||||
use std::{pin::pin, sync::Arc, task::Poll};
|
use std::{
|
||||||
|
borrow::Borrow,
|
||||||
|
future::Future,
|
||||||
|
pin::pin,
|
||||||
|
sync::Arc,
|
||||||
|
task::{ready, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
use futures::{Sink, Stream, StreamExt};
|
use futures::{FutureExt, Sink, SinkExt, Stream, StreamExt};
|
||||||
use jid::ParseError;
|
use jid::ParseError;
|
||||||
use rsasl::config::SASLConfig;
|
use rsasl::config::SASLConfig;
|
||||||
use stanza::{
|
use stanza::{
|
||||||
|
@ -8,9 +14,11 @@ use stanza::{
|
||||||
sasl::Mechanisms,
|
sasl::Mechanisms,
|
||||||
stream::{Feature, Features},
|
stream::{Feature, Features},
|
||||||
};
|
};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
connection::{Tls, Unencrypted},
|
connection::{Tls, Unencrypted},
|
||||||
|
jabber_stream::bound_stream::BoundJabberStream,
|
||||||
Connection, Error, JabberStream, Result, JID,
|
Connection, Error, JabberStream, Result, JID,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -56,7 +64,7 @@ impl JabberClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn inner(self) -> Result<JabberStream<Tls>> {
|
pub(crate) fn inner(self) -> Result<BoundJabberStream<Tls>> {
|
||||||
match self.connection {
|
match self.connection {
|
||||||
ConnectionState::Disconnected => return Err(Error::Disconnected),
|
ConnectionState::Disconnected => return Err(Error::Disconnected),
|
||||||
ConnectionState::Connecting(_connecting) => return Err(Error::Connecting),
|
ConnectionState::Connecting(_connecting) => return Err(Error::Connecting),
|
||||||
|
@ -64,21 +72,137 @@ impl JabberClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_stanza(&mut self, stanza: &Stanza) -> Result<()> {
|
// pub async fn send_stanza(&mut self, stanza: &Stanza) -> Result<()> {
|
||||||
match &mut self.connection {
|
// match &mut self.connection {
|
||||||
ConnectionState::Disconnected => return Err(Error::Disconnected),
|
// ConnectionState::Disconnected => return Err(Error::Disconnected),
|
||||||
ConnectionState::Connecting(_connecting) => return Err(Error::Connecting),
|
// ConnectionState::Connecting(_connecting) => return Err(Error::Connecting),
|
||||||
ConnectionState::Connected(jabber_stream) => {
|
// ConnectionState::Connected(jabber_stream) => {
|
||||||
Ok(jabber_stream.send_stanza(stanza).await?)
|
// Ok(jabber_stream.send_stanza(stanza).await?)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sink<Stanza> for JabberClient {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn poll_ready(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||||
|
self.get_mut().connection.poll_ready_unpin(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_send(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
item: Stanza,
|
||||||
|
) -> std::result::Result<(), Self::Error> {
|
||||||
|
self.get_mut().connection.start_send_unpin(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||||
|
self.get_mut().connection.poll_flush_unpin(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_close(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||||
|
self.get_mut().connection.poll_flush_unpin(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream for JabberClient {
|
||||||
|
type Item = Result<Stanza>;
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<Option<Self::Item>> {
|
||||||
|
self.get_mut().connection.poll_next_unpin(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ConnectionState {
|
pub enum ConnectionState {
|
||||||
Disconnected,
|
Disconnected,
|
||||||
Connecting(Connecting),
|
Connecting(Connecting),
|
||||||
Connected(JabberStream<Tls>),
|
Connected(BoundJabberStream<Tls>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sink<Stanza> for ConnectionState {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn poll_ready(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||||
|
match self.get_mut() {
|
||||||
|
ConnectionState::Disconnected => Poll::Ready(Err(Error::Disconnected)),
|
||||||
|
ConnectionState::Connecting(_connecting) => Poll::Pending,
|
||||||
|
ConnectionState::Connected(bound_jabber_stream) => {
|
||||||
|
bound_jabber_stream.poll_ready_unpin(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_send(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
item: Stanza,
|
||||||
|
) -> std::result::Result<(), Self::Error> {
|
||||||
|
match self.get_mut() {
|
||||||
|
ConnectionState::Disconnected => Err(Error::Disconnected),
|
||||||
|
ConnectionState::Connecting(_connecting) => Err(Error::Connecting),
|
||||||
|
ConnectionState::Connected(bound_jabber_stream) => {
|
||||||
|
bound_jabber_stream.start_send_unpin(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||||
|
match self.get_mut() {
|
||||||
|
ConnectionState::Disconnected => Poll::Ready(Err(Error::Disconnected)),
|
||||||
|
ConnectionState::Connecting(_connecting) => Poll::Pending,
|
||||||
|
ConnectionState::Connected(bound_jabber_stream) => {
|
||||||
|
bound_jabber_stream.poll_flush_unpin(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_close(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<std::result::Result<(), Self::Error>> {
|
||||||
|
match self.get_mut() {
|
||||||
|
ConnectionState::Disconnected => Poll::Ready(Err(Error::Disconnected)),
|
||||||
|
ConnectionState::Connecting(_connecting) => Poll::Pending,
|
||||||
|
ConnectionState::Connected(bound_jabber_stream) => {
|
||||||
|
bound_jabber_stream.poll_close_unpin(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream for ConnectionState {
|
||||||
|
type Item = Result<Stanza>;
|
||||||
|
|
||||||
|
fn poll_next(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut std::task::Context<'_>,
|
||||||
|
) -> Poll<Option<Self::Item>> {
|
||||||
|
match self.get_mut() {
|
||||||
|
ConnectionState::Disconnected => Poll::Ready(Some(Err(Error::Disconnected))),
|
||||||
|
ConnectionState::Connecting(_connecting) => Poll::Pending,
|
||||||
|
ConnectionState::Connected(bound_jabber_stream) => {
|
||||||
|
bound_jabber_stream.poll_next_unpin(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConnectionState {
|
impl ConnectionState {
|
||||||
|
@ -150,7 +274,9 @@ impl ConnectionState {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Connecting::Bind(jabber_stream) => {
|
Connecting::Bind(jabber_stream) => {
|
||||||
self = ConnectionState::Connected(jabber_stream.bind(jid).await?)
|
self = ConnectionState::Connected(
|
||||||
|
jabber_stream.bind(jid).await?.to_bound_jabber(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
connected => return Ok(connected),
|
connected => return Ok(connected),
|
||||||
|
@ -194,11 +320,20 @@ pub enum InsecureConnecting {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::time::Duration;
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use super::JabberClient;
|
use super::JabberClient;
|
||||||
|
use futures::{SinkExt, StreamExt};
|
||||||
|
use stanza::{
|
||||||
|
client::{
|
||||||
|
iq::{Iq, IqType, Query},
|
||||||
|
Stanza,
|
||||||
|
},
|
||||||
|
xep_0199::Ping,
|
||||||
|
};
|
||||||
use test_log::test;
|
use test_log::test;
|
||||||
use tokio::time::sleep;
|
use tokio::{sync::Mutex, time::sleep};
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
#[test(tokio::test)]
|
#[test(tokio::test)]
|
||||||
async fn login() {
|
async fn login() {
|
||||||
|
@ -206,4 +341,50 @@ mod tests {
|
||||||
client.connect().await.unwrap();
|
client.connect().await.unwrap();
|
||||||
sleep(Duration::from_secs(5)).await
|
sleep(Duration::from_secs(5)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test(tokio::test)]
|
||||||
|
async fn ping_parallel() {
|
||||||
|
let mut client = JabberClient::new("test@blos.sm", "slayed").unwrap();
|
||||||
|
client.connect().await.unwrap();
|
||||||
|
sleep(Duration::from_secs(5)).await;
|
||||||
|
let jid = client.jid.clone();
|
||||||
|
let server = client.server.clone();
|
||||||
|
let mut client = Arc::new(Mutex::new(client));
|
||||||
|
|
||||||
|
tokio::join!(
|
||||||
|
async {
|
||||||
|
let mut client = client.lock().await;
|
||||||
|
client
|
||||||
|
.send(Stanza::Iq(Iq {
|
||||||
|
from: Some(jid.clone()),
|
||||||
|
id: "c2s1".to_string(),
|
||||||
|
to: Some(server.clone().try_into().unwrap()),
|
||||||
|
r#type: IqType::Get,
|
||||||
|
lang: None,
|
||||||
|
query: Some(Query::Ping(Ping)),
|
||||||
|
errors: Vec::new(),
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
},
|
||||||
|
async {
|
||||||
|
let mut client = client.lock().await;
|
||||||
|
client
|
||||||
|
.send(Stanza::Iq(Iq {
|
||||||
|
from: Some(jid.clone()),
|
||||||
|
id: "c2s2".to_string(),
|
||||||
|
to: Some(server.clone().try_into().unwrap()),
|
||||||
|
r#type: IqType::Get,
|
||||||
|
lang: None,
|
||||||
|
query: Some(Query::Ping(Ping)),
|
||||||
|
errors: Vec::new(),
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
},
|
||||||
|
async {
|
||||||
|
while let Some(stanza) = client.lock().await.next().await {
|
||||||
|
info!("{:#?}", stanza);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ use rsasl::mechname::MechanismNameError;
|
||||||
use stanza::client::error::Error as ClientError;
|
use stanza::client::error::Error as ClientError;
|
||||||
use stanza::sasl::Failure;
|
use stanza::sasl::Failure;
|
||||||
use stanza::stream::Error as StreamError;
|
use stanza::stream::Error as StreamError;
|
||||||
|
use tokio::task::JoinError;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
@ -28,6 +29,7 @@ pub enum Error {
|
||||||
MissingError,
|
MissingError,
|
||||||
Disconnected,
|
Disconnected,
|
||||||
Connecting,
|
Connecting,
|
||||||
|
JoinError(JoinError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -42,6 +44,12 @@ impl From<rsasl::prelude::SASLError> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<JoinError> for Error {
|
||||||
|
fn from(e: JoinError) -> Self {
|
||||||
|
Self::JoinError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<peanuts::DeserializeError> for Error {
|
impl From<peanuts::DeserializeError> for Error {
|
||||||
fn from(e: peanuts::DeserializeError) -> Self {
|
fn from(e: peanuts::DeserializeError) -> Self {
|
||||||
Error::Deserialization(e)
|
Error::Deserialization(e)
|
||||||
|
|
|
@ -27,7 +27,7 @@ pub mod bound_stream;
|
||||||
// open stream (streams started)
|
// open stream (streams started)
|
||||||
pub struct JabberStream<S> {
|
pub struct JabberStream<S> {
|
||||||
reader: Reader<ReadHalf<S>>,
|
reader: Reader<ReadHalf<S>>,
|
||||||
writer: Writer<WriteHalf<S>>,
|
pub(crate) writer: Writer<WriteHalf<S>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> JabberStream<S>
|
impl<S> JabberStream<S>
|
||||||
|
@ -368,12 +368,12 @@ mod tests {
|
||||||
async fn sink() {
|
async fn sink() {
|
||||||
let mut client = JabberClient::new("test@blos.sm", "slayed").unwrap();
|
let mut client = JabberClient::new("test@blos.sm", "slayed").unwrap();
|
||||||
client.connect().await.unwrap();
|
client.connect().await.unwrap();
|
||||||
let stream = client.inner().unwrap();
|
// let stream = client.inner().unwrap();
|
||||||
let sink = sink::unfold(stream, |mut stream, stanza: Stanza| async move {
|
// let sink = sink::unfold(stream, |mut stream, stanza: Stanza| async move {
|
||||||
stream.writer.write(&stanza).await?;
|
// stream.writer.write(&stanza).await?;
|
||||||
Ok::<JabberStream<Tls>, Error>(stream)
|
// Ok::<JabberStream<Tls>, Error>(stream)
|
||||||
});
|
// });
|
||||||
todo!()
|
// todo!()
|
||||||
// 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()
|
||||||
|
|
|
@ -1,70 +1,71 @@
|
||||||
|
use std::future::ready;
|
||||||
use std::pin::pin;
|
use std::pin::pin;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::task::Poll;
|
||||||
|
|
||||||
|
use futures::ready;
|
||||||
|
use futures::FutureExt;
|
||||||
use futures::{sink, stream, Sink, Stream};
|
use futures::{sink, stream, Sink, Stream};
|
||||||
use peanuts::{Reader, Writer};
|
use peanuts::{Reader, Writer};
|
||||||
use pin_project::pin_project;
|
use pin_project::pin_project;
|
||||||
use stanza::client::Stanza;
|
use stanza::client::Stanza;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite, ReadHalf, WriteHalf};
|
use tokio::io::{AsyncRead, AsyncWrite, ReadHalf, WriteHalf};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
use super::JabberStream;
|
use super::JabberStream;
|
||||||
|
|
||||||
#[pin_project]
|
#[pin_project]
|
||||||
pub struct BoundJabberStream<R, W, S>
|
pub struct BoundJabberStream<S>
|
||||||
where
|
where
|
||||||
R: Stream,
|
|
||||||
W: Sink<Stanza>,
|
|
||||||
S: AsyncWrite + AsyncRead + Unpin + Send,
|
S: AsyncWrite + AsyncRead + Unpin + Send,
|
||||||
{
|
{
|
||||||
reader: Arc<Mutex<Option<Reader<ReadHalf<S>>>>>,
|
reader: Arc<Mutex<Reader<ReadHalf<S>>>>,
|
||||||
writer: Arc<Mutex<Option<Writer<WriteHalf<S>>>>>,
|
writer: Arc<Mutex<Writer<WriteHalf<S>>>>,
|
||||||
stream: R,
|
write_handle: Option<JoinHandle<Result<(), Error>>>,
|
||||||
sink: W,
|
read_handle: Option<JoinHandle<Result<Stanza, Error>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R, W, S> BoundJabberStream<R, W, S>
|
impl<S> BoundJabberStream<S>
|
||||||
where
|
where
|
||||||
R: Stream,
|
|
||||||
W: Sink<Stanza>,
|
|
||||||
S: AsyncWrite + AsyncRead + Unpin + Send,
|
S: AsyncWrite + AsyncRead + Unpin + Send,
|
||||||
{
|
{
|
||||||
// TODO: look into biased mutex, to close stream ASAP
|
// TODO: look into biased mutex, to close stream ASAP
|
||||||
pub async fn close_stream(self) -> Result<JabberStream<S>, Error> {
|
// TODO: put into connection
|
||||||
if let Some(reader) = self.reader.lock().await.take() {
|
// pub async fn close_stream(self) -> Result<JabberStream<S>, Error> {
|
||||||
if let Some(writer) = self.writer.lock().await.take() {
|
// let reader = self.reader.lock().await.into_self();
|
||||||
// TODO: writer </stream:stream>
|
// let writer = self.writer.lock().await.into_self();
|
||||||
return Ok(JabberStream { reader, writer });
|
// // TODO: writer </stream:stream>
|
||||||
}
|
// return Ok(JabberStream { reader, writer });
|
||||||
}
|
// }
|
||||||
return Err(Error::StreamClosed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait JabberStreamTrait: AsyncWrite + AsyncRead + Unpin + Send {}
|
pub trait JabberStreamTrait: AsyncWrite + AsyncRead + Unpin + Send {}
|
||||||
|
|
||||||
impl<R, W, S> Sink<Stanza> for BoundJabberStream<R, W, S>
|
impl<S> Sink<Stanza> for BoundJabberStream<S>
|
||||||
where
|
where
|
||||||
R: Stream,
|
S: AsyncWrite + AsyncRead + Unpin + Send + 'static,
|
||||||
W: Sink<Stanza> + Unpin,
|
|
||||||
S: AsyncWrite + AsyncRead + Unpin + Send,
|
|
||||||
{
|
{
|
||||||
type Error = <W as Sink<Stanza>>::Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn poll_ready(
|
fn poll_ready(
|
||||||
self: std::pin::Pin<&mut Self>,
|
self: std::pin::Pin<&mut Self>,
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
) -> std::task::Poll<Result<(), Self::Error>> {
|
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||||
let this = self.project();
|
self.poll_flush(cx)
|
||||||
pin!(this.sink).poll_ready(cx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_send(self: std::pin::Pin<&mut Self>, item: Stanza) -> Result<(), Self::Error> {
|
fn start_send(self: std::pin::Pin<&mut Self>, item: Stanza) -> Result<(), Self::Error> {
|
||||||
let this = self.project();
|
let this = self.project();
|
||||||
pin!(this.sink).start_send(item)
|
if let Some(_write_handle) = this.write_handle {
|
||||||
|
panic!("start_send called without poll_ready")
|
||||||
|
} else {
|
||||||
|
*this.write_handle = Some(tokio::spawn(write(this.writer.clone(), item)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_flush(
|
fn poll_flush(
|
||||||
|
@ -72,32 +73,55 @@ where
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
) -> std::task::Poll<Result<(), Self::Error>> {
|
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||||
let this = self.project();
|
let this = self.project();
|
||||||
pin!(this.sink).poll_flush(cx)
|
Poll::Ready(if let Some(join_handle) = this.write_handle.as_mut() {
|
||||||
|
match ready!(join_handle.poll_unpin(cx)) {
|
||||||
|
Ok(state) => {
|
||||||
|
*this.write_handle = None;
|
||||||
|
state
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
*this.write_handle = None;
|
||||||
|
Err(err.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_close(
|
fn poll_close(
|
||||||
self: std::pin::Pin<&mut Self>,
|
self: std::pin::Pin<&mut Self>,
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
) -> std::task::Poll<Result<(), Self::Error>> {
|
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||||
let this = self.project();
|
self.poll_flush(cx)
|
||||||
pin!(this.sink).poll_close(cx)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R, W, S> Stream for BoundJabberStream<R, W, S>
|
impl<S> Stream for BoundJabberStream<S>
|
||||||
where
|
where
|
||||||
R: Stream + Unpin,
|
S: AsyncWrite + AsyncRead + Unpin + Send + 'static,
|
||||||
W: Sink<Stanza>,
|
|
||||||
S: AsyncWrite + AsyncRead + Unpin + Send,
|
|
||||||
{
|
{
|
||||||
type Item = <R as Stream>::Item;
|
type Item = Result<Stanza, Error>;
|
||||||
|
|
||||||
fn poll_next(
|
fn poll_next(
|
||||||
self: Pin<&mut Self>,
|
self: Pin<&mut Self>,
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
) -> std::task::Poll<Option<Self::Item>> {
|
) -> std::task::Poll<Option<Self::Item>> {
|
||||||
let this = self.project();
|
let this = self.project();
|
||||||
pin!(this.stream).poll_next(cx)
|
|
||||||
|
loop {
|
||||||
|
if let Some(join_handle) = this.read_handle.as_mut() {
|
||||||
|
let stanza = ready!(join_handle.poll_unpin(cx));
|
||||||
|
if let Ok(item) = stanza {
|
||||||
|
*this.read_handle = None;
|
||||||
|
return Poll::Ready(Some(item));
|
||||||
|
} else if let Err(err) = stanza {
|
||||||
|
return Poll::Ready(Some(Err(err.into())));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*this.read_handle = Some(tokio::spawn(read(this.reader.clone())))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,49 +129,36 @@ impl<S> JabberStream<S>
|
||||||
where
|
where
|
||||||
S: AsyncWrite + AsyncRead + Unpin + Send,
|
S: AsyncWrite + AsyncRead + Unpin + Send,
|
||||||
{
|
{
|
||||||
pub fn to_bound_jabber(self) -> BoundJabberStream<impl Stream, impl Sink<Stanza>, S> {
|
pub fn to_bound_jabber(self) -> BoundJabberStream<S> {
|
||||||
let reader = Arc::new(Mutex::new(Some(self.reader)));
|
let reader = Arc::new(Mutex::new(self.reader));
|
||||||
let writer = Arc::new(Mutex::new(Some(self.writer)));
|
let writer = Arc::new(Mutex::new(self.writer));
|
||||||
let sink = sink::unfold(writer.clone(), |writer, s: Stanza| async move {
|
|
||||||
write(writer, s).await
|
|
||||||
});
|
|
||||||
let stream = stream::unfold(reader.clone(), |reader| async { read(reader).await });
|
|
||||||
BoundJabberStream {
|
BoundJabberStream {
|
||||||
sink,
|
|
||||||
stream,
|
|
||||||
writer,
|
writer,
|
||||||
reader,
|
reader,
|
||||||
|
write_handle: None,
|
||||||
|
read_handle: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn write<W: AsyncWrite + Unpin + Send>(
|
pub async fn write<W: AsyncWrite + Unpin + Send>(
|
||||||
writer: Arc<Mutex<Option<Writer<WriteHalf<W>>>>>,
|
writer: Arc<Mutex<Writer<WriteHalf<W>>>>,
|
||||||
stanza: Stanza,
|
stanza: Stanza,
|
||||||
) -> Result<Arc<Mutex<Option<Writer<WriteHalf<W>>>>>, Error> {
|
) -> Result<(), Error> {
|
||||||
{
|
{
|
||||||
if let Some(writer) = writer.lock().await.as_mut() {
|
let mut writer = writer.lock().await;
|
||||||
writer.write(&stanza).await?;
|
writer.write(&stanza).await?;
|
||||||
} else {
|
|
||||||
return Err(Error::StreamClosed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(writer)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read<R: AsyncRead + Unpin + Send>(
|
pub async fn read<R: AsyncRead + Unpin + Send>(
|
||||||
reader: Arc<Mutex<Option<Reader<ReadHalf<R>>>>>,
|
reader: Arc<Mutex<Reader<ReadHalf<R>>>>,
|
||||||
) -> Option<(
|
) -> Result<Stanza, Error> {
|
||||||
Result<Stanza, Error>,
|
|
||||||
Arc<Mutex<Option<Reader<ReadHalf<R>>>>>,
|
|
||||||
)> {
|
|
||||||
let stanza: Result<Stanza, Error>;
|
let stanza: Result<Stanza, Error>;
|
||||||
{
|
{
|
||||||
if let Some(reader) = reader.lock().await.as_mut() {
|
let mut reader = reader.lock().await;
|
||||||
stanza = reader.read().await.map_err(|e| e.into());
|
stanza = reader.read().await.map_err(|e| e.into());
|
||||||
} else {
|
|
||||||
stanza = Err(Error::StreamClosed)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
Some((stanza, reader))
|
stanza
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use peanuts::{
|
||||||
|
|
||||||
pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-bind";
|
pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-bind";
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Bind {
|
pub struct Bind {
|
||||||
pub r#type: Option<BindType>,
|
pub r#type: Option<BindType>,
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ impl IntoElement for Bind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum BindType {
|
pub enum BindType {
|
||||||
Resource(ResourceType),
|
Resource(ResourceType),
|
||||||
Jid(FullJidType),
|
Jid(FullJidType),
|
||||||
|
@ -56,7 +56,7 @@ impl IntoElement for BindType {
|
||||||
}
|
}
|
||||||
|
|
||||||
// minLength 8 maxLength 3071
|
// minLength 8 maxLength 3071
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct FullJidType(pub JID);
|
pub struct FullJidType(pub JID);
|
||||||
|
|
||||||
impl FromElement for FullJidType {
|
impl FromElement for FullJidType {
|
||||||
|
@ -77,7 +77,7 @@ impl IntoElement for FullJidType {
|
||||||
}
|
}
|
||||||
|
|
||||||
// minLength 1 maxLength 1023
|
// minLength 1 maxLength 1023
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ResourceType(pub String);
|
pub struct ResourceType(pub String);
|
||||||
|
|
||||||
impl FromElement for ResourceType {
|
impl FromElement for ResourceType {
|
||||||
|
|
|
@ -9,10 +9,12 @@ use peanuts::{
|
||||||
use crate::{
|
use crate::{
|
||||||
bind::{self, Bind},
|
bind::{self, Bind},
|
||||||
client::error::Error,
|
client::error::Error,
|
||||||
|
xep_0199::{self, Ping},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::XMLNS;
|
use super::XMLNS;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Iq {
|
pub struct Iq {
|
||||||
pub from: Option<JID>,
|
pub from: Option<JID>,
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
@ -25,9 +27,10 @@ pub struct Iq {
|
||||||
pub errors: Vec<Error>,
|
pub errors: Vec<Error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Query {
|
pub enum Query {
|
||||||
Bind(Bind),
|
Bind(Bind),
|
||||||
|
Ping(Ping),
|
||||||
Unsupported,
|
Unsupported,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +38,7 @@ impl FromElement for Query {
|
||||||
fn from_element(element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
|
fn from_element(element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
|
||||||
match element.identify() {
|
match element.identify() {
|
||||||
(Some(bind::XMLNS), "bind") => Ok(Query::Bind(Bind::from_element(element)?)),
|
(Some(bind::XMLNS), "bind") => Ok(Query::Bind(Bind::from_element(element)?)),
|
||||||
|
(Some(xep_0199::XMLNS), "ping") => Ok(Query::Ping(Ping::from_element(element)?)),
|
||||||
_ => Ok(Query::Unsupported),
|
_ => Ok(Query::Unsupported),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +48,7 @@ impl IntoElement for Query {
|
||||||
fn builder(&self) -> peanuts::element::ElementBuilder {
|
fn builder(&self) -> peanuts::element::ElementBuilder {
|
||||||
match self {
|
match self {
|
||||||
Query::Bind(bind) => bind.builder(),
|
Query::Bind(bind) => bind.builder(),
|
||||||
|
Query::Ping(ping) => ping.builder(),
|
||||||
// TODO: consider what to do if attempt to serialize unsupported
|
// TODO: consider what to do if attempt to serialize unsupported
|
||||||
Query::Unsupported => todo!(),
|
Query::Unsupported => todo!(),
|
||||||
}
|
}
|
||||||
|
@ -88,7 +93,7 @@ impl IntoElement for Iq {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum IqType {
|
pub enum IqType {
|
||||||
Error,
|
Error,
|
||||||
Get,
|
Get,
|
||||||
|
|
|
@ -8,6 +8,7 @@ use peanuts::{
|
||||||
|
|
||||||
use super::XMLNS;
|
use super::XMLNS;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
from: Option<JID>,
|
from: Option<JID>,
|
||||||
id: Option<String>,
|
id: Option<String>,
|
||||||
|
@ -69,7 +70,7 @@ impl IntoElement for Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Eq, Copy, Clone)]
|
#[derive(Default, PartialEq, Eq, Copy, Clone, Debug)]
|
||||||
pub enum MessageType {
|
pub enum MessageType {
|
||||||
Chat,
|
Chat,
|
||||||
Error,
|
Error,
|
||||||
|
@ -106,7 +107,7 @@ impl ToString for MessageType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Body {
|
pub struct Body {
|
||||||
lang: Option<String>,
|
lang: Option<String>,
|
||||||
body: Option<String>,
|
body: Option<String>,
|
||||||
|
@ -132,7 +133,7 @@ impl IntoElement for Body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Subject {
|
pub struct Subject {
|
||||||
lang: Option<String>,
|
lang: Option<String>,
|
||||||
subject: Option<String>,
|
subject: Option<String>,
|
||||||
|
@ -158,7 +159,7 @@ impl IntoElement for Subject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Thread {
|
pub struct Thread {
|
||||||
parent: Option<String>,
|
parent: Option<String>,
|
||||||
thread: Option<String>,
|
thread: Option<String>,
|
||||||
|
|
|
@ -15,6 +15,7 @@ pub mod presence;
|
||||||
|
|
||||||
pub const XMLNS: &str = "jabber:client";
|
pub const XMLNS: &str = "jabber:client";
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Stanza {
|
pub enum Stanza {
|
||||||
Message(Message),
|
Message(Message),
|
||||||
Presence(Presence),
|
Presence(Presence),
|
||||||
|
|
|
@ -8,6 +8,7 @@ use peanuts::{
|
||||||
|
|
||||||
use super::{error::Error, XMLNS};
|
use super::{error::Error, XMLNS};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Presence {
|
pub struct Presence {
|
||||||
from: Option<JID>,
|
from: Option<JID>,
|
||||||
id: Option<String>,
|
id: Option<String>,
|
||||||
|
@ -70,7 +71,7 @@ impl IntoElement for Presence {
|
||||||
|
|
||||||
pub enum Other {}
|
pub enum Other {}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum PresenceType {
|
pub enum PresenceType {
|
||||||
Error,
|
Error,
|
||||||
Probe,
|
Probe,
|
||||||
|
@ -112,7 +113,7 @@ impl ToString for PresenceType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum Show {
|
pub enum Show {
|
||||||
Away,
|
Away,
|
||||||
Chat,
|
Chat,
|
||||||
|
@ -160,7 +161,7 @@ impl ToString for Show {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Status {
|
pub struct Status {
|
||||||
lang: Option<String>,
|
lang: Option<String>,
|
||||||
status: String1024,
|
status: String1024,
|
||||||
|
@ -188,7 +189,7 @@ impl IntoElement for Status {
|
||||||
|
|
||||||
// TODO: enforce?
|
// TODO: enforce?
|
||||||
/// minLength 1 maxLength 1024
|
/// minLength 1 maxLength 1024
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct String1024(pub String);
|
pub struct String1024(pub String);
|
||||||
|
|
||||||
impl FromStr for String1024 {
|
impl FromStr for String1024 {
|
||||||
|
@ -206,7 +207,7 @@ impl ToString for String1024 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// xs:byte
|
// xs:byte
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Priority(pub i8);
|
pub struct Priority(pub i8);
|
||||||
|
|
||||||
impl FromElement for Priority {
|
impl FromElement for Priority {
|
||||||
|
|
|
@ -7,5 +7,6 @@ pub mod stanza_error;
|
||||||
pub mod starttls;
|
pub mod starttls;
|
||||||
pub mod stream;
|
pub mod stream;
|
||||||
pub mod stream_error;
|
pub mod stream_error;
|
||||||
|
pub mod xep_0199;
|
||||||
|
|
||||||
pub static XML_VERSION: VersionInfo = VersionInfo::One;
|
pub static XML_VERSION: VersionInfo = VersionInfo::One;
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
use peanuts::{
|
||||||
|
element::{FromElement, IntoElement},
|
||||||
|
Element,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const XMLNS: &str = "urn:xmpp:ping";
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Ping;
|
||||||
|
|
||||||
|
impl FromElement for Ping {
|
||||||
|
fn from_element(element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> {
|
||||||
|
element.check_name("ping")?;
|
||||||
|
element.check_namespace(XMLNS)?;
|
||||||
|
|
||||||
|
element.no_more_content()?;
|
||||||
|
|
||||||
|
Ok(Ping)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoElement for Ping {
|
||||||
|
fn builder(&self) -> peanuts::element::ElementBuilder {
|
||||||
|
Element::builder("ping", Some(XMLNS))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue