parsers: use Base64 codec to derive more things

Jonas Schäfer created

Change summary

parsers/src/bob.rs             |  76 +++++++++++------
parsers/src/cert_management.rs |  19 ++--
parsers/src/hashes.rs          |  57 +++++++++----
parsers/src/ibb.rs             |  32 +++---
parsers/src/legacy_omemo.rs    | 156 ++++++++++++++++++------------------
parsers/src/openpgp.rs         |  19 ++--
parsers/src/sasl.rs            |  90 ++++++++++----------
parsers/src/util/macros.rs     |  23 +++++
8 files changed, 268 insertions(+), 204 deletions(-)

Detailed changes

parsers/src/bob.rs 🔗

@@ -4,11 +4,12 @@
 // 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::{error::Error, text::Base64, FromXml, FromXmlText, IntoXml, IntoXmlText};
+
 use crate::hashes::{Algo, Hash};
-use crate::util::text_node_codecs::{Base64, Codec};
+use crate::ns;
 use minidom::IntoAttributeValue;
 use std::str::FromStr;
-use xso::error::Error;
 
 /// A Content-ID, as defined in RFC2111.
 ///
@@ -49,6 +50,23 @@ impl FromStr for ContentId {
     }
 }
 
+impl FromXmlText for ContentId {
+    fn from_xml_text(value: String) -> Result<Self, Error> {
+        value.parse().map_err(Error::text_parse_error)
+    }
+}
+
+impl IntoXmlText for ContentId {
+    fn into_xml_text(self) -> Result<String, Error> {
+        let algo = match self.hash.algo {
+            Algo::Sha_1 => "sha1",
+            Algo::Sha_256 => "sha256",
+            _ => unimplemented!(),
+        };
+        Ok(format!("{}+{}@bob.xmpp.org", algo, self.hash.to_hex()))
+    }
+}
+
 impl IntoAttributeValue for ContentId {
     fn into_attribute_value(self) -> Option<String> {
         let algo = match self.hash.algo {
@@ -60,30 +78,32 @@ impl IntoAttributeValue for ContentId {
     }
 }
 
-generate_element!(
-    /// Request for an uncached cid file.
-    Data, "data", BOB,
-    attributes: [
-        /// The cid in question.
-        cid: Required<ContentId> = "cid",
-
-        /// How long to cache it (in seconds).
-        max_age: Option<usize> = "max-age",
-
-        /// The MIME type of the data being transmitted.
-        ///
-        /// See the [IANA MIME Media Types Registry][1] for a list of
-        /// registered types, but unregistered or yet-to-be-registered are
-        /// accepted too.
-        ///
-        /// [1]: <https://www.iana.org/assignments/media-types/media-types.xhtml>
-        type_: Option<String> = "type"
-    ],
-    text: (
-        /// The actual data.
-        data: Base64
-    )
-);
+/// Request for an uncached cid file.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::BOB, name = "data")]
+pub struct Data {
+    /// The cid in question.
+    #[xml(attribute)]
+    pub cid: ContentId,
+
+    /// How long to cache it (in seconds).
+    #[xml(attribute(default, name = "max-age"))]
+    pub max_age: Option<usize>,
+
+    /// The MIME type of the data being transmitted.
+    ///
+    /// See the [IANA MIME Media Types Registry][1] for a list of
+    /// registered types, but unregistered or yet-to-be-registered are
+    /// accepted too.
+    ///
+    /// [1]: <https://www.iana.org/assignments/media-types/media-types.xhtml>
+    #[xml(attribute(default, name = "type"))]
+    pub type_: Option<String>,
+
+    /// The actual data.
+    #[xml(text = Base64)]
+    pub data: Vec<u8>,
+}
 
 #[cfg(test)]
 mod tests {
@@ -169,7 +189,7 @@ mod tests {
 
     #[test]
     fn unknown_child() {
-        let elem: Element = "<data xmlns='urn:xmpp:bob'><coucou/></data>"
+        let elem: Element = "<data xmlns='urn:xmpp:bob' cid='sha1+8f35fef110ffc5df08d579a50083ff9308fb6242@bob.xmpp.org'><coucou/></data>"
             .parse()
             .unwrap();
         let error = Data::try_from(elem).unwrap_err();
@@ -177,6 +197,6 @@ mod tests {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown child in data element.");
+        assert_eq!(message, "Unknown child in Data element.");
     }
 }

parsers/src/cert_management.rs 🔗

@@ -4,11 +4,10 @@
 // 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 xso::{text::Base64, FromXml, IntoXml};
 
 use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
 use crate::ns;
-use crate::util::text_node_codecs::{Base64, Codec};
 
 generate_elem_id!(
     /// The name of a certificate.
@@ -17,14 +16,14 @@ generate_elem_id!(
     SASL_CERT
 );
 
-generate_element!(
-    /// An X.509 certificate.
-    Cert, "x509cert", SASL_CERT,
-    text: (
-        /// The BER X.509 data.
-        data: Base64
-    )
-);
+/// An X.509 certificate.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::SASL_CERT, name = "x509cert")]
+pub struct Cert {
+    /// The BER X.509 data.
+    #[xml(text = Base64)]
+    pub data: Vec<u8>,
+}
 
 generate_element!(
     /// For the client to upload an X.509 certificate.

parsers/src/hashes.rs 🔗

@@ -4,15 +4,15 @@
 // 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::{FromXmlText, IntoXmlText};
+use xso::{error::Error, text::Base64, FromXml, FromXmlText, IntoXml, IntoXmlText};
 
-use crate::util::text_node_codecs::{Base64, Codec};
 use base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
 use minidom::IntoAttributeValue;
 use std::num::ParseIntError;
 use std::ops::{Deref, DerefMut};
 use std::str::FromStr;
-use xso::error::Error;
+
+use crate::ns;
 
 /// List of the algorithms we support, or Unknown.
 #[allow(non_camel_case_types)]
@@ -91,25 +91,46 @@ impl From<Algo> for String {
     }
 }
 
+impl FromXmlText for Algo {
+    fn from_xml_text(value: String) -> Result<Self, Error> {
+        value.parse().map_err(Error::text_parse_error)
+    }
+}
+
+impl IntoXmlText for Algo {
+    fn into_xml_text(self) -> Result<String, Error> {
+        Ok(String::from(match self {
+            Algo::Sha_1 => "sha-1",
+            Algo::Sha_256 => "sha-256",
+            Algo::Sha_512 => "sha-512",
+            Algo::Sha3_256 => "sha3-256",
+            Algo::Sha3_512 => "sha3-512",
+            Algo::Blake2b_256 => "blake2b-256",
+            Algo::Blake2b_512 => "blake2b-512",
+            Algo::Unknown(text) => return Ok(text),
+        }))
+    }
+}
+
 impl IntoAttributeValue for Algo {
     fn into_attribute_value(self) -> Option<String> {
         Some(String::from(self))
     }
 }
 
-generate_element!(
-    /// This element represents a hash of some data, defined by the hash
-    /// algorithm used and the computed value.
-    Hash, "hash", HASHES,
-    attributes: [
-        /// The algorithm used to create this hash.
-        algo: Required<Algo> = "algo"
-    ],
-    text: (
-        /// The hash value, as a vector of bytes.
-        hash: Base64
-    )
-);
+/// This element represents a hash of some data, defined by the hash
+/// algorithm used and the computed value.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::HASHES, name = "hash")]
+pub struct Hash {
+    /// The algorithm used to create this hash.
+    #[xml(attribute)]
+    pub algo: Algo,
+
+    /// The hash value, as a vector of bytes.
+    #[xml(text = Base64)]
+    pub hash: Vec<u8>,
+}
 
 impl Hash {
     /// Creates a [struct@Hash] element with the given algo and data.
@@ -281,7 +302,7 @@ mod tests {
 
     #[test]
     fn test_invalid_child() {
-        let elem: Element = "<hash xmlns='urn:xmpp:hashes:2'><coucou/></hash>"
+        let elem: Element = "<hash xmlns='urn:xmpp:hashes:2' algo='sha-1'><coucou/></hash>"
             .parse()
             .unwrap();
         let error = Hash::try_from(elem).unwrap_err();
@@ -289,6 +310,6 @@ mod tests {
             FromElementError::Invalid(Error::Other(string)) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown child in hash element.");
+        assert_eq!(message, "Unknown child in Hash element.");
     }
 }

parsers/src/ibb.rs 🔗

@@ -4,11 +4,10 @@
 // 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 xso::{text::Base64, FromXml, IntoXml};
 
 use crate::iq::IqSetPayload;
 use crate::ns;
-use crate::util::text_node_codecs::{Base64, Codec};
 
 generate_id!(
     /// An identifier matching a stream.
@@ -44,21 +43,22 @@ attributes: [
 
 impl IqSetPayload for Open {}
 
-generate_element!(
 /// Exchange a chunk of data in an open stream.
-Data, "data", IBB,
-    attributes: [
-        /// Sequence number of this chunk, must wraparound after 65535.
-        seq: Required<u16> = "seq",
-
-        /// The identifier of the stream on which data is being exchanged.
-        sid: Required<StreamId> = "sid"
-    ],
-    text: (
-        /// Vector of bytes to be exchanged.
-        data: Base64
-    )
-);
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::IBB, name = "data")]
+pub struct Data {
+    /// Sequence number of this chunk, must wraparound after 65535.
+    #[xml(attribute)]
+    pub seq: u16,
+
+    /// The identifier of the stream on which data is being exchanged.
+    #[xml(attribute)]
+    pub sid: StreamId,
+
+    /// Vector of bytes to be exchanged.
+    #[xml(text = Base64)]
+    pub data: Vec<u8>,
+}
 
 impl IqSetPayload for Data {}
 

parsers/src/legacy_omemo.rs 🔗

@@ -4,12 +4,11 @@
 // 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 xso::{text::Base64, FromXml, IntoXml};
 
 use crate::message::MessagePayload;
 use crate::ns;
 use crate::pubsub::PubSubPayload;
-use crate::util::text_node_codecs::{Base64, Codec};
 
 /// Element of the device list
 #[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
@@ -32,38 +31,38 @@ generate_element!(
 
 impl PubSubPayload for DeviceList {}
 
-generate_element!(
-    /// SignedPreKey public key
-    /// Part of a device's bundle
-    SignedPreKeyPublic, "signedPreKeyPublic", LEGACY_OMEMO,
-    attributes: [
-        /// SignedPreKey id
-        signed_pre_key_id: Option<u32> = "signedPreKeyId"
-    ],
-    text: (
-        /// Serialized PublicKey
-        data: Base64
-    )
-);
+/// SignedPreKey public key
+/// Part of a device's bundle
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::LEGACY_OMEMO, name = "signedPreKeyPublic")]
+pub struct SignedPreKeyPublic {
+    /// SignedPreKey id
+    #[xml(attribute(default, name = "signedPreKeyId"))]
+    pub signed_pre_key_id: Option<u32>,
 
-generate_element!(
-    /// SignedPreKey signature
-    /// Part of a device's bundle
-    SignedPreKeySignature, "signedPreKeySignature", LEGACY_OMEMO,
-    text: (
-        /// Signature bytes
-        data: Base64
-    )
-);
+    /// Serialized PublicKey
+    #[xml(text = Base64)]
+    pub data: Vec<u8>,
+}
 
-generate_element!(
-    /// Part of a device's bundle
-    IdentityKey, "identityKey", LEGACY_OMEMO,
-    text: (
-        /// Serialized PublicKey
-        data: Base64
-    )
-);
+/// SignedPreKey signature
+/// Part of a device's bundle
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::LEGACY_OMEMO, name = "signedPreKeySignature")]
+pub struct SignedPreKeySignature {
+    /// Signature bytes
+    #[xml(text = Base64)]
+    pub data: Vec<u8>,
+}
+
+/// Part of a device's bundle
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::LEGACY_OMEMO, name = "identityKey")]
+pub struct IdentityKey {
+    /// Serialized PublicKey
+    #[xml(text = Base64)]
+    pub data: Vec<u8>,
+}
 
 generate_element!(
     /// List of (single use) PreKeys
@@ -75,19 +74,19 @@ generate_element!(
     ]
 );
 
-generate_element!(
-    /// PreKey public key
-    /// Part of a device's bundle
-    PreKeyPublic, "preKeyPublic", LEGACY_OMEMO,
-    attributes: [
-        /// PreKey id
-        pre_key_id: Required<u32> = "preKeyId",
-    ],
-    text: (
-        /// Serialized PublicKey
-        data: Base64
-    )
-);
+/// PreKey public key
+/// Part of a device's bundle
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::LEGACY_OMEMO, name = "preKeyPublic")]
+pub struct PreKeyPublic {
+    /// PreKey id
+    #[xml(attribute = "preKeyId")]
+    pub pre_key_id: u32,
+
+    /// Serialized PublicKey
+    #[xml(text = Base64)]
+    pub data: Vec<u8>,
+}
 
 generate_element!(
     /// A collection of publicly accessible data that can be used to build a session with a device, namely its public IdentityKey, a signed PreKey with corresponding signature, and a list of (single use) PreKeys.
@@ -123,14 +122,14 @@ generate_element!(
     ]
 );
 
