identifiers: Add more crypto-related types
Co-authored-by: Isaiah Inuwa <isaiah.inuwa@gmail.com>
This commit is contained in:
parent
63678df887
commit
1ceade7b61
@ -251,7 +251,7 @@ mod tests {
|
||||
|
||||
use maplit::btreemap;
|
||||
use matches::assert_matches;
|
||||
use ruma_identifiers::{server_key_id, server_name};
|
||||
use ruma_identifiers::{server_name, server_signing_key_id};
|
||||
use ruma_serde::Raw;
|
||||
use serde_json::{from_value as from_json_value, json};
|
||||
|
||||
@ -411,7 +411,7 @@ mod tests {
|
||||
&& mxid == "@alice:example.org"
|
||||
&& signatures == btreemap! {
|
||||
server_name!("magic.forest") => btreemap! {
|
||||
server_key_id!("ed25519:3") => "foobar".to_owned()
|
||||
server_signing_key_id!("ed25519:3") => "foobar".to_owned()
|
||||
}
|
||||
}
|
||||
&& token == "abc123"
|
||||
@ -497,7 +497,7 @@ mod tests {
|
||||
&& mxid == "@alice:example.org"
|
||||
&& signatures == btreemap! {
|
||||
server_name!("magic.forest") => btreemap! {
|
||||
server_key_id!("ed25519:3") => "foobar".to_owned()
|
||||
server_signing_key_id!("ed25519:3") => "foobar".to_owned()
|
||||
}
|
||||
}
|
||||
&& token == "abc123"
|
||||
|
@ -9,7 +9,7 @@ use ruma_events::{
|
||||
pdu::{EventHash, Pdu, RoomV1Pdu, RoomV3Pdu},
|
||||
EventType,
|
||||
};
|
||||
use ruma_identifiers::{event_id, room_id, server_key_id, server_name, user_id};
|
||||
use ruma_identifiers::{event_id, room_id, server_name, server_signing_key_id, user_id};
|
||||
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
||||
|
||||
#[test]
|
||||
@ -17,7 +17,7 @@ fn serialize_pdu_as_v1() {
|
||||
let mut signatures = BTreeMap::new();
|
||||
let mut inner_signature = BTreeMap::new();
|
||||
inner_signature.insert(
|
||||
server_key_id!("ed25519:key_version"),
|
||||
server_signing_key_id!("ed25519:key_version"),
|
||||
"86BytesOfSignatureOfTheRedactedEvent".into(),
|
||||
);
|
||||
signatures.insert(server_name!("example.com"), inner_signature);
|
||||
@ -84,7 +84,7 @@ fn serialize_pdu_as_v3() {
|
||||
let mut signatures = BTreeMap::new();
|
||||
let mut inner_signature = BTreeMap::new();
|
||||
inner_signature.insert(
|
||||
server_key_id!("ed25519:key_version"),
|
||||
server_signing_key_id!("ed25519:key_version"),
|
||||
"86BytesOfSignatureOfTheRedactedEvent".into(),
|
||||
);
|
||||
signatures.insert(server_name!("example.com"), inner_signature);
|
||||
|
@ -2,8 +2,7 @@ use proc_macro::TokenStream;
|
||||
|
||||
use quote::quote;
|
||||
use ruma_identifiers_validation::{
|
||||
device_key_id, event_id, room_alias_id, room_id, room_version_id, server_name, signing_key_id,
|
||||
user_id,
|
||||
device_key_id, event_id, key_id, room_alias_id, room_id, room_version_id, server_name, user_id,
|
||||
};
|
||||
use syn::{parse::Parse, parse_macro_input, LitStr, Path, Token};
|
||||
|
||||
@ -83,9 +82,9 @@ pub fn room_version_id(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn server_key_id(input: TokenStream) -> TokenStream {
|
||||
pub fn server_signing_key_id(input: TokenStream) -> TokenStream {
|
||||
let Input { dollar_crate, id } = parse_macro_input!(input as Input);
|
||||
assert!(signing_key_id::validate(&id.value()).is_ok(), "Invalid server_key_id");
|
||||
assert!(key_id::validate(&id.value()).is_ok(), "Invalid server_key_id");
|
||||
|
||||
let output = quote! {
|
||||
<#dollar_crate::ServerSigningKeyId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
|
||||
@ -108,18 +107,6 @@ pub fn server_name(input: TokenStream) -> TokenStream {
|
||||
output.into()
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn signing_key_id(input: TokenStream) -> TokenStream {
|
||||
let Input { dollar_crate, id } = parse_macro_input!(input as Input);
|
||||
assert!(signing_key_id::validate(&id.value()).is_ok(), "Invalid signing_key_id");
|
||||
|
||||
let output = quote! {
|
||||
<#dollar_crate::ServerSigningKeyId as ::std::convert::TryFrom<&str>>::try_from(#id).unwrap()
|
||||
};
|
||||
|
||||
output.into()
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn user_id(input: TokenStream) -> TokenStream {
|
||||
let Input { dollar_crate, id } = parse_macro_input!(input as Input);
|
||||
|
@ -1,12 +1,12 @@
|
||||
pub mod device_key_id;
|
||||
pub mod error;
|
||||
pub mod event_id;
|
||||
pub mod key_id;
|
||||
pub mod room_alias_id;
|
||||
pub mod room_id;
|
||||
pub mod room_id_or_alias_id;
|
||||
pub mod room_version_id;
|
||||
pub mod server_name;
|
||||
pub mod signing_key_id;
|
||||
pub mod user_id;
|
||||
|
||||
use std::num::NonZeroU8;
|
||||
|
@ -25,6 +25,7 @@ serde = ["serde1", "ruma-identifiers-validation/serde"]
|
||||
|
||||
[dependencies]
|
||||
either = { version = "1.5.3", optional = true }
|
||||
paste = "1.0.3"
|
||||
rand = { version = "0.7.3", optional = true }
|
||||
ruma-identifiers-macros = { version = "=0.17.4", path = "../ruma-identifiers-macros" }
|
||||
ruma-identifiers-validation = { version = "0.1.1", path = "../ruma-identifiers-validation", default-features = false }
|
||||
|
@ -1,146 +0,0 @@
|
||||
//! Matrix device identifiers.
|
||||
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
mem,
|
||||
};
|
||||
|
||||
#[cfg(feature = "rand")]
|
||||
use crate::generate_localpart;
|
||||
|
||||
/// A Matrix device ID.
|
||||
///
|
||||
/// Device identifiers in Matrix are completely opaque character sequences. This type is provided
|
||||
/// simply for its semantic value.
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent, crate = "serde"))]
|
||||
pub struct DeviceId(str);
|
||||
|
||||
/// An owned device identifier.
|
||||
pub type DeviceIdBox = Box<DeviceId>;
|
||||
|
||||
impl DeviceId {
|
||||
#[allow(clippy::transmute_ptr_to_ptr)]
|
||||
fn from_borrowed(s: &str) -> &Self {
|
||||
unsafe { mem::transmute(s) }
|
||||
}
|
||||
|
||||
fn from_owned(s: Box<str>) -> Box<Self> {
|
||||
unsafe { mem::transmute(s) }
|
||||
}
|
||||
|
||||
fn into_owned(self: Box<Self>) -> Box<str> {
|
||||
unsafe { mem::transmute(self) }
|
||||
}
|
||||
|
||||
/// Generates a random `DeviceId`, suitable for assignment to a new device.
|
||||
#[cfg(feature = "rand")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
|
||||
pub fn new() -> Box<Self> {
|
||||
Self::from_owned(generate_localpart(8))
|
||||
}
|
||||
|
||||
/// Creates a string slice from this `DeviceId`.
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Creates a byte slice from this `DeviceId`.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<DeviceId> {
|
||||
fn clone(&self) -> Self {
|
||||
(**self).to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOwned for DeviceId {
|
||||
type Owned = Box<DeviceId>;
|
||||
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
Self::from_owned(self.0.to_owned().into_boxed_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&DeviceId> for Box<DeviceId> {
|
||||
fn from(id: &DeviceId) -> Self {
|
||||
id.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for DeviceId {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for Box<DeviceId> {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for &'a DeviceId {
|
||||
fn from(s: &'a str) -> Self {
|
||||
DeviceId::from_borrowed(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Box<DeviceId> {
|
||||
fn from(s: &str) -> Self {
|
||||
DeviceId::from_owned(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Box<DeviceId> {
|
||||
fn from(s: String) -> Self {
|
||||
DeviceId::from_owned(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<DeviceId>> for String {
|
||||
fn from(id: Box<DeviceId>) -> Self {
|
||||
id.into_owned().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DeviceId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for Box<DeviceId> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
crate::deserialize_id(deserializer, "An IP address or hostname")
|
||||
}
|
||||
}
|
||||
|
||||
partial_eq_string!(DeviceId);
|
||||
partial_eq_string!(Box<DeviceId>);
|
||||
|
||||
/// Generates a random `DeviceId`, suitable for assignment to a new device.
|
||||
#[cfg(feature = "rand")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
|
||||
#[deprecated = "use DeviceId::new instead"]
|
||||
pub fn generate() -> Box<DeviceId> {
|
||||
DeviceId::new()
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "rand"))]
|
||||
mod tests {
|
||||
use super::DeviceId;
|
||||
|
||||
#[test]
|
||||
fn generate_device_id() {
|
||||
assert_eq!(DeviceId::new().as_str().len(), 8);
|
||||
}
|
||||
}
|
199
ruma-identifiers/src/key_id.rs
Normal file
199
ruma-identifiers/src/key_id.rs
Normal file
@ -0,0 +1,199 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
marker::PhantomData,
|
||||
num::NonZeroU8,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use crate::{crypto_algorithms::SigningKeyAlgorithm, DeviceId, Error, KeyName};
|
||||
|
||||
/// A key algorithm and key name delimited by a colon
|
||||
pub struct KeyId<A, K: ?Sized> {
|
||||
full_id: Box<str>,
|
||||
colon_idx: NonZeroU8,
|
||||
_phantom: PhantomData<(A, K)>,
|
||||
}
|
||||
|
||||
impl<A, K: ?Sized> KeyId<A, K> {
|
||||
/// Creates a `KeyId` from an algorithm and key name.
|
||||
pub fn from_parts(algorithm: A, key_name: &K) -> Self
|
||||
where
|
||||
A: AsRef<str>,
|
||||
K: AsRef<str>,
|
||||
{
|
||||
let algorithm = algorithm.as_ref();
|
||||
let key_name = key_name.as_ref();
|
||||
|
||||
let mut res = String::with_capacity(algorithm.len() + 1 + key_name.len());
|
||||
res.push_str(algorithm);
|
||||
res.push(':');
|
||||
res.push_str(key_name);
|
||||
|
||||
let colon_idx =
|
||||
NonZeroU8::new(algorithm.len().try_into().expect("no algorithm name len > 255"))
|
||||
.expect("no empty algorithm name");
|
||||
|
||||
KeyId { full_id: res.into(), colon_idx, _phantom: PhantomData }
|
||||
}
|
||||
|
||||
/// Returns key algorithm of the key ID.
|
||||
pub fn algorithm(&self) -> A
|
||||
where
|
||||
A: FromStr,
|
||||
{
|
||||
A::from_str(&self.full_id[..self.colon_idx.get() as usize])
|
||||
.unwrap_or_else(|_| unreachable!())
|
||||
}
|
||||
|
||||
/// Returns the key name of the key ID.
|
||||
pub fn key_name<'a>(&'a self) -> &'a K
|
||||
where
|
||||
&'a K: From<&'a str>,
|
||||
{
|
||||
self.full_id[self.colon_idx.get() as usize + 1..].into()
|
||||
}
|
||||
}
|
||||
|
||||
fn try_from<S, A, K: ?Sized>(key_identifier: S) -> Result<KeyId<A, K>, Error>
|
||||
where
|
||||
S: AsRef<str> + Into<Box<str>>,
|
||||
{
|
||||
let colon_idx = ruma_identifiers_validation::key_id::validate(key_identifier.as_ref())?;
|
||||
Ok(KeyId { full_id: key_identifier.into(), colon_idx, _phantom: PhantomData })
|
||||
}
|
||||
|
||||
impl<A, K: ?Sized> KeyId<A, K> {
|
||||
/// Creates a string slice from this `KeyId<A, K>`
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.full_id
|
||||
}
|
||||
|
||||
/// Creates a byte slice from this `KeyId<A, K>`
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.full_id.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, K: ?Sized> Clone for KeyId<A, K> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { full_id: self.full_id.clone(), colon_idx: self.colon_idx, _phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, K: ?Sized> AsRef<str> for KeyId<A, K> {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, K: ?Sized> From<KeyId<A, K>> for String {
|
||||
fn from(id: KeyId<A, K>) -> Self {
|
||||
id.full_id.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, K: ?Sized> FromStr for KeyId<A, K> {
|
||||
type Err = crate::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
try_from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, K: ?Sized> TryFrom<&str> for KeyId<A, K> {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
try_from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, K: ?Sized> TryFrom<String> for KeyId<A, K>
|
||||
where
|
||||
A: FromStr,
|
||||
K: From<String>,
|
||||
{
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
try_from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, K: ?Sized> fmt::Debug for KeyId<A, K> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Using struct debug format for consistency with other ID types.
|
||||
// FIXME: Change all ID types to have just a string debug format?
|
||||
f.debug_struct("KeyId")
|
||||
.field("full_id", &self.full_id)
|
||||
.field("colon_idxs", &self.colon_idx)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, K: ?Sized> fmt::Display for KeyId<A, K> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, K: ?Sized> PartialEq for KeyId<A, K> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.as_str() == other.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, K: ?Sized> Eq for KeyId<A, K> {}
|
||||
|
||||
impl<A, K: ?Sized> PartialOrd for KeyId<A, K> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.as_str().partial_cmp(other.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, K: ?Sized> Ord for KeyId<A, K> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.as_str().cmp(other.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, K: ?Sized> Hash for KeyId<A, K> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.as_str().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<A, K: ?Sized> serde::Serialize for KeyId<A, K> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de, A, K: ?Sized> serde::Deserialize<'de> for KeyId<A, K> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
crate::deserialize_id(deserializer, "Key name with algorithm and key identifier")
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
partial_eq_string!(KeyId<A, K> [A, K]);
|
||||
|
||||
/// Algorithm + key name for signing keys.
|
||||
pub type SigningKeyId<K> = KeyId<SigningKeyAlgorithm, K>;
|
||||
|
||||
/// Algorithm + key name for homeserver signing keys.
|
||||
pub type ServerSigningKeyId = SigningKeyId<KeyName>;
|
||||
|
||||
/// Algorithm + key name for device keys.
|
||||
pub type DeviceSigningKeyId = SigningKeyId<DeviceId>;
|
@ -22,15 +22,16 @@ use serde::de::{self, Deserializer, Unexpected};
|
||||
#[doc(inline)]
|
||||
pub use crate::{
|
||||
crypto_algorithms::{DeviceKeyAlgorithm, EventEncryptionAlgorithm, SigningKeyAlgorithm},
|
||||
device_id::{DeviceId, DeviceIdBox},
|
||||
device_key_id::DeviceKeyId,
|
||||
event_id::EventId,
|
||||
key_id::{DeviceSigningKeyId, KeyId, ServerSigningKeyId, SigningKeyId},
|
||||
opaque_ids::{DeviceId, DeviceIdBox, KeyName, KeyNameBox},
|
||||
room_alias_id::RoomAliasId,
|
||||
room_id::RoomId,
|
||||
room_id_or_room_alias_id::RoomIdOrAliasId,
|
||||
room_version_id::RoomVersionId,
|
||||
server_name::{ServerName, ServerNameBox},
|
||||
signing_key_id::ServerSigningKeyId,
|
||||
signatures::{DeviceSignatures, EntitySignatures, ServerSignatures, Signatures},
|
||||
user_id::UserId,
|
||||
};
|
||||
#[doc(inline)]
|
||||
@ -39,18 +40,19 @@ pub use ruma_identifiers_validation::error::Error;
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
pub mod device_id;
|
||||
pub mod user_id;
|
||||
|
||||
mod crypto_algorithms;
|
||||
mod device_key_id;
|
||||
mod event_id;
|
||||
mod key_id;
|
||||
mod opaque_ids;
|
||||
mod room_alias_id;
|
||||
mod room_id;
|
||||
mod room_id_or_room_alias_id;
|
||||
mod room_version_id;
|
||||
mod server_name;
|
||||
mod signing_key_id;
|
||||
mod signatures;
|
||||
|
||||
/// Check whether a given string is a valid server name according to [the specification][].
|
||||
///
|
||||
@ -138,9 +140,18 @@ macro_rules! room_version_id {
|
||||
|
||||
/// Compile-time checked `ServerSigningKeyId` construction.
|
||||
#[macro_export]
|
||||
#[deprecated = "use server_signing_key_id!()"]
|
||||
macro_rules! server_key_id {
|
||||
($s:literal) => {
|
||||
$crate::_macros::server_key_id!($crate, $s)
|
||||
$crate::_macros::server_signing_key_id!($crate, $s)
|
||||
};
|
||||
}
|
||||
|
||||
/// Compile-time checked `ServerSigningKeyId` construction.
|
||||
#[macro_export]
|
||||
macro_rules! server_signing_key_id {
|
||||
($s:literal) => {
|
||||
$crate::_macros::server_signing_key_id!($crate, $s)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -6,16 +6,16 @@ macro_rules! doc_concat {
|
||||
}
|
||||
|
||||
macro_rules! partial_eq_string {
|
||||
($id:ty) => {
|
||||
partial_eq_string!(@imp, $id, str);
|
||||
partial_eq_string!(@imp, $id, &str);
|
||||
partial_eq_string!(@imp, $id, String);
|
||||
partial_eq_string!(@imp, str, $id);
|
||||
partial_eq_string!(@imp, &str, $id);
|
||||
partial_eq_string!(@imp, String, $id);
|
||||
($id:ty $([$( $g:ident ),*])?) => {
|
||||
partial_eq_string!(@imp $(<$($g),*>)?, $id, str);
|
||||
partial_eq_string!(@imp $(<$($g),*>)?, $id, &str);
|
||||
partial_eq_string!(@imp $(<$($g),*>)?, $id, String);
|
||||
partial_eq_string!(@imp $(<$($g),*>)?, str, $id);
|
||||
partial_eq_string!(@imp $(<$($g),*>)?, &str, $id);
|
||||
partial_eq_string!(@imp $(<$($g),*>)?, String, $id);
|
||||
};
|
||||
(@imp, $l:ty, $r:ty) => {
|
||||
impl ::std::cmp::PartialEq<$r> for $l {
|
||||
(@imp $(<$( $g:ident ),*>)?, $l:ty, $r:ty) => {
|
||||
impl $(<$($g),*>)? ::std::cmp::PartialEq<$r> for $l {
|
||||
fn eq(&self, other: &$r) -> bool {
|
||||
::std::convert::AsRef::<str>::as_ref(self)
|
||||
== ::std::convert::AsRef::<str>::as_ref(other)
|
||||
@ -24,60 +24,14 @@ macro_rules! partial_eq_string {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! common_impls {
|
||||
($id:ty, $try_from:ident, $desc:literal) => {
|
||||
impl $id {
|
||||
doc_concat! {
|
||||
#[doc = concat!("Creates a string slice from this `", stringify!($id), "`")]
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.full_id
|
||||
}
|
||||
}
|
||||
|
||||
doc_concat! {
|
||||
#[doc = concat!("Creates a byte slice from this `", stringify!($id), "`")]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.full_id.as_bytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! as_str_based_impls {
|
||||
($id:ty) => {
|
||||
impl ::std::convert::AsRef<str> for $id {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::From<$id> for ::std::string::String {
|
||||
fn from(id: $id) -> Self {
|
||||
id.full_id.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::str::FromStr for $id {
|
||||
type Err = crate::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
$try_from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::TryFrom<&str> for $id {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
$try_from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::TryFrom<String> for $id {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
$try_from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::fmt::Display for $id {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
@ -119,6 +73,56 @@ macro_rules! common_impls {
|
||||
serializer.serialize_str(self.as_str())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! common_impls {
|
||||
($id:ty, $try_from:ident, $desc:literal) => {
|
||||
impl $id {
|
||||
doc_concat! {
|
||||
#[doc = concat!("Creates a string slice from this `", stringify!($id), "`")]
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.full_id
|
||||
}
|
||||
}
|
||||
|
||||
doc_concat! {
|
||||
#[doc = concat!("Creates a byte slice from this `", stringify!($id), "`")]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.full_id.as_bytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::From<$id> for ::std::string::String {
|
||||
fn from(id: $id) -> Self {
|
||||
id.full_id.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::str::FromStr for $id {
|
||||
type Err = crate::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
$try_from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::TryFrom<&str> for $id {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
$try_from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::TryFrom<String> for $id {
|
||||
type Error = crate::Error;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
$try_from(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> ::serde::Deserialize<'de> for $id {
|
||||
@ -130,6 +134,119 @@ macro_rules! common_impls {
|
||||
}
|
||||
}
|
||||
|
||||
as_str_based_impls!($id);
|
||||
partial_eq_string!($id);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! opaque_identifier {
|
||||
(
|
||||
$( #[doc = $docs:literal] )*
|
||||
$vis:vis type $id:ident;
|
||||
) => {
|
||||
$( #[doc = $docs] )*
|
||||
#[repr(transparent)]
|
||||
#[derive(::std::fmt::Debug)]
|
||||
pub struct $id(str);
|
||||
|
||||
::paste::paste! {
|
||||
doc_concat! {
|
||||
#[doc = concat!("An owned [", stringify!($id), "].")]
|
||||
pub type [<$id Box>] = ::std::boxed::Box<$id>;
|
||||
}
|
||||
}
|
||||
|
||||
impl $id {
|
||||
#[allow(clippy::transmute_ptr_to_ptr)]
|
||||
fn from_borrowed(s: &str) -> &Self {
|
||||
unsafe { ::std::mem::transmute(s) }
|
||||
}
|
||||
|
||||
pub(super) fn from_owned(s: ::std::boxed::Box<str>) -> ::std::boxed::Box<Self> {
|
||||
unsafe { ::std::mem::transmute(s) }
|
||||
}
|
||||
|
||||
fn into_owned(self: ::std::boxed::Box<Self>) -> ::std::boxed::Box<str> {
|
||||
unsafe { ::std::mem::transmute(self) }
|
||||
}
|
||||
|
||||
doc_concat! {
|
||||
#[doc = concat!("Creates a string slice from this `", stringify!($id), "`.")]
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
doc_concat! {
|
||||
#[doc = concat!("Creates a byte slice from this `", stringify!($id), "`.")]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
self.0.as_bytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ::std::boxed::Box<$id> {
|
||||
fn clone(&self) -> Self {
|
||||
(**self).to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToOwned for $id {
|
||||
type Owned = ::std::boxed::Box<$id>;
|
||||
|
||||
fn to_owned(&self) -> Self::Owned {
|
||||
Self::from_owned(self.0.to_owned().into_boxed_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&$id> for ::std::boxed::Box<$id> {
|
||||
fn from(id: &$id) -> Self {
|
||||
id.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for ::std::boxed::Box<$id> {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for &'a $id {
|
||||
fn from(s: &'a str) -> Self {
|
||||
$id::from_borrowed(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ::std::boxed::Box<$id> {
|
||||
fn from(s: &str) -> Self {
|
||||
$id::from_owned(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ::std::boxed::Box<$id> {
|
||||
fn from(s: String) -> Self {
|
||||
$id::from_owned(s.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<::std::boxed::Box<$id>> for String {
|
||||
fn from(id: ::std::boxed::Box<$id>) -> Self {
|
||||
id.into_owned().into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> ::serde::Deserialize<'de> for ::std::boxed::Box<$id> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: ::serde::Deserializer<'de>,
|
||||
{
|
||||
::std::boxed::Box::<str>::deserialize(deserializer).map($id::from_owned)
|
||||
}
|
||||
}
|
||||
|
||||
as_str_based_impls!($id);
|
||||
partial_eq_string!($id);
|
||||
partial_eq_string!(::std::boxed::Box<$id>);
|
||||
};
|
||||
}
|
||||
|
39
ruma-identifiers/src/opaque_ids.rs
Normal file
39
ruma-identifiers/src/opaque_ids.rs
Normal file
@ -0,0 +1,39 @@
|
||||
//! Matrix device identifiers.
|
||||
|
||||
#[cfg(feature = "rand")]
|
||||
use crate::generate_localpart;
|
||||
|
||||
opaque_identifier! {
|
||||
/// A Matrix key ID.
|
||||
///
|
||||
/// Device identifiers in Matrix are completely opaque character sequences. This type is
|
||||
/// provided simply for its semantic value.
|
||||
pub type DeviceId;
|
||||
}
|
||||
|
||||
impl DeviceId {
|
||||
/// Generates a random `DeviceId`, suitable for assignment to a new device.
|
||||
#[cfg(feature = "rand")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
|
||||
pub fn new() -> Box<Self> {
|
||||
Self::from_owned(generate_localpart(8))
|
||||
}
|
||||
}
|
||||
|
||||
opaque_identifier! {
|
||||
/// A Matrix key identifier.
|
||||
///
|
||||
/// Key identifiers in Matrix are opaque character sequences of `[a-zA-Z_]`. This type is provided
|
||||
/// simply for its semantic value.
|
||||
pub type KeyName;
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "rand"))]
|
||||
mod tests {
|
||||
use super::DeviceId;
|
||||
|
||||
#[test]
|
||||
fn generate_device_id() {
|
||||
assert_eq!(DeviceId::new().as_str().len(), 8);
|
||||
}
|
||||
}
|
38
ruma-identifiers/src/signatures.rs
Normal file
38
ruma-identifiers/src/signatures.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::{DeviceIdBox, KeyNameBox, ServerNameBox, SigningKeyId, UserId};
|
||||
|
||||
/// Map of key identifier to signature values.
|
||||
pub type EntitySignatures<K> = BTreeMap<SigningKeyId<K>, String>;
|
||||
|
||||
/// Map of all signatures, grouped by entity
|
||||
///
|
||||
/// ```
|
||||
/// let key_identifier = KeyId::from_parts(SigningKeyAlgorithm::Ed25519, "1");
|
||||
/// let mut signatures = Signatures::new();
|
||||
/// let server_name = server_name!("example.org");
|
||||
/// let signature = "YbJva03ihSj5mPk+CHMJKUKlCXCPFXjXOK6VqBnN9nA2evksQcTGn6hwQfrgRHIDDXO2le49x7jnWJHMJrJoBQ";
|
||||
/// signatures.add(server_name, key_identifier, signature);
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Signatures<E: Ord, K>(BTreeMap<E, EntitySignatures<K>>);
|
||||
|
||||
impl<E: Ord, K: Ord> Signatures<E, K> {
|
||||
/// Add a signature for the given server name and key identifier.
|
||||
///
|
||||
/// If there was already one, it is returned.
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
entity: E,
|
||||
key_identifier: SigningKeyId<K>,
|
||||
value: String,
|
||||
) -> Option<String> {
|
||||
self.0.entry(entity).or_insert_with(Default::default).insert(key_identifier, value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Map of server signatures for an event, grouped by server.
|
||||
pub type ServerSignatures = Signatures<ServerNameBox, KeyNameBox>;
|
||||
|
||||
/// Map of device signatures for an event, grouped by user.
|
||||
pub type DeviceSignatures = Signatures<UserId, DeviceIdBox>;
|
@ -1,99 +0,0 @@
|
||||
//! Identifiers for homeserver signing keys used for federation.
|
||||
|
||||
use std::{convert::TryInto, num::NonZeroU8, str::FromStr};
|
||||
|
||||
use crate::{crypto_algorithms::SigningKeyAlgorithm, Error};
|
||||
|
||||
/// Key identifiers used for homeserver signing keys.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ServerSigningKeyId {
|
||||
full_id: Box<str>,
|
||||
colon_idx: NonZeroU8,
|
||||
}
|
||||
|
||||
impl ServerSigningKeyId {
|
||||
/// Create a `ServerSigningKeyId` from a `SigningKeyAlgorithm` and a `ServerId`.
|
||||
pub fn from_parts(algorithm: SigningKeyAlgorithm, version: &str) -> Self {
|
||||
let algorithm: &str = algorithm.as_ref();
|
||||
|
||||
let mut res = String::with_capacity(algorithm.len() + 1 + version.len());
|
||||
res.push_str(algorithm);
|
||||
res.push(':');
|
||||
res.push_str(version);
|
||||
|
||||
let colon_idx =
|
||||
NonZeroU8::new(algorithm.len().try_into().expect("no algorithm name len > 255"))
|
||||
.expect("no empty algorithm name");
|
||||
|
||||
ServerSigningKeyId { full_id: res.into(), colon_idx }
|
||||
}
|
||||
|
||||
/// Returns key algorithm of the server key ID.
|
||||
pub fn algorithm(&self) -> SigningKeyAlgorithm {
|
||||
SigningKeyAlgorithm::from_str(&self.full_id[..self.colon_idx.get() as usize]).unwrap()
|
||||
}
|
||||
|
||||
/// Returns the version of the server key ID.
|
||||
pub fn version(&self) -> &str {
|
||||
&self.full_id[self.colon_idx.get() as usize + 1..]
|
||||
}
|
||||
}
|
||||
|
||||
fn try_from<S>(key_id: S) -> Result<ServerSigningKeyId, Error>
|
||||
where
|
||||
S: AsRef<str> + Into<Box<str>>,
|
||||
{
|
||||
let colon_idx = ruma_identifiers_validation::signing_key_id::validate(key_id.as_ref())?;
|
||||
Ok(ServerSigningKeyId { full_id: key_id.into(), colon_idx })
|
||||
}
|
||||
|
||||
common_impls!(ServerSigningKeyId, try_from, "Key ID with algorithm and version");
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use crate::crypto_algorithms::SigningKeyAlgorithm;
|
||||
use crate::{Error, ServerSigningKeyId};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[test]
|
||||
fn deserialize_id() {
|
||||
let server_key_id: ServerSigningKeyId = from_json_value(json!("ed25519:Abc_1")).unwrap();
|
||||
assert_eq!(server_key_id.algorithm(), SigningKeyAlgorithm::Ed25519);
|
||||
assert_eq!(server_key_id.version(), "Abc_1");
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[test]
|
||||
fn serialize_id() {
|
||||
let server_key_id: ServerSigningKeyId =
|
||||
ServerSigningKeyId::try_from("ed25519:abc123").unwrap();
|
||||
assert_eq!(to_json_value(&server_key_id).unwrap(), json!("ed25519:abc123"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_version_characters() {
|
||||
assert_eq!(
|
||||
ServerSigningKeyId::try_from("ed25519:Abc-1").unwrap_err(),
|
||||
Error::InvalidCharacters
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_key_algorithm() {
|
||||
assert_eq!(ServerSigningKeyId::try_from(":Abc-1").unwrap_err(), Error::InvalidKeyAlgorithm,);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_delimiter() {
|
||||
assert_eq!(
|
||||
ServerSigningKeyId::try_from("ed25519|Abc_1").unwrap_err(),
|
||||
Error::MissingDelimiter,
|
||||
);
|
||||
}
|
||||
}
|
@ -26,10 +26,12 @@ pub use ruma_serde as serde;
|
||||
|
||||
pub use ruma_serde::Outgoing;
|
||||
|
||||
#[allow(deprecated)] // Allow re-export of deprecated items
|
||||
pub use ruma_identifiers::{
|
||||
device_id, device_key_id, event_id, room_alias_id, room_id, room_version_id, server_key_id,
|
||||
server_name, user_id, DeviceId, DeviceKeyAlgorithm, DeviceKeyId, EventId, RoomAliasId, RoomId,
|
||||
RoomIdOrAliasId, RoomVersionId, ServerName, ServerSigningKeyId, SigningKeyAlgorithm, UserId,
|
||||
server_name, server_signing_key_id, user_id, DeviceId, DeviceKeyAlgorithm, DeviceKeyId,
|
||||
EventId, RoomAliasId, RoomId, RoomIdOrAliasId, RoomVersionId, ServerName, ServerSigningKeyId,
|
||||
SigningKeyAlgorithm, UserId,
|
||||
};
|
||||
|
||||
#[cfg(feature = "events")]
|
||||
|
Loading…
x
Reference in New Issue
Block a user