1use 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 pub fn encode_device_id(device_id: u64) -> String {
48 format!("{device_id:x}")
49 }
50
51 pub fn new(user_id: Uuid, device_id: u64, domain: &str) -> Self {
53 let string = format!(
54 "{user_id}{delimiter}{device_id}{seperator}{domain}",
55 user_id = user_id.hyphenated(),
56 delimiter = Self::DELIMITER,
57 device_id = Self::encode_device_id(device_id),
58 seperator = Self::DOMAIN_SEPERATOR
59 );
60 let bytes = string.into_bytes();
61 #[cfg(debug_assertions)]
62 Self::try_parse_bytes(&bytes).expect("client id format string is correct");
63 Self(bytes)
64 }
65
66 pub(crate) fn try_from_str_with_base64_user_id(str_with_base64_user_id: &str) -> Result<Self> {
68 E2eiClientId::try_from_qualified(str_with_base64_user_id)
69 .map(ClientId::from_e2ei_client_id)
70 .map_err(|_| Error::InvalidQualifiedClientId)
71 }
72
73 fn from_e2ei_client_id(e2ei_client_id: E2eiClientId) -> Self {
74 Self::new(e2ei_client_id.user_id, e2ei_client_id.device_id, &e2ei_client_id.domain)
75 }
76
77 pub(crate) fn new_ephemeral() -> Self {
78 let user_id = Uuid::new_v4().hyphenated().to_string();
79 let bytes = format!("{HISTORY_CLIENT_ID_PREFIX}{}{user_id}", Self::DELIMITER).into_bytes();
80 Self(bytes)
81 }
82
83 pub fn deserialize(&self) -> DeserializedClientId {
85 let (user_id, device_id, domain) = Self::try_parse_bytes(&self.0).expect("We just invert initialization");
86
87 DeserializedClientId {
88 user_id,
89 device_id,
90 domain,
91 }
92 }
93
94 pub(crate) fn new_from_bytes(bytes: Vec<u8>) -> Result<Self> {
95 Self::try_parse_bytes(&bytes)?;
96 Ok(Self(bytes))
97 }
98
99 fn try_parse_bytes(bytes: &[u8]) -> Result<(Uuid, u64, String)> {
101 let client_id = std::str::from_utf8(bytes).map_err(|_| Error::InvalidQualifiedClientId)?;
102 let (user_id, rest) = client_id
103 .split_once(Self::DELIMITER)
104 .ok_or(Error::InvalidQualifiedClientId)?;
105 let user_id = Self::parse_user_id(user_id)?;
106 let (device_id, domain) = rest
107 .split_once(Self::DOMAIN_SEPERATOR)
108 .ok_or(Error::InvalidQualifiedClientId)?;
109 let device_id = Self::parse_device_id(device_id)?;
110 Ok((user_id, device_id, domain.to_owned()))
111 }
112
113 fn parse_user_id(user_id: &str) -> Result<Uuid> {
115 Uuid::try_parse(user_id).map_err(|_| Error::InvalidQualifiedClientId)
116 }
117
118 fn parse_device_id(device_id: &str) -> Result<u64> {
119 u64::from_str_radix(device_id, 16).map_err(|_| Error::InvalidQualifiedClientId)
120 }
121
122 #[cfg(test)]
123 pub(crate) fn as_bytes(&self) -> &[u8] {
124 &self.0
125 }
126
127 pub fn as_e2ei_client_id(&self) -> E2eiClientId {
129 let (user_id, device_id, domain) = Self::try_parse_bytes(&self.0).expect("We just invert initialization");
130 E2eiClientId::try_new(user_id.to_string(), device_id, &domain).expect("We just invert intialization")
131 }
132
133 pub(crate) fn into_inner(self) -> Vec<u8> {
134 self.0
135 }
136}
137
138impl TryFrom<&[u8]> for ClientId {
139 type Error = Error;
140
141 fn try_from(value: &[u8]) -> Result<Self> {
142 Self::try_parse_bytes(value)?;
143 Ok(Self(value.into()))
144 }
145}
146
147impl Deref for ClientId {
148 type Target = ClientIdRef;
149
150 fn deref(&self) -> &Self::Target {
151 ClientIdRef::new(&self.0)
152 }
153}
154
155impl AsRef<[u8]> for ClientId {
156 fn as_ref(&self) -> &[u8] {
157 &self.0
158 }
159}
160
161impl AsRef<ClientIdRef> for ClientId {
162 fn as_ref(&self) -> &ClientIdRef {
163 ClientIdRef::new(&self.0)
164 }
165}
166
167impl std::fmt::Display for ClientId {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 write!(f, "{}", hex::encode(self.0.as_slice()))
170 }
171}
172
173impl<T> PartialEq<T> for ClientId
174where
175 ClientIdRef: PartialEq<T>,
176{
177 fn eq(&self, other: &T) -> bool {
178 (**self).eq(other)
179 }
180}
181
182#[cfg(test)]
184impl From<&str> for ClientId {
185 fn from(value: &str) -> Self {
186 Self(value.as_bytes().into())
187 }
188}
189
190#[repr(transparent)]
196#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, derive_more::Deref)]
197pub struct ClientIdRef([u8]);
198
199impl ClientIdRef {
200 pub fn new<Bytes>(bytes: &Bytes) -> &ClientIdRef
202 where
203 Bytes: AsRef<[u8]> + ?Sized,
204 {
205 unsafe { &*(bytes.as_ref() as *const [u8] as *const ClientIdRef) }
208 }
209
210 pub fn as_slice(&self) -> &[u8] {
212 self.as_ref()
213 }
214}
215
216impl<'a> From<&'a [u8]> for &'a ClientIdRef {
217 fn from(value: &'a [u8]) -> Self {
218 ClientIdRef::new(value)
219 }
220}
221
222impl<'a> From<&'a Vec<u8>> for &'a ClientIdRef {
223 fn from(value: &'a Vec<u8>) -> Self {
224 ClientIdRef::new(value.as_slice())
225 }
226}
227
228impl Borrow<ClientIdRef> for ClientId {
229 fn borrow(&self) -> &ClientIdRef {
230 ClientIdRef::new(&self.0)
231 }
232}
233
234impl Borrow<ClientIdRef> for &'_ ClientId {
235 fn borrow(&self) -> &ClientIdRef {
236 ClientIdRef::new(&*self.0)
237 }
238}
239
240impl ToOwned for ClientIdRef {
241 type Owned = ClientId;
242
243 fn to_owned(&self) -> Self::Owned {
244 ClientId(self.0.to_owned())
245 }
246}
247
248impl AsRef<[u8]> for ClientIdRef {
249 fn as_ref(&self) -> &[u8] {
250 &self.0
251 }
252}
253
254impl<'a> From<&'a ClientIdRef> for Cow<'a, [u8]> {
255 fn from(value: &'a ClientIdRef) -> Self {
256 Cow::Borrowed(value.as_ref())
257 }
258}
259
260impl PartialEq<ClientId> for ClientIdRef {
261 fn eq(&self, other: &ClientId) -> bool {
262 &self.0 == other.as_slice()
263 }
264}
265
266impl PartialEq<[u8]> for ClientIdRef {
267 fn eq(&self, other: &[u8]) -> bool {
268 &self.0 == other
269 }
270}
271
272impl PartialEq<&'_ [u8]> for ClientIdRef {
273 fn eq(&self, other: &&'_ [u8]) -> bool {
274 &self.0 == *other
275 }
276}
277
278impl std::fmt::Display for ClientIdRef {
279 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280 write!(f, "{}", hex::encode(&self.0))
281 }
282}
283
284macro_rules! impl_eq {
285 ($( $t:ty => |$self:ident, $other:ident| $impl:expr ; )+) => {
286 $(
287 impl PartialEq<$t> for ClientIdRef {
288 fn eq(&self, other: &$t) -> bool {
289 let $self = self;
290 let $other = other;
291 $impl
292 }
293 }
294
295 impl PartialEq<ClientIdRef> for $t {
296 fn eq(&self, other: &ClientIdRef) -> bool {
297 other.eq(self)
298 }
299 }
300
301 impl PartialEq<$t> for &'_ ClientIdRef {
302 fn eq(&self, other: &$t) -> bool {
303 let $self = self;
304 let $other = other;
305 $impl
306 }
307 }
308
309 impl PartialEq<&'_ ClientIdRef> for $t {
310 fn eq(&self, other: &&'_ ClientIdRef) -> bool {
311 other.eq(self)
312 }
313 }
314 )+
315 };
316}
317
318impl_eq!(
319 Vec<u8> => |me, other| me.0.eq(other.as_slice());
320 Cow<'_, ClientIdRef> => |me, other| me.eq(&other.as_slice());
321);
322
323impl fmt::Debug for ClientIdRef {
330 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331 f.debug_tuple("ClientIdRef")
332 .field(&obfuscate::Obfuscated::from(&self.0))
333 .finish()
334 }
335}
336
337#[cfg(test)]
338impl ClientId {
339 pub(crate) fn as_user_id(&self) -> Uuid {
340 self.deserialize().user_id
341 }
342
343 pub(crate) fn with_user(&self) -> (ClientId, Uuid) {
344 (self.clone(), self.as_user_id())
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use uuid::Uuid;
351
352 use super::ClientId;
353
354 const USER_ID: Uuid = Uuid::from_u128(0x0195_7b1e_4f00_7c9b_8a3d_2e11_9c44_0d7f);
355 const DOMAIN: &str = "wire.com";
356
357 #[test]
365 fn device_id_is_encoded_as_unpadded_hex() {
366 for (device_id, expected) in [
367 (0x0, "0"),
368 (0xf, "f"),
369 (0x8e64_2443_0d3b_28be, "8e6424430d3b28be"),
370 (u64::MAX, "ffffffffffffffff"),
371 ] {
372 assert_eq!(ClientId::encode_device_id(device_id), expected);
373
374 let client_id = ClientId::new(USER_ID, device_id, DOMAIN);
375 assert_eq!(
376 std::str::from_utf8(client_id.as_bytes()).unwrap(),
377 format!("{}:{expected}@{DOMAIN}", USER_ID.hyphenated()),
378 );
379 }
380 }
381
382 #[test]
384 fn client_id_round_trips_through_its_own_encoding() {
385 for device_id in [0x0, 0xf, 0x00ff, 0x8e64_2443_0d3b_28be, u64::MAX] {
386 let client_id = ClientId::new(USER_ID, device_id, DOMAIN);
387 let deserialized = client_id.deserialize();
388
389 assert_eq!(deserialized.user_id, USER_ID);
390 assert_eq!(deserialized.device_id, device_id);
391 assert_eq!(deserialized.domain, DOMAIN);
392 }
393 }
394
395 #[test]
399 fn a_padded_device_id_still_parses() {
400 let padded = format!("{}:000000000000000f@{DOMAIN}", USER_ID.hyphenated());
401 let client_id =
402 ClientId::new_from_bytes(padded.clone().into_bytes()).expect("a padded device id must still parse");
403
404 assert_eq!(std::str::from_utf8(client_id.as_bytes()).unwrap(), padded);
405 assert_eq!(client_id.deserialize().device_id, 0xf);
406 assert_ne!(client_id, ClientId::new(USER_ID, 0xf, DOMAIN));
407 }
408}