use std::{fmt, io::Read};
use mcproto_codec::{
error::{CodecError, CodecKind, InvalidEncodingReason},
varint::{VarIntRead, VarIntWrite},
};
use crate::{
TypeCodec, TypeStructCodec,
basic::{Uuid, decode_prefixed_string, encode_prefixed_string},
contextual::PrefixedOptional,
};
pub const MAX_GAME_PROFILE_PROPERTIES: usize = 16;
#[repr(transparent)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct GameProfileString<const MAX_UTF16_CODE_UNITS: usize>(String);
impl<const MAX_UTF16_CODE_UNITS: usize> GameProfileString<MAX_UTF16_CODE_UNITS> {
pub const MAX_UTF16_CODE_UNITS: usize = MAX_UTF16_CODE_UNITS;
pub const MAX_BYTES: usize = MAX_UTF16_CODE_UNITS.saturating_mul(3);
pub fn new(value: impl Into<String>) -> Result<Self, GameProfileStringTooLong> {
let value = value.into();
let actual_code_units = value.encode_utf16().count();
if actual_code_units > MAX_UTF16_CODE_UNITS {
return Err(GameProfileStringTooLong {
max_code_units: MAX_UTF16_CODE_UNITS,
actual_code_units,
});
}
Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_inner(self) -> String {
self.0
}
}
impl<const MAX_UTF16_CODE_UNITS: usize> AsRef<str> for GameProfileString<MAX_UTF16_CODE_UNITS> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<const MAX_UTF16_CODE_UNITS: usize> fmt::Display for GameProfileString<MAX_UTF16_CODE_UNITS> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.as_str())
}
}
impl<const MAX_UTF16_CODE_UNITS: usize> TryFrom<String>
for GameProfileString<MAX_UTF16_CODE_UNITS>
{
type Error = GameProfileStringTooLong;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl<const MAX_UTF16_CODE_UNITS: usize> TryFrom<&str> for GameProfileString<MAX_UTF16_CODE_UNITS> {
type Error = GameProfileStringTooLong;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl<const MAX_UTF16_CODE_UNITS: usize> From<GameProfileString<MAX_UTF16_CODE_UNITS>> for String {
fn from(value: GameProfileString<MAX_UTF16_CODE_UNITS>) -> Self {
value.into_inner()
}
}
impl<const MAX_UTF16_CODE_UNITS: usize> TypeCodec for GameProfileString<MAX_UTF16_CODE_UNITS> {
fn encode(&self, writer: &mut impl std::io::Write) -> Result<(), CodecError> {
encode_prefixed_string(
&self.0,
writer,
CodecKind::String,
Self::MAX_BYTES,
MAX_UTF16_CODE_UNITS,
)
}
fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
decode_prefixed_string(
reader,
CodecKind::String,
Self::MAX_BYTES,
MAX_UTF16_CODE_UNITS,
)
.map(|(value, _)| Self(value))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct GameProfileStringTooLong {
pub max_code_units: usize,
pub actual_code_units: usize,
}
impl fmt::Display for GameProfileStringTooLong {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"game-profile string contains {} UTF-16 code units; maximum is {}",
self.actual_code_units, self.max_code_units
)
}
}
impl std::error::Error for GameProfileStringTooLong {}
pub type GameProfileUsername = GameProfileString<16>;
pub type GameProfilePropertyName = GameProfileString<64>;
pub type GameProfilePropertyValue = GameProfileString<32767>;
pub type GameProfilePropertySignature = GameProfileString<1024>;
#[derive(Debug, Clone, PartialEq, Eq, Hash, TypeStructCodec)]
#[type_struct_codec(kind = GameProfileProperty)]
pub struct GameProfileProperty {
pub name: GameProfilePropertyName,
pub value: GameProfilePropertyValue,
pub signature: PrefixedOptional<GameProfilePropertySignature>,
}
impl GameProfileProperty {
#[must_use]
pub const fn new(name: GameProfilePropertyName, value: GameProfilePropertyValue) -> Self {
Self {
name,
value,
signature: PrefixedOptional::none(),
}
}
}
#[repr(transparent)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct GameProfileProperties(Vec<GameProfileProperty>);
impl GameProfileProperties {
pub const MAX_LEN: usize = MAX_GAME_PROFILE_PROPERTIES;
pub fn new(properties: Vec<GameProfileProperty>) -> Result<Self, TooManyGameProfileProperties> {
if properties.len() > Self::MAX_LEN {
return Err(TooManyGameProfileProperties {
actual: properties.len(),
});
}
Ok(Self(properties))
}
#[must_use]
pub const fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[must_use]
pub const fn as_slice(&self) -> &[GameProfileProperty] {
self.0.as_slice()
}
#[must_use]
pub fn into_vec(self) -> Vec<GameProfileProperty> {
self.0
}
pub fn push(
&mut self,
property: GameProfileProperty,
) -> Result<(), TooManyGameProfileProperties> {
if self.len() == Self::MAX_LEN {
return Err(TooManyGameProfileProperties {
actual: self.len() + 1,
});
}
self.0.push(property);
Ok(())
}
}
impl TryFrom<Vec<GameProfileProperty>> for GameProfileProperties {
type Error = TooManyGameProfileProperties;
fn try_from(value: Vec<GameProfileProperty>) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<GameProfileProperties> for Vec<GameProfileProperty> {
fn from(value: GameProfileProperties) -> Self {
value.into_vec()
}
}
impl TypeCodec for GameProfileProperties {
fn encode(&self, writer: &mut impl std::io::Write) -> Result<(), CodecError> {
writer
.write_varint(self.len() as i32)
.map_err(|error| error.with_context(CodecKind::PrefixedArray))?;
for property in &self.0 {
property
.encode(writer)
.map_err(|error| error.with_context(CodecKind::PrefixedArray))?;
}
Ok(())
}
fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
let (length, prefix_size) = reader
.read_varint_with_size()
.map_err(|error| error.with_context(CodecKind::PrefixedArray))?;
if length < 0 {
return Err(CodecError::invalid_encoding(
CodecKind::PrefixedArray,
prefix_size,
InvalidEncodingReason::NegativeLength { value: length },
));
}
let length = length as usize;
if length > Self::MAX_LEN {
return Err(CodecError::invalid_encoding(
CodecKind::PrefixedArray,
prefix_size,
InvalidEncodingReason::LengthOutOfRange {
max: Self::MAX_LEN,
actual: length,
},
));
}
let mut properties = Vec::with_capacity(length);
for _ in 0..length {
properties.push(
GameProfileProperty::decode(reader)
.map_err(|error| error.with_context(CodecKind::PrefixedArray))?,
);
}
Ok(Self(properties))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TooManyGameProfileProperties {
pub actual: usize,
}
impl fmt::Display for TooManyGameProfileProperties {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"game profile contains {} properties; maximum is {}",
self.actual, MAX_GAME_PROFILE_PROPERTIES
)
}
}
impl std::error::Error for TooManyGameProfileProperties {}
#[derive(Debug, Clone, PartialEq, Eq, Hash, TypeStructCodec)]
#[type_struct_codec(kind = GameProfile)]
pub struct GameProfile {
pub uuid: Uuid,
pub username: GameProfileUsername,
pub properties: GameProfileProperties,
}
impl GameProfile {
#[must_use]
pub const fn new(uuid: Uuid, username: GameProfileUsername) -> Self {
Self {
uuid,
username,
properties: GameProfileProperties(Vec::new()),
}
}
}