stanzastream: start test suite
This commit is contained in:
parent
f78ea06bfb
commit
199218f263
2 changed files with 208 additions and 0 deletions
|
|
@ -44,6 +44,8 @@ mod error;
|
||||||
mod negotiation;
|
mod negotiation;
|
||||||
mod queue;
|
mod queue;
|
||||||
mod stream_management;
|
mod stream_management;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
mod worker;
|
mod worker;
|
||||||
|
|
||||||
use self::queue::QueueEntry;
|
use self::queue::QueueEntry;
|
||||||
|
|
|
||||||
206
tokio-xmpp/src/stanzastream/tests.rs
Normal file
206
tokio-xmpp/src/stanzastream/tests.rs
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use futures::StreamExt;
|
||||||
|
|
||||||
|
use xmpp_parsers::{
|
||||||
|
bind::{BindFeature, BindQuery, BindResponse},
|
||||||
|
iq::Iq,
|
||||||
|
message::Message,
|
||||||
|
sm,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::jid::{BareJid, ResourcePart};
|
||||||
|
use crate::xmlstream::{
|
||||||
|
accept_stream, initiate_stream, ReadError, RecvFeaturesError, StreamHeader, Timeouts,
|
||||||
|
XmppStreamElement,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
static STREAM_ID: &str = "stream-id";
|
||||||
|
static STREAM_MANAGEMENT_ID: &str = "sm-id";
|
||||||
|
|
||||||
|
fn map_eof<T>(v: Option<Result<T, ReadError>>) -> Result<T, ReadError> {
|
||||||
|
match v {
|
||||||
|
Some(v) => v,
|
||||||
|
None => Err(ReadError::HardError(io::Error::new(
|
||||||
|
io::ErrorKind::UnexpectedEof,
|
||||||
|
"eof while receiving stream element",
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_io<T>(v: Option<Result<T, ReadError>>) -> Result<T, io::Error> {
|
||||||
|
match map_eof(v) {
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
Err(e) => match e {
|
||||||
|
ReadError::SoftTimeout => Err(io::Error::new(
|
||||||
|
io::ErrorKind::TimedOut,
|
||||||
|
"soft timeout tripped",
|
||||||
|
)),
|
||||||
|
ReadError::HardError(e) => Err(e),
|
||||||
|
ReadError::ParseError(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)),
|
||||||
|
ReadError::StreamFooterReceived => Err(io::Error::new(
|
||||||
|
io::ErrorKind::UnexpectedEof,
|
||||||
|
"unexpected stream footer",
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn custom_stream_pair(
|
||||||
|
identity: BareJid,
|
||||||
|
mut features: StreamFeatures,
|
||||||
|
) -> io::Result<(StanzaStream, XmppStream)> {
|
||||||
|
// We must always offer the resource binding feature.
|
||||||
|
features
|
||||||
|
.bind
|
||||||
|
.get_or_insert_with(|| BindFeature { required: false });
|
||||||
|
|
||||||
|
let (lhs, rhs) = tokio::io::duplex(8192);
|
||||||
|
let lhs = tokio::io::BufReader::new(lhs);
|
||||||
|
let rhs = tokio::io::BufReader::new(rhs);
|
||||||
|
|
||||||
|
let lhs_identity = identity.clone();
|
||||||
|
let rhs_identity = identity.clone();
|
||||||
|
|
||||||
|
let lhs_task = tokio::spawn(async move {
|
||||||
|
let lhs = initiate_stream(
|
||||||
|
lhs,
|
||||||
|
"jabber:client",
|
||||||
|
StreamHeader {
|
||||||
|
from: Some(Cow::Borrowed(lhs_identity.as_str())),
|
||||||
|
to: Some(Cow::Borrowed(lhs_identity.domain().as_str())),
|
||||||
|
id: Some(Cow::Borrowed(STREAM_ID)),
|
||||||
|
},
|
||||||
|
Timeouts::default(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
lhs.recv_features().await.map_err(|e| match e {
|
||||||
|
RecvFeaturesError::StreamError(e) => io::Error::new(io::ErrorKind::Other, e),
|
||||||
|
RecvFeaturesError::Io(e) => e,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let rhs_task = tokio::spawn(async move {
|
||||||
|
let rhs = accept_stream(rhs, "jabber:client", Timeouts::default()).await?;
|
||||||
|
let rhs = rhs
|
||||||
|
.send_header(StreamHeader {
|
||||||
|
to: Some(Cow::Borrowed(rhs_identity.as_str())),
|
||||||
|
from: Some(Cow::Borrowed(rhs_identity.domain().as_str())),
|
||||||
|
id: Some(Cow::Borrowed(STREAM_ID)),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
rhs.send_features(&features).await
|
||||||
|
});
|
||||||
|
|
||||||
|
let lhs = lhs_task.await.unwrap()?;
|
||||||
|
let mut server = rhs_task.await.unwrap()?.box_stream();
|
||||||
|
|
||||||
|
let mut lhs = Some((lhs, identity.clone()));
|
||||||
|
let client = StanzaStream::new(
|
||||||
|
Box::new(move |_, sink| match lhs.take() {
|
||||||
|
Some(((features, stream), identity)) => match sink.send(Connection {
|
||||||
|
features,
|
||||||
|
stream: stream.box_stream(),
|
||||||
|
identity: identity.into(),
|
||||||
|
}) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(_) => {
|
||||||
|
panic!("stanza stream crashed while test suite was preparing the stream!")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => panic!(
|
||||||
|
"reconnection attempt, but reconnection logic is not available in this test suite"
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
16,
|
||||||
|
);
|
||||||
|
|
||||||
|
match map_io(server.next().await)? {
|
||||||
|
XmppStreamElement::Stanza(stanza) => match stanza {
|
||||||
|
Stanza::Iq(Iq::Set {
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
id,
|
||||||
|
payload,
|
||||||
|
}) => {
|
||||||
|
let bind: BindQuery = payload.try_into().expect("failed to parse BindQuery");
|
||||||
|
let identity = if let Some(resource) = bind.resource {
|
||||||
|
identity.with_resource(&*ResourcePart::new(&resource).unwrap())
|
||||||
|
} else {
|
||||||
|
identity.with_resource(&*ResourcePart::new("foobar").unwrap())
|
||||||
|
};
|
||||||
|
let response = BindResponse { jid: identity };
|
||||||
|
let response = Iq::Result {
|
||||||
|
from: to,
|
||||||
|
to: from,
|
||||||
|
id: id,
|
||||||
|
payload: Some(response.into()),
|
||||||
|
};
|
||||||
|
server.send(&response).await?;
|
||||||
|
}
|
||||||
|
v => panic!("unexpected stanza: {v:?}"),
|
||||||
|
},
|
||||||
|
v => panic!("unexpected stream element: {v:?}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((client, server))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn plain_pair() -> io::Result<(StanzaStream, XmppStream)> {
|
||||||
|
let (mut client, server) = custom_stream_pair(
|
||||||
|
"client@domain.example".parse().unwrap(),
|
||||||
|
StreamFeatures::default(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
match client.next().await {
|
||||||
|
Some(Event::Stream(StreamEvent::Reset { .. })) => (),
|
||||||
|
other => panic!(
|
||||||
|
"unexpected recv result on client side (immediately after stream setup): {other:?}"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
Ok((client, server))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fresh_sm_pair() -> io::Result<(StanzaStream, XmppStream)> {
|
||||||
|
let mut features = StreamFeatures::default();
|
||||||
|
features.stream_management = Some(sm::StreamManagement { optional: false });
|
||||||
|
let (mut client, mut server) =
|
||||||
|
custom_stream_pair("client@domain.example".parse().unwrap(), features).await?;
|
||||||
|
match map_io(server.next().await)? {
|
||||||
|
XmppStreamElement::SM(sm) => match sm {
|
||||||
|
sm::Nonza::Enable(sm::Enable { max, resume }) => {
|
||||||
|
server
|
||||||
|
.send(&sm::Enabled {
|
||||||
|
id: Some(sm::StreamId(STREAM_MANAGEMENT_ID.to_owned())),
|
||||||
|
location: None,
|
||||||
|
max,
|
||||||
|
resume,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
v => panic!("unexpected SM element: {v:?}"),
|
||||||
|
},
|
||||||
|
v => panic!("unexpected stream element: {v:?}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
match client.next().await {
|
||||||
|
Some(Event::Stream(StreamEvent::Reset { .. })) => (),
|
||||||
|
other => panic!(
|
||||||
|
"unexpected recv result on client side (immediately after stream setup): {other:?}"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((client, server))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn sm_negotiation() {
|
||||||
|
let (client, mut server) = fresh_sm_pair().await.expect("stream setup failed");
|
||||||
|
client.send(Box::new(Message::new(None).into())).await;
|
||||||
|
match map_io(server.next().await) {
|
||||||
|
Ok(XmppStreamElement::Stanza(Stanza::Message(Message { .. }))) => (),
|
||||||
|
other => panic!("unexpected recv result on server side: {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue