diff --git a/src/error.rs b/src/error.rs
index 0122a28..ba4ca76 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -13,7 +13,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-use crate::room::Session;
+use crate::session::Session;
use std::error::Error as StdError;
use std::fmt;
diff --git a/src/main.rs b/src/main.rs
index 2b4881c..ce35657 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -21,7 +21,9 @@
mod component;
mod error;
mod handlers;
+mod occupant;
mod room;
+mod session;
#[cfg(test)]
mod tests;
diff --git a/src/occupant.rs b/src/occupant.rs
new file mode 100644
index 0000000..e2c4dc0
--- /dev/null
+++ b/src/occupant.rs
@@ -0,0 +1,196 @@
+// Copyright (C) 2022-2099 The crate authors.
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published by the
+// Free Software Foundation, either version 3 of the License, or (at your
+// option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
+// for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+use crate::error::Error;
+use crate::session::{Nick, Session};
+
+use std::iter::IntoIterator;
+
+use xmpp_parsers::{presence::Presence, BareJid, FullJid, Jid};
+
+/// An occupant in a room. May contain multiple sessions (Multi-Session Nicks)
+#[derive(Debug, Clone, PartialEq)]
+pub struct Occupant {
+ /// Public Jid for the Occupant
+ pub real: BareJid,
+ pub participant: FullJid,
+ pub nick: Nick,
+ pub sessions: Vec,
+}
+
+impl Occupant {
+ /// New occupant
+ pub fn new(presence: Presence) -> Result {
+ 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, 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(new_session.real),
+ ));
+ }
+
+ for session in &self.sessions {
+ if &new_session.real == &session.real {
+ return Err(Error::SessionAlreadyExists(new_session));
+ }
+ }
+
+ self.sessions.push(new_session);
+ Ok(())
+ }
+
+ /// Remove a session from the occupant
+ 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 != own_session.real);
+
+ // An item has been removed
+ if len != self.sessions.len() {
+ Ok(())
+ } else {
+ 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>(&self, _jid: &J) -> bool {
+ true
+ }
+}
+
+impl IntoIterator for Occupant {
+ type Item = Session;
+ type IntoIter = std::vec::IntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.sessions.into_iter()
+ }
+}
+
+impl Occupant {
+ pub fn iter(&self) -> std::slice::Iter<'_, Session> {
+ self.sessions.iter()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::tests::templates::{
+ LOUISE_FULL1, LOUISE_FULL2, LOUISE_NICK, LOUISE_ROOM1_PART, ROOM1_BARE,
+ };
+ use xmpp_parsers::{
+ muc::{
+ user::{Affiliation, Item as MucItem, Role},
+ Muc, MucUser,
+ },
+ presence::{Presence, Show as PresenceShow, Type as PresenceType},
+ };
+
+ #[tokio::test]
+ async fn test_occupant_update_presence() {
+ let presence_louise1 = Presence::new(PresenceType::None)
+ .with_from(LOUISE_FULL1.clone())
+ .with_to(LOUISE_ROOM1_PART.clone());
+ let presence_louise2 = Presence::new(PresenceType::None)
+ .with_from(LOUISE_FULL2.clone())
+ .with_to(LOUISE_ROOM1_PART.clone());
+
+ let mut occupant = Occupant::new(presence_louise1).unwrap();
+ occupant.add_session(presence_louise2).unwrap();
+
+ let presence1 = Presence::new(PresenceType::None)
+ .with_from(LOUISE_FULL1.clone())
+ .with_to(ROOM1_BARE.clone())
+ .with_show(PresenceShow::Away)
+ .with_payloads(vec![
+ Muc::new().into(),
+ MucUser {
+ status: Vec::new(),
+ items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
+ }
+ .into(),
+ ]);
+ match occupant.update_presence(presence1.clone()) {
+ Ok(()) => (),
+ err => panic!("Err: {:?}", err),
+ }
+ assert_eq!(occupant.sessions[0].presence, presence1);
+
+ let presence2 = Presence::new(PresenceType::None)
+ .with_from(LOUISE_FULL2.clone())
+ .with_to(ROOM1_BARE.clone())
+ .with_show(PresenceShow::Xa);
+
+ match occupant.update_presence(presence2.clone()) {
+ Err(Error::SecondarySession(session)) if session.real == *LOUISE_FULL2 => (),
+ err => panic!(
+ "Should return Error::SecondarySession(Session {{ {:?} }}), returned: {:?}",
+ *LOUISE_FULL2, err,
+ ),
+ }
+ assert_eq!(
+ occupant.sessions[1],
+ Session::with_nick(presence2.clone(), LOUISE_NICK.clone()).unwrap()
+ );
+
+ let presence_leave_louise2 = Presence::new(PresenceType::Unavailable)
+ .with_from(LOUISE_FULL2.clone())
+ .with_to(LOUISE_ROOM1_PART.clone());
+ occupant.remove_session(presence_leave_louise2).unwrap();
+
+ match occupant.update_presence(presence2) {
+ Err(Error::NonexistantSession(session)) if session.real == *LOUISE_FULL2 => (),
+ err => panic!(
+ "Should return Error::SecondarySession(Session {{ {:?} }}), returned: {:?}",
+ *LOUISE_FULL2, err,
+ ),
+ }
+ }
+}
diff --git a/src/room.rs b/src/room.rs
index ccf098b..0d6b870 100644
--- a/src/room.rs
+++ b/src/room.rs
@@ -15,9 +15,10 @@
use crate::component::ComponentTrait;
use crate::error::Error;
+use crate::occupant::Occupant;
+use crate::session::{Nick, Session};
use std::collections::BTreeMap;
-use std::iter::IntoIterator;
use chrono::{FixedOffset, Utc};
use log::debug;
@@ -30,11 +31,9 @@ use xmpp_parsers::{
MucUser,
},
presence::{Presence, Type as PresenceType},
- BareJid, FullJid, Jid,
+ BareJid, Jid,
};
-pub type Nick = String;
-
#[derive(Debug, PartialEq, Eq)]
pub enum BroadcastPresence {
/// Resource joined the room. It needs to know about all other participants, and other
@@ -383,182 +382,22 @@ impl Room {
}
}
-/// An occupant session
-#[derive(Debug, Clone)]
-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>(presence: Presence, nick: N) -> Result {
- 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 for Session {
- type Error = Error;
-
- fn try_from(presence: Presence) -> Result {
- 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)]
-pub struct Occupant {
- /// Public Jid for the Occupant
- pub real: BareJid,
- pub participant: FullJid,
- pub nick: Nick,
- pub sessions: Vec,
-}
-
-impl Occupant {
- /// New occupant
- pub fn new(presence: Presence) -> Result {
- 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, 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(new_session.real),
- ));
- }
-
- for session in &self.sessions {
- if &new_session.real == &session.real {
- return Err(Error::SessionAlreadyExists(new_session));
- }
- }
-
- self.sessions.push(new_session);
- Ok(())
- }
-
- /// Remove a session from the occupant
- 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 != own_session.real);
-
- // An item has been removed
- if len != self.sessions.len() {
- Ok(())
- } else {
- 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>(&self, _jid: &J) -> bool {
- true
- }
-}
-
-impl IntoIterator for Occupant {
- type Item = Session;
- type IntoIter = std::vec::IntoIter;
-
- fn into_iter(self) -> Self::IntoIter {
- self.sessions.into_iter()
- }
-}
-
-impl Occupant {
- fn iter(&self) -> std::slice::Iter<'_, Session> {
- self.sessions.iter()
- }
-}
-
#[cfg(test)]
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,
+ LOUISE_FULL1, 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},
- Muc, MucUser,
+ MucUser,
},
- presence::{Presence, Show as PresenceShow, Type as PresenceType},
- BareJid, Element,
+ presence::{Presence, Type as PresenceType},
+ BareJid, Element, FullJid,
};
#[tokio::test]
@@ -915,65 +754,4 @@ mod tests {
.await
.unwrap();
}
-
- #[tokio::test]
- async fn test_occupant_update_presence() {
- let presence_louise1 = Presence::new(PresenceType::None)
- .with_from(LOUISE_FULL1.clone())
- .with_to(LOUISE_ROOM1_PART.clone());
- let presence_louise2 = Presence::new(PresenceType::None)
- .with_from(LOUISE_FULL2.clone())
- .with_to(LOUISE_ROOM1_PART.clone());
-
- let mut occupant = Occupant::new(presence_louise1).unwrap();
- occupant.add_session(presence_louise2).unwrap();
-
- let presence1 = Presence::new(PresenceType::None)
- .with_from(LOUISE_FULL1.clone())
- .with_to(ROOM1_BARE.clone())
- .with_show(PresenceShow::Away)
- .with_payloads(vec![
- Muc::new().into(),
- MucUser {
- status: Vec::new(),
- items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
- }
- .into(),
- ]);
- match occupant.update_presence(presence1.clone()) {
- Ok(()) => (),
- err => panic!("Err: {:?}", err),
- }
- assert_eq!(occupant.sessions[0].presence, presence1);
-
- let presence2 = Presence::new(PresenceType::None)
- .with_from(LOUISE_FULL2.clone())
- .with_to(ROOM1_BARE.clone())
- .with_show(PresenceShow::Xa);
-
- match occupant.update_presence(presence2.clone()) {
- Err(Error::SecondarySession(session)) if session.real == *LOUISE_FULL2 => (),
- err => panic!(
- "Should return Error::SecondarySession(Session {{ {:?} }}), returned: {:?}",
- *LOUISE_FULL2, err,
- ),
- }
- assert_eq!(
- occupant.sessions[1],
- Session::with_nick(presence2.clone(), LOUISE_NICK.clone()).unwrap()
- );
-
- let presence_leave_louise2 = Presence::new(PresenceType::Unavailable)
- .with_from(LOUISE_FULL2.clone())
- .with_to(LOUISE_ROOM1_PART.clone());
- occupant.remove_session(presence_leave_louise2).unwrap();
-
- match occupant.update_presence(presence2) {
- Err(Error::NonexistantSession(session)) if session.real == *LOUISE_FULL2 => (),
- err => panic!(
- "Should return Error::SecondarySession(Session {{ {:?} }}), returned: {:?}",
- *LOUISE_FULL2, err,
- ),
- }
- }
}
diff --git a/src/session.rs b/src/session.rs
new file mode 100644
index 0000000..018efd2
--- /dev/null
+++ b/src/session.rs
@@ -0,0 +1,81 @@
+// Copyright (C) 2022-2099 The crate authors.
+//
+// This program is free software: you can redistribute it and/or modify it
+// under the terms of the GNU Affero General Public License as published by the
+// Free Software Foundation, either version 3 of the License, or (at your
+// option) any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
+// for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+use crate::error::Error;
+
+use xmpp_parsers::{presence::Presence, BareJid, FullJid};
+
+pub type Nick = String;
+
+/// An occupant session
+#[derive(Debug, Clone)]
+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.
+ pub fn with_nick>(presence: Presence, nick: N) -> Result {
+ 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 for Session {
+ type Error = Error;
+
+ fn try_from(presence: Presence) -> Result {
+ 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,
+ })
+ }
+}
diff --git a/src/tests/templates.rs b/src/tests/templates.rs
index f66f807..01b74bd 100644
--- a/src/tests/templates.rs
+++ b/src/tests/templates.rs
@@ -13,7 +13,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-use crate::room::{Occupant, Room};
+use crate::occupant::Occupant;
+use crate::room::Room;
use std::str::FromStr;
use std::sync::LazyLock;
use xmpp_parsers::{