-generate_element!(
-    /// IV used for payload encryption
-    IV, "iv", LEGACY_OMEMO,
-    text: (
-        /// IV bytes
-        data: Base64
-    )
-);
+/// IV used for payload encryption
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::LEGACY_OMEMO, name = "iv")]
+pub struct IV {
+    /// IV bytes
+    #[xml(text = Base64)]
+    pub data: Vec<u8>,
+}
 
 generate_attribute!(
     /// prekey attribute for the key element.
@@ -139,33 +138,34 @@ generate_attribute!(
     bool
 );
 
-generate_element!(
-    /// Part of the OMEMO element header
-    Key, "key", LEGACY_OMEMO,
-    attributes: [
-        /// The device id this key is encrypted for.
-        rid: Required<u32> = "rid",
+/// Part of the OMEMO element header
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::LEGACY_OMEMO, name = "key")]
+pub struct Key {
+    /// The device id this key is encrypted for.
+    #[xml(attribute)]
+    pub rid: u32,
 
-        /// The key element MUST be tagged with a prekey attribute set to true
-        /// if a PreKeySignalMessage is being used.
-        prekey: Default<IsPreKey> = "prekey",
-    ],
-    text: (
-        /// The 16 bytes key and the GCM authentication tag concatenated together
-        /// and encrypted using the corresponding long-standing SignalProtocol
-        /// session
-        data: Base64
-    )
-);
+    /// The key element MUST be tagged with a prekey attribute set to true
+    /// if a PreKeySignalMessage is being used.
+    #[xml(attribute(default))]
+    pub prekey: IsPreKey,
 
