1use std::{fmt, io::Read};
4
5use mcproto_codec::{
6 error::{CodecError, CodecKind, InvalidEncodingReason},
7 varint::{VarIntRead, VarIntWrite},
8};
9
10use crate::{
11 TypeCodec, TypeStructCodec,
12 basic::{Uuid, decode_prefixed_string, encode_prefixed_string},
13 contextual::PrefixedOptional,
14};
15
16pub const MAX_GAME_PROFILE_PROPERTIES: usize = 16;
18
19#[repr(transparent)]
25#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
26pub struct GameProfileString<const MAX_UTF16_CODE_UNITS: usize>(String);
27
28impl<const MAX_UTF16_CODE_UNITS: usize> GameProfileString<MAX_UTF16_CODE_UNITS> {
29 pub const MAX_UTF16_CODE_UNITS: usize = MAX_UTF16_CODE_UNITS;
31 pub const MAX_BYTES: usize = MAX_UTF16_CODE_UNITS.saturating_mul(3);
33
34 pub fn new(value: impl Into<String>) -> Result<Self, GameProfileStringTooLong> {
36 let value = value.into();
37 let actual_code_units = value.encode_utf16().count();
38 if actual_code_units > MAX_UTF16_CODE_UNITS {
39 return Err(GameProfileStringTooLong {
40 max_code_units: MAX_UTF16_CODE_UNITS,
41 actual_code_units,
42 });
43 }
44 Ok(Self(value))
45 }
46
47 #[must_use]
49 pub fn as_str(&self) -> &str {
50 &self.0
51 }
52
53 #[must_use]
55 pub fn into_inner(self) -> String {
56 self.0
57 }
58}
59
60impl<const MAX_UTF16_CODE_UNITS: usize> AsRef<str> for GameProfileString<MAX_UTF16_CODE_UNITS> {
61 fn as_ref(&self) -> &str {
62 self.as_str()
63 }
64}
65
66impl<const MAX_UTF16_CODE_UNITS: usize> fmt::Display for GameProfileString<MAX_UTF16_CODE_UNITS> {
67 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
68 formatter.write_str(self.as_str())
69 }
70}
71
72impl<const MAX_UTF16_CODE_UNITS: usize> TryFrom<String>
73 for GameProfileString<MAX_UTF16_CODE_UNITS>
74{
75 type Error = GameProfileStringTooLong;
76
77 fn try_from(value: String) -> Result<Self, Self::Error> {
78 Self::new(value)
79 }
80}
81
82impl<const MAX_UTF16_CODE_UNITS: usize> TryFrom<&str> for GameProfileString<MAX_UTF16_CODE_UNITS> {
83 type Error = GameProfileStringTooLong;
84
85 fn try_from(value: &str) -> Result<Self, Self::Error> {
86 Self::new(value)
87 }
88}
89
90impl<const MAX_UTF16_CODE_UNITS: usize> From<GameProfileString<MAX_UTF16_CODE_UNITS>> for String {
91 fn from(value: GameProfileString<MAX_UTF16_CODE_UNITS>) -> Self {
92 value.into_inner()
93 }
94}
95
96impl<const MAX_UTF16_CODE_UNITS: usize> TypeCodec for GameProfileString<MAX_UTF16_CODE_UNITS> {
97 fn encode(&self, writer: &mut impl std::io::Write) -> Result<(), CodecError> {
98 encode_prefixed_string(
99 &self.0,
100 writer,
101 CodecKind::String,
102 Self::MAX_BYTES,
103 MAX_UTF16_CODE_UNITS,
104 )
105 }
106
107 fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
108 decode_prefixed_string(
109 reader,
110 CodecKind::String,
111 Self::MAX_BYTES,
112 MAX_UTF16_CODE_UNITS,
113 )
114 .map(|(value, _)| Self(value))
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
120pub struct GameProfileStringTooLong {
121 pub max_code_units: usize,
123 pub actual_code_units: usize,
125}
126
127impl fmt::Display for GameProfileStringTooLong {
128 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
129 write!(
130 formatter,
131 "game-profile string contains {} UTF-16 code units; maximum is {}",
132 self.actual_code_units, self.max_code_units
133 )
134 }
135}
136
137impl std::error::Error for GameProfileStringTooLong {}
138
139pub type GameProfileUsername = GameProfileString<16>;
141pub type GameProfilePropertyName = GameProfileString<64>;
143pub type GameProfilePropertyValue = GameProfileString<32767>;
145pub type GameProfilePropertySignature = GameProfileString<1024>;
147
148#[derive(Debug, Clone, PartialEq, Eq, Hash, TypeStructCodec)]
150#[type_struct_codec(kind = GameProfileProperty)]
151pub struct GameProfileProperty {
152 pub name: GameProfilePropertyName,
154 pub value: GameProfilePropertyValue,
156 pub signature: PrefixedOptional<GameProfilePropertySignature>,
158}
159
160impl GameProfileProperty {
161 #[must_use]
163 pub const fn new(name: GameProfilePropertyName, value: GameProfilePropertyValue) -> Self {
164 Self {
165 name,
166 value,
167 signature: PrefixedOptional::none(),
168 }
169 }
170}
171
172#[repr(transparent)]
174#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
175pub struct GameProfileProperties(Vec<GameProfileProperty>);
176
177impl GameProfileProperties {
178 pub const MAX_LEN: usize = MAX_GAME_PROFILE_PROPERTIES;
180
181 pub fn new(properties: Vec<GameProfileProperty>) -> Result<Self, TooManyGameProfileProperties> {
183 if properties.len() > Self::MAX_LEN {
184 return Err(TooManyGameProfileProperties {
185 actual: properties.len(),
186 });
187 }
188 Ok(Self(properties))
189 }
190
191 #[must_use]
193 pub const fn len(&self) -> usize {
194 self.0.len()
195 }
196
197 #[must_use]
199 pub const fn is_empty(&self) -> bool {
200 self.0.is_empty()
201 }
202
203 #[must_use]
205 pub const fn as_slice(&self) -> &[GameProfileProperty] {
206 self.0.as_slice()
207 }
208
209 #[must_use]
211 pub fn into_vec(self) -> Vec<GameProfileProperty> {
212 self.0
213 }
214
215 pub fn push(
217 &mut self,
218 property: GameProfileProperty,
219 ) -> Result<(), TooManyGameProfileProperties> {
220 if self.len() == Self::MAX_LEN {
221 return Err(TooManyGameProfileProperties {
222 actual: self.len() + 1,
223 });
224 }
225 self.0.push(property);
226 Ok(())
227 }
228}
229
230impl TryFrom<Vec<GameProfileProperty>> for GameProfileProperties {
231 type Error = TooManyGameProfileProperties;
232
233 fn try_from(value: Vec<GameProfileProperty>) -> Result<Self, Self::Error> {
234 Self::new(value)
235 }
236}
237
238impl From<GameProfileProperties> for Vec<GameProfileProperty> {
239 fn from(value: GameProfileProperties) -> Self {
240 value.into_vec()
241 }
242}
243
244impl TypeCodec for GameProfileProperties {
245 fn encode(&self, writer: &mut impl std::io::Write) -> Result<(), CodecError> {
246 writer
247 .write_varint(self.len() as i32)
248 .map_err(|error| error.with_context(CodecKind::PrefixedArray))?;
249 for property in &self.0 {
250 property
251 .encode(writer)
252 .map_err(|error| error.with_context(CodecKind::PrefixedArray))?;
253 }
254 Ok(())
255 }
256
257 fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
258 let (length, prefix_size) = reader
259 .read_varint_with_size()
260 .map_err(|error| error.with_context(CodecKind::PrefixedArray))?;
261 if length < 0 {
262 return Err(CodecError::invalid_encoding(
263 CodecKind::PrefixedArray,
264 prefix_size,
265 InvalidEncodingReason::NegativeLength { value: length },
266 ));
267 }
268 let length = length as usize;
269 if length > Self::MAX_LEN {
270 return Err(CodecError::invalid_encoding(
271 CodecKind::PrefixedArray,
272 prefix_size,
273 InvalidEncodingReason::LengthOutOfRange {
274 max: Self::MAX_LEN,
275 actual: length,
276 },
277 ));
278 }
279
280 let mut properties = Vec::with_capacity(length);
281 for _ in 0..length {
282 properties.push(
283 GameProfileProperty::decode(reader)
284 .map_err(|error| error.with_context(CodecKind::PrefixedArray))?,
285 );
286 }
287 Ok(Self(properties))
288 }
289}
290
291#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
293pub struct TooManyGameProfileProperties {
294 pub actual: usize,
296}
297
298impl fmt::Display for TooManyGameProfileProperties {
299 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
300 write!(
301 formatter,
302 "game profile contains {} properties; maximum is {}",
303 self.actual, MAX_GAME_PROFILE_PROPERTIES
304 )
305 }
306}
307
308impl std::error::Error for TooManyGameProfileProperties {}
309
310#[derive(Debug, Clone, PartialEq, Eq, Hash, TypeStructCodec)]
343#[type_struct_codec(kind = GameProfile)]
344pub struct GameProfile {
345 pub uuid: Uuid,
347 pub username: GameProfileUsername,
349 pub properties: GameProfileProperties,
351}
352
353impl GameProfile {
354 #[must_use]
356 pub const fn new(uuid: Uuid, username: GameProfileUsername) -> Self {
357 Self {
358 uuid,
359 username,
360 properties: GameProfileProperties(Vec::new()),
361 }
362 }
363}