core_crypto/mls/session/
id.rs1use std::{
2 borrow::{Borrow, Cow},
3 fmt,
4 ops::Deref,
5};
6
7use uuid::Uuid;
8use wire_e2e_identity::E2eiClientId;
9
10use super::error::{Error, Result};
11use crate::HISTORY_CLIENT_ID_PREFIX;
12
13#[derive(
19 core_crypto_macros::Debug, Clone, Eq, PartialOrd, Ord, Hash, derive_more::Into, serde::Serialize, serde::Deserialize,
20)]
21#[sensitive]
22pub struct ClientId(Vec<u8>);
23
24pub struct DeserializedClientId {
25 pub user_id: Uuid,
26 pub device_id: u64,
27 pub domain: String,
28}
29
30impl ClientId {
31 pub const DELIMITER: &'static str = ":";
33 pub const DOMAIN_SEPERATOR: &'static str = "@";
35
36 pub fn new(user_id: Uuid, device_id: u64, domain: &str) -> Self {
38 let string = format!(
39 "{user_id}{delimiter}{device_id:x}{seperator}{domain}",
40 user_id = user_id.hyphenated(),
41 delimiter = Self::DELIMITER,
42 seperator = Self::DOMAIN_SEPERATOR
43 );
44 let bytes = string.into_bytes();
45 #[cfg(debug_assertions)]
46 Self::try_parse_bytes(&bytes).expect("client id format string is correct");
47 Self(bytes)
48 }
49
50 pub(crate) fn try_from_str_with_base64_user_id(str_with_base64_user_id: &str) -> Result<Self> {
52 E2eiClientId::try_from_qualified(str_with_base64_user_id)
53 .map(ClientId::from_e2ei_client_id)
54 .map_err(|_| Error::InvalidQualifiedClientId)
55 }
56
57 fn from_e2ei_client_id(e2ei_client_id: E2eiClientId) -> Self {
58 Self::new(e2ei_client_id.user_id, e2ei_client_id.device_id, &e2ei_client_id.domain)
59 }
60
61 pub(crate) fn new_ephemeral() -> Self {
62 let user_id = Uuid::new_v4().hyphenated().to_string();
63 let bytes = format!("{HISTORY_CLIENT_ID_PREFIX}{}{user_id}", Self::DELIMITER).into_bytes();
64 Self(bytes)
65 }
66
67 pub fn deserialize(&self) -> DeserializedClientId {
69 let (user_id, device_id, domain) = Self::try_parse_bytes(&self.0).expect("We just invert initialization");
70
71 DeserializedClientId {
72 user_id,
73 device_id,
74 domain,
75 }
76 }
77
78 pub(crate) fn new_from_bytes(bytes: Vec<u8>) -> Result<Self> {
79 Self::try_parse_bytes(&bytes)?;
80 Ok(Self(bytes))
81 }
82
83 fn try_parse_bytes(bytes: &[u8]) -> Result<(Uuid, u64, String)> {
85 let client_id = std::str::from_utf8(bytes).map_err(|_| Error::InvalidQualifiedClientId)?;
86 let (user_id, rest) = client_id
87 .split_once(Self::DELIMITER)
88 .ok_or(Error::InvalidQualifiedClientId)?;
89 let user_id = Self::parse_user_id(user_id)?;
90 let (device_id, domain) = rest
91 .split_once(Self::DOMAIN_SEPERATOR)
92 .ok_or(Error::InvalidQualifiedClientId)?;
93 let device_id = Self::parse_device_id(device_id)?;
94 Ok((user_id, device_id, domain.to_owned()))
95 }
96
97 fn parse_user_id(user_id: &str) -> Result<Uuid> {
99 Uuid::try_parse(user_id).map_err(|_| Error::InvalidQualifiedClientId)
100 }
101
102 fn parse_device_id(device_id: &str) -> Result<u64> {
103 u64::from_str_radix(device_id, 16).map_err(|_| Error::InvalidQualifiedClientId)
104 }
105
106 #[cfg(test)]
107 pub(crate) fn as_bytes(&self) -> &[u8] {
108 &self.0
109 }
110
111 pub fn as_e2ei_client_id(&self) -> E2eiClientId {
113 let (user_id, device_id, domain) = Self::try_parse_bytes(&self.0).expect("We just invert initialization");
114 E2eiClientId::try_new(user_id.to_string(), device_id, &domain).expect("We just invert intialization")
115 }
116
117 pub(crate) fn into_inner(self) -> Vec<u8> {
118 self.0
119 }
120}
121
122impl TryFrom<&[u8]> for ClientId {
123 type Error = Error;
124
125 fn try_from(value: &[u8]) -> Result<Self> {
126 Self::try_parse_bytes(value)?;
127 Ok(Self(value.into()))
128 }
129}
130
131impl Deref for ClientId {
132 type Target = ClientIdRef;
133
134 fn deref(&self) -> &Self::Target {
135 ClientIdRef::new(&self.0)
136 }
137}
138
139impl AsRef<[u8]> for ClientId {
140 fn as_ref(&self) -> &[u8] {
141 &self.0
142 }
143}
144
145impl AsRef<ClientIdRef> for ClientId {
146 fn as_ref(&self) -> &ClientIdRef {
147 ClientIdRef::new(&self.0)
148 }
149}
150
151impl std::fmt::Display for ClientId {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153 write!(f, "{}", hex::encode(self.0.as_slice()))
154 }
155}
156
157impl<T> PartialEq<T> for ClientId
158where
159 ClientIdRef: PartialEq<T>,
160{
161 fn eq(&self, other: &T) -> bool {
162 (**self).eq(other)
163 }
164}
165
166#[cfg(test)]
168impl From<&str> for ClientId {
169 fn from(value: &str) -> Self {
170 Self(value.as_bytes().into())
171 }
172}
173
174#[repr(transparent)]
180#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, derive_more::Deref)]
181pub struct ClientIdRef([u8]);
182
183impl ClientIdRef {
184 pub fn new<Bytes>(bytes: &Bytes) -> &ClientIdRef
186 where
187 Bytes: AsRef<[u8]> + ?Sized,
188 {
189 unsafe { &*(bytes.as_ref() as *const [u8] as *const ClientIdRef) }
192 }
193
194 pub fn as_slice(&self) -> &[u8] {
196 self.as_ref()
197 }
198}
199
200impl<'a> From<&'a [u8]> for &'a ClientIdRef {
201 fn from(value: &'a [u8]) -> Self {
202 ClientIdRef::new(value)
203 }
204}
205
206impl<'a> From<&'a Vec<u8>> for &'a ClientIdRef {
207 fn from(value: &'a Vec<u8>) -> Self {
208 ClientIdRef::new(value.as_slice())
209 }
210}
211
212impl Borrow<ClientIdRef> for ClientId {
213 fn borrow(&self) -> &ClientIdRef {
214 ClientIdRef::new(&self.0)
215 }
216}
217
218impl Borrow<ClientIdRef> for &'_ ClientId {
219 fn borrow(&self) -> &ClientIdRef {
220 ClientIdRef::new(&*self.0)
221 }
222}
223
224impl ToOwned for ClientIdRef {
225 type Owned = ClientId;
226
227 fn to_owned(&self) -> Self::Owned {
228 ClientId(self.0.to_owned())
229 }
230}
231
232impl AsRef<[u8]> for ClientIdRef {
233 fn as_ref(&self) -> &[u8] {
234 &self.0
235 }
236}
237
238impl<'a> From<&'a ClientIdRef> for Cow<'a, [u8]> {
239 fn from(value: &'a ClientIdRef) -> Self {
240 Cow::Borrowed(value.as_ref())
241 }
242}
243
244impl PartialEq<ClientId> for ClientIdRef {
245 fn eq(&self, other: &ClientId) -> bool {
246 &self.0 == other.as_slice()
247 }
248}
249
250impl PartialEq<[u8]> for ClientIdRef {
251 fn eq(&self, other: &[u8]) -> bool {
252 &self.0 == other
253 }
254}
255
256impl PartialEq<&'_ [u8]> for ClientIdRef {
257 fn eq(&self, other: &&'_ [u8]) -> bool {
258 &self.0 == *other
259 }
260}
261
262impl std::fmt::Display for ClientIdRef {
263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264 write!(f, "{}", hex::encode(&self.0))
265 }
266}
267
268macro_rules! impl_eq {
269 ($( $t:ty => |$self:ident, $other:ident| $impl:expr ; )+) => {
270 $(
271 impl PartialEq<$t> for ClientIdRef {
272 fn eq(&self, other: &$t) -> bool {
273 let $self = self;
274 let $other = other;
275 $impl
276 }
277 }
278
279 impl PartialEq<ClientIdRef> for $t {
280 fn eq(&self, other: &ClientIdRef) -> bool {
281 other.eq(self)
282 }
283 }
284
285 impl PartialEq<$t> for &'_ ClientIdRef {
286 fn eq(&self, other: &$t) -> bool {
287 let $self = self;
288 let $other = other;
289 $impl
290 }
291 }
292
293 impl PartialEq<&'_ ClientIdRef> for $t {
294 fn eq(&self, other: &&'_ ClientIdRef) -> bool {
295 other.eq(self)
296 }
297 }
298 )+
299 };
300}
301
302impl_eq!(
303 Vec<u8> => |me, other| me.0.eq(other.as_slice());
304 Cow<'_, ClientIdRef> => |me, other| me.eq(&other.as_slice());
305);
306
307impl fmt::Debug for ClientIdRef {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 f.debug_tuple("ClientIdRef")
316 .field(&obfuscate::Obfuscated::from(&self.0))
317 .finish()
318 }
319}
320
321type LegacyClientId = wire_e2e_identity::legacy::id::ClientId;
322
323impl From<ClientId> for LegacyClientId {
324 fn from(value: ClientId) -> Self {
325 Self::from(value.0)
326 }
327}
328#[cfg(test)]
329impl ClientId {
330 pub(crate) fn as_user_id(&self) -> Uuid {
331 self.deserialize().user_id
332 }
333
334 pub(crate) fn with_user(&self) -> (ClientId, Uuid) {
335 (self.clone(), self.as_user_id())
336 }
337}