use std::{
cmp::Ordering,
hash::{Hash, Hasher},
marker::PhantomData,
};
use ruma_macros::IdZst;
use super::{
crypto_algorithms::SigningKeyAlgorithm, Base64PublicKey, Base64PublicKeyOrDeviceId, DeviceId,
DeviceKeyAlgorithm, KeyName, OneTimeKeyAlgorithm, OneTimeKeyName, ServerSigningKeyVersion,
};
#[repr(transparent)]
#[derive(IdZst)]
#[ruma_id(
validate = ruma_identifiers_validation::key_id::validate::<K>,
)]
pub struct KeyId<A: KeyAlgorithm, K: KeyName + ?Sized>(PhantomData<(A, K)>, str);
impl<A: KeyAlgorithm, K: KeyName + ?Sized> KeyId<A, K> {
pub fn from_parts(algorithm: A, key_name: &K) -> OwnedKeyId<A, K> {
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);
Self::from_borrowed(&res).to_owned()
}
pub fn algorithm(&self) -> A {
A::from(&self.as_str()[..self.colon_idx()])
}
pub fn key_name<'a>(&'a self) -> &'a K
where
&'a K: TryFrom<&'a str>,
{
<&'a K>::try_from(&self.as_str()[(self.colon_idx() + 1)..])
.unwrap_or_else(|_| unreachable!())
}
fn colon_idx(&self) -> usize {
self.as_str().find(':').unwrap()
}
}
pub type SigningKeyId<K> = KeyId<SigningKeyAlgorithm, K>;
pub type OwnedSigningKeyId<K> = OwnedKeyId<SigningKeyAlgorithm, K>;
pub type ServerSigningKeyId = SigningKeyId<ServerSigningKeyVersion>;
pub type OwnedServerSigningKeyId = OwnedSigningKeyId<ServerSigningKeyVersion>;
pub type DeviceSigningKeyId = SigningKeyId<DeviceId>;
pub type OwnedDeviceSigningKeyId = OwnedSigningKeyId<DeviceId>;
pub type CrossSigningKeyId = SigningKeyId<Base64PublicKey>;
pub type OwnedCrossSigningKeyId = OwnedSigningKeyId<Base64PublicKey>;
pub type CrossSigningOrDeviceSigningKeyId = SigningKeyId<Base64PublicKeyOrDeviceId>;
pub type OwnedCrossSigningOrDeviceSigningKeyId = OwnedSigningKeyId<Base64PublicKeyOrDeviceId>;
pub type DeviceKeyId = KeyId<DeviceKeyAlgorithm, DeviceId>;
pub type OwnedDeviceKeyId = OwnedKeyId<DeviceKeyAlgorithm, DeviceId>;
pub type OneTimeKeyId = KeyId<OneTimeKeyAlgorithm, OneTimeKeyName>;
pub type OwnedOneTimeKeyId = OwnedKeyId<OneTimeKeyAlgorithm, OneTimeKeyName>;
impl<A: KeyAlgorithm, K: KeyName + ?Sized> PartialEq for KeyId<A, K> {
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl<A: KeyAlgorithm, K: KeyName + ?Sized> Eq for KeyId<A, K> {}
impl<A: KeyAlgorithm, K: KeyName + ?Sized> PartialOrd for KeyId<A, K> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<A: KeyAlgorithm, K: KeyName + ?Sized> Ord for KeyId<A, K> {
fn cmp(&self, other: &Self) -> Ordering {
Ord::cmp(self.as_str(), other.as_str())
}
}
impl<A: KeyAlgorithm, K: KeyName + ?Sized> Hash for KeyId<A, K> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
pub trait KeyAlgorithm: for<'a> From<&'a str> + AsRef<str> {}
impl KeyAlgorithm for SigningKeyAlgorithm {}
impl KeyAlgorithm for DeviceKeyAlgorithm {}
impl KeyAlgorithm for OneTimeKeyAlgorithm {}
#[cfg(test)]
mod tests {
use assert_matches2::assert_matches;
use ruma_identifiers_validation::Error;
use super::DeviceKeyId;
#[test]
fn algorithm_and_key_name_are_correctly_extracted() {
let key_id = DeviceKeyId::parse("ed25519:MYDEVICE").expect("Should parse correctly");
assert_eq!(key_id.algorithm().as_str(), "ed25519");
assert_eq!(key_id.key_name(), "MYDEVICE");
}
#[test]
fn empty_key_name_is_correctly_extracted() {
let key_id = DeviceKeyId::parse("ed25519:").expect("Should parse correctly");
assert_eq!(key_id.algorithm().as_str(), "ed25519");
assert_eq!(key_id.key_name(), "");
}
#[test]
fn missing_colon_fails_to_parse() {
let error = DeviceKeyId::parse("ed25519_MYDEVICE").expect_err("Should fail to parse");
assert_matches!(error, Error::MissingColon);
}
#[test]
fn empty_algorithm_fails_to_parse() {
let error = DeviceKeyId::parse(":MYDEVICE").expect_err("Should fail to parse");
assert_matches!(error, Error::MissingColon);
}
}