-generate_element!(
-    /// The encrypted message body
-    Payload, "payload", LEGACY_OMEMO,
-    text: (
-        /// Encrypted with AES-128 in Galois/Counter Mode (GCM)
-        data: Base64
-    )
-);
+    /// The 16 bytes key and the GCM authentication tag concatenated together
+    /// and encrypted using the corresponding long-standing SignalProtocol
+    /// session
+    #[xml(text = Base64)]
+    pub data: Vec<u8>,
+}
+
+/// The encrypted message body
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::LEGACY_OMEMO, name = "payload")]
+pub struct Payload {
+    /// Encrypted with AES-128 in Galois/Counter Mode (GCM)
+    #[xml(text = Base64)]
+    pub data: Vec<u8>,
+}
 
 generate_element!(
     /// An OMEMO element, which can be either a MessageElement (with payload),

parsers/src/openpgp.rs 🔗

@@ -4,22 +4,21 @@
 // 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 xso::{text::Base64, FromXml, IntoXml};
 
 use crate::date::DateTime;
 use crate::ns;
 use crate::pubsub::PubSubPayload;
-use crate::util::text_node_codecs::{Base64, Codec};
 
+/// Data contained in the PubKey element
 // TODO: Merge this container with the PubKey struct
-generate_element!(
-    /// Data contained in the PubKey element
-    PubKeyData, "data", OX,
-    text: (
-        /// Base64 data
-        data: Base64
-    )
-);
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::OX, name = "data")]
+pub struct PubKeyData {
+    /// Base64 data
+    #[xml(text = Base64)]
+    pub data: Vec<u8>,
+}
 
 generate_element!(
     /// Pubkey element to be used in PubSub publish payloads.

parsers/src/sasl.rs 🔗

@@ -4,13 +4,15 @@
 // 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 xso::{
+    error::{Error, FromElementError},
+    text::Base64,
+    FromXml, IntoXml,
+};
 
 use crate::ns;
