azalea_auth/
game_profile.rs

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