diff options
author | 2025-04-03 13:20:29 +0100 | |
---|---|---|
committer | 2025-04-03 13:20:29 +0100 | |
commit | f48642bbd5a210b68e60715b59b1f24cf2d77fea (patch) | |
tree | 4316bbb8355cc1dfd66a22dde480a49e9158ec59 /filamento | |
parent | bf00184a09418750caeb488d8d71f9dc7afd7aff (diff) | |
download | luz-f48642bbd5a210b68e60715b59b1f24cf2d77fea.tar.gz luz-f48642bbd5a210b68e60715b59b1f24cf2d77fea.tar.bz2 luz-f48642bbd5a210b68e60715b59b1f24cf2d77fea.zip |
feat(filamento): caps 1.0
Diffstat (limited to 'filamento')
-rw-r--r-- | filamento/migrations/20240113011930_luz.sql | 4 | ||||
-rw-r--r-- | filamento/src/caps.rs | 117 | ||||
-rw-r--r-- | filamento/src/error.rs | 5 | ||||
-rw-r--r-- | filamento/src/logic/process_stanza.rs | 70 | ||||
-rw-r--r-- | filamento/src/presence.rs | 4 |
5 files changed, 170 insertions, 30 deletions
diff --git a/filamento/migrations/20240113011930_luz.sql b/filamento/migrations/20240113011930_luz.sql index c2b5a97..8c1b01c 100644 --- a/filamento/migrations/20240113011930_luz.sql +++ b/filamento/migrations/20240113011930_luz.sql @@ -132,7 +132,9 @@ insert into cached_status (id) values (0); create table capability_hash_nodes ( node text primary key not null, - timestamp text not null, + timestamp text, -- TODO: normalization capabilities text not null ); + +insert into capability_hash_nodes ( node, capabilities ) values ('https://bunny.garden/filamento#mSavc/SLnHm8zazs5RlcbD/iXoc=', 'aHR0cDovL2phYmJlci5vcmcvcHJvdG9jb2wvY2Fwcx9odHRwOi8vamFiYmVyLm9yZy9wcm90b2NvbC9kaXNjbyNpbmZvH2h0dHA6Ly9qYWJiZXIub3JnL3Byb3RvY29sL2Rpc2NvI2l0ZW1zH2h0dHA6Ly9qYWJiZXIub3JnL3Byb3RvY29sL25pY2sfaHR0cDovL2phYmJlci5vcmcvcHJvdG9jb2wvbmljaytub3RpZnkfHGNsaWVudB9wYx8fZmlsYW1lbnRvIDAuMS4wHx4cHA=='); diff --git a/filamento/src/caps.rs b/filamento/src/caps.rs index a0709eb..49d05ba 100644 --- a/filamento/src/caps.rs +++ b/filamento/src/caps.rs @@ -13,10 +13,41 @@ use stanza::{ use tracing::trace; use crate::{ - disco::{Identity, Info, identity::Category}, - error::{CapsDecodeError, CapsEncodeError, HashNodeConversionError}, + disco::{ + Identity, Info, + identity::{self, Category}, + }, + error::{CapsDecodeError, CapsEncodeError, CapsNodeConversionError, HashNodeConversionError}, }; +pub const CLIENT_URI: &str = "https://bunny.garden/filamento"; + +// <c xmlns="http://jabber.org/protocol/caps" hash="sha-1" ver="mSavc/SLnHm8zazs5RlcbD/iXoc=" node="https://bunny.garden/filamento"/> +pub fn c() -> C { + caps(CLIENT_URI.to_string(), client_info().into()).unwrap() +} + +pub fn caps_node() -> String { + caps_to_node(c()) +} + +pub fn client_info() -> Info { + Info { + node: None, + features: vec![ + "http://jabber.org/protocol/disco#items".to_string(), + "http://jabber.org/protocol/disco#info".to_string(), + "http://jabber.org/protocol/caps".to_string(), + "http://jabber.org/protocol/nick".to_string(), + "http://jabber.org/protocol/nick+notify".to_string(), + ], + identities: vec![Identity { + name: Some("filamento 0.1.0".to_string()), + category: Category::Client(identity::Client::PC), + }], + } +} + pub fn caps(node: String, query: info::Query) -> Result<xep_0115::C, CapsEncodeError> { let mut string = String::new(); @@ -113,7 +144,7 @@ pub fn caps(node: String, query: info::Query) -> Result<xep_0115::C, CapsEncodeE }) } -pub fn caps2(query: info::Query) -> xep_0390::C { +pub fn encode_caps2(query: info::Query) -> String { let mut string = String::new(); // features string @@ -181,6 +212,16 @@ pub fn caps2(query: info::Query) -> xep_0390::C { let extensions_string = extensions.concat(); string.push_str(&extensions_string); string.push('\x1c'); + string +} + +pub fn encode_info_base64(info: info::Query) -> String { + let string = encode_caps2(info); + BASE64_STANDARD.encode(string) +} + +pub fn caps2(query: info::Query) -> xep_0390::C { + let string = encode_caps2(query); let mut sha256 = Sha256::new(); @@ -209,7 +250,7 @@ pub fn caps2(query: info::Query) -> xep_0390::C { } /// takes a base64 encoded cached caps string and converts it into a disco info result -pub fn info(info: String) -> Result<Info, CapsDecodeError> { +pub fn decode_info_base64(info: String) -> Result<Info, CapsDecodeError> { let info = String::from_utf8(BASE64_STANDARD.decode(info)?)?; let mut strings = info.split_terminator('\x1c'); @@ -255,6 +296,22 @@ pub fn info(info: String) -> Result<Info, CapsDecodeError> { }) } +pub fn caps_to_node(caps: C) -> String { + caps.node + "#" + caps.ver.as_str() +} + +pub fn node_to_caps(node: String) -> Result<C, CapsNodeConversionError> { + let (node, ver) = node + .rsplit_once('#') + .ok_or(CapsNodeConversionError::MissingHashtag)?; + Ok(C { + ext: None, + hash: "sha-1".to_string(), + node: node.to_string(), + ver: ver.to_string(), + }) +} + pub fn hash_to_node(hash: xep_0300::Hash) -> String { let mut string = String::from("urn:xmpp:caps#"); string.push_str(&hash.algo.to_string()); @@ -276,12 +333,6 @@ pub fn node_to_hash(node: String) -> Result<Hash, HashNodeConversionError> { }) } -static CLIENT_INFO: Info = Info { - node: None, - features: vec![], - identities: vec![], -}; - #[cfg(test)] mod tests { use peanuts::{Writer, element::IntoElement}; @@ -390,9 +441,51 @@ mod tests { items: Vec::new(), }], }; - let caps = caps("https://macaw.chat".to_string(), info).unwrap(); + let test_caps = caps("https://macaw.chat".to_string(), info).unwrap(); + let stdout = tokio::io::stdout(); + let mut writer = Writer::new(stdout); + writer.write(&test_caps).await.unwrap(); + } + + #[tokio::test] + pub async fn test_gen_client_caps() { let stdout = tokio::io::stdout(); let mut writer = Writer::new(stdout); - writer.write(&caps).await; + // let info = info::Query { + // node: Some("https://bunny.garden/filamento".to_string()), + // features: vec![ + // Feature { + // var: "http://jabber.org/protocol/disco#items".to_string(), + // }, + // Feature { + // var: "http://jabber.org/protocol/disco#info".to_string(), + // }, + // Feature { + // var: "http://jabber.org/protocol/caps".to_string(), + // }, + // Feature { + // var: "http://jabber.org/protocol/nick".to_string(), + // }, + // Feature { + // var: "http://jabber.org/protocol/nick+notify".to_string(), + // }, + // ], + // identities: vec![Identity { + // category: "client".to_string(), + // name: Some("filamento 0.1.0".to_string()), + // r#type: "pc".to_string(), + // lang: None, + // }], + // extensions: vec![], + // }; + let info: info::Query = client_info().into(); + let client_caps = caps(CLIENT_URI.to_string(), info.clone()).unwrap(); + writer.write(&client_caps).await.unwrap(); + let node = caps_to_node(client_caps); + println!("client caps node: `{}`", node); + let client_caps_base64 = encode_info_base64(info); + println!("client caps: `{}`", client_caps_base64); + let client_caps = decode_info_base64(client_caps_base64).unwrap(); + println!("client caps: `{:?}`", client_caps); } } diff --git a/filamento/src/error.rs b/filamento/src/error.rs index b785f26..5111413 100644 --- a/filamento/src/error.rs +++ b/filamento/src/error.rs @@ -262,5 +262,10 @@ pub enum HashNodeConversionError { MissingPeriod, } +#[derive(Debug, Error, Clone)] +pub enum CapsNodeConversionError { + #[error("missing hashtag")] + MissingHashtag, +} // #[derive(Debug, Error, Clone)] // pub enum CapsError {} diff --git a/filamento/src/logic/process_stanza.rs b/filamento/src/logic/process_stanza.rs index e9787a9..182fb43 100644 --- a/filamento/src/logic/process_stanza.rs +++ b/filamento/src/logic/process_stanza.rs @@ -8,13 +8,13 @@ use stanza::{ iq::{self, Iq, IqType}, }, stanza_error::Error as StanzaError, - xep_0030, + xep_0030::{self, info}, }; use tracing::{debug, error, info, warn}; use uuid::Uuid; use crate::{ - UpdateMessage, + UpdateMessage, caps, chat::{Body, Message}, error::{DatabaseError, Error, IqError, MessageRecvError, PresenceError, RosterError}, presence::{Offline, Online, Presence, PresenceType, Show}, @@ -220,22 +220,58 @@ pub async fn recv_iq( .unwrap_or_else(|| connection.server().clone()); if let Some(query) = iq.query { match query { - stanza::client::iq::Query::DiscoInfo(_query) => { - // TODO: should this only be replied to server? + stanza::client::iq::Query::DiscoInfo(query) => { info!("received disco#info request from {}", from); - let disco = xep_0030::info::Query { - node: None, - features: vec![xep_0030::info::Feature { - var: "http://jabber.org/protocol/disco#info".to_string(), - }], - identities: vec![xep_0030::info::Identity { - category: "client".to_string(), - name: Some("filamento".to_string()), - r#type: "pc".to_string(), - lang: None, - }], - extensions: Vec::new(), - }; + let current_caps_node = caps::caps_node(); + let disco: info::Query = + if query.node.is_none() || query.node == Some(current_caps_node) { + let mut info = caps::client_info(); + info.node = query.node; + info.into() + } else { + match logic + .db() + .read_capabilities(&query.node.clone().unwrap()) + .await + { + Ok(c) => match caps::decode_info_base64(c) { + Ok(mut i) => { + i.node = query.node; + i.into() + } + Err(_e) => { + let iq = Iq { + from: Some(connection.jid().clone()), + id: iq.id, + to: iq.from, + r#type: IqType::Error, + lang: None, + query: Some(iq::Query::DiscoInfo(query)), + errors: vec![StanzaError::ItemNotFound.into()], + }; + // TODO: log error + connection.write_handle().write(Stanza::Iq(iq)).await?; + info!("replied to disco#info request from {}", from); + return Ok(None); + } + }, + Err(_e) => { + let iq = Iq { + from: Some(connection.jid().clone()), + id: iq.id, + to: iq.from, + r#type: IqType::Error, + lang: None, + query: Some(iq::Query::DiscoInfo(query)), + errors: vec![StanzaError::ItemNotFound.into()], + }; + // TODO: log error + connection.write_handle().write(Stanza::Iq(iq)).await?; + info!("replied to disco#info request from {}", from); + return Ok(None); + } + } + }; let iq = Iq { from: Some(connection.jid().clone()), id: iq.id, diff --git a/filamento/src/presence.rs b/filamento/src/presence.rs index bae8793..a7a8965 100644 --- a/filamento/src/presence.rs +++ b/filamento/src/presence.rs @@ -2,6 +2,8 @@ use chrono::{DateTime, Utc}; use sqlx::Sqlite; use stanza::{client::presence::String1024, xep_0203::Delay}; +use crate::caps; + #[derive(Debug, Default, sqlx::FromRow, Clone)] pub struct Online { pub show: Option<Show>, @@ -96,6 +98,7 @@ impl Online { from: None, stamp: timestamp, }), + c: Some(caps::c()), ..Default::default() } } @@ -116,6 +119,7 @@ impl Offline { from: None, stamp: timestamp, }), + c: Some(caps::c()), ..Default::default() } } |