-use crate::util::text_node_codecs::{Base64, Codec};
 use crate::Element;
 use std::collections::BTreeMap;
-use xso::error::{Error, FromElementError};
 
 generate_attribute!(
     /// The list of available SASL mechanisms.
@@ -44,41 +46,41 @@ generate_attribute!(
     }
 );
 
-generate_element!(
-    /// The first step of the SASL process, selecting the mechanism and sending
-    /// the first part of the handshake.
-    Auth, "auth", SASL,
-    attributes: [
-        /// The mechanism used.
-        mechanism: Required<Mechanism> = "mechanism"
-    ],
-    text: (
-        /// The content of the handshake.
-        data: Base64
-    )
-);
+/// The first step of the SASL process, selecting the mechanism and sending
+/// the first part of the handshake.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::SASL, name = "auth")]
+pub struct Auth {
+    /// The mechanism used.
+    #[xml(attribute)]
+    pub mechanism: Mechanism,
+
+    /// The content of the handshake.
+    #[xml(text = Base64)]
+    pub data: Vec<u8>,
+}
 
-generate_element!(
-    /// In case the mechanism selected at the [auth](struct.Auth.html) step
-    /// requires a second step, the server sends this element with additional
-    /// data.
-    Challenge, "challenge", SASL,
-    text: (
-        /// The challenge data.
-        data: Base64
-    )
-);
+/// In case the mechanism selected at the [auth](struct.Auth.html) step
+/// requires a second step, the server sends this element with additional
+/// data.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::SASL, name = "challenge")]
+pub struct Challenge {
+    /// The challenge data.
+    #[xml(text = Base64)]
+    pub data: Vec<u8>,
+}
 
