Skip to main content

core_crypto/mls/session/
user_id.rs

1use super::{Error, Result};
2
3/// Unique identifier of a User (human person holding some devices).
4///
5/// This contradicts the initial design requirements of this project since it was supposed to be
6/// agnostic from Wire.
7/// End-to-end Identity re-shuffled that... But we still want to keep this isolated from the rest
8/// of the crate that's why this should remain here and be used cautiously, having the context quoted
9/// above in mind.
10/// This parses the qualified `<user-id>:<device-id>@<domain>` form, taking everything before the
11/// first `:`. For example, in `LcksJb74Tm6N12cDjFy7lQ:8e6424430d3b28be@wire.com` the [UserId] is
12/// `LcksJb74Tm6N12cDjFy7lQ`.
13///
14/// It does *not* parse the `wireapp://` URI form: there the first `:` belongs to the scheme, so a
15/// URI would yield a [UserId] of `wireapp` rather than an error.
16#[derive(Debug, Clone, Copy, Eq, PartialEq, derive_more::Deref)]
17pub struct UserId<'a>(&'a [u8]);
18
19impl UserId<'_> {
20    const USER_ID_DELIMITER: u8 = b':';
21}
22
23impl<'a> TryFrom<&'a str> for UserId<'a> {
24    type Error = Error;
25
26    fn try_from(client_id: &'a str) -> Result<Self> {
27        client_id.as_bytes().try_into()
28    }
29}
30
31impl<'a> TryFrom<&'a [u8]> for UserId<'a> {
32    type Error = Error;
33
34    fn try_from(id: &'a [u8]) -> Result<Self> {
35        let found = id
36            .splitn(2, |&b| b == Self::USER_ID_DELIMITER)
37            .next()
38            .ok_or(Error::InvalidUserId)?;
39        if found.len() == id.len() {
40            return Err(Error::InvalidUserId);
41        }
42        Ok(Self(found))
43    }
44}
45
46impl TryFrom<UserId<'_>> for String {
47    type Error = Error;
48
49    fn try_from(uid: UserId<'_>) -> Result<Self> {
50        Ok(std::str::from_utf8(&uid).map_err(|_| Error::InvalidUserId)?.to_string())
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[macro_rules_attribute::apply(smol_macros::test)]
59    async fn should_parse_client_id() {
60        let user_id = "LcksJb74Tm6N12cDjFy7lQ:8e6424430d3b28be@world.com";
61        let user_id = UserId::try_from(user_id).unwrap();
62        assert_eq!(user_id, UserId("LcksJb74Tm6N12cDjFy7lQ".as_bytes()));
63    }
64
65    #[macro_rules_attribute::apply(smol_macros::test)]
66    async fn should_fail_when_invalid() {
67        let user_id = "LcksJb74Tm6N12cDjFy7lQ/8e6424430d3b28be@world.com";
68        let user_id = UserId::try_from(user_id);
69        assert!(matches!(user_id.unwrap_err(), Error::InvalidUserId));
70    }
71}