wire_e2e_identity/legacy/
id.rs

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