core_crypto/e2e_identity/
id.rs

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