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) -> Result<DeserializedClientId> {
87 let (user_id, device_id, domain) = Self::try_parse_bytes(&self.0)?;
88 let deserialized = DeserializedClientId {
89 user_id,
90 device_id,
91 domain,
92 };
93 Ok(deserialized)
94 }
95
96 pub(crate) fn new_from_bytes(bytes: Vec<u8>) -> Result<Self> {
97 Self::try_parse_bytes(&bytes)?;
98 Ok(Self(bytes))
99 }
100
101 fn try_parse_bytes(bytes: &[u8]) -> Result<(Uuid, u64, String)> {
103 let client_id = std::str::from_utf8(bytes).map_err(|_| Error::InvalidQualifiedClientId)?;
104 let (user_id, rest) = client_id
105 .split_once(Self::DELIMITER)
106 .ok_or(Error::InvalidQualifiedClientId)?;
107 let user_id = Self::parse_user_id(user_id)?;
108 let (device_id, domain) = rest
109 .split_once(Self::DOMAIN_SEPERATOR)
110 .ok_or(Error::InvalidQualifiedClientId)?;
111 let device_id = Self::parse_device_id(device_id)?;
112 Ok((user_id, device_id, domain.to_owned()))
113 }
114
115 fn parse_user_id(user_id: &str) -> Result<Uuid> {
117 Uuid::try_parse(user_id).map_err(|_| Error::InvalidQualifiedClientId)
118 }
119
120 fn parse_device_id(device_id: &str) -> Result<u64> {
121 u64::from_str_radix(device_id, 16).map_err(|_| Error::InvalidQualifiedClientId)
122 }
123
124 #[cfg(test)]
125 pub(crate) fn as_bytes(&self) -> &[u8] {
126 &self.0
127 }
128
129 pub fn as_e2ei_client_id(&self) -> Result<E2eiClientId> {
133 let (user_id, device_id, domain) = Self::try_parse_bytes(&self.0)?;
134 let e2ei_client_id =
135 E2eiClientId::try_new(user_id.to_string(), device_id, &domain).expect("parsing just succeeded");
136 Ok(e2ei_client_id)
137 }
138
139 pub(crate) fn into_inner(self) -> Vec<u8> {
140 self.0
141 }
142}
143
144impl TryFrom<&[u8]> for ClientId {
145 type Error = Error;
146
147 fn try_from(value: &[u8]) -> Result<Self> {
148 Self::try_parse_bytes(value)?;
149 Ok(Self(value.into()))
150 }
151}
152
153impl Deref for ClientId {
154 type Target = ClientIdRef;
155
156 fn deref(&self) -> &Self::Target {
157 ClientIdRef::new(&self.0)
158 }
159}
160
161impl AsRef<[u8]> for ClientId {
162 fn as_ref(&self) -> &[u8] {
163 &self.0
164 }
165}
166
167impl AsRef<ClientIdRef> for ClientId {
168 fn as_ref(&self) -> &ClientIdRef {
169 ClientIdRef::new(&self.0)
170 }
171}
172
173impl std::fmt::Display for ClientId {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 write!(f, "{}", hex::encode(self.0.as_slice()))
176 }
177}
178
179impl<T> PartialEq<T> for ClientId
180where
181 ClientIdRef: PartialEq<T>,
182{
183 fn eq(&self, other: &T) -> bool {
184 (**self).eq(other)
185 }
186}
187
188#[cfg(test)]
190impl From<&str> for ClientId {
191 fn from(value: &str) -> Self {
192 Self(value.as_bytes().into())
193 }
194}
195
196#[repr(transparent)]
202#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, derive_more::Deref)]
203pub struct ClientIdRef([u8]);
204
205impl ClientIdRef {
206 pub fn new<Bytes>(bytes: &Bytes) -> &ClientIdRef
208 where
209 Bytes: AsRef<[u8]> + ?Sized,
210 {
211 unsafe { &*(bytes.as_ref() as *const [u8] as *const ClientIdRef) }
214 }
215
216 pub fn as_slice(&self) -> &[u8] {
218 self.as_ref()
219 }
220}
221
222impl<'a> From<&'a [u8]> for &'a ClientIdRef {
223 fn from(value: &'a [u8]) -> Self {
224 ClientIdRef::new(value)
225 }
226}
227
228impl<'a> From<&'a Vec<u8>> for &'a ClientIdRef {
229 fn from(value: &'a Vec<u8>) -> Self {
230 ClientIdRef::new(value.as_slice())
231 }
232}
233
234impl Borrow<ClientIdRef> for ClientId {
235 fn borrow(&self) -> &ClientIdRef {
236 ClientIdRef::new(&self.0)
237 }
238}
239
240impl Borrow<ClientIdRef> for &'_ ClientId {
241 fn borrow(&self) -> &ClientIdRef {
242 ClientIdRef::new(&*self.0)
243 }
244}
245
246impl ToOwned for ClientIdRef {
247 type Owned = ClientId;
248
249 fn to_owned(&self) -> Self::Owned {
250 ClientId(self.0.to_owned())
251 }
252}
253
254impl AsRef<[u8]> for ClientIdRef {
255 fn as_ref(&self) -> &[u8] {
256 &self.0
257 }
258}
259
260impl<'a> From<&'a ClientIdRef> for Cow<'a, [u8]> {
261 fn from(value: &'a ClientIdRef) -> Self {
262 Cow::Borrowed(value.as_ref())
263 }
264}
265
266impl PartialEq<ClientId> for ClientIdRef {
267 fn eq(&self, other: &ClientId) -> bool {
268 &self.0 == other.as_slice()
269 }
270}
271
272impl PartialEq<[u8]> for ClientIdRef {
273 fn eq(&self, other: &[u8]) -> bool {
274 &self.0 == other
275 }
276}
277
278impl PartialEq<&'_ [u8]> for ClientIdRef {
279 fn eq(&self, other: &&'_ [u8]) -> bool {
280 &self.0 == *other
281 }
282}
283
284impl std::fmt::Display for ClientIdRef {
285 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 write!(f, "{}", hex::encode(&self.0))
287 }
288}
289
290macro_rules! impl_eq {
291 ($( $t:ty => |$self:ident, $other:ident| $impl:expr ; )+) => {
292 $(
293 impl PartialEq<$t> for ClientIdRef {
294 fn eq(&self, other: &$t) -> bool {
295 let $self = self;
296 let $other = other;
297 $impl
298 }
299 }
300
301 impl PartialEq<ClientIdRef> for $t {
302 fn eq(&self, other: &ClientIdRef) -> bool {
303 other.eq(self)
304 }
305 }
306
307 impl PartialEq<$t> for &'_ ClientIdRef {
308 fn eq(&self, other: &$t) -> bool {
309 let $self = self;
310 let $other = other;
311 $impl
312 }
313 }
314
315 impl PartialEq<&'_ ClientIdRef> for $t {
316 fn eq(&self, other: &&'_ ClientIdRef) -> bool {
317 other.eq(self)
318 }
319 }
320 )+
321 };
322}
323
324impl_eq!(
325 Vec<u8> => |me, other| me.0.eq(other.as_slice());
326 Cow<'_, ClientIdRef> => |me, other| me.eq(&other.as_slice());
327);
328
329impl fmt::Debug for ClientIdRef {
336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337 f.debug_tuple("ClientIdRef")
338 .field(&obfuscate::Obfuscated::from(&self.0))
339 .finish()
340 }
341}
342
343#[cfg(test)]
344impl ClientId {
345 pub(crate) fn as_user_id(&self) -> Uuid {
346 self.deserialize().unwrap().user_id
347 }
348
349 pub(crate) fn with_user(&self) -> (ClientId, Uuid) {
350 (self.clone(), self.as_user_id())
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use uuid::Uuid;
357
358 use super::ClientId;
359
360 const USER_ID: Uuid = Uuid::from_u128(0x0195_7b1e_4f00_7c9b_8a3d_2e11_9c44_0d7f);
361 const DOMAIN: &str = "wire.com";
362
363 #[test]
371 fn device_id_is_encoded_as_unpadded_hex() {
372 for (device_id, expected) in [
373 (0x0, "0"),
374 (0xf, "f"),
375 (0x8e64_2443_0d3b_28be, "8e6424430d3b28be"),
376 (u64::MAX, "ffffffffffffffff"),
377 ] {
378 assert_eq!(ClientId::encode_device_id(device_id), expected);
379
380 let client_id = ClientId::new(USER_ID, device_id, DOMAIN);
381 assert_eq!(
382 std::str::from_utf8(client_id.as_bytes()).unwrap(),
383 format!("{}:{expected}@{DOMAIN}", USER_ID.hyphenated()),
384 );
385 }
386 }
387
388 #[test]
390 fn client_id_round_trips_through_its_own_encoding() {
391 for device_id in [0x0, 0xf, 0x00ff, 0x8e64_2443_0d3b_28be, u64::MAX] {
392 let client_id = ClientId::new(USER_ID, device_id, DOMAIN);
393 let deserialized = client_id.deserialize().unwrap();
394
395 assert_eq!(deserialized.user_id, USER_ID);
396 assert_eq!(deserialized.device_id, device_id);
397 assert_eq!(deserialized.domain, DOMAIN);
398 }
399 }
400
401 #[test]
405 fn a_padded_device_id_still_parses() {
406 let padded = format!("{}:000000000000000f@{DOMAIN}", USER_ID.hyphenated());
407 let client_id =
408 ClientId::new_from_bytes(padded.clone().into_bytes()).expect("a padded device id must still parse");
409
410 assert_eq!(std::str::from_utf8(client_id.as_bytes()).unwrap(), padded);
411 assert_eq!(client_id.deserialize().unwrap().device_id, 0xf);
412 assert_ne!(client_id, ClientId::new(USER_ID, 0xf, DOMAIN));
413 }
414}