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