-generate_element!(
-    /// In case the mechanism selected at the [auth](struct.Auth.html) step
-    /// requires a second step, this contains the client’s response to the
-    /// server’s [challenge](struct.Challenge.html).
-    Response, "response", SASL,
-    text: (
-        /// The response data.
-        data: Base64
-    )
-);
+/// In case the mechanism selected at the [auth](struct.Auth.html) step
+/// requires a second step, this contains the client’s response to the
+/// server’s [challenge](struct.Challenge.html).
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::SASL, name = "response")]
+pub struct Response {
+    /// The response data.
+    #[xml(text = Base64)]
+    pub data: Vec<u8>,
+}
 
 /// Sent by the client at any point after [auth](struct.Auth.html) if it
 /// wants to cancel the current authentication process.
@@ -86,14 +88,14 @@ generate_element!(
 #[xml(namespace = ns::SASL, name = "abort")]
 pub struct Abort;
 
-generate_element!(
-    /// Sent by the server on SASL success.
-    Success, "success", SASL,
-    text: (
-        /// Possible data sent on success.
-        data: Base64
-    )
-);
+/// Sent by the server on SASL success.
+#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
+#[xml(namespace = ns::SASL, name = "success")]
+pub struct Success {
+    /// Possible data sent on success.
+    #[xml(text = Base64)]
+    pub data: Vec<u8>,
+}
 
 generate_element_enum!(
     /// List of possible failure conditions for SASL.

parsers/src/util/macros.rs 🔗

@@ -211,6 +211,29 @@ macro_rules! generate_attribute {
                 })
             }
         }
+        impl ::xso::FromXmlText for $elem {
+            fn from_xml_text(s: String) -> Result<$elem, xso::error::Error> {
+                match s.parse::<bool>().map_err(xso::error::Error::text_parse_error)? {
+                    true => Ok(Self::True),
+                    false => Ok(Self::False),
+                }
+            }
+        }
+        impl ::xso::IntoXmlText for $elem {
+            fn into_xml_text(self) -> Result<String, xso::error::Error> {
+                match self {
+                    Self::True => Ok("true".to_owned()),
+                    Self::False => Ok("false".to_owned()),
+                }
+            }
+
+            fn into_optional_xml_text(self) -> Result<Option<String>, xso::error::Error> {
+                match self {
+                    Self::True => Ok(Some("true".to_owned())),
+                    Self::False => Ok(None),
+                }
+            }
+        }
         impl ::minidom::IntoAttributeValue for $elem {
             fn into_attribute_value(self) -> Option<String> {
                 match self {