mas_data_model/compat/
device.rs1use oauth2_types::scope::ScopeToken;
8use rand::{
9 RngCore,
10 distributions::{Alphanumeric, DistString},
11};
12use serde::{Deserialize, Serialize};
13use thiserror::Error;
14
15static GENERATED_DEVICE_ID_LENGTH: usize = 10;
16static UNSTABLE_DEVICE_SCOPE_PREFIX: &str = "urn:matrix:org.matrix.msc2967.client:device:";
17static STABLE_DEVICE_SCOPE_PREFIX: &str = "urn:matrix:client:device:";
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(transparent)]
21pub struct Device {
22 id: String,
23}
24
25#[derive(Debug, Error)]
26pub enum ToScopeTokenError {
27 #[error("Device ID contains characters that can't be encoded in a scope")]
28 InvalidCharacters,
29}
30
31impl Device {
32 pub fn to_scope_token(&self) -> Result<[ScopeToken; 2], ToScopeTokenError> {
39 Ok([
40 format!("{STABLE_DEVICE_SCOPE_PREFIX}{}", self.id)
41 .parse()
42 .map_err(|_| ToScopeTokenError::InvalidCharacters)?,
43 format!("{UNSTABLE_DEVICE_SCOPE_PREFIX}{}", self.id)
44 .parse()
45 .map_err(|_| ToScopeTokenError::InvalidCharacters)?,
46 ])
47 }
48
49 #[must_use]
53 pub fn from_scope_token(token: &ScopeToken) -> Option<Self> {
54 let stable = token.as_str().strip_prefix(STABLE_DEVICE_SCOPE_PREFIX);
55 let unstable = token.as_str().strip_prefix(UNSTABLE_DEVICE_SCOPE_PREFIX);
56 let id = stable.or(unstable)?;
57 Some(Device::from(id.to_owned()))
58 }
59
60 pub fn generate<R: RngCore + ?Sized>(rng: &mut R) -> Self {
62 let id: String = Alphanumeric.sample_string(rng, GENERATED_DEVICE_ID_LENGTH);
63 Self { id }
64 }
65
66 #[must_use]
68 pub fn as_str(&self) -> &str {
69 &self.id
70 }
71}
72
73impl From<String> for Device {
74 fn from(id: String) -> Self {
75 Self { id }
76 }
77}
78
79impl From<Device> for String {
80 fn from(device: Device) -> Self {
81 device.id
82 }
83}
84
85impl std::fmt::Display for Device {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 f.write_str(&self.id)
88 }
89}
90
91#[cfg(test)]
92mod test {
93 use oauth2_types::scope::OPENID;
94
95 use crate::Device;
96
97 #[test]
98 fn test_device_id_to_from_scope_token() {
99 let device = Device::from("AABBCCDDEE".to_owned());
100 let [stable_scope_token, unstable_scope_token] = device.to_scope_token().unwrap();
101 assert_eq!(
102 stable_scope_token.as_str(),
103 "urn:matrix:client:device:AABBCCDDEE"
104 );
105 assert_eq!(
106 unstable_scope_token.as_str(),
107 "urn:matrix:org.matrix.msc2967.client:device:AABBCCDDEE"
108 );
109 assert_eq!(
110 Device::from_scope_token(&unstable_scope_token).as_ref(),
111 Some(&device)
112 );
113 assert_eq!(
114 Device::from_scope_token(&stable_scope_token).as_ref(),
115 Some(&device)
116 );
117 assert_eq!(Device::from_scope_token(&OPENID), None);
118 }
119}