diff --git a/src/component.rs b/src/component.rs index 5dd1e28..fc0bcdd 100644 --- a/src/component.rs +++ b/src/component.rs @@ -68,12 +68,7 @@ impl DerefMut for Component { } impl Component { - pub async fn new( - jid: &str, - password: &str, - server: &str, - port: u16, - ) -> Result { + pub async fn new(jid: &str, password: &str, server: &str, port: u16) -> Result { Ok(Component( TokioXMPPComponent::new(jid, password, server, port).await?, )) @@ -109,7 +104,9 @@ impl TestComponent { match (out, expected) { (None, None) => break, - (Some(out), Some(expected)) => assert_eq!(String::from(&expected), String::from(&out)), + (Some(out), Some(expected)) => { + assert_eq!(String::from(&expected), String::from(&out)) + } (Some(out), None) => assert_eq!(format!(""), String::from(&out)), (None, Some(expected)) => assert_eq!(String::from(&expected), format!("")), } diff --git a/src/handlers.rs b/src/handlers.rs index 6655c12..d8ce3f5 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -15,8 +15,9 @@ use crate::component::ComponentTrait; use crate::error::Error; -use crate::types::{Nick, Room, ROOMS}; +use crate::types::{Nick, Room}; +use std::collections::HashMap; use std::ops::ControlFlow; use futures::stream::StreamExt; @@ -103,6 +104,7 @@ async fn handle_iq(component: &mut C, iq: Iq) -> Result<(), E async fn handle_presence( component: &mut C, presence: Presence, + rooms: &mut HashMap, ) -> Result<(), Error> { let muc = presence .payloads @@ -124,14 +126,14 @@ async fn handle_presence( let nick: Nick = participant.resource.clone(); // Room already exists - if let Some(room) = unsafe { ROOMS.lock().unwrap().get_mut(&roomjid) } { + if let Some(room) = rooms.get_mut(&roomjid) { debug!("Presence received to existing room: {}", &roomjid); room.add_session(component, realjid, nick).await.unwrap(); } else { debug!("Presence received to new room: {}", &roomjid); let mut room = Room::new(roomjid.clone()); room.add_session(component, realjid, nick).await.unwrap(); - let _ = unsafe { ROOMS.lock().unwrap().insert(roomjid, room) }; + let _ = rooms.insert(roomjid, room); } } @@ -145,7 +147,10 @@ async fn handle_message( Ok(()) } -pub(crate) async fn handle_stanza(component: &mut C) -> Result<(), Error> { +pub(crate) async fn handle_stanza( + component: &mut C, + rooms: &mut HashMap, +) -> Result<(), Error> { while let Some(elem) = component.next().await { debug!("RECV {}", String::from(&elem)); if elem.is("iq", ns::COMPONENT_ACCEPT) { @@ -156,7 +161,7 @@ pub(crate) async fn handle_stanza(component: &mut C) -> Resul handle_message(component, message).await?; } else if elem.is("presence", ns::COMPONENT_ACCEPT) { let presence = Presence::try_from(elem).unwrap(); - handle_presence(component, presence).await?; + handle_presence(component, presence, rooms).await?; } } diff --git a/src/main.rs b/src/main.rs index 99e1e3f..75ee9f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -#![feature(once_cell)] #![feature(let_chains)] mod component; @@ -27,12 +26,15 @@ mod tests; use crate::component::Component; use crate::error::Error; use crate::handlers::handle_stanza; +use crate::types::Room; +use std::collections::HashMap; use std::env::args; use std::process::exit; use env_logger; use log::info; +use xmpp_parsers::BareJid; #[tokio::main] async fn main() -> Result<(), Error> { @@ -52,7 +54,9 @@ async fn main() -> Result<(), Error> { let mut component = Component::new(jid, passwd, server, port).await.unwrap(); info!("Online as {}!", component.jid); - handle_stanza(&mut component).await?; + let mut rooms: HashMap = HashMap::new(); + + handle_stanza(&mut component, &mut rooms).await?; Ok(()) } diff --git a/src/tests.rs b/src/tests.rs index 3ab6a1d..d95ecc6 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -13,16 +13,24 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use std::str::FromStr; - use crate::component::TestComponent; use crate::handlers::handle_stanza; +use crate::types::Room; + +use std::collections::{BTreeMap, HashMap}; +use std::str::FromStr; use lazy_static::lazy_static; use xmpp_parsers::{ iq::{Iq, IqType}, - BareJid, Element, FullJid, Jid, + message::{Message, MessageType, Subject as MessageSubject}, + muc::{ + user::{Affiliation, Item as MucItem, Role, Status as MucStatus}, + Muc, MucUser, + }, + presence::{Presence, Type as PresenceType}, stanza_error::{DefinedCondition, ErrorType, StanzaError}, + BareJid, Element, FullJid, Jid, }; lazy_static! { @@ -39,22 +47,77 @@ async fn test_iq_unimplemented() { to: Some(to.clone()), id: String::from("disco"), payload: IqType::Get(Element::builder("x", "urn:example:unimplemented").build()), - }.into(); + } + .into(); - let reply: Element = Iq::from_error("disco", StanzaError::new( - ErrorType::Cancel, - DefinedCondition::ServiceUnavailable, - "en", - "No handler defined for this kind of iq.", - )) + let reply: Element = Iq::from_error( + "disco", + StanzaError::new( + ErrorType::Cancel, + DefinedCondition::ServiceUnavailable, + "en", + "No handler defined for this kind of iq.", + ), + ) .with_from(to) .with_to(from) .into(); let mut component = TestComponent::new(vec![disco]); + let mut rooms: HashMap = HashMap::new(); + component.expect(reply); - - handle_stanza(&mut component).await.unwrap(); - + handle_stanza(&mut component, &mut rooms).await.unwrap(); + component.assert(); +} + +#[tokio::test] +async fn test_0045_join_presence_empty_room() { + let from = FullJid::from_str("foo@bar/qxx").unwrap(); + let to = COMPONENT_JID + .clone() + .with_node("room") + .with_resource("nick"); + + // + let join: Element = Presence::new(PresenceType::None) + .with_from(Jid::Full(from.clone())) + .with_to(Jid::Full(to.clone())) + .with_payloads(vec![Muc::new().into()]) + .into(); + + let mut component = TestComponent::new(vec![join]); + let mut rooms: HashMap = HashMap::new(); + + // Room is empty so there should be: + // - No presence sent except for self-presence + // - No message history + // - Empty subject. + component.expect( + Presence::new(PresenceType::None) + .with_from(Jid::Full(to.clone())) + .with_to(Jid::Full(from.clone())) + .with_payloads(vec![MucUser { + status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick], + items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)], + } + .into()]), + ); + + let mut subjects = BTreeMap::new(); + subjects.insert(String::from("en"), MessageSubject::from_str("").unwrap()); + component.expect(Message { + // Set by the first participant + from: Some(Jid::Full(to)), + to: Some(Jid::Full(from)), + id: None, + type_: MessageType::Groupchat, + bodies: BTreeMap::new(), + subjects, + thread: None, + payloads: Vec::new(), + }); + + handle_stanza(&mut component, &mut rooms).await.unwrap(); component.assert(); } diff --git a/src/types.rs b/src/types.rs index 1a97fd0..24b1e0d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -18,7 +18,6 @@ use crate::error::Error; use std::collections::HashMap; use std::iter::IntoIterator; -use std::sync::{LazyLock, Mutex}; use log::debug; use xmpp_parsers::{ @@ -89,7 +88,7 @@ impl Room { let status = vec![MucStatus::SelfPresence, MucStatus::AssignedNick]; let items = vec![MucItem::new(Affiliation::Owner, Role::Moderator)]; let self_presence = Presence::new(PresenceType::None) - .with_from(participant) + .with_from(participant.clone()) .with_to(realjid.clone()) .with_payloads(vec![MucUser { status, items }.into()]); component.send_stanza(self_presence).await?; @@ -97,10 +96,10 @@ impl Room { // Send subject debug!("Sending subject!"); let mut subject = Message::new(Some(Jid::Full(realjid))); - subject.from = Some(Jid::Bare(self.jid.clone())); + subject.from = Some(Jid::Full(participant)); subject .subjects - .insert(String::from("en"), Subject(String::from("Hanabi"))); + .insert(String::from("en"), Subject(String::from(""))); subject.type_ = MessageType::Groupchat; component.send_stanza(subject).await?; } @@ -149,9 +148,6 @@ impl Occupant { } } -pub(crate) static mut ROOMS: LazyLock>> = - LazyLock::new(|| Mutex::new(HashMap::new())); - #[cfg(test)] mod tests { use super::*;