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