parsers: use Base64 codec to derive more things

This commit is contained in:
Jonas Schäfer 2024-06-26 19:35:56 +02:00
parent 7c7f6d1f23
commit 1367764f85
8 changed files with 265 additions and 201 deletions

View file

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

View file

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

View file

@ -4,15 +4,15 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // 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/. // 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 base64::{engine::general_purpose::STANDARD as Base64Engine, Engine};
use minidom::IntoAttributeValue; use minidom::IntoAttributeValue;
use std::num::ParseIntError; use std::num::ParseIntError;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::str::FromStr; use std::str::FromStr;
use xso::error::Error;
use crate::ns;
/// List of the algorithms we support, or Unknown. /// List of the algorithms we support, or Unknown.
#[allow(non_camel_case_types)] #[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 { impl IntoAttributeValue for Algo {
fn into_attribute_value(self) -> Option<String> { fn into_attribute_value(self) -> Option<String> {
Some(String::from(self)) Some(String::from(self))
} }
} }
generate_element!( /// This element represents a hash of some data, defined by the hash
/// This element represents a hash of some data, defined by the hash /// algorithm used and the computed value.
/// algorithm used and the computed value. #[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
Hash, "hash", HASHES, #[xml(namespace = ns::HASHES, name = "hash")]
attributes: [ pub struct Hash {
/// The algorithm used to create this hash. /// The algorithm used to create this hash.
algo: Required<Algo> = "algo" #[xml(attribute)]
], pub algo: Algo,
text: (
/// The hash value, as a vector of bytes. /// The hash value, as a vector of bytes.
hash: Base64 #[xml(text = Base64)]
) pub hash: Vec<u8>,
); }
impl Hash { impl Hash {
/// Creates a [struct@Hash] element with the given algo and data. /// Creates a [struct@Hash] element with the given algo and data.
@ -281,7 +302,7 @@ mod tests {
#[test] #[test]
fn test_invalid_child() { 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() .parse()
.unwrap(); .unwrap();
let error = Hash::try_from(elem).unwrap_err(); let error = Hash::try_from(elem).unwrap_err();
@ -289,6 +310,6 @@ mod tests {
FromElementError::Invalid(Error::Other(string)) => string, FromElementError::Invalid(Error::Other(string)) => string,
_ => panic!(), _ => panic!(),
}; };
assert_eq!(message, "Unknown child in hash element."); assert_eq!(message, "Unknown child in Hash element.");
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -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 { impl ::minidom::IntoAttributeValue for $elem {
fn into_attribute_value(self) -> Option<String> { fn into_attribute_value(self) -> Option<String> {
match self { match self {