core_crypto/e2e_identity/
id.rs1use 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#[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
34impl<'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#[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
110impl<'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}