rust-analyzer.toml 🔗
@@ -0,0 +1,7 @@
+[cargo]
+features = [
+ "xmpp/component",
+ "xmpp/insecure-tcp",
+ "tokio-xmpp/component",
+ "tokio-xmpp/insecure-tcp",
+]
Phillip Davis created
rust-analyzer.toml | 7 +
xmpp/Cargo.toml | 8 +
xmpp/comile.sh | 1
xmpp/compile.sh | 1
xmpp/src/component.rs | 249 +++++++++++++++++++++++++++++++++++++++++++++
xmpp/src/lib.rs | 3
6 files changed, 267 insertions(+), 2 deletions(-)
@@ -0,0 +1,7 @@
+[cargo]
+features = [
+ "xmpp/component",
+ "xmpp/insecure-tcp",
+ "tokio-xmpp/component",
+ "tokio-xmpp/insecure-tcp",
+]
@@ -21,7 +21,8 @@ log = "0.4"
reqwest = { version = "0.12", features = ["stream"], default-features = false }
tokio-util = { version = "0.7", features = ["codec"] }
# same repository dependencies
-tokio-xmpp = { version = "5.0", path = "../tokio-xmpp", default-features = false }
+tokio-xmpp = { version = "5.0", features = ["component", "insecure-tcp"], path = "../tokio-xmpp", default-features = false }
+thiserror = "2.0"
[dev-dependencies]
env_logger = { version = "0.11", default-features = false, features = ["auto-color", "humantime"] }
@@ -31,7 +32,7 @@ name = "hello_bot"
required-features = ["avatars"]
[features]
-default = ["avatars", "aws_lc_rs", "starttls", "rustls-native-certs"]
+default = ["avatars", "aws_lc_rs", "starttls", "rustls-native-certs", "component", "insecure-tcp"]
aws_lc_rs = ["rustls-any-backend", "tokio-xmpp/aws_lc_rs", "reqwest/rustls-tls-no-provider"]
ring = ["rustls-any-backend", "tokio-xmpp/ring", "reqwest/rustls-tls-no-provider"]
@@ -45,6 +46,9 @@ webpki-roots = ["tokio-xmpp/webpki-roots"]
starttls = ["tokio-xmpp/starttls"]
+insecure-tcp = ["tokio-xmpp/insecure-tcp"]
+component = ["tokio-xmpp/component"]
+
avatars = []
escape-hatch = []
syntax-highlighting = [ "tokio-xmpp/syntax-highlighting" ]
@@ -0,0 +1 @@
+cargo b --features component,insecure
@@ -0,0 +1 @@
+cargo b --features component,insecure-tcp
@@ -0,0 +1,249 @@
+use futures::stream::StreamExt;
+use std::{fmt::Debug, marker::PhantomData};
+
+use thiserror::Error;
+use tokio_xmpp::{
+ Component as TokioComponent, Stanza,
+ connect::ServerConnector,
+ jid::{BareJid, Jid},
+ parsers::{
+ data_forms::DataForm,
+ ibr::{FormQuery, LegacyQuery},
+ iq::Iq,
+ ns,
+ stanza_error::{DefinedCondition, ErrorType, StanzaError},
+ },
+ xmlstream::Timeouts,
+};
+
+pub trait IbrData: Clone + Send + Sync + Sized {}
+
+pub trait LegacyIbrData: Clone + Send + Sync + Sized + IbrData {
+ fn from_legacy(query: &LegacyQuery) -> Self;
+}
+
+pub trait DataFormIbrData: Clone + Send + Sync + Sized + IbrData {
+ fn from_data_form(form: &FormQuery) -> Self;
+}
+
+pub trait IbrStore: Send + Sync {
+ type Data: IbrData;
+
+ type E: std::error::Error + Send + Sync;
+
+ fn get(&self, jid: &BareJid) -> impl Future<Output = Result<Option<Self::Data>, Self::E>>;
+
+ fn register(
+ &self,
+ jid: &BareJid,
+ data: Self::Data,
+ ) -> impl Future<Output = Result<(), Self::E>>;
+
+ fn unregister(
+ &self,
+ jid: &BareJid,
+ data: Self::Data,
+ ) -> impl Future<Output = Result<(), Self::E>>;
+}
+
+// Must be empty
+pub enum IbrFields {
+ Legacy(LegacyQuery),
+ DataForm(DataForm),
+}
+
+// Must not be empty
+pub enum IbrSubmission {
+ Legacy(LegacyQuery),
+ DataForm(DataForm),
+}
+
+/// Errors that can occur during IBR operations.
+#[derive(Debug)]
+pub enum IbrError {
+ /// User is not registered (for cancel/change operations).
+ NotRegistered,
+ /// Username conflict.
+ Conflict,
+ /// Required fields missing.
+ NotAcceptable,
+ /// Unsupported submission type (e.g., legacy when only DataForm is implemented).
+ UnsupportedSubmissionType,
+ /// Storage error.
+ Store(Box<dyn std::error::Error + Send + Sync>),
+}
+
+pub trait IbrHandler: IbrStore {
+ fn fields(
+ &self,
+ from: &BareJid,
+ status: Option<Self::Data>,
+ ) -> impl Future<Output = IbrFields> + Send;
+
+ fn on_register(
+ &self,
+ from: &BareJid,
+ submission: IbrSubmission,
+ ) -> impl Future<Output = Result<(), IbrError>>;
+
+ fn on_cancel(&self, from: &BareJid) -> impl Future<Output = Result<(), IbrError>> + Send;
+}
+
+impl IbrData for () {}
+
+#[derive(Debug, Error)]
+pub enum IbrNotSupportedShouldBeUnreachableError {
+ #[error("If you see this, there is a serious bug in xmpp-rs. Please report")]
+ CriticalUnreachableError,
+}
+
+pub struct IbrNotSupported {}
+
+impl IbrStore for IbrNotSupported {
+ type Data = ();
+ type E = IbrNotSupportedShouldBeUnreachableError;
+
+ fn get(&self, _: &BareJid) -> impl Future<Output = Result<Option<Self::Data>, Self::E>> {
+ unreachable!();
+ std::future::ready(Err(
+ IbrNotSupportedShouldBeUnreachableError::CriticalUnreachableError,
+ ))
+ }
+
+ fn register(&self, _: &BareJid, _: Self::Data) -> impl Future<Output = Result<(), Self::E>> {
+ unreachable!();
+ std::future::ready(Err(
+ IbrNotSupportedShouldBeUnreachableError::CriticalUnreachableError,
+ ))
+ }
+
+ fn unregister(&self, _: &BareJid, _: Self::Data) -> impl Future<Output = Result<(), Self::E>> {
+ unreachable!();
+ std::future::ready(Err(
+ IbrNotSupportedShouldBeUnreachableError::CriticalUnreachableError,
+ ))
+ }
+}
+
+impl IbrHandler for IbrNotSupported {}
+
+#[derive(Debug)]
+pub enum ComponentError {
+ IbrError(IbrError),
+}
+
+struct Config<C, Handler>
+where
+ C: ServerConnector,
+ Handler: ComponentHandler<C>,
+{
+ jid: Jid,
+ password: String,
+ hander: Handler,
+ phantom_connector: PhantomData<C>,
+}
+
+pub enum Continuation {
+ Next(Stanza),
+ Error(StanzaError),
+}
+
+pub trait ComponentHandler<C>: Sized
+where
+ C: ServerConnector,
+{
+ fn handle_register() -> impl Future<Output = Result<(), ComponentError>>;
+
+ async fn handle_iq(&mut self, iq: Iq) -> Result<Continuation, ComponentError> {
+ match iq {
+ Iq::Get {
+ from,
+ to,
+ id,
+ payload,
+ } => match (payload.name(), payload.ns().as_str()) {
+ ("query", ns::REGISTER) => {
+ let fields = self.ibr_fields().await;
+ todo!()
+ }
+ _ => {
+ let err = StanzaError::new(
+ ErrorType::Cancel,
+ DefinedCondition::ServiceUnavailable,
+ "en",
+ "No handler defined for this kind of iq.",
+ );
+
+ Ok(Continuation::Error(err))
+ }
+ },
+
+ _ => todo!(),
+ }
+ }
+}
+
+struct Component<C, Handler, Ibr>
+where
+ C: ServerConnector,
+ Handler: ComponentHandler<C>,
+ Ibr: IbrHandler,
+{
+ jid: Jid,
+ password: String,
+ handler: Handler,
+ inner: TokioComponent<C>, // low-level component implementation
+ ibr: Ibr,
+}
+
+impl<C, Handler> Component<C, Handler, Ibr>
+where
+ C: ServerConnector,
+ Handler: ComponentHandler<C>,
+ Ibr: IbrHandler,
+{
+ async fn run(mut self) -> Result<(), ComponentError> {
+ while let Some(stanza) = self.inner.next().await {
+ match stanza {
+ Stanza::Iq(iq) => {
+ self.handler.handle_iq(iq);
+ }
+ _ => todo!(),
+ }
+ }
+
+ Ok(())
+ }
+}
+
+impl<C, Handler> Config<C, Handler, Ibr>
+where
+ C: ServerConnector,
+ Handler: ComponentHandler<C>,
+ Ibr: IbrHandler,
+{
+ pub async fn run(
+ self,
+ connector: C,
+ timeouts: Timeouts,
+ handler: Handler,
+ ) -> Result<(), ComponentError> {
+ let inner_component = TokioComponent::new_with_connector(
+ &self.jid.as_str(),
+ &self.password.as_str(),
+ connector,
+ timeouts,
+ )
+ .await
+ .unwrap(); // TODO: Handle this error
+
+ let inner_component = Component {
+ jid: self.jid,
+ password: self.password,
+ handler,
+ inner: inner_component,
+ };
+
+ inner_component.run().await
+ }
+}
@@ -65,6 +65,9 @@ use parsers::message::Id as MessageId;
pub mod agent;
pub mod builder;
+#[cfg(feature = "component")]
+#[cfg(feature = "insecure-tcp")]
+pub mod component;
pub mod config;
pub mod delay;
pub mod disco;