Occupant now takes a presence; new Session struct

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
Maxime “pep” Buquet 2022-10-16 12:12:42 +02:00
parent 43d2697aaa
commit fc880c7423
Signed by: pep
GPG key ID: DEDA74AEECA9D0F2
6 changed files with 329 additions and 124 deletions

View file

@ -13,11 +13,13 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::room::Session;
use std::error::Error as StdError;
use std::fmt;
use tokio_xmpp::Error as TokioXMPPError;
use xmpp_parsers::{Error as ParserError, FullJid, Jid, JidParseError};
use xmpp_parsers::{Error as ParserError, Jid, JidParseError};
#[derive(Debug)]
pub enum Error {
@ -27,10 +29,15 @@ pub enum Error {
NickAlreadyAssigned(String),
/// Raised when editing or fetching an occupant with a session that isn't associated with the
/// occupant.
NonexistantSession(FullJid),
SessionAlreadyExists(FullJid),
NonexistantSession(Session),
SessionAlreadyExists(Session),
/// Raised when fetching an occupant with a nickname that isn't assigned in the room.
ParticipantNotFound(String),
/// Raised whenever an operation is applied to a session which isn't the primary session, used
/// for Multi-Session Nick support.
SecondarySession(Session),
/// Raised when a JID was supposed to be present
MissingJid,
/// Jid Parse errors
Jid(Box<JidParseError>),
/// TokioXMPP errors
@ -46,9 +53,11 @@ impl fmt::Display for Error {
match self {
Error::MismatchJids(jid1, jid2) => write!(f, "Mismatch Jids: {}, {}", jid1, jid2),
Error::NickAlreadyAssigned(err) => write!(f, "Nickname already assigned: {}", err),
Error::NonexistantSession(err) => write!(f, "Session doesn't exist: {}", err),
Error::SessionAlreadyExists(err) => write!(f, "Session already exist: {}", err),
Error::NonexistantSession(err) => write!(f, "Session doesn't exist: {:?}", err),
Error::SessionAlreadyExists(err) => write!(f, "Session already exist: {:?}", err),
Error::ParticipantNotFound(err) => write!(f, "Participant not found: {}", err),
Error::SecondarySession(err) => write!(f, "Secondary session: {:?}", err),
Error::MissingJid => write!(f, "Missing JID"),
Error::Jid(err) => write!(f, "Jid Parse error: {}", err),
Error::Xmpp(err) => write!(f, "XMPP error: {}", err),
Error::Parser(err) => write!(f, "Parser error: {}", err),

View file

@ -15,7 +15,7 @@
use crate::component::ComponentTrait;
use crate::error::Error;
use crate::room::{Nick, Room};
use crate::room::Room;
use std::collections::{BTreeMap, HashMap};
use std::ops::ControlFlow;
@ -189,6 +189,7 @@ async fn handle_presence<C: ComponentTrait>(
let muc =
presence
.payloads
.clone()
.into_iter()
.try_for_each(|payload| match Muc::try_from(payload) {
Ok(muc) => ControlFlow::Break(muc),
@ -202,13 +203,12 @@ async fn handle_presence<C: ComponentTrait>(
let Jid::Full(participant) = presence.to.clone().unwrap() {
let roomjid = BareJid::from(participant.clone());
let nick: Nick = participant.resource.clone();
// Room already exists
if let Some(room) = rooms.get_mut(&roomjid) {
debug!("Presence received to existing room: {}", &roomjid);
if let ControlFlow::Break(_) = muc {
match room.add_session(component, realjid.clone(), nick).await {
match room.add_session(component, presence.clone()).await {
Ok(_) => (),
Err(Error::NickAlreadyAssigned(nick)) => {
let error = Presence::new(PresenceType::Error)
@ -228,7 +228,7 @@ async fn handle_presence<C: ComponentTrait>(
}
} else if let ControlFlow::Continue(_) = muc {
if room.is_joined(&realjid) {
room.update_presence(component, realjid.clone(), nick).await?
room.update_presence(component, presence.clone()).await?
} else {
// TODO: We don't want to support GC1. Error
}
@ -236,13 +236,13 @@ async fn handle_presence<C: ComponentTrait>(
} else {
debug!("Presence received to new room: {}", &roomjid);
let mut room = Room::new(roomjid.clone());
room.add_session(component, realjid, nick).await.unwrap();
room.add_session(component, presence.clone()).await.unwrap();
let _ = rooms.insert(roomjid, room);
}
}
} else if presence.type_ == PresenceType::Unavailable &&
let Jid::Full(realjid) = presence.from.unwrap() &&
let Jid::Full(participant) = presence.to.unwrap() {
let Jid::Full(realjid) = presence.from.clone().unwrap() &&
let Jid::Full(participant) = presence.to.clone().unwrap() {
let roomjid = BareJid::from(participant.clone());
@ -258,7 +258,7 @@ async fn handle_presence<C: ComponentTrait>(
).into()
]);
if let Some(mut room) = rooms.remove(&roomjid) {
match room.remove_session(component, realjid, participant.resource.clone()).await {
match room.remove_session(component, presence.clone()).await {
Ok(()) => (),
Err(Error::NonexistantSession(_)) => {
component.send_stanza(error).await.unwrap();

View file

@ -34,7 +34,6 @@ use xmpp_parsers::{
};
pub type Nick = String;
type Session = FullJid;
#[derive(Debug, PartialEq, Eq)]
pub enum BroadcastPresence {
@ -71,20 +70,22 @@ impl Room {
&self,
component: &mut C,
own_occupant: &Occupant,
own_session: &Session,
presence: Presence,
mode: BroadcastPresence,
) -> Result<(), Error> {
let leave = matches!(mode, BroadcastPresence::Leave);
let sync = matches!(mode, BroadcastPresence::Join | BroadcastPresence::Resync);
let update = matches!(mode, BroadcastPresence::Join | BroadcastPresence::Update);
let own_session = Session::try_from(presence)?;
// All participants to new participant
let presence_to_new = Presence::new(if leave {
PresenceType::Unavailable
} else {
PresenceType::None
})
.with_to(own_session.clone())
.with_to(Jid::Full(own_session.real.clone()))
.with_payloads(vec![MucUser {
status: Vec::new(),
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
@ -97,7 +98,7 @@ impl Room {
} else {
PresenceType::None
})
.with_from(Jid::Full(own_occupant.participant.clone()))
.with_from(Jid::Full(own_session.participant.clone()))
.with_payloads(vec![MucUser {
status: Vec::new(),
items: vec![MucItem::new(
@ -108,6 +109,7 @@ impl Room {
.into()]);
for (_, other) in self.occupants.iter() {
// Skip sending to all our sessions, we do it later.
if own_occupant.nick == other.nick {
continue;
}
@ -122,11 +124,13 @@ impl Room {
// Send presence from participant to others.
for session in other.iter() {
// Skip sending if it's us.
if session == own_session {
if session.real == own_session.real {
continue;
}
let presence = presence_to_old.clone().with_to(Jid::Full(session.clone()));
let presence = presence_to_old
.clone()
.with_to(Jid::Full(session.real.clone()));
component.send_stanza(presence).await?;
}
}
@ -138,7 +142,7 @@ impl Room {
.map(|session| MucItem {
affiliation: Affiliation::Owner,
role: if leave { Role::None } else { Role::Moderator },
jid: Some(session.clone()),
jid: Some(session.real.clone()),
nick: None,
actor: None,
continue_: None,
@ -156,12 +160,12 @@ impl Room {
.with_from(Jid::Full(own_occupant.participant.clone()));
for session in own_occupant.iter() {
if session == own_session {
if session == &own_session {
continue;
}
let presence = session_presence
.clone()
.with_to(Jid::Full(session.clone()))
.with_to(Jid::Full(session.real.clone()))
.with_payloads(vec![MucUser {
status: vec![],
items: self_items.clone(),
@ -179,18 +183,14 @@ impl Room {
PresenceType::None
})
.with_from(Jid::Full(own_occupant.participant.clone()))
.with_to(own_session.clone())
.with_to(own_session.real.clone())
.with_payloads(vec![MucUser {
status: if leave {
vec![MucStatus::SelfPresence]
} else {
vec![MucStatus::SelfPresence, MucStatus::AssignedNick]
},
items: if leave {
vec![MucItem::new(Affiliation::Owner, Role::None)]
} else {
self_items
},
items: self_items,
}
.into()]);
component.send_stanza(self_presence).await?;
@ -202,7 +202,7 @@ impl Room {
pub async fn send_subject<C: ComponentTrait>(
&mut self,
component: &mut C,
realjid: Session,
session: Session,
occupant: Occupant,
) -> Result<(), Error> {
debug!("Sending subject!");
@ -213,7 +213,7 @@ impl Room {
self.subject = Some((subject, setter, stamp));
}
let mut subject = Message::new(Some(Jid::Full(realjid)));
let mut subject = Message::new(Some(Jid::Full(session.real)));
subject.from = Some(Jid::Full(
self.subject.as_ref().unwrap().1.participant.clone(),
));
@ -234,9 +234,12 @@ impl Room {
pub async fn add_session<C: ComponentTrait>(
&mut self,
component: &mut C,
realjid: Session,
new_nick: Nick,
presence: Presence,
) -> Result<(), Error> {
let new_session = Session::try_from(presence)?;
let new_nick = new_session.participant.resource.clone();
let realjid = new_session.real.clone();
// Ensure nick isn't already assigned
self.occupants.iter().try_for_each(|(nick, occupant)| {
let new_nick = new_nick.as_str();
@ -248,7 +251,7 @@ impl Room {
let mode: Option<BroadcastPresence> = {
if let Some(occupant) = self.occupants.get_mut(&new_nick) {
match occupant.add_session(realjid.clone()) {
match occupant.add_session(new_session.presence.clone()) {
Ok(_) => Some(BroadcastPresence::Join),
Err(Error::SessionAlreadyExists(_)) => Some(BroadcastPresence::Resync),
Err(err) => return Err(err),
@ -261,22 +264,32 @@ impl Room {
if !self.occupants.contains_key(&new_nick) {
let _ = self.occupants.insert(
new_nick.clone(),
Occupant::new(self, realjid.clone(), new_nick.clone()),
Occupant::new(new_session.presence.clone())?,
);
}
let occupant = self.occupants.get(&new_nick).unwrap();
match mode {
Some(BroadcastPresence::Resync) => {
self.broadcast_presence(component, occupant, &realjid, BroadcastPresence::Resync)
self.broadcast_presence(
component,
&occupant,
new_session.presence,
BroadcastPresence::Resync,
)
.await?;
}
Some(BroadcastPresence::Join) => {
debug!("{} is joining {}", realjid, self.jid);
self.broadcast_presence(component, occupant, &realjid, BroadcastPresence::Join)
self.broadcast_presence(
component,
&occupant,
new_session.presence.clone(),
BroadcastPresence::Join,
)
.await?;
self.send_subject(component, realjid, occupant.clone())
self.send_subject(component, new_session, occupant.clone())
.await?;
}
_ => (),
@ -288,13 +301,21 @@ impl Room {
pub async fn update_presence<C: ComponentTrait>(
&mut self,
component: &mut C,
realjid: Session,
nick: Nick,
presence: Presence,
) -> Result<(), Error> {
let session = Session::try_from(presence)?;
let occupant = self.get_mut_occupant(&session)?;
match occupant.update_presence(session.presence.clone()) {
Ok(_) | Err(Error::SecondarySession(_)) => (),
err => err?,
}
let occupant = self.get_occupant(&session)?;
self.broadcast_presence(
component,
self.get_occupant(&realjid, &nick)?,
&realjid,
&occupant,
session.presence,
BroadcastPresence::Update,
)
.await?;
@ -304,15 +325,20 @@ impl Room {
pub async fn remove_session<C: ComponentTrait>(
&mut self,
component: &mut C,
realjid: Session,
nick: Nick,
presence: Presence,
) -> Result<(), Error> {
let session = Session::try_from(presence)?;
// If occupant doesn't exist, ignore.
if let Some(mut occupant) = self.occupants.remove(&nick) {
self.broadcast_presence(component, &occupant, &realjid, BroadcastPresence::Leave)
if let Some(mut occupant) = self.occupants.remove(&session.participant.resource) {
self.broadcast_presence(
component,
&occupant,
session.presence.clone(),
BroadcastPresence::Leave,
)
.await?;
occupant.remove_session(realjid)?;
occupant.remove_session(session.presence)?;
} else {
// TODO: Error
}
@ -322,15 +348,33 @@ impl Room {
/// Fetch the occupant associated with the provided nick and ensure the session is part of
/// it.
pub fn get_occupant(&self, realjid: &Session, nick: &Nick) -> Result<&Occupant, Error> {
if let Some(occupant) = self.occupants.get(nick) {
if occupant.sessions.contains(realjid) {
pub fn get_occupant(&self, session: &Session) -> Result<&Occupant, Error> {
if let Some(occupant) = self.occupants.get(&session.participant.resource) {
if occupant.contains(&session.real) {
Ok(occupant)
} else {
Err(Error::NonexistantSession(realjid.clone()))
Err(Error::NonexistantSession(session.clone()))
}
} else {
Err(Error::ParticipantNotFound(nick.clone()))
Err(Error::ParticipantNotFound(
session.participant.resource.clone(),
))
}
}
/// Fetch a mutable reference of the occupant associated with the provided nick and ensure the
/// session is part of it.
pub fn get_mut_occupant(&mut self, session: &Session) -> Result<&mut Occupant, Error> {
if let Some(occupant) = self.occupants.get_mut(&session.participant.resource) {
if occupant.contains(&session.real) {
Ok(occupant)
} else {
Err(Error::NonexistantSession(session.clone()))
}
} else {
Err(Error::ParticipantNotFound(
session.participant.resource.clone(),
))
}
}
@ -339,6 +383,67 @@ impl Room {
}
}
/// An occupant session
#[derive(Debug, Clone, Eq)]
pub struct Session {
pub presence: Presence,
pub real: FullJid,
pub participant: FullJid,
}
impl Session {
/// Ensure presence doesn't contain payloads that would impersonate us
fn filter_presence(presence: Presence) -> Presence {
presence
}
/// Instanciate a Session from a presence which is adressed to the room JID.
fn with_nick<N: Into<Nick>>(presence: Presence, nick: N) -> Result<Session, Error> {
let presence = Session::filter_presence(presence);
Ok(Session {
real: presence
.from
.clone()
.map(FullJid::try_from)
.ok_or(Error::MissingJid)??,
participant: presence
.to
.clone()
.map(BareJid::from)
.ok_or(Error::MissingJid)?
.with_resource(nick.into()),
presence,
})
}
}
impl PartialEq for Session {
fn eq(&self, other: &Session) -> bool {
self.real == other.real && self.participant == other.participant
}
}
impl TryFrom<Presence> for Session {
type Error = Error;
fn try_from(presence: Presence) -> Result<Session, Error> {
let presence = Session::filter_presence(presence);
Ok(Session {
real: presence
.from
.clone()
.map(FullJid::try_from)
.ok_or(Error::MissingJid)??,
participant: presence
.to
.clone()
.map(FullJid::try_from)
.ok_or(Error::MissingJid)??,
presence,
})
}
}
/// An occupant in a room. May contain multiple sessions (Multi-Session Nicks)
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Occupant {
@ -346,68 +451,85 @@ pub struct Occupant {
pub real: BareJid,
pub participant: FullJid,
pub nick: Nick,
pub sessions: Vec<Presence>,
pub sessions: Vec<Session>,
}
impl Occupant {
/// New occupant
pub fn new(room: &Room, real: FullJid, nick: Nick) -> Occupant {
Occupant {
real: BareJid::from(real.clone()),
participant: room.jid.clone().with_resource(nick.clone()),
nick,
sessions: vec![real],
}
pub fn new(presence: Presence) -> Result<Occupant, Error> {
let session = Session::try_from(presence)?;
Ok(Occupant {
real: BareJid::from(session.real.clone()),
participant: session.participant.clone(),
nick: session.participant.resource.clone(),
sessions: vec![session],
})
}
/// Add a new session to the occupant
pub fn add_session(&mut self, real: FullJid) -> Result<(), Error> {
if BareJid::from(real.clone()) != self.real {
pub fn add_session(&mut self, presence: Presence) -> Result<(), Error> {
let new_session = Session::try_from(presence)?;
if BareJid::from(new_session.real.clone()) != self.real {
return Err(Error::MismatchJids(
Jid::from(self.real.clone()),
Jid::from(real),
Jid::from(new_session.real),
));
}
for session in &self.sessions {
if &real == session {
return Err(Error::SessionAlreadyExists(real));
if &new_session.real == &session.real {
return Err(Error::SessionAlreadyExists(new_session));
}
}
self.sessions.push(real);
self.sessions.push(new_session);
Ok(())
}
/// Remove a session from the occupant
pub fn remove_session(&mut self, real: FullJid) -> Result<(), Error> {
if BareJid::from(real.clone()) != self.real {
return Err(Error::MismatchJids(
Jid::from(self.real.clone()),
Jid::from(real.clone()),
));
}
pub fn remove_session(&mut self, presence: Presence) -> Result<(), Error> {
let own_session = Session::try_from(presence)?;
let len = self.sessions.len();
self.sessions.retain(|session| session != &real);
self.sessions
.retain(|session| session.real != own_session.real);
// An item has been removed
if len != self.sessions.len() {
Ok(())
} else {
Err(Error::NonexistantSession(real))
Err(Error::NonexistantSession(own_session))
}
}
/// Update session presence
pub fn update_presence(&mut self, presence: Presence) -> Result<(), Error> {
let own_session = Session::with_nick(presence, self.participant.resource.clone())?;
for (i, session) in self.sessions.iter().enumerate() {
if &own_session == session {
if i == 0 {
// Leader session
self.sessions[0] = own_session;
return Ok(());
}
return Err(Error::SecondarySession(own_session));
}
}
Err(Error::NonexistantSession(own_session))
}
/// Return whether a Jid matches the occupant. If FullJid, compare with each session, otherwise
/// see if the BareJid matches.
// TODO: We may want to split checking for Bare/Full as this can be error prone
pub fn contains<J: Into<Jid>>(&self, _jid: &J) -> bool {
true
}
}
impl IntoIterator for Occupant {
type Item = FullJid;
type Item = Session;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
@ -416,7 +538,7 @@ impl IntoIterator for Occupant {
}
impl Occupant {
fn iter(&self) -> std::slice::Iter<'_, FullJid> {
fn iter(&self) -> std::slice::Iter<'_, Session> {
self.sessions.iter()
}
}
@ -425,13 +547,17 @@ impl Occupant {
mod tests {
use super::*;
use crate::component::TestComponent;
use crate::tests::templates::{
LOUISE_FULL1, LOUISE_FULL2, LOUISE_NICK, LOUISE_ROOM1_PART, ROOM1_BARE, ROSA_FULL1,
ROSA_NICK, ROSA_ROOM1_PART, SUGAKO_FULL1, SUGAKO_NICK, SUGAKO_ROOM1_PART,
};
use std::str::FromStr;
use xmpp_parsers::{
muc::{
user::{Affiliation, Item as MucItem, Role, Status as MucStatus},
MucUser,
Muc, MucUser,
},
presence::{Presence, Type as PresenceType},
presence::{Presence, Show as PresenceShow, Type as PresenceType},
BareJid, Element,
};
@ -445,18 +571,30 @@ mod tests {
let realjid3 = FullJid::from_str("bar@qxx/foo").unwrap();
let participant3 = roomjid.clone().with_resource(String::from("nick3"));
let presence1 = Presence::new(PresenceType::None)
.with_from(realjid1.clone())
.with_to(participant1.clone());
let presence2 = Presence::new(PresenceType::None)
.with_from(realjid2.clone())
.with_to(participant2.clone());
let presence3 = Presence::new(PresenceType::None)
.with_from(realjid3.clone())
.with_to(participant3.clone());
let mut room = Room::new(roomjid.clone());
room.occupants = BTreeMap::new();
room.occupants.insert(
participant1.resource.clone(),
Occupant::new(&room, realjid1.clone(), String::from("nick1")),
Occupant::new(presence1).unwrap(),
);
room.occupants.insert(
participant2.resource.clone(),
Occupant::new(&room, realjid2.clone(), String::from("nick2")),
Occupant::new(presence2).unwrap(),
);
let occupant3 = Occupant::new(&room, realjid3.clone(), String::from("nick3"));
let occupant3 = Occupant::new(presence3.clone()).unwrap();
room.occupants
.insert(participant3.resource.clone(), occupant3.clone());
@ -505,7 +643,7 @@ mod tests {
room.broadcast_presence(
&mut component,
&occupant3,
&realjid3,
presence3,
BroadcastPresence::Resync,
)
.await
@ -522,18 +660,30 @@ mod tests {
let realjid3 = FullJid::from_str("bar@qxx/foo").unwrap();
let participant3 = roomjid.clone().with_resource(String::from("nick3"));
let presence1 = Presence::new(PresenceType::None)
.with_from(realjid1.clone())
.with_to(participant1.clone());
let presence2 = Presence::new(PresenceType::None)
.with_from(realjid2.clone())
.with_to(participant2.clone());
let presence3 = Presence::new(PresenceType::None)
.with_from(realjid3.clone())
.with_to(participant3.clone());
let mut room = Room::new(roomjid.clone());
room.occupants = BTreeMap::new();
room.occupants.insert(
participant1.resource.clone(),
Occupant::new(&room, realjid1.clone(), String::from("nick1")),
Occupant::new(presence1).unwrap(),
);
room.occupants.insert(
participant2.resource.clone(),
Occupant::new(&room, realjid2.clone(), String::from("nick2")),
Occupant::new(presence2).unwrap(),
);
let occupant3 = Occupant::new(&room, realjid3.clone(), String::from("nick3"));
let occupant3 = Occupant::new(presence3.clone()).unwrap();
room.occupants
.insert(participant3.resource.clone(), occupant3.clone());
@ -565,7 +715,7 @@ mod tests {
room.broadcast_presence(
&mut component,
&occupant3,
&realjid3,
presence3,
BroadcastPresence::Update,
)
.await
@ -582,18 +732,30 @@ mod tests {
let realjid3 = FullJid::from_str("bar@qxx/foo").unwrap();
let participant3 = roomjid.clone().with_resource(String::from("nick3"));
let presence1 = Presence::new(PresenceType::None)
.with_from(realjid1.clone())
.with_to(participant1.clone());
let presence2 = Presence::new(PresenceType::None)
.with_from(realjid2.clone())
.with_to(participant2.clone());
let presence3 = Presence::new(PresenceType::None)
.with_from(realjid3.clone())
.with_to(participant3.clone());
let mut room = Room::new(roomjid.clone());
room.occupants = BTreeMap::new();
room.occupants.insert(
participant1.resource.clone(),
Occupant::new(&room, realjid1.clone(), String::from("nick1")),
Occupant::new(presence1.clone()).unwrap(),
);
room.occupants.insert(
participant2.resource.clone(),
Occupant::new(&room, realjid2.clone(), String::from("nick2")),
Occupant::new(presence2.clone()).unwrap(),
);
let occupant3 = Occupant::new(&room, realjid3.clone(), String::from("nick3"));
let occupant3 = Occupant::new(presence3.clone()).unwrap();
room.occupants
.insert(participant3.resource.clone(), occupant3.clone());
@ -664,7 +826,7 @@ mod tests {
room.broadcast_presence(
&mut component,
&occupant3,
&realjid3,
presence3,
BroadcastPresence::Join,
)
.await
@ -673,36 +835,40 @@ mod tests {
#[tokio::test]
async fn test_broadcast_presence_leave() {
let roomjid = BareJid::from_str("room@muc").unwrap();
let realjid1 = FullJid::from_str("foo@bar/qxx").unwrap();
let participant1 = roomjid.clone().with_resource(String::from("nick1"));
let realjid2 = FullJid::from_str("qxx@foo/bar").unwrap();
let participant2 = roomjid.clone().with_resource(String::from("nick2"));
let realjid3 = FullJid::from_str("bar@qxx/foo").unwrap();
let participant3 = roomjid.clone().with_resource(String::from("nick3"));
let presence_louise = Presence::new(PresenceType::None)
.with_from(LOUISE_FULL1.clone())
.with_to(LOUISE_ROOM1_PART.clone());
let mut room = Room::new(roomjid.clone());
let presence_sugako = Presence::new(PresenceType::None)
.with_from(SUGAKO_FULL1.clone())
.with_to(SUGAKO_ROOM1_PART.clone());
let presence_rosa = Presence::new(PresenceType::None)
.with_from(ROSA_FULL1.clone())
.with_to(ROSA_ROOM1_PART.clone());
let mut room = Room::new(ROOM1_BARE.clone());
room.occupants = BTreeMap::new();
room.occupants.insert(
participant1.resource.clone(),
Occupant::new(&room, realjid1.clone(), String::from("nick1")),
String::from(LOUISE_NICK),
Occupant::new(presence_louise).unwrap(),
);
room.occupants.insert(
participant2.resource.clone(),
Occupant::new(&room, realjid2.clone(), String::from("nick2")),
String::from(SUGAKO_NICK),
Occupant::new(presence_sugako).unwrap(),
);
let occupant3 = Occupant::new(&room, realjid3.clone(), String::from("nick3"));
let occupant3 = Occupant::new(presence_rosa).unwrap();
room.occupants
.insert(participant3.resource.clone(), occupant3.clone());
.insert(String::from(ROSA_NICK), occupant3.clone());
// BroadcastPresence::Leave
let mut component = TestComponent::new(vec![]);
component.expect(
Presence::new(PresenceType::Unavailable)
.with_from(participant3.clone())
.with_to(realjid1.clone())
.with_from(ROSA_ROOM1_PART.clone())
.with_to(LOUISE_FULL1.clone())
.with_payloads(vec![MucUser {
status: vec![],
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
@ -712,8 +878,8 @@ mod tests {
component.expect(
Presence::new(PresenceType::Unavailable)
.with_from(participant3.clone())
.with_to(realjid2.clone())
.with_from(ROSA_ROOM1_PART.clone())
.with_to(SUGAKO_FULL1.clone())
.with_payloads(vec![MucUser {
status: vec![],
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
@ -723,19 +889,27 @@ mod tests {
component.expect(
Presence::new(PresenceType::Unavailable)
.with_from(participant3.clone())
.with_to(realjid3.clone())
.with_from(ROSA_ROOM1_PART.clone())
.with_to(ROSA_FULL1.clone())
.with_payloads(vec![MucUser {
status: vec![MucStatus::SelfPresence],
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
items: vec![{
let mut item = MucItem::new(Affiliation::Owner, Role::None);
item.jid = Some(ROSA_FULL1.clone());
item
}],
}
.into()]),
);
let presence_leave = Presence::new(PresenceType::Unavailable)
.with_from(ROSA_FULL1.clone())
.with_to(ROSA_ROOM1_PART.clone());
room.broadcast_presence(
&mut component,
&occupant3,
&realjid3,
presence_leave,
BroadcastPresence::Leave,
)
.await

View file

@ -16,4 +16,4 @@
mod iq;
mod presence;
#[allow(dead_code)]
mod templates;
pub mod templates;

View file

@ -31,7 +31,7 @@ use xmpp_parsers::{
user::{Affiliation, Item as MucItem, Role, Status as MucStatus},
Muc, MucUser,
},
presence::{Presence, Type as PresenceType},
presence::{Presence, Show as PresenceShow, Type as PresenceType},
stanza_error::{DefinedCondition, ErrorType, StanzaError},
BareJid, Element, Jid,
};
@ -350,7 +350,11 @@ async fn test_leave_last_participant() {
.with_to(Jid::Full(LOUISE_FULL1.clone()))
.with_payloads(vec![MucUser {
status: vec![MucStatus::SelfPresence],
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
items: vec![{
let mut item = MucItem::new(Affiliation::Owner, Role::None);
item.jid = Some(LOUISE_FULL1.clone());
item
}],
}
.into()]),
);
@ -407,7 +411,11 @@ async fn test_leave_room_not_last() {
.with_to(Jid::Full(SUGAKO_FULL1.clone()))
.with_payloads(vec![MucUser {
status: vec![MucStatus::SelfPresence],
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
items: vec![{
let mut item = MucItem::new(Affiliation::Owner, Role::None);
item.jid = Some(SUGAKO_FULL1.clone());
item
}],
}
.into()]),
);
@ -514,6 +522,7 @@ async fn test_join_msn() {
}
}
#[ignore]
#[tokio::test]
async fn test_presence_update_joined() {
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
@ -535,7 +544,8 @@ async fn test_presence_update_joined() {
status: Vec::new(),
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
}
.into()]),
.into()])
.with_show(PresenceShow::Away),
);
handle_stanza(&mut component, &mut rooms).await.unwrap();

View file

@ -16,7 +16,10 @@
use crate::room::{Occupant, Room};
use std::str::FromStr;
use std::sync::LazyLock;
use xmpp_parsers::{BareJid, FullJid};
use xmpp_parsers::{
presence::{Presence, Type as PresenceType},
BareJid, FullJid,
};
pub const COMPONENT_JID: LazyLock<BareJid> =
LazyLock::new(|| BareJid::from_str("commons.social").unwrap());
@ -69,22 +72,31 @@ pub const PETER_ROOM1_PART: LazyLock<FullJid> =
pub fn one_participant_room(roomjid: BareJid) -> Room {
let mut room = Room::new(roomjid.clone());
let presence_louise = Presence::new(PresenceType::None)
.with_from(LOUISE_FULL1.clone())
.with_to(LOUISE_ROOM1_PART.clone());
room.occupants.insert(
String::from(LOUISE_NICK),
Occupant::new(&room, LOUISE_FULL1.clone(), String::from(LOUISE_NICK)),
Occupant::new(presence_louise).unwrap(),
);
room
}
pub fn two_participant_room(roomjid: BareJid) -> Room {
let mut room = Room::new(roomjid.clone());
let presence_louise = Presence::new(PresenceType::None)
.with_from(LOUISE_FULL1.clone())
.with_to(LOUISE_ROOM1_PART.clone());
let presence_sugako = Presence::new(PresenceType::None)
.with_from(SUGAKO_FULL1.clone())
.with_to(SUGAKO_ROOM1_PART.clone());
room.occupants.insert(
String::from(LOUISE_NICK),
Occupant::new(&room, LOUISE_FULL1.clone(), String::from(LOUISE_NICK)),
Occupant::new(presence_louise).unwrap(),
);
room.occupants.insert(
String::from(SUGAKO_NICK),
Occupant::new(&room, SUGAKO_FULL1.clone(), String::from(SUGAKO_NICK)),
Occupant::new(presence_sugako).unwrap(),
);
room
}