Skip to main content

core_crypto/mls/session/
id.rs

1mod qualified;
2
3use std::{
4    borrow::{Borrow, Cow},
5    fmt,
6    ops::Deref,
7};
8
9pub use qualified::QualifiedClientId;
10
11use super::error::Error;
12
13/// A Client identifier
14///
15/// A unique identifier for clients. A client is an identifier for each App a user is using, such as desktop,
16/// mobile, etc. Users can have multiple clients.
17/// More information [here](https://messaginglayersecurity.rocks/mls-architecture/draft-ietf-mls-architecture.html#name-group-members-and-clients)
18#[derive(
19    core_crypto_macros::Debug,
20    Clone,
21    Eq,
22    PartialOrd,
23    Ord,
24    Hash,
25    derive_more::From,
26    derive_more::Into,
27    serde::Serialize,
28    serde::Deserialize,
29)]
30#[sensitive]
31pub struct ClientId(pub(crate) Vec<u8>);
32
33impl ClientId {
34    pub(crate) fn into_inner(self) -> Vec<u8> {
35        self.0
36    }
37}
38
39impl From<Box<[u8]>> for ClientId {
40    fn from(value: Box<[u8]>) -> Self {
41        Self(value.into())
42    }
43}
44
45impl<const N: usize> From<[u8; N]> for ClientId {
46    fn from(value: [u8; N]) -> Self {
47        Self(value.into())
48    }
49}
50
51impl<'a> From<&'a [u8]> for ClientId {
52    fn from(value: &'a [u8]) -> Self {
53        Self(value.into())
54    }
55}
56
57impl From<ClientId> for Box<[u8]> {
58    fn from(value: ClientId) -> Self {
59        value.0.into_boxed_slice()
60    }
61}
62
63impl Deref for ClientId {
64    type Target = ClientIdRef;
65
66    fn deref(&self) -> &Self::Target {
67        ClientIdRef::new(&self.0)
68    }
69}
70
71impl AsRef<[u8]> for ClientId {
72    fn as_ref(&self) -> &[u8] {
73        &self.0
74    }
75}
76
77impl AsRef<ClientIdRef> for ClientId {
78    fn as_ref(&self) -> &ClientIdRef {
79        ClientIdRef::new(&self.0)
80    }
81}
82
83impl From<ClientId> for Cow<'_, [u8]> {
84    fn from(value: ClientId) -> Self {
85        Cow::Owned(value.0)
86    }
87}
88
89impl std::fmt::Display for ClientId {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        write!(f, "{}", hex::encode(self.0.as_slice()))
92    }
93}
94
95impl std::str::FromStr for ClientId {
96    type Err = Error;
97
98    fn from_str(s: &str) -> Result<Self, Self::Err> {
99        Ok(Self(hex::decode(s).unwrap_or_else(|_| s.as_bytes().to_vec())))
100    }
101}
102
103impl<T> PartialEq<T> for ClientId
104where
105    ClientIdRef: PartialEq<T>,
106{
107    fn eq(&self, other: &T) -> bool {
108        (**self).eq(other)
109    }
110}
111
112#[cfg(test)]
113impl From<&str> for ClientId {
114    fn from(value: &str) -> Self {
115        Self(value.as_bytes().into())
116    }
117}
118
119/// Reference to a [`ClientId`].
120///
121/// This type is `!Sized` and is only ever seen as a reference, like `str` or `[u8]`.
122//
123// pattern from https://stackoverflow.com/a/64990850
124#[repr(transparent)]
125#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, derive_more::Deref)]
126pub struct ClientIdRef([u8]);
127
128impl ClientIdRef {
129    /// Creates a `ClientId` Ref, needed to implement `Borrow<ClientIdRef>` for `T`
130    pub fn new<Bytes>(bytes: &Bytes) -> &ClientIdRef
131    where
132        Bytes: AsRef<[u8]> + ?Sized,
133    {
134        // safety: because of `repr(transparent)` we know that `ClientIdRef` has a memory layout
135        // identical to `[u8]`, so we can perform this cast
136        unsafe { &*(bytes.as_ref() as *const [u8] as *const ClientIdRef) }
137    }
138
139    /// View this reference as a byte slice
140    pub fn as_slice(&self) -> &[u8] {
141        self.as_ref()
142    }
143}
144
145impl<'a> From<&'a [u8]> for &'a ClientIdRef {
146    fn from(value: &'a [u8]) -> Self {
147        ClientIdRef::new(value)
148    }
149}
150
151impl<'a> From<&'a Vec<u8>> for &'a ClientIdRef {
152    fn from(value: &'a Vec<u8>) -> Self {
153        ClientIdRef::new(value.as_slice())
154    }
155}
156
157impl Borrow<ClientIdRef> for ClientId {
158    fn borrow(&self) -> &ClientIdRef {
159        ClientIdRef::new(&self.0)
160    }
161}
162
163impl Borrow<ClientIdRef> for &'_ ClientId {
164    fn borrow(&self) -> &ClientIdRef {
165        ClientIdRef::new(&*self.0)
166    }
167}
168
169impl ToOwned for ClientIdRef {
170    type Owned = ClientId;
171
172    fn to_owned(&self) -> Self::Owned {
173        ClientId(self.0.to_owned())
174    }
175}
176
177impl AsRef<[u8]> for ClientIdRef {
178    fn as_ref(&self) -> &[u8] {
179        &self.0
180    }
181}
182
183impl<'a> From<&'a ClientIdRef> for Cow<'a, [u8]> {
184    fn from(value: &'a ClientIdRef) -> Self {
185        Cow::Borrowed(value.as_ref())
186    }
187}
188
189impl PartialEq<ClientId> for ClientIdRef {
190    fn eq(&self, other: &ClientId) -> bool {
191        &self.0 == other.as_slice()
192    }
193}
194
195impl PartialEq<[u8]> for ClientIdRef {
196    fn eq(&self, other: &[u8]) -> bool {
197        &self.0 == other
198    }
199}
200
201impl PartialEq<&'_ [u8]> for ClientIdRef {
202    fn eq(&self, other: &&'_ [u8]) -> bool {
203        &self.0 == *other
204    }
205}
206
207impl std::fmt::Display for ClientIdRef {
208    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
209        write!(f, "{}", hex::encode(&self.0))
210    }
211}
212
213macro_rules! impl_eq {
214    ($( $t:ty => |$self:ident, $other:ident| $impl:expr ; )+) => {
215        $(
216            impl PartialEq<$t> for ClientIdRef {
217                fn eq(&self, other: &$t) -> bool {
218                    let $self = self;
219                    let $other = other;
220                    $impl
221                }
222            }
223
224            impl PartialEq<ClientIdRef> for $t {
225                fn eq(&self, other: &ClientIdRef) -> bool {
226                    other.eq(self)
227                }
228            }
229
230            impl PartialEq<$t> for &'_ ClientIdRef {
231                fn eq(&self, other: &$t) -> bool {
232                    let $self = self;
233                    let $other = other;
234                    $impl
235                }
236            }
237
238            impl PartialEq<&'_ ClientIdRef> for $t {
239                fn eq(&self, other: &&'_ ClientIdRef) -> bool {
240                    other.eq(self)
241                }
242            }
243        )+
244    };
245}
246
247impl_eq!(
248    Vec<u8> => |me, other| me.0.eq(other.as_slice());
249    Cow<'_, ClientIdRef> => |me, other| me.eq(&other.as_slice());
250);
251
252// we can't use `core_crypto_macros::Debug` to generate this because `ClientIdRef: !Sized`,
253// and the `log` crate maintainers did not explicitly opt-in to allowing `!Sized` in their
254// `Value::from_debug` impl, even though it might make sense to.
255//
256// this has the consequence that we can't natively log a `ClientIdRef` as a value;
257// if we want to, we have to do `id_ref.to_owned()`. Which might be ok.
258impl fmt::Debug for ClientIdRef {
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260        f.debug_tuple("ClientIdRef")
261            .field(&obfuscate::Obfuscated::from(&self.0))
262            .finish()
263    }
264}
265
266type LegacyClientId = wire_e2e_identity::legacy::id::ClientId;
267
268impl From<ClientId> for LegacyClientId {
269    fn from(value: ClientId) -> Self {
270        Self::from(value.0)
271    }
272}
273#[cfg(test)]
274impl ClientId {
275    pub(crate) fn to_user_id(&self) -> String {
276        let self_bytes: &[u8] = &self.0;
277        wire_e2e_identity::legacy::id::WireQualifiedClientId::try_from(self_bytes)
278            .unwrap()
279            .get_user_id()
280    }
281
282    pub(crate) fn to_string_triple(&self) -> [String; 3] {
283        let cid = wire_e2e_identity::legacy::id::ClientId::from(self.0.clone());
284        let qualified_id = wire_e2e_identity::legacy::id::QualifiedE2eiClientId::from(cid);
285        let id_string: String = qualified_id.try_into().unwrap();
286        [id_string, "".into(), self.to_user_id()]
287    }
288}