fast.rs

  1// Copyright (c) 2024 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7use xso::{AsXml, FromXml};
  8
  9use crate::date::DateTime;
 10use crate::ns;
 11
 12generate_elem_id!(
 13    /// A `<mechanism/>` element, describing one mechanism allowed by the server to authenticate.
 14    Mechanism, "mechanism", FAST
 15);
 16
 17/// This is the `<fast/>` element sent by the server as a SASL2 inline feature.
 18#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 19#[xml(namespace = ns::FAST, name = "fast")]
 20pub struct FastQuery {
 21    /// Whether TLS zero-roundtrip is possible.
 22    #[xml(attribute(default, name = "tls-0rtt"))]
 23    pub tls_0rtt: bool,
 24
 25    /// A list of `<mechanism/>` elements, listing all server allowed mechanisms.
 26    #[xml(child(n = ..))]
 27    pub mechanisms: Vec<Mechanism>,
 28}
 29
 30/// This is the `<fast/>` element the client MUST include within its SASL2 authentication request.
 31#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 32#[xml(namespace = ns::FAST, name = "fast")]
 33pub struct FastResponse {
 34    /// Servers MUST reject any authentication requests received via TLS 0-RTT payloads that do not
 35    /// include a 'count' attribute, or where the count is less than or equal to a count that has
 36    /// already been processed for this token.  This protects against replay attacks that 0-RTT is
 37    /// susceptible to.
 38    #[xml(attribute)]
 39    pub count: u32,
 40
 41    /// If true and the client has successfully authenticated, the server MUST invalidate the
 42    /// token.
 43    #[xml(attribute(default))]
 44    pub invalidate: bool,
 45}
 46
 47/// This is the `<request-token/>` element sent by the client in the SASL2 authenticate step.
 48#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 49#[xml(namespace = ns::FAST, name = "request-token")]
 50pub struct RequestToken {
 51    /// This element MUST contain a 'mechanism' attribute, the value of which MUST be one of the
 52    /// FAST mechanisms advertised by the server.
 53    #[xml(attribute)]
 54    pub mechanism: String,
 55}
 56
 57/// This is the `<token/>` element sent by the server on successful SASL2 authentication containing
 58/// a `<request-token/>` element.
 59#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
 60#[xml(namespace = ns::FAST, name = "token")]
 61pub struct Token {
 62    /// The secret token to be used for subsequent authentications, as generated by the server.
 63    #[xml(attribute)]
 64    pub token: String,
 65
 66    /// The timestamp at which the token will expire.
 67    #[xml(attribute)]
 68    pub expiry: DateTime,
 69}
 70
 71#[cfg(test)]
 72mod tests {
 73    use super::*;
 74    use core::str::FromStr;
 75    use minidom::Element;
 76
 77    #[test]
 78    fn test_simple() {
 79        let elem: Element = "<fast xmlns='urn:xmpp:fast:0'><mechanism>FOO-BAR</mechanism></fast>"
 80            .parse()
 81            .unwrap();
 82        let request = FastQuery::try_from(elem).unwrap();
 83        assert_eq!(request.tls_0rtt, false);
 84        assert_eq!(request.mechanisms, [Mechanism(String::from("FOO-BAR"))]);
 85
 86        let elem: Element = "<fast xmlns='urn:xmpp:fast:0' count='123'/>"
 87            .parse()
 88            .unwrap();
 89        let response = FastResponse::try_from(elem).unwrap();
 90        assert_eq!(response.count, 123);
 91        assert_eq!(response.invalidate, false);
 92
 93        let elem: Element = "<request-token xmlns='urn:xmpp:fast:0' mechanism='FOO-BAR'/>"
 94            .parse()
 95            .unwrap();
 96        let request_token = RequestToken::try_from(elem).unwrap();
 97        assert_eq!(request_token.mechanism, "FOO-BAR");
 98
 99        let elem: Element =
100            "<token xmlns='urn:xmpp:fast:0' token='ABCD' expiry='2024-06-30T17:13:57+02:00'/>"
101                .parse()
102                .unwrap();
103        let token = Token::try_from(elem).unwrap();
104        assert_eq!(token.token, "ABCD");
105        assert_eq!(
106            token.expiry,
107            DateTime::from_str("2024-06-30T17:13:57+02:00").unwrap()
108        );
109    }
110}