azalea_auth/
game_profile.rs

1use std::{
2    io::{self, Write},
3    sync::Arc,
4};
5
6use azalea_buf::{
7    AzBuf, AzaleaRead, AzaleaReadLimited, AzaleaReadVar, AzaleaWrite, AzaleaWriteVar, BufReadError,
8};
9use indexmap::IndexMap;
10use serde::{Deserialize, Serialize, Serializer};
11use uuid::Uuid;
12
13/// Information about the player that's usually stored on Mojang's servers.
14#[derive(Debug, Clone, Default, Eq, PartialEq, AzBuf)]
15pub struct GameProfile {
16    /// The UUID of the player.
17    ///
18    /// Typically a UUIDv4 for online-mode players and UUIDv3 for offline-mode
19    /// players.
20    pub uuid: Uuid,
21    /// The username of the player.
22    ///
23    /// Limited to 16 bytes.
24    pub name: String,
25    /// The properties of the player, including their in-game skin and cape.
26    ///
27    /// This is an `Arc` to make it cheaper to clone.
28    pub properties: Arc<GameProfileProperties>,
29}
30
31impl GameProfile {
32    pub fn new(uuid: Uuid, name: String) -> Self {
33        GameProfile {
34            uuid,
35            name,
36            properties: Arc::new(GameProfileProperties::default()),
37        }
38    }
39}
40
41impl From<SerializableGameProfile> for GameProfile {
42    fn from(value: SerializableGameProfile) -> Self {
43        Self {
44            uuid: value.id.unwrap_or_default(),
45            name: value.name.unwrap_or_default(),
46            properties: Arc::new(value.properties.into()),
47        }
48    }
49}
50
51/// The properties of the player, including their in-game skin and cape.
52#[derive(Debug, Clone, Default, Eq, PartialEq)]
53pub struct GameProfileProperties {
54    pub map: IndexMap<String, ProfilePropertyValue>,
55}
56impl AzaleaRead for GameProfileProperties {
57    fn azalea_read(buf: &mut io::Cursor<&[u8]>) -> Result<Self, BufReadError> {
58        let mut properties = IndexMap::new();
59        let properties_len = u32::azalea_read_var(buf)?;
60        if properties_len > 16 {
61            return Err(BufReadError::VecLengthTooLong {
62                length: properties_len,
63                max_length: 16,
64            });
65        }
66        for _ in 0..properties_len {
67            let key = String::azalea_read_limited(buf, 16)?;
68            let value = ProfilePropertyValue::azalea_read(buf)?;
69            properties.insert(key, value);
70        }
71        Ok(GameProfileProperties { map: properties })
72    }
73}
74impl AzaleaWrite for GameProfileProperties {
75    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
76        (self.map.len() as u64).azalea_write_var(buf)?;
77        for (key, value) in &self.map {
78            key.azalea_write(buf)?;
79            value.azalea_write(buf)?;
80        }
81        Ok(())
82    }
83}
84
85#[derive(Debug, Clone, Eq, PartialEq)]
86pub struct ProfilePropertyValue {
87    pub value: String,
88    pub signature: Option<String>,
89}
90impl AzaleaRead for ProfilePropertyValue {
91    fn azalea_read(buf: &mut io::Cursor<&[u8]>) -> Result<Self, BufReadError> {
92        let value = String::azalea_read_limited(buf, 32767)?;
93        let signature = Option::<String>::azalea_read_limited(buf, 1024)?;
94        Ok(ProfilePropertyValue { value, signature })
95    }
96}
97impl AzaleaWrite for ProfilePropertyValue {
98    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
99        self.value.azalea_write(buf)?;
100        self.signature.azalea_write(buf)?;
101        Ok(())
102    }
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct SerializableGameProfile {
107    #[serde(default)]
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub id: Option<Uuid>,
110    #[serde(default)]
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub name: Option<String>,
113    #[serde(default)]
114    #[serde(skip_serializing_if = "SerializableProfileProperties::is_empty")]
115    pub properties: SerializableProfileProperties,
116}
117
118impl From<GameProfile> for SerializableGameProfile {
119    fn from(value: GameProfile) -> Self {
120        Self {
121            id: Some(value.uuid),
122            name: Some(value.name),
123            properties: (*value.properties).clone().into(),
124        }
125    }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, Default)]
129#[serde(transparent)]
130pub struct SerializableProfileProperties {
131    pub list: Vec<SerializableProfilePropertyValue>,
132}
133impl SerializableProfileProperties {
134    pub fn is_empty(&self) -> bool {
135        self.list.is_empty()
136    }
137}
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct SerializableProfilePropertyValue {
140    pub name: String,
141    pub value: String,
142    #[serde(default)]
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub signature: Option<String>,
145}
146
147impl From<GameProfileProperties> for SerializableProfileProperties {
148    fn from(value: GameProfileProperties) -> Self {
149        let mut list = Vec::new();
150        for (name, entry) in value.map {
151            list.push(SerializableProfilePropertyValue {
152                name,
153                value: entry.value,
154                signature: entry.signature,
155            });
156        }
157        Self { list }
158    }
159}
160impl From<SerializableProfileProperties> for GameProfileProperties {
161    fn from(value: SerializableProfileProperties) -> Self {
162        let mut map = IndexMap::new();
163        for entry in value.list {
164            map.insert(
165                entry.name,
166                ProfilePropertyValue {
167                    value: entry.value,
168                    signature: entry.signature,
169                },
170            );
171        }
172        Self { map }
173    }
174}
175impl Serialize for GameProfile {
176    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
177        let serializable = SerializableGameProfile::from(self.clone());
178        serializable.serialize(serializer)
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_deserialize_game_profile() {
188        let json = r#"{
189            "id": "f1a2b3c4-d5e6-f7a8-b9c0-d1e2f3a4b5c6",
190            "name": "Notch",
191            "properties": [
192                {
193                    "name": "qwer",
194                    "value": "asdf",
195                    "signature": "zxcv"
196                }
197            ]
198        }"#;
199        let profile =
200            GameProfile::from(serde_json::from_str::<SerializableGameProfile>(json).unwrap());
201        assert_eq!(
202            profile,
203            GameProfile {
204                uuid: Uuid::parse_str("f1a2b3c4-d5e6-f7a8-b9c0-d1e2f3a4b5c6").unwrap(),
205                name: "Notch".to_string(),
206                properties: {
207                    let mut map = IndexMap::new();
208                    map.insert(
209                        "qwer".to_string(),
210                        ProfilePropertyValue {
211                            value: "asdf".to_string(),
212                            signature: Some("zxcv".to_string()),
213                        },
214                    );
215                    GameProfileProperties { map }.into()
216                },
217            }
218        );
219    }
220}