core_crypto/e2e_identity/
id.rs

1use crate::{prelude::ClientId, CryptoError, CryptoResult};
2use base64::Engine;
3
4#[cfg(test)]
5const DOMAIN: &str = "wire.com";
6const COLON: u8 = 58;
7
8/// This format: 'bd4c7053-1c5a-4020-9559-cd7bf7961954:4959bc6ab12f2846@wire.com'
9#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::Into, derive_more::Deref)]
10pub struct WireQualifiedClientId(ClientId);
11
12#[cfg(test)]
13impl WireQualifiedClientId {
14    pub fn get_user_id(&self) -> String {
15        let mut split = self.0.split(|b| b == &COLON);
16        let user_id = split.next().unwrap();
17        uuid::Uuid::try_parse_ascii(user_id).unwrap().to_string()
18    }
19
20    pub fn generate() -> Self {
21        let user_id = uuid::Uuid::new_v4().to_string();
22        let device_id = rand::random::<u64>();
23        let client_id = format!("{user_id}:{device_id:x}@{DOMAIN}");
24        Self(client_id.into_bytes().into())
25    }
26}
27
28/// e.g. from 'vUxwUxxaQCCVWc1795YZVA:4959bc6ab12f2846@wire.com'
29impl<'a> TryFrom<&'a [u8]> for WireQualifiedClientId {
30    type Error = CryptoError;
31
32    fn try_from(bytes: &'a [u8]) -> CryptoResult<Self> {
33        const COLON: u8 = 58;
34        let mut split = bytes.split(|b| b == &COLON);
35        let user_id = split.next().ok_or(CryptoError::InvalidClientId)?;
36
37        let user_id = base64::prelude::BASE64_URL_SAFE_NO_PAD
38            .decode(user_id)
39            .map_err(|_| CryptoError::InvalidClientId)?;
40
41        let user_id = uuid::Uuid::from_slice(&user_id).map_err(|_| CryptoError::InvalidClientId)?;
42        let mut buf = [0; uuid::fmt::Hyphenated::LENGTH];
43        let user_id = user_id.hyphenated().encode_lower(&mut buf);
44
45        let rest = split.next().ok_or(CryptoError::InvalidClientId)?;
46        if split.next().is_some() {
47            return Err(CryptoError::InvalidClientId);
48        }
49
50        let client_id = [user_id.as_bytes(), &[COLON], rest].concat();
51        Ok(Self(client_id.into()))
52    }
53}
54
55impl std::str::FromStr for WireQualifiedClientId {
56    type Err = CryptoError;
57
58    fn from_str(s: &str) -> CryptoResult<Self> {
59        s.as_bytes().try_into()
60    }
61}
62
63impl TryFrom<WireQualifiedClientId> for String {
64    type Error = CryptoError;
65
66    fn try_from(id: WireQualifiedClientId) -> CryptoResult<Self> {
67        Ok(String::from_utf8(id.to_vec())?)
68    }
69}
70
71/// This format: 'vUxwUxxaQCCVWc1795YZVA:4959bc6ab12f2846@wire.com'
72#[derive(Debug, Clone, PartialEq, Eq, Hash, derive_more::From, derive_more::Into, derive_more::Deref)]
73pub struct QualifiedE2eiClientId(ClientId);
74
75#[cfg(test)]
76impl QualifiedE2eiClientId {
77    pub fn generate() -> Self {
78        Self::generate_from_user_id(&uuid::Uuid::new_v4())
79    }
80
81    pub fn generate_with_domain(domain: &str) -> Self {
82        Self::generate_from_user_id_and_domain(&uuid::Uuid::new_v4(), domain)
83    }
84
85    pub fn generate_from_user_id(user_id: &uuid::Uuid) -> Self {
86        Self::generate_from_user_id_and_domain(user_id, DOMAIN)
87    }
88
89    pub fn generate_from_user_id_and_domain(user_id: &uuid::Uuid, domain: &str) -> Self {
90        use base64::Engine as _;
91
92        let user_id = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(user_id.as_bytes());
93
94        let device_id = rand::random::<u64>();
95        let client_id = format!("{user_id}:{device_id:x}@{domain}");
96        Self(client_id.into_bytes().into())
97    }
98
99    pub fn from_str_unchecked(s: &str) -> Self {
100        Self(s.as_bytes().into())
101    }
102}
103
104/// e.g. from 'bd4c7053-1c5a-4020-9559-cd7bf7961954:4959bc6ab12f2846@wire.com'
105impl<'a> TryFrom<&'a [u8]> for QualifiedE2eiClientId {
106    type Error = CryptoError;
107
108    fn try_from(bytes: &'a [u8]) -> CryptoResult<Self> {
109        let mut split = bytes.split(|b| b == &COLON);
110        let user_id = split.next().ok_or(CryptoError::InvalidClientId)?;
111
112        let user_id = std::str::from_utf8(user_id)?
113            .parse::<uuid::Uuid>()
114            .map_err(|_| CryptoError::InvalidClientId)?;
115
116        let user_id = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(user_id.as_bytes());
117
118        let rest = split.next().ok_or(CryptoError::InvalidClientId)?;
119        if split.next().is_some() {
120            return Err(CryptoError::InvalidClientId);
121        }
122
123        let client_id = [user_id.as_bytes(), &[COLON], rest].concat();
124        Ok(Self(client_id.into()))
125    }
126}
127
128#[cfg(test)]
129impl std::str::FromStr for QualifiedE2eiClientId {
130    type Err = CryptoError;
131
132    fn from_str(s: &str) -> CryptoResult<Self> {
133        s.as_bytes().try_into()
134    }
135}
136
137impl TryFrom<QualifiedE2eiClientId> for String {
138    type Error = CryptoError;
139
140    fn try_from(id: QualifiedE2eiClientId) -> CryptoResult<Self> {
141        Ok(String::from_utf8(id.to_vec())?)
142    }
143}