diff --git a/parsers/ChangeLog b/parsers/ChangeLog
index 54215339..9768f2d2 100644
--- a/parsers/ChangeLog
+++ b/parsers/ChangeLog
@@ -1,5 +1,7 @@
Version xxx:
0000-00-00 Authors
+ * New parsers/serialisers:
+ - Fast Authentication Streamlining Tokens (XEP-0484)
* Improvements:
- Re-export the jid module entirely.
- Add serde feature, passed to jid crate
diff --git a/parsers/doap.xml b/parsers/doap.xml
index d54e2c0a..11795578 100644
--- a/parsers/doap.xml
+++ b/parsers/doap.xml
@@ -608,6 +608,14 @@
NEXT
+
+
+
+ complete
+ 0.1.1
+ NEXT
+
+
diff --git a/parsers/src/fast.rs b/parsers/src/fast.rs
new file mode 100644
index 00000000..e287cf98
--- /dev/null
+++ b/parsers/src/fast.rs
@@ -0,0 +1,116 @@
+// Copyright (c) 2024 Emmanuel Gil Peyrot
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use xso::{FromXml, IntoXml};
+
+use crate::date::DateTime;
+use crate::ns;
+
+generate_elem_id!(
+ /// A `` element, describing one mechanism allowed by the server to authenticate.
+ Mechanism, "mechanism", FAST
+);
+
+// TODO: Replace this with a proper bool once we can derive FromXml and IntoXml on FastQuery.
+generate_attribute!(
+ /// Whether TLS zero-roundtrip is possible.
+ Tls0Rtt, "tls-0rtt", bool
+);
+
+generate_element!(
+/// This is the `` element sent by the server as a SASL2 inline feature.
+FastQuery, "fast", FAST,
+attributes: [
+ /// Whether TLS zero-roundtrip is possible.
+ tls_0rtt: Default = "tls-0rtt",
+],
+children: [
+ /// A list of `` elements, listing all server allowed mechanisms.
+ mechanisms: Vec = ("mechanism", FAST) => Mechanism
+]
+);
+
+/// This is the `` element the client MUST include within its SASL2 authentication request.
+#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::FAST, name = "fast")]
+pub struct FastResponse {
+ /// Servers MUST reject any authentication requests received via TLS 0-RTT payloads that do not
+ /// include a 'count' attribute, or where the count is less than or equal to a count that has
+ /// already been processed for this token. This protects against replay attacks that 0-RTT is
+ /// susceptible to.
+ #[xml(attribute)]
+ pub count: u32,
+
+ /// If true and the client has successfully authenticated, the server MUST invalidate the
+ /// token.
+ #[xml(attribute(default))]
+ pub invalidate: bool,
+}
+
+/// This is the `` element sent by the client in the SASL2 authenticate step.
+#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::FAST, name = "request-token")]
+pub struct RequestToken {
+ /// This element MUST contain a 'mechanism' attribute, the value of which MUST be one of the
+ /// FAST mechanisms advertised by the server.
+ #[xml(attribute)]
+ pub mechanism: String,
+}
+
+/// This is the `` element sent by the server on successful SASL2 authentication containing
+/// a `` element.
+#[derive(FromXml, IntoXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::FAST, name = "token")]
+pub struct Token {
+ /// The secret token to be used for subsequent authentications, as generated by the server.
+ #[xml(attribute)]
+ pub token: String,
+
+ /// The timestamp at which the token will expire.
+ #[xml(attribute)]
+ pub expiry: DateTime,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::Element;
+ use std::str::FromStr;
+
+ #[test]
+ fn test_simple() {
+ let elem: Element = "FOO-BAR"
+ .parse()
+ .unwrap();
+ let request = FastQuery::try_from(elem).unwrap();
+ assert_eq!(request.tls_0rtt, Tls0Rtt::False);
+ assert_eq!(request.mechanisms, [Mechanism(String::from("FOO-BAR"))]);
+
+ let elem: Element = ""
+ .parse()
+ .unwrap();
+ let response = FastResponse::try_from(elem).unwrap();
+ assert_eq!(response.count, 123);
+ assert_eq!(response.invalidate, false);
+
+ let elem: Element = ""
+ .parse()
+ .unwrap();
+ let request_token = RequestToken::try_from(elem).unwrap();
+ assert_eq!(request_token.mechanism, "FOO-BAR");
+
+ let elem: Element =
+ ""
+ .parse()
+ .unwrap();
+ let token = Token::try_from(elem).unwrap();
+ assert_eq!(token.token, "ABCD");
+ assert_eq!(
+ token.expiry,
+ DateTime::from_str("2024-06-30T17:13:57+02:00").unwrap()
+ );
+ }
+}
diff --git a/parsers/src/lib.rs b/parsers/src/lib.rs
index 04dd15f4..cf2fd3b5 100644
--- a/parsers/src/lib.rs
+++ b/parsers/src/lib.rs
@@ -266,3 +266,6 @@ pub mod mam_prefs;
/// XEP-0444: Message Reactions
pub mod reactions;
+
+/// XEP-0484: Fast Authentication Streamlining Tokens
+pub mod fast;
diff --git a/parsers/src/ns.rs b/parsers/src/ns.rs
index 88dbe67d..2915f233 100644
--- a/parsers/src/ns.rs
+++ b/parsers/src/ns.rs
@@ -302,3 +302,6 @@ pub const DEFAULT_NS: &str = JABBER_CLIENT;
/// "jabber:component:accept" when the component feature is enabled.
#[cfg(feature = "component")]
pub const DEFAULT_NS: &str = COMPONENT_ACCEPT;
+
+/// XEP-0484: Fast Authentication Streamlining Tokens
+pub const FAST: &str = "urn:xmpp:fast:0";