Skip to main content

mcproto_types/entity_metadata/
metadata.rs

1//! Entity metadata entries, value types, and terminated list encoding.
2
3use std::{fmt, io::Read};
4
5use mcproto_codec::error::{CodecError, CodecKind, CodecOperation, InvalidEncodingReason};
6
7use crate::{
8    Boolean, Byte, Float, IdOr, PaintingVariant, Position, PrefixedString, ProtocolEnum,
9    ResolvableProfile, Slot, TextComponent, TypeCodec, UnsignedByte, VarInt, VarLong,
10};
11
12use super::{
13    ArmadilloState, BlockStateId, CatSoundVariantId, CatVariantId, ChickenSoundVariantId,
14    ChickenVariantId, CopperGolemState, CowSoundVariantId, CowVariantId, Direction, FrogVariantId,
15    HumanoidArm, OptionalBlockState, OptionalGlobalPosition, OptionalLivingEntityReference,
16    OptionalPosition, OptionalTextComponent, OptionalVarInt, Particle, Particles,
17    PigSoundVariantId, PigVariantId, Pose, Quaternion, Rotations, SnifferState, Vector3,
18    VillagerData, WeatheringCopperState, WolfSoundVariantId, WolfVariantId,
19    ZombieNautilusVariantId,
20};
21
22/// Error returned when `0xff` is used as an entity metadata entry index.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub struct InvalidEntityMetadataIndex {
25    index: u8,
26}
27
28impl InvalidEntityMetadataIndex {
29    /// Returns the rejected index.
30    #[must_use]
31    pub const fn index(self) -> u8 {
32        self.index
33    }
34}
35
36impl fmt::Display for InvalidEntityMetadataIndex {
37    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
38        write!(
39            formatter,
40            "entity metadata index 0x{:02X} is reserved as the terminator",
41            self.index
42        )
43    }
44}
45
46impl std::error::Error for InvalidEntityMetadataIndex {}
47
48/// A valid entity metadata index in the inclusive range `0..=254`.
49///
50/// Byte value `0xff` cannot be constructed because it terminates the complete
51/// [`EntityMetadata`] sequence.
52#[repr(transparent)]
53#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
54pub struct EntityMetadataIndex(u8);
55
56impl EntityMetadataIndex {
57    /// Largest index available to an entry.
58    pub const MAX: u8 = 0xfe;
59    /// Reserved byte that terminates an entity metadata sequence.
60    pub const TERMINATOR: u8 = 0xff;
61
62    /// Creates a valid metadata index.
63    pub const fn new(index: u8) -> Result<Self, InvalidEntityMetadataIndex> {
64        if index == Self::TERMINATOR {
65            Err(InvalidEntityMetadataIndex { index })
66        } else {
67            Ok(Self(index))
68        }
69    }
70
71    /// Returns the raw unsigned-byte index.
72    #[must_use]
73    pub const fn get(self) -> u8 {
74        self.0
75    }
76}
77
78impl TryFrom<u8> for EntityMetadataIndex {
79    type Error = InvalidEntityMetadataIndex;
80
81    fn try_from(value: u8) -> Result<Self, Self::Error> {
82        Self::new(value)
83    }
84}
85
86impl From<EntityMetadataIndex> for u8 {
87    fn from(value: EntityMetadataIndex) -> Self {
88        value.get()
89    }
90}
91
92impl TypeCodec for EntityMetadataIndex {
93    fn encode(&self, writer: &mut impl std::io::Write) -> Result<(), CodecError> {
94        UnsignedByte(self.0)
95            .encode(writer)
96            .map_err(|error| error.with_context(CodecKind::EntityMetadataEntry))
97    }
98
99    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
100        let index = UnsignedByte::decode(reader)
101            .map_err(|error| error.with_context(CodecKind::EntityMetadataEntry))?
102            .0;
103        Self::new(index).map_err(|_| {
104            CodecError::invalid_encoding(
105                CodecKind::EntityMetadataEntry,
106                1,
107                InvalidEncodingReason::InvalidEntityMetadataIndex { index },
108            )
109        })
110    }
111}
112
113macro_rules! define_metadata_values {
114    ($($id:literal => $variant:ident($payload:ty) = $name:literal,)*) => {
115        /// Numeric value-type selector used by an entity metadata entry.
116        ///
117        /// Storing this selector separately from a payload is unnecessary and
118        /// could permit mismatches. [`EntityMetadataValue`] is therefore the
119        /// primary wire value; this enum is exposed for inspection and registry
120        /// mapping, and is returned by [`EntityMetadataValue::value_type`].
121        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ProtocolEnum)]
122        #[protocol_enum(repr = VarInt)]
123        pub enum EntityMetadataValueType {
124            $(
125                #[doc = concat!("The `", $name, "` value type (ID `", stringify!($id), "`).")]
126                $variant = $id,
127            )*
128        }
129
130        /// A metadata type ID bound to exactly the payload required by that type.
131        ///
132        /// Every variant fixes both the VarInt type selector and its payload,
133        /// so an ID/value-layout mismatch cannot be represented in memory.
134        #[derive(Debug, Clone, PartialEq)]
135        pub enum EntityMetadataValue {
136            $(
137                #[doc = concat!("A `", $name, "` metadata value.")]
138                $variant($payload),
139            )*
140        }
141
142        impl EntityMetadataValue {
143            /// Returns this value's protocol type selector.
144            #[must_use]
145            pub const fn value_type(&self) -> EntityMetadataValueType {
146                match self {
147                    $(Self::$variant(_) => EntityMetadataValueType::$variant,)*
148                }
149            }
150
151            /// Returns the numeric VarInt type ID.
152            #[must_use]
153            pub fn type_id(&self) -> i32 {
154                self.value_type().discriminant() as i32
155            }
156        }
157
158        impl TypeCodec for EntityMetadataValue {
159            fn encode(
160                &self,
161                writer: &mut impl std::io::Write,
162            ) -> Result<(), CodecError> {
163                self.value_type()
164                    .encode(writer)
165                    .map_err(with_value_context)?;
166                match self {
167                    $(Self::$variant(value) => value.encode(writer).map_err(with_value_context),)*
168                }
169            }
170
171            fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
172                let value_type = EntityMetadataValueType::decode(reader)
173                    .map_err(with_value_context)?;
174                match value_type {
175                    $(
176                        EntityMetadataValueType::$variant => <$payload>::decode(reader)
177                            .map(Self::$variant)
178                            .map_err(with_value_context),
179                    )*
180                }
181            }
182        }
183    };
184}
185
186define_metadata_values! {
187    0 => Byte(Byte) = "Byte",
188    1 => VarInt(VarInt) = "VarInt",
189    2 => VarLong(VarLong) = "VarLong",
190    3 => Float(Float) = "Float",
191    4 => String(PrefixedString) = "String",
192    5 => TextComponent(TextComponent) = "Text Component",
193    6 => OptionalTextComponent(OptionalTextComponent) = "Optional Text Component",
194    7 => Slot(Slot) = "Slot",
195    8 => Boolean(Boolean) = "Boolean",
196    9 => Rotations(Rotations) = "Rotations",
197    10 => Position(Position) = "Position",
198    11 => OptionalPosition(OptionalPosition) = "Optional Position",
199    12 => Direction(Direction) = "Direction",
200    13 => OptionalLivingEntityReference(OptionalLivingEntityReference) = "Optional Living Entity Reference",
201    14 => BlockState(BlockStateId) = "Block State",
202    15 => OptionalBlockState(OptionalBlockState) = "Optional Block State",
203    16 => Particle(Particle) = "Particle",
204    17 => Particles(Particles) = "Particles",
205    18 => VillagerData(VillagerData) = "Villager Data",
206    19 => OptionalVarInt(OptionalVarInt) = "Optional VarInt",
207    20 => Pose(Pose) = "Pose",
208    21 => CatVariant(CatVariantId) = "Cat Variant",
209    22 => CatSoundVariant(CatSoundVariantId) = "Cat Sound Variant",
210    23 => CowVariant(CowVariantId) = "Cow Variant",
211    24 => CowSoundVariant(CowSoundVariantId) = "Cow Sound Variant",
212    25 => WolfVariant(WolfVariantId) = "Wolf Variant",
213    26 => WolfSoundVariant(WolfSoundVariantId) = "Wolf Sound Variant",
214    27 => FrogVariant(FrogVariantId) = "Frog Variant",
215    28 => PigVariant(PigVariantId) = "Pig Variant",
216    29 => PigSoundVariant(PigSoundVariantId) = "Pig Sound Variant",
217    30 => ChickenVariant(ChickenVariantId) = "Chicken Variant",
218    31 => ChickenSoundVariant(ChickenSoundVariantId) = "Chicken Sound Variant",
219    32 => ZombieNautilusVariant(ZombieNautilusVariantId) = "Zombie Nautilus Variant",
220    33 => OptionalGlobalPosition(OptionalGlobalPosition) = "Optional Global Position",
221    34 => PaintingVariant(IdOr<PaintingVariant>) = "Painting Variant",
222    35 => SnifferState(SnifferState) = "Sniffer State",
223    36 => ArmadilloState(ArmadilloState) = "Armadillo State",
224    37 => CopperGolemState(CopperGolemState) = "Copper Golem State",
225    38 => WeatheringCopperState(WeatheringCopperState) = "Weathering Copper State",
226    39 => Vector3(Vector3) = "Vector3",
227    40 => Quaternion(Quaternion) = "Quaternion",
228    41 => ResolvableProfile(ResolvableProfile) = "Resolvable Profile",
229    42 => HumanoidArm(HumanoidArm) = "Humanoid Arm",
230}
231
232fn with_value_context(error: CodecError) -> CodecError {
233    if error.context() == Some(CodecKind::EntityMetadataValue) {
234        error
235    } else {
236        error.with_context(CodecKind::EntityMetadataValue)
237    }
238}
239
240/// One index and one type-safe value in an entity metadata sequence.
241#[derive(Debug, Clone, PartialEq)]
242pub struct EntityMetadataEntry {
243    index: EntityMetadataIndex,
244    value: EntityMetadataValue,
245}
246
247impl EntityMetadataEntry {
248    /// Creates an entry from an already validated index.
249    #[must_use]
250    pub const fn new(index: EntityMetadataIndex, value: EntityMetadataValue) -> Self {
251        Self { index, value }
252    }
253
254    /// Creates an entry from a raw index, rejecting the `0xff` terminator.
255    pub fn try_new(
256        index: u8,
257        value: EntityMetadataValue,
258    ) -> Result<Self, InvalidEntityMetadataIndex> {
259        Ok(Self::new(EntityMetadataIndex::new(index)?, value))
260    }
261
262    /// Returns this entry's unique index key.
263    #[must_use]
264    pub const fn index(&self) -> EntityMetadataIndex {
265        self.index
266    }
267
268    /// Returns this entry's typed value.
269    #[must_use]
270    pub const fn value(&self) -> &EntityMetadataValue {
271        &self.value
272    }
273
274    /// Extracts the typed value.
275    #[must_use]
276    pub fn into_value(self) -> EntityMetadataValue {
277        self.value
278    }
279}
280
281impl TypeCodec for EntityMetadataEntry {
282    fn encode(&self, writer: &mut impl std::io::Write) -> Result<(), CodecError> {
283        self.index.encode(writer)?;
284        self.value
285            .encode(writer)
286            .map_err(|error| error.with_context(CodecKind::EntityMetadataEntry))
287    }
288
289    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
290        let index = EntityMetadataIndex::decode(reader)?;
291        let value = EntityMetadataValue::decode(reader)
292            .map_err(|error| error.with_context(CodecKind::EntityMetadataEntry))?;
293        Ok(Self { index, value })
294    }
295}
296
297/// Error returned when an entity metadata sequence repeats an index.
298#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
299pub struct DuplicateEntityMetadataIndex {
300    index: EntityMetadataIndex,
301}
302
303impl DuplicateEntityMetadataIndex {
304    /// Returns the repeated index.
305    #[must_use]
306    pub const fn index(self) -> EntityMetadataIndex {
307        self.index
308    }
309}
310
311impl fmt::Display for DuplicateEntityMetadataIndex {
312    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
313        write!(
314            formatter,
315            "duplicate entity metadata index {}",
316            self.index.get()
317        )
318    }
319}
320
321impl std::error::Error for DuplicateEntityMetadataIndex {}
322
323/// A complete `0xff`-terminated entity metadata sequence.
324///
325/// Entry indices are unique and can only be in `0..=254`; the list codec always
326/// appends `0xff` and consumes no bytes after that terminator. Metadata fields
327/// may be omitted, so an empty value is encoded as the terminator alone.
328///
329/// # Examples
330///
331/// ```
332/// use mcproto_types::{
333///     Byte, EntityMetadata, EntityMetadataEntry, EntityMetadataValue, TypeCodec,
334/// };
335///
336/// let metadata = EntityMetadata::new(vec![EntityMetadataEntry::try_new(
337///     0,
338///     EntityMetadataValue::Byte(Byte(0x20)),
339/// )?])?;
340///
341/// let mut encoded = Vec::new();
342/// metadata.encode(&mut encoded)?;
343/// assert_eq!(encoded, [0x00, 0x00, 0x20, 0xff]);
344///
345/// let mut input = encoded.as_slice();
346/// assert_eq!(EntityMetadata::decode(&mut input)?, metadata);
347/// assert!(input.is_empty());
348/// # Ok::<(), Box<dyn std::error::Error>>(())
349/// ```
350///
351/// See the official [Entity Metadata Format] documentation.
352///
353/// [Entity Metadata Format]: https://minecraft.wiki/w/Java_Edition_protocol/Entity_metadata#Entity_Metadata_Format
354#[derive(Debug, Clone, PartialEq, Default)]
355pub struct EntityMetadata {
356    entries: Vec<EntityMetadataEntry>,
357}
358
359impl EntityMetadata {
360    /// Creates a metadata sequence after checking that all indices are unique.
361    pub fn new(entries: Vec<EntityMetadataEntry>) -> Result<Self, DuplicateEntityMetadataIndex> {
362        validate_unique_indices(&entries)?;
363        Ok(Self { entries })
364    }
365
366    /// Returns the entries in wire order.
367    #[must_use]
368    pub fn entries(&self) -> &[EntityMetadataEntry] {
369        &self.entries
370    }
371
372    /// Extracts the entries in wire order.
373    #[must_use]
374    pub fn into_entries(self) -> Vec<EntityMetadataEntry> {
375        self.entries
376    }
377
378    /// Returns the number of metadata entries, excluding the terminator.
379    #[must_use]
380    pub const fn len(&self) -> usize {
381        self.entries.len()
382    }
383
384    /// Returns whether this sequence contains no entries.
385    #[must_use]
386    pub const fn is_empty(&self) -> bool {
387        self.entries.is_empty()
388    }
389
390    /// Returns the entry at `index`, if present.
391    #[must_use]
392    pub fn get(&self, index: EntityMetadataIndex) -> Option<&EntityMetadataEntry> {
393        self.entries.iter().find(|entry| entry.index == index)
394    }
395
396    /// Appends an entry if its index is not already present.
397    pub fn push(&mut self, entry: EntityMetadataEntry) -> Result<(), DuplicateEntityMetadataIndex> {
398        if self.get(entry.index).is_some() {
399            return Err(DuplicateEntityMetadataIndex { index: entry.index });
400        }
401        self.entries.push(entry);
402        Ok(())
403    }
404}
405
406impl TypeCodec for EntityMetadata {
407    fn encode(&self, writer: &mut impl std::io::Write) -> Result<(), CodecError> {
408        if let Err(error) = validate_unique_indices(&self.entries) {
409            return Err(CodecError::invalid_encoding_for_operation(
410                CodecKind::EntityMetadata,
411                CodecOperation::Write,
412                0,
413                InvalidEncodingReason::DuplicateEntityMetadataIndex {
414                    index: error.index.get(),
415                },
416            ));
417        }
418        for entry in &self.entries {
419            entry
420                .encode(writer)
421                .map_err(|error| error.with_context(CodecKind::EntityMetadata))?;
422        }
423        UnsignedByte(EntityMetadataIndex::TERMINATOR)
424            .encode(writer)
425            .map_err(|error| error.with_context(CodecKind::EntityMetadata))
426    }
427
428    fn decode(reader: &mut impl Read) -> Result<Self, CodecError> {
429        let mut entries = Vec::new();
430        let mut seen = [false; EntityMetadataIndex::TERMINATOR as usize];
431        loop {
432            let index = UnsignedByte::decode(reader)
433                .map_err(|error| error.with_context(CodecKind::EntityMetadata))?
434                .0;
435            if index == EntityMetadataIndex::TERMINATOR {
436                return Ok(Self { entries });
437            }
438            if seen[index as usize] {
439                return Err(CodecError::invalid_encoding(
440                    CodecKind::EntityMetadata,
441                    0,
442                    InvalidEncodingReason::DuplicateEntityMetadataIndex { index },
443                ));
444            }
445            seen[index as usize] = true;
446            let value = EntityMetadataValue::decode(reader)
447                .map_err(|error| error.with_context(CodecKind::EntityMetadataEntry))
448                .map_err(|error| error.with_context(CodecKind::EntityMetadata))?;
449            entries.push(EntityMetadataEntry {
450                index: EntityMetadataIndex(index),
451                value,
452            });
453        }
454    }
455}
456
457fn validate_unique_indices(
458    entries: &[EntityMetadataEntry],
459) -> Result<(), DuplicateEntityMetadataIndex> {
460    let mut seen = [false; EntityMetadataIndex::TERMINATOR as usize];
461    for entry in entries {
462        let index = entry.index.get() as usize;
463        if seen[index] {
464            return Err(DuplicateEntityMetadataIndex { index: entry.index });
465        }
466        seen[index] = true;
467    }
468    Ok(())
469}