Partial XEP-0410 support

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
Maxime “pep” Buquet 2022-09-24 01:41:32 +02:00
parent eba5ea9773
commit 1f4e6f6451
4 changed files with 136 additions and 5 deletions

View file

@ -17,7 +17,7 @@ use std::error::Error as StdError;
use std::fmt;
use tokio_xmpp::Error as TokioXMPPError;
use xmpp_parsers::{FullJid, Jid};
use xmpp_parsers::{Error as ParserError, FullJid, Jid};
#[derive(Debug)]
pub enum Error {
@ -26,6 +26,7 @@ pub enum Error {
NonexistantSession(FullJid),
SessionAlreadyExists(FullJid),
XMPPError(TokioXMPPError),
ParserError(ParserError),
}
impl StdError for Error {}
@ -38,6 +39,7 @@ impl fmt::Display for Error {
Error::NonexistantSession(err) => write!(f, "Session doesn't exist: {}", err),
Error::SessionAlreadyExists(err) => write!(f, "Session already exist: {}", err),
Error::XMPPError(err) => write!(f, "XMPP error: {}", err),
Error::ParserError(err) => write!(f, "Parser error: {}", err),
}
}
}
@ -47,3 +49,9 @@ impl From<TokioXMPPError> for Error {
Error::XMPPError(err)
}
}
impl From<ParserError> for Error {
fn from(err: ParserError) -> Error {
Error::ParserError(err)
}
}

View file

