diff --git a/crates/ruma-serde/src/base64.rs b/crates/ruma-serde/src/base64.rs index bf13387a..d6ec03f3 100644 --- a/crates/ruma-serde/src/base64.rs +++ b/crates/ruma-serde/src/base64.rs @@ -1,4 +1,6 @@ -use std::fmt; +//!Transparent base64 encoding / decoding as part of (de)serialization. + +use std::{fmt, marker::PhantomData}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; @@ -9,37 +11,50 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Base64> { bytes: B, + _phantom_conf: PhantomData<*mut C>, } +/// Config used for the [`Base64`] type. pub trait Base64Config { - const CONF: base64::Config; + /// The config as a constant. + /// + /// Opaque so our interface is not tied to the base64 crate version. + #[doc(hidden)] + const CONF: Conf; } +#[doc(hidden)] +pub struct Conf(base64::Config); + /// Standard base64 character set without padding. /// /// Allows trailing bits in decoding for maximum compatibility. #[non_exhaustive] +// Easier than implementing these all for Base64 manually to avoid the `C: Trait` bounds. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Standard; impl Base64Config for Standard { // See https://github.com/matrix-org/matrix-doc/issues/3211 - const CONF: base64::Config = base64::STANDARD_NO_PAD.decode_allow_trailing_bits(true); + const CONF: Conf = Conf(base64::STANDARD_NO_PAD.decode_allow_trailing_bits(true)); } /// Url-safe base64 character set without padding. /// /// Allows trailing bits in decoding for maximum compatibility. #[non_exhaustive] +// Easier than implementing these all for Base64 manually to avoid the `C: Trait` bounds. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct UrlSafe; impl Base64Config for UrlSafe { - const CONF: base64::Config = base64::URL_SAFE_NO_PAD.decode_allow_trailing_bits(true); + const CONF: Conf = Conf(base64::URL_SAFE_NO_PAD.decode_allow_trailing_bits(true)); } -impl> Base64 { +impl> Base64 { /// Create a `Base64` instance from raw bytes, to be base64-encoded in serialialization. pub fn new(bytes: B) -> Self { - Self { bytes } + Self { bytes, _phantom_conf: PhantomData } } /// Get a reference to the raw bytes held by this `Base64` instance. @@ -49,42 +64,42 @@ impl> Base64 { /// Encode the bytes contained in this `Base64` instance to unpadded base64. pub fn encode(&self) -> String { - base64::encode_config(&self.bytes, BASE64_CONFIG) + base64::encode_config(&self.bytes, C::CONF.0) } } -impl Base64 { +impl Base64 { /// Get the raw bytes held by this `Base64` instance. pub fn into_inner(self) -> B { self.bytes } } -impl Base64 { +impl Base64 { /// Create a `Base64` instance containing an empty `Vec`. pub fn empty() -> Self { - Self { bytes: Vec::new() } + Self::new(Vec::new()) } /// Parse some base64-encoded data to create a `Base64` instance. pub fn parse(encoded: impl AsRef<[u8]>) -> Result { - base64::decode_config(encoded, BASE64_CONFIG).map(Self::new) + base64::decode_config(encoded, C::CONF.0).map(Self::new) } } -impl> fmt::Debug for Base64 { +impl> fmt::Debug for Base64 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.encode().fmt(f) } } -impl> fmt::Display for Base64 { +impl> fmt::Display for Base64 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.encode().fmt(f) } } -impl<'de> Deserialize<'de> for Base64 { +impl<'de, C: Base64Config> Deserialize<'de> for Base64 { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, @@ -94,7 +109,7 @@ impl<'de> Deserialize<'de> for Base64 { } } -impl> Serialize for Base64 { +impl> Serialize for Base64 { fn serialize(&self, serializer: S) -> Result where S: Serializer, diff --git a/crates/ruma-signatures/src/functions.rs b/crates/ruma-signatures/src/functions.rs index c43bc058..4a12d259 100644 --- a/crates/ruma-signatures/src/functions.rs +++ b/crates/ruma-signatures/src/functions.rs @@ -9,7 +9,7 @@ use std::{ use base64::{encode_config, STANDARD_NO_PAD, URL_SAFE_NO_PAD}; use ruma_identifiers::{EventId, RoomVersionId, ServerName, UserId}; -use ruma_serde::{Base64, CanonicalJsonObject, CanonicalJsonValue}; +use ruma_serde::{base64::Standard, Base64, CanonicalJsonObject, CanonicalJsonValue}; use serde_json::{from_str as from_json_str, to_string as to_json_string}; use sha2::{digest::Digest, Sha256}; @@ -291,7 +291,7 @@ pub fn verify_json( ) })?; - let signature = Base64::parse(signature) + let signature = Base64::::parse(signature) .map_err(|e| ParseError::base64("signature", signature, e))?; verify_json_with( @@ -332,8 +332,6 @@ where /// Creates a *content hash* for an event. /// -/// Returns the hash as a base64-encoded string, using the standard character set, without padding. -/// /// The content hash of an event covers the complete event including the unredacted contents. It is /// used during federation and is described in the Matrix server-server specification. /// @@ -344,7 +342,7 @@ where /// # Errors /// /// Returns an error if the event is too large. -pub fn content_hash(object: &CanonicalJsonObject) -> Result, Error> { +pub fn content_hash(object: &CanonicalJsonObject) -> Result, Error> { let json = canonical_json_with_fields_to_remove(object, CONTENT_HASH_FIELDS_TO_REMOVE)?; if json.len() > MAX_PDU_BYTES { return Err(Error::PduSize); @@ -656,8 +654,8 @@ pub fn verify_event( let public_key = signature_and_pubkey.public_key; - let signature = - Base64::parse(signature).map_err(|e| ParseError::base64("signature", signature, e))?; + let signature = Base64::::parse(signature) + .map_err(|e| ParseError::base64("signature", signature, e))?; verify_json_with( &Ed25519Verifier, @@ -669,7 +667,7 @@ pub fn verify_event( let calculated_hash = content_hash(object)?; - if let Ok(hash) = Base64::parse(hash) { + if let Ok(hash) = Base64::::parse(hash) { if hash.as_bytes() == calculated_hash.as_bytes() { return Ok(Verified::All); }