@ -17,8 +17,9 @@ use crate::component::ComponentTrait;
use crate::error::Error;
use crate::room::{Nick, Room};
use std::collections::HashMap;
use std::collections::{BTreeMap, HashMap};
use std::ops::ControlFlow;
use std::str::FromStr;
use futures::stream::StreamExt;
use log::{debug, error};
@ -28,6 +29,7 @@ use xmpp_parsers::{
message::Message,
muc::Muc,
ns,
ping::Ping,
presence::{Presence, Type as PresenceType},
stanza_error::{DefinedCondition, ErrorType, StanzaError},
BareJid, Element, Jid,
@ -43,6 +45,7 @@ async fn handle_iq_disco<C: ComponentTrait>(
let identities = vec![Identity::new("conference", "text", "en", "Hanabi")];
let features = vec![
Feature::new("http://jabber.org/protocol/disco#info"),
Feature::new("http://jabber.org/protocol/muc#self-ping-optimization"),
Feature::new("xmpp:bouah.net:hanabi:muc:0"),
];
let extensions = Vec::new();
@ -75,11 +78,88 @@ async fn handle_iq_disco<C: ComponentTrait>(
Ok(())
}
async fn handle_iq<C: ComponentTrait>(component: &mut C, iq: Iq) -> Result<(), Error> {
async fn handle_iq_ping<C: ComponentTrait>(
component: &mut C,
iq: Iq,
payload: Element,
rooms: &mut HashMap<BareJid, Room>,
) -> Result<(), Error> {
match Ping::try_from(payload) {
Ok(_) => {
let success = Iq::empty_result(iq.from.as_ref().unwrap().clone(), iq.id.clone());
let from = iq.from.unwrap();
let to = iq.to.unwrap();
// Pinging a participant
if let Jid::Full(participant) = to.clone() {
// TODO: Reply from participant if joined and nick is correct
let bare = BareJid::from(to.clone());
if let Some(room) = rooms.get(&bare) {
if room.is_joined(&participant) &&
let Some(occupant) = room.occupants.get(&participant.resource) {
if occupant.contains(&from) {
let success = success.clone().with_from(Jid::Full(participant));
component.send_stanza(success).await?;
return Ok(());
}
}
return Ok(());
}
} else if let Jid::Bare(to) = to.clone() {
// Pinging the component
if to.node.is_none() {
let domain = BareJid::from_str(&to.domain).unwrap();
let success = success.clone().with_from(Jid::Bare(domain));
component.send_stanza(success).await?;
// Pinging a room
} else {
if let Some(room) = rooms.get(&to) {
if room.is_joined(&from) {
let success = success.clone().with_from(Jid::Bare(to));
component.send_stanza(success).await?;
return Ok(());
}
}
let success = success.clone().with_from(Jid::Bare(to));
component.send_stanza(success).await?;
}
return Ok(());
}
let error = Iq::from_error(
iq.id,
StanzaError {
type_: ErrorType::Modify,
defined_condition: DefinedCondition::NotAcceptable,
by: Some(Jid::Bare(BareJid::from(to))),
texts: BTreeMap::new(),
other: None,
},
);
component.send_stanza(error).await?;
Ok(())
}
Err(err) => {
error!("Failed to parse iq as ping: {}", err);
return Err(err.into());
}
}
}
async fn handle_iq<C: ComponentTrait>(
component: &mut C,
iq: Iq,
rooms: &mut HashMap<BareJid, Room>,
) -> Result<(), Error> {
match iq.payload {
IqType::Get(ref payload) => {
if payload.is("query", ns::DISCO_INFO) {
handle_iq_disco(component, iq.clone(), payload.clone()).await?
} else if payload.is("ping", ns::PING) {
handle_iq_ping(component, iq.clone(), payload.clone(), rooms).await?
} else {
// We MUST answer unhandled get iqs with a service-unavailable error.
let error = StanzaError::new(
@ -209,7 +289,7 @@ pub async fn handle_stanza<C: ComponentTrait>(
debug!("RECV {}", String::from(&elem));
if elem.is("iq", ns::COMPONENT_ACCEPT) {
let iq = Iq::try_from(elem).unwrap();
handle_iq(component, iq).await?;
handle_iq(component, iq, rooms).await?;
} else if elem.is("message", ns::COMPONENT_ACCEPT) {
let message = Message::try_from(elem).unwrap();
handle_message(component, message).await?;

View file

@ -310,6 +310,10 @@ impl Room {
Ok(())
}
pub fn is_joined<J: Into<Jid>>(&self, _jid: &J) -> bool {
true
}
}
#[derive(Debug, Clone, PartialEq)]
@ -367,6 +371,10 @@ impl Occupant {
Err(Error::NonexistantSession(real))
}
}
pub fn contains<J: Into<Jid>>(&self, _jid: &J) -> bool {
true
}
}
impl IntoIterator for Occupant {

View file

@ -16,12 +16,16 @@ use crate::component::TestComponent;
use crate::handlers::handle_stanza;
use crate::room::Room;
use std::collections::HashMap;
use std::collections::{BTreeMap, HashMap};
use std::str::FromStr;
use lazy_static::lazy_static;
use xmpp_parsers::{
disco::{DiscoInfoQuery, DiscoInfoResult, Feature},
iq::{Iq, IqType},
muc::Muc,
ping::Ping,
presence::{Presence, Type as PresenceType},
stanza_error::{DefinedCondition, ErrorType, StanzaError},
BareJid, Element, FullJid, Jid,
};
@ -62,3 +66,34 @@ async fn test_iq_unimplemented() {
component.expect(reply);
handle_stanza(&mut component, &mut rooms).await.unwrap();
}
#[tokio::test]
async fn test_self_ping_participant_non_existing() {
let realjid1 = Jid::Full(FullJid::from_str("foo@bar/qxx").unwrap());
let roomjid = COMPONENT_JID.clone().with_node("room");
let participant1 = roomjid.clone().with_resource("nick1");
let ping: Element = Iq {
from: Some(realjid1.clone()),
to: Some(Jid::Full(participant1.clone())),
id: String::from("ping"),
payload: IqType::Get(Ping {}.into()),
}
.into();
let mut component = TestComponent::new(vec![ping]);
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
component.expect(Into::<Element>::into(Iq::from_error(
"ping",
StanzaError {
type_: ErrorType::Modify,
defined_condition: DefinedCondition::NotAcceptable,
by: Some(Jid::Bare(roomjid)),
texts: BTreeMap::new(),
other: None,
},
)));
handle_stanza(&mut component, &mut rooms).await.unwrap();
}