1
0
Fork 0
mirror of https://github.com/azalea-rs/simdnbt.git synced 2025-08-02 23:44:40 +00:00

additional optimizations

This commit is contained in:
mat 2025-05-03 09:19:27 -13:00
commit 27a1b63ace
6 changed files with 361 additions and 173 deletions

View file

@ -70,7 +70,6 @@ pub fn deserialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenSt
let where_clause = &generics.where_clause;
let struct_attrs = attrs::parse_struct_attrs(&input.attrs);
let deny_unknown_fields = struct_attrs.deny_unknown_fields;
// let extra_checks = if struct_attrs.deny_unknown_fields {
// quote! {
@ -162,25 +161,39 @@ pub fn deserialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenSt
let nbt_name = &field_data.nbt_name;
update_partial_inner.extend(if field_data.is_flatten {
quote! {
if <#field_type>::update_partial(&mut partial.#index, tag_name, tag_type, data)? {
if <#field_type>::update_partial(&mut partial.#index, validator, tag_name, tag_type, data)? {
} else
}
} else {
quote! {
if <#field_type>::type_matches(tag_type) && tag_name == #nbt_name {
partial.#index = Some(simdnbt::Deserialize::read_value_direct_with_explicit_type(data, tag_type)?);
partial.#index = Some(simdnbt::Deserialize::read_value_direct_with_explicit_type(data, validator, tag_type)?);
} else
}
});
}
let on_unknown_field = if struct_attrs.deny_unknown_fields {
quote! {
return Err(simdnbt::DeserializeError::UnknownField(
tag_name.to_str().into(),
));
}
} else {
quote! {
// skip the field
validator.internal_read_tag(data, tag_type)?;
continue;
}
};
let output = quote! {
impl #generics simdnbt::Deserialize<#data_lifetime> for #ident #generics #where_clause {
const NBT_TYPE_ID: u8 = simdnbt::common::COMPOUND_ID;
type Partial<'PARTIAL> = (#partial_type_inner);
#[inline]
fn read_value_direct(data: &mut simdnbt::reader::Reader<#data_lifetime>) -> Result<Self, simdnbt::DeserializeError> {
fn read_value_direct(data: &mut simdnbt::reader::Reader<#data_lifetime>, validator: &mut simdnbt::validate::NbtValidator) -> Result<Self, simdnbt::DeserializeError> {
let mut partial = Self::Partial::default();
loop {
@ -190,20 +203,11 @@ pub fn deserialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenSt
}
let tag_name = simdnbt::common::read_string(data)?;
let matched = Self::update_partial(&mut partial, tag_name, tag_type, data)?;
let matched = Self::update_partial(&mut partial, validator, tag_name, tag_type, data)?;
if !matched {
let field_name = #name_to_field_if_statements {
if #deny_unknown_fields {
return Err(simdnbt::DeserializeError::UnknownField(
tag_name.to_str().into(),
));
}
// skip the field
simdnbt::validate::internal_read_tag(data, tag_type)?;
continue;
#on_unknown_field
};
return Err(simdnbt::DeserializeError::MismatchedFieldType(field_name));
@ -216,6 +220,7 @@ pub fn deserialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenSt
#[inline]
fn update_partial(
partial: &mut Self::Partial<'_>,
validator: &mut simdnbt::validate::NbtValidator,
tag_name: &simdnbt::Mutf8Str,
tag_type: u8,
data: &mut simdnbt::reader::Reader<#data_lifetime>,

View file

@ -40,7 +40,9 @@ fn bench_read_file(filename: &str, c: &mut Criterion) {
});
group.bench_function("simdnbt_validate_parse", |b| {
b.iter(|| {
simdnbt::validate::read(&mut input_stream).unwrap();
simdnbt::validate::NbtValidator::new()
.read(&mut input_stream)
.unwrap();
input_stream.set_position(0);
})
});
@ -76,10 +78,10 @@ pub struct Item<'a> {
#[derive(Deserialize, Debug, Clone, PartialEq)]
pub struct ItemTag<'a> {
#[simdnbt(rename = "SkullOwner")]
pub skull_owner: Option<SkullOwner<'a>>,
pub skull_owner: Option<Box<SkullOwner<'a>>>,
#[simdnbt(rename = "ExtraAttributes")]
pub extra_attributes: Option<ExtraAttributes<'a>>,
pub display: Option<ItemDisplay<'a>>,
pub extra_attributes: Option<Box<ExtraAttributes<'a>>>,
pub display: Option<Box<ItemDisplay<'a>>>,
}
#[derive(Deserialize, Debug, Clone, PartialEq)]

View file

@ -28,7 +28,7 @@ impl<T, A: Allocator> FastVec<T, A> {
// this is faster than Layout::array
alloc::alloc(Layout::from_size_align_unchecked(
capacity * element_size,
element_size,
Self::alignment(),
))
};
let ptr = NonNull::new(ptr as *mut T).expect("allocation failed");
@ -41,6 +41,11 @@ impl<T, A: Allocator> FastVec<T, A> {
}
}
#[inline]
fn alignment() -> usize {
mem::align_of::<T>()
}
#[inline(always)]
pub fn push(&mut self, element: T) {
if self.cur == self.end {
@ -72,8 +77,8 @@ impl<T, A: Allocator> FastVec<T, A> {
let new_ptr = unsafe {
self.alloc.grow(
self.ptr,
Layout::from_size_align_unchecked(old_cap_bytes, element_size),
Layout::from_size_align_unchecked(new_cap_bytes, element_size),
Layout::from_size_align_unchecked(old_cap_bytes, Self::alignment()),
Layout::from_size_align_unchecked(new_cap_bytes, Self::alignment()),
)
};
let new_ptr = new_ptr.expect("allocation failed");
@ -99,8 +104,8 @@ impl<T, A: Allocator> FastVec<T, A> {
let new_ptr = unsafe {
self.alloc.grow(
self.ptr,
Layout::from_size_align_unchecked(old_cap_bytes, element_size),
Layout::from_size_align_unchecked(new_cap_bytes, element_size),
Layout::from_size_align_unchecked(old_cap_bytes, Self::alignment()),
Layout::from_size_align_unchecked(new_cap_bytes, Self::alignment()),
)
};
let new_ptr = new_ptr.expect("allocation failed");
@ -202,11 +207,22 @@ impl<T> FastVec<T, alloc::Global> {
Self::with_capacity_in(capacity, alloc::Global)
}
/// Create a new FastVec with the default capacity of 1.
pub fn new() -> Self {
Self::default()
}
pub unsafe fn from_raw_parts(ptr: *mut T, len: usize, capacity: usize) -> Self {
Self::from_raw_parts_in(ptr, len, capacity, alloc::Global)
}
}
/// A [`FastVec`] that allows having a capacity of 0.
pub enum OptionalFastVec<T> {
Empty,
FastVec(FastVec<T>),
}
const DEFAULT_CAPACITY: usize = 1;
impl<T> Default for FastVec<T> {
@ -216,7 +232,7 @@ impl<T> Default for FastVec<T> {
// this is faster than Layout::array
alloc::alloc(Layout::from_size_align_unchecked(
DEFAULT_CAPACITY * element_size,
element_size,
Self::alignment(),
))
};
let ptr = NonNull::new(ptr as *mut T).expect("allocation failed");

View file

@ -4,9 +4,11 @@ use byteorder::ReadBytesExt;
use crate::{
common::{self, read_string},
fastvec::{FastVec, OptionalFastVec},
mutf8::Mutf8String,
owned,
reader::{Reader, ReaderFromCursor},
validate::NbtValidator,
DeserializeError, Error, Mutf8Str,
};
@ -25,22 +27,27 @@ pub trait Deserialize<'a>: Sized {
let mut data = ReaderFromCursor::new(data);
let name = read_string(&mut data)?;
let mut validator = NbtValidator::new();
Ok((
name,
Self::read_value_direct_with_explicit_type(&mut data, root_type)?,
Self::read_value_direct_with_explicit_type(&mut data, &mut validator, root_type)?,
))
}
fn read_value_direct(data: &mut Reader<'a>) -> Result<Self, DeserializeError>;
fn read_value_direct(
data: &mut Reader<'a>,
validator: &mut NbtValidator,
) -> Result<Self, DeserializeError>;
#[inline]
fn read_value_direct_with_explicit_type(
data: &mut Reader<'a>,
validator: &mut NbtValidator,
type_id: u8,
) -> Result<Self, DeserializeError> {
debug_assert_eq!(type_id, Self::NBT_TYPE_ID);
Self::read_value_direct(data)
Self::read_value_direct(data, validator)
}
#[inline]
@ -51,6 +58,7 @@ pub trait Deserialize<'a>: Sized {
#[inline]
fn update_partial(
_partial: &mut Self::Partial<'_>,
_validator: &mut NbtValidator,
_name: &Mutf8Str,
_tag_type: u8,
_data: &mut Reader<'a>,
@ -89,7 +97,10 @@ impl<'a, K: From<&'a Mutf8Str> + Eq + Hash, V: Deserialize<'a>> Deserialize<'a>
const NBT_TYPE_ID: u8 = common::COMPOUND_ID;
type Partial<'b> = HashMap<K, V>;
fn read_value_direct(data: &mut Reader<'a>) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader<'a>,
validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
let mut map = HashMap::new();
loop {
@ -102,7 +113,7 @@ impl<'a, K: From<&'a Mutf8Str> + Eq + Hash, V: Deserialize<'a>> Deserialize<'a>
if !V::type_matches(tag_type) {
return Err(DeserializeError::MismatchedFieldType("HashMap"));
}
let value = V::read_value_direct_with_explicit_type(data, tag_type)?;
let value = V::read_value_direct_with_explicit_type(data, validator, tag_type)?;
map.insert(name.into(), value);
}
@ -114,8 +125,11 @@ impl<'a, K: From<&'a Mutf8Str> + Eq + Hash, V: Deserialize<'a>> Deserialize<'a>
const NBT_TYPE_ID: u8 = common::COMPOUND_ID;
type Partial<'b> = HashMap<K, V>;
fn read_value_direct(data: &mut Reader<'a>) -> Result<Self, DeserializeError> {
let mut map = Vec::new();
fn read_value_direct(
data: &mut Reader<'a>,
validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
let mut map = FastVec::new();
loop {
let tag_type = data.read_u8()?;
@ -127,12 +141,12 @@ impl<'a, K: From<&'a Mutf8Str> + Eq + Hash, V: Deserialize<'a>> Deserialize<'a>
if !V::type_matches(tag_type) {
return Err(DeserializeError::MismatchedFieldType("HashMap"));
}
let value = V::read_value_direct_with_explicit_type(data, tag_type)?;
let value = V::read_value_direct_with_explicit_type(data, validator, tag_type)?;
map.push((name.into(), value));
}
Ok(map)
Ok(map.into())
}
}
impl<K: Into<Mutf8String> + Eq + Hash, V: ToNbtTag> Serialize for HashMap<K, V> {
@ -152,12 +166,16 @@ impl Deserialize<'_> for owned::NbtCompound {
type Partial<'b> = owned::NbtCompound;
fn read_value_direct(data: &mut Reader<'_>) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader<'_>,
_validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
owned::NbtCompound::read(data).map_err(Into::into)
}
fn update_partial(
partial: &mut Self::Partial<'_>,
_validator: &mut NbtValidator,
name: &Mutf8Str,
tag_type: u8,
data: &mut Reader<'_>,
@ -188,12 +206,16 @@ impl Deserialize<'_> for owned::NbtTag {
type Partial<'b> = ();
fn read_value_direct(_data: &mut Reader<'_>) -> Result<Self, DeserializeError> {
fn read_value_direct(
_data: &mut Reader<'_>,
_validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
unimplemented!("can't deserialize an NbtTag without the type being known")
}
fn read_value_direct_with_explicit_type(
data: &mut Reader<'_>,
_validator: &mut NbtValidator,
type_id: u8,
) -> Result<Self, DeserializeError> {
let tag = owned::NbtTag::read_with_type(data, type_id, 0).map_err(Error::from)?;
@ -211,7 +233,10 @@ impl Deserialize<'_> for i8 {
const NBT_TYPE_ID: u8 = common::BYTE_ID;
type Partial<'b> = ();
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader,
_validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
data.read_i8().map_err(Into::into)
}
}
@ -225,7 +250,10 @@ impl Deserialize<'_> for i16 {
const NBT_TYPE_ID: u8 = common::SHORT_ID;
type Partial<'b> = ();
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader,
_validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
data.read_i16().map_err(Into::into)
}
}
@ -239,7 +267,10 @@ impl Deserialize<'_> for i32 {
const NBT_TYPE_ID: u8 = common::INT_ID;
type Partial<'b> = ();
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader,
_validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
data.read_i32().map_err(Into::into)
}
}
@ -253,7 +284,10 @@ impl Deserialize<'_> for i64 {
const NBT_TYPE_ID: u8 = common::LONG_ID;
type Partial<'b> = ();
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader,
_validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
data.read_i64().map_err(Into::into)
}
}
@ -267,7 +301,10 @@ impl Deserialize<'_> for f32 {
const NBT_TYPE_ID: u8 = common::FLOAT_ID;
type Partial<'b> = ();
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader,
_validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
data.read_f32().map_err(Into::into)
}
}
@ -281,7 +318,10 @@ impl Deserialize<'_> for f64 {
const NBT_TYPE_ID: u8 = common::DOUBLE_ID;
type Partial<'b> = ();
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader,
_validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
data.read_f64().map_err(Into::into)
}
}
@ -295,7 +335,10 @@ impl Deserialize<'_> for String {
const NBT_TYPE_ID: u8 = common::STRING_ID;
type Partial<'b> = ();
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader,
_validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
let str = common::read_string(data)?;
Ok(str.to_string())
}
@ -304,7 +347,10 @@ impl<'a> Deserialize<'a> for &'a Mutf8Str {
const NBT_TYPE_ID: u8 = common::STRING_ID;
type Partial<'b> = ();
fn read_value_direct(data: &mut Reader<'a>) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader<'a>,
_validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
common::read_string(data).map_err(Into::into)
}
}
@ -312,7 +358,10 @@ impl<'a> Deserialize<'a> for Cow<'a, str> {
const NBT_TYPE_ID: u8 = common::STRING_ID;
type Partial<'b> = ();
fn read_value_direct(data: &mut Reader<'a>) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader<'a>,
_validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
common::read_string(data)
.map_err(Into::into)
.map(|s| s.to_str())
@ -351,7 +400,10 @@ impl Deserialize<'_> for u8 {
const NBT_TYPE_ID: u8 = common::BYTE_ID;
type Partial<'b> = ();
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader,
_validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
data.read_u8().map_err(Into::into)
}
}
@ -365,7 +417,10 @@ impl Deserialize<'_> for u16 {
const NBT_TYPE_ID: u8 = common::SHORT_ID;
type Partial<'b> = ();
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader,
_validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
data.read_u16().map_err(Into::into)
}
}
@ -379,7 +434,10 @@ impl Deserialize<'_> for u32 {
const NBT_TYPE_ID: u8 = common::INT_ID;
type Partial<'b> = ();
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader,
_validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
data.read_u32().map_err(Into::into)
}
}
@ -393,7 +451,10 @@ impl Deserialize<'_> for u64 {
const NBT_TYPE_ID: u8 = common::LONG_ID;
type Partial<'b> = ();
fn read_value_direct(data: &mut Reader) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader,
_validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
data.read_u64().map_err(Into::into)
}
}
@ -417,7 +478,10 @@ impl<'a, T: Deserialize<'a>> Deserialize<'a> for Option<T> {
const NBT_TYPE_ID: u8 = T::NBT_TYPE_ID;
type Partial<'b> = Option<T>;
fn read_value_direct(data: &mut Reader<'a>) -> Result<Self, DeserializeError> {
fn read_value_direct(
data: &mut Reader<'a>,
validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
// empty compounds also count as None
if Self::NBT_TYPE_ID == common::COMPOUND_ID {
let next_tag_type = data.peek_u8()?;
@ -427,9 +491,10 @@ impl<'a, T: Deserialize<'a>> Deserialize<'a> for Option<T> {
}
}
Ok(Some(T::read_value_direct(data)?))
Ok(Some(T::read_value_direct(data, validator)?))
}
#[inline]
fn try_flatten_with_option(other: Option<Self>) -> Option<Self> {
Some(other.flatten())
}
@ -456,28 +521,85 @@ impl<T: Serialize> ToNbtTag for Vec<Option<T>> {
}
}
impl<'a, T: Deserialize<'a>> Deserialize<'a> for Vec<T> {
impl<'a, T: Deserialize<'a>> Deserialize<'a> for Box<T> {
const NBT_TYPE_ID: u8 = T::NBT_TYPE_ID;
type Partial<'b> = T::Partial<'b>;
fn read_value_direct(
data: &mut Reader<'a>,
validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
T::read_value_direct(data, validator).map(Box::new)
}
fn update_partial(
partial: &mut Self::Partial<'_>,
validator: &mut NbtValidator,
name: &Mutf8Str,
tag_type: u8,
data: &mut Reader<'a>,
) -> Result<bool, DeserializeError> {
T::update_partial(partial, validator, name, tag_type, data)
}
fn from_partial(partial: Self::Partial<'_>) -> Result<Self, DeserializeError> {
T::from_partial(partial).map(Box::new)
}
}
impl<'a, T: Deserialize<'a>> Deserialize<'a> for OptionalFastVec<T> {
const NBT_TYPE_ID: u8 = common::LIST_ID;
type Partial<'b> = ();
fn read_value_direct(data: &mut Reader<'a>) -> Result<Self, DeserializeError> {
#[inline(always)]
fn read_value_direct(
data: &mut Reader<'a>,
validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
let tag_type = data.read_u8()?;
let list_length = data.read_i32()?;
if tag_type == common::END_ID || list_length <= 0 {
return Ok(Vec::new());
return Ok(OptionalFastVec::Empty);
}
if !T::type_matches(tag_type) {
return Err(DeserializeError::MismatchedListType(tag_type));
}
let mut vec = Vec::with_capacity(list_length.min(128) as usize);
let mut vec = FastVec::with_capacity((list_length as usize).next_power_of_two().min(128));
for _ in 0..list_length {
vec.push(T::read_value_direct_with_explicit_type(data, tag_type)?);
let value = T::read_value_direct_with_explicit_type(data, validator, tag_type)?;
vec.push(value);
}
Ok(vec)
Ok(OptionalFastVec::FastVec(vec))
}
}
impl<'a, T: Deserialize<'a>> Deserialize<'a> for Vec<T> {
const NBT_TYPE_ID: u8 = common::LIST_ID;
type Partial<'b> = ();
fn read_value_direct(
data: &mut Reader<'a>,
validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
OptionalFastVec::<T>::read_value_direct(data, validator).map(|v| match v {
OptionalFastVec::Empty => Vec::new(),
OptionalFastVec::FastVec(v) => v.into(),
})
}
}
impl<'a, T: Deserialize<'a>> Deserialize<'a> for Box<[T]> {
const NBT_TYPE_ID: u8 = common::LIST_ID;
type Partial<'b> = ();
fn read_value_direct(
data: &mut Reader<'a>,
validator: &mut NbtValidator,
) -> Result<Self, DeserializeError> {
Vec::<T>::read_value_direct(data, validator).map(|v| v.into_boxed_slice())
}
}
impl<T: Serialize> ToNbtTag for Vec<T> {
fn to_nbt_tag(self) -> owned::NbtTag {
owned::NbtTag::List(owned::NbtList::Compound(

View file

@ -48,6 +48,11 @@ impl ParsingStack {
}
}
pub fn clear(&mut self) {
self.depth = 0;
// the other two fields are reset as they're used
}
#[inline]
pub fn push(&mut self, state: ParsingStackElementKind) -> Result<(), NonRootError> {
unsafe { self.stack.get_unchecked_mut(self.depth).write(state) };

View file

@ -21,105 +21,119 @@ use crate::{
Error,
};
/// Read a normal root NBT compound. This is either empty or has a name and
/// compound tag.
pub fn read(data: &mut Cursor<&[u8]>) -> Result<(), Error> {
let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if root_type == END_ID {
return Ok(());
}
if root_type != COMPOUND_ID {
return Err(Error::InvalidRootType(root_type));
}
// our Reader type is faster than Cursor
let mut data = ReaderFromCursor::new(data);
read_string(&mut data)?;
let mut stack = ParsingStack::new();
stack.push(ParsingStackElementKind::Compound)?;
read_with_stack(&mut data, &mut stack)?;
Ok(())
pub struct NbtValidator {
stack: ParsingStack,
}
/// Read a root NBT compound, but without reading the name. This is used in
/// Minecraft when reading NBT over the network.
///
/// This is similar to [`read_tag`], but only allows the data to be empty or a
/// compound.
pub fn read_unnamed(data: &mut Cursor<&[u8]>) -> Result<(), Error> {
let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if root_type == END_ID {
return Ok(());
}
if root_type != COMPOUND_ID {
return Err(Error::InvalidRootType(root_type));
}
read_compound(data)?;
Ok(())
}
/// Read a compound tag. This may have any number of items.
pub fn read_compound(data: &mut Cursor<&[u8]>) -> Result<(), Error> {
let mut stack = ParsingStack::new();
let mut data = ReaderFromCursor::new(data);
stack.push(ParsingStackElementKind::Compound)?;
read_with_stack(&mut data, &mut stack)?;
Ok(())
}
/// Read an NBT tag, without reading its name. This may be any type of tag
/// except for an end tag. If you need to be able to handle end tags, use
/// [`read_optional_tag`].
pub fn read_tag(data: &mut Cursor<&[u8]>) -> Result<(), Error> {
let mut stack = ParsingStack::new();
let mut data = ReaderFromCursor::new(data);
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if tag_type == END_ID {
return Err(Error::InvalidRootType(0));
}
compound::read_tag(&mut data, &mut stack, tag_type)?;
read_with_stack(&mut data, &mut stack)?;
Ok(())
}
#[doc(hidden)]
pub fn internal_read_tag(data: &mut Reader, tag_type: u8) -> Result<(), Error> {
let mut stack = ParsingStack::new();
compound::read_tag(data, &mut stack, tag_type)?;
read_with_stack(data, &mut stack)?;
Ok(())
}
/// Read any NBT tag, without reading its name. This may be any type of tag,
/// including an end tag.
pub fn read_optional_tag(data: &mut Cursor<&[u8]>) -> Result<(), Error> {
let mut stack = ParsingStack::new();
let mut data = ReaderFromCursor::new(data);
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if tag_type == END_ID {
return Ok(());
}
compound::read_tag(&mut data, &mut stack, tag_type)?;
read_with_stack(&mut data, &mut stack)?;
Ok(())
}
fn read_with_stack<'a>(data: &mut Reader<'a>, stack: &mut ParsingStack) -> Result<(), Error> {
while !stack.is_empty() {
let top = stack.peek();
match top {
ParsingStackElementKind::Compound => read_tag_in_compound(data, stack)?,
ParsingStackElementKind::ListOfCompounds => read_compound_in_list(data, stack)?,
ParsingStackElementKind::ListOfLists => read_list_in_list(data, stack)?,
impl NbtValidator {
pub fn new() -> Self {
Self {
stack: ParsingStack::new(),
}
}
Ok(())
/// Read a normal root NBT compound. This is either empty or has a name and
/// compound tag.
pub fn read(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> {
let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if root_type == END_ID {
return Ok(());
}
if root_type != COMPOUND_ID {
return Err(Error::InvalidRootType(root_type));
}
// our Reader type is faster than Cursor
let mut data = ReaderFromCursor::new(data);
read_string(&mut data)?;
self.stack.clear();
self.stack.push(ParsingStackElementKind::Compound)?;
self.read_with_stack(&mut data)?;
Ok(())
}
#[doc(hidden)]
pub fn internal_read_tag(&mut self, data: &mut Reader, tag_type: u8) -> Result<(), Error> {
self.stack.clear();
compound::read_tag(data, &mut self.stack, tag_type)?;
self.read_with_stack(data)?;
Ok(())
}
/// Read a root NBT compound, but without reading the name. This is used in
/// Minecraft when reading NBT over the network.
///
/// This is similar to [`read_tag`], but only allows the data to be empty or
/// a compound.
pub fn read_unnamed(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> {
let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if root_type == END_ID {
return Ok(());
}
if root_type != COMPOUND_ID {
return Err(Error::InvalidRootType(root_type));
}
self.read_compound(data)?;
Ok(())
}
/// Read a compound tag. This may have any number of items.
pub fn read_compound(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> {
self.stack.clear();
let mut data = ReaderFromCursor::new(data);
self.stack.push(ParsingStackElementKind::Compound)?;
self.read_with_stack(&mut data)?;
Ok(())
}
/// Read an NBT tag, without reading its name. This may be any type of tag
/// except for an end tag. If you need to be able to handle end tags, use
/// [`read_optional_tag`].
pub fn read_tag(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> {
self.stack.clear();
let mut data = ReaderFromCursor::new(data);
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if tag_type == END_ID {
return Err(Error::InvalidRootType(0));
}
compound::read_tag(&mut data, &mut self.stack, tag_type)?;
self.read_with_stack(&mut data)?;
Ok(())
}
/// Read any NBT tag, without reading its name. This may be any type of tag,
/// including an end tag.
pub fn read_optional_tag(&mut self, data: &mut Cursor<&[u8]>) -> Result<(), Error> {
let mut stack = ParsingStack::new();
let mut data = ReaderFromCursor::new(data);
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if tag_type == END_ID {
return Ok(());
}
compound::read_tag(&mut data, &mut stack, tag_type)?;
self.read_with_stack(&mut data)?;
Ok(())
}
fn read_with_stack<'a>(&mut self, data: &mut Reader<'a>) -> Result<(), Error> {
while !self.stack.is_empty() {
let top = self.stack.peek();
match top {
ParsingStackElementKind::Compound => read_tag_in_compound(data, &mut self.stack)?,
ParsingStackElementKind::ListOfCompounds => {
read_compound_in_list(data, &mut self.stack)?
}
ParsingStackElementKind::ListOfLists => read_list_in_list(data, &mut self.stack)?,
}
}
Ok(())
}
}
#[cfg(test)]
@ -134,10 +148,11 @@ mod tests {
#[test]
fn hello_world() {
super::read(&mut Cursor::new(include_bytes!(
"../../tests/hello_world.nbt"
)))
.unwrap();
super::NbtValidator::new()
.read(&mut Cursor::new(include_bytes!(
"../../tests/hello_world.nbt"
)))
.unwrap();
}
#[test]
@ -147,7 +162,9 @@ mod tests {
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
let mut decoded_src = Vec::new();
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
super::read(&mut Cursor::new(&decoded_src)).unwrap();
super::NbtValidator::new()
.read(&mut Cursor::new(&decoded_src))
.unwrap();
}
#[test]
@ -157,21 +174,26 @@ mod tests {
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
let mut decoded_src = Vec::new();
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
super::read(&mut Cursor::new(&decoded_src)).unwrap();
super::NbtValidator::new()
.read(&mut Cursor::new(&decoded_src))
.unwrap();
}
#[test]
fn read_hypixel() {
let src = include_bytes!("../../tests/hypixel.nbt").to_vec();
super::read(&mut Cursor::new(&src[..])).unwrap();
super::NbtValidator::new()
.read(&mut Cursor::new(&src[..]))
.unwrap();
}
#[test]
fn inttest_1023() {
super::read(&mut Cursor::new(include_bytes!(
"../../tests/inttest1023.nbt"
)))
.unwrap();
super::NbtValidator::new()
.read(&mut Cursor::new(include_bytes!(
"../../tests/inttest1023.nbt"
)))
.unwrap();
}
#[test]
@ -188,7 +210,9 @@ mod tests {
}
data.write_u8(END_ID).unwrap();
super::read(&mut Cursor::new(&data)).unwrap();
super::NbtValidator::new()
.read(&mut Cursor::new(&data))
.unwrap();
}
#[test]
@ -205,7 +229,9 @@ mod tests {
}
data.write_u8(END_ID).unwrap();
super::read(&mut Cursor::new(&data)).unwrap();
super::NbtValidator::new()
.read(&mut Cursor::new(&data))
.unwrap();
}
#[test]
@ -222,7 +248,9 @@ mod tests {
}
data.write_u8(END_ID).unwrap();
super::read(&mut Cursor::new(&data)).unwrap();
super::NbtValidator::new()
.read(&mut Cursor::new(&data))
.unwrap();
}
#[test]
@ -234,7 +262,7 @@ mod tests {
data.write_u16::<BE>(0).unwrap(); // first element name length
// eof
let res = super::read(&mut Cursor::new(&data));
let res = super::NbtValidator::new().read(&mut Cursor::new(&data));
assert_eq!(res, Err(Error::UnexpectedEof));
}
@ -251,14 +279,18 @@ mod tests {
decoded_src_as_tag.extend_from_slice(&decoded_src);
decoded_src_as_tag.push(END_ID);
super::read_tag(&mut Cursor::new(&decoded_src_as_tag)).unwrap();
super::NbtValidator::new()
.read_tag(&mut Cursor::new(&decoded_src_as_tag))
.unwrap();
}
#[test]
fn byte_array() {
// found from fuzzing
let data = [10, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0];
super::read(&mut Cursor::new(&data)).unwrap();
super::NbtValidator::new()
.read(&mut Cursor::new(&data))
.unwrap();
}
#[test]
fn list_of_empty_lists() {
@ -266,14 +298,18 @@ mod tests {
// BaseNbt { name: m"", tag: NbtTag::NbtCompound { m"":
// NbtTag::List(List::List([List::Empty])) } }
let data = [10, 0, 0, 9, 0, 0, 9, 0, 0, 0, 1, 0, 9, 0, 0, 0, 0];
super::read(&mut Cursor::new(&data)).unwrap();
super::NbtValidator::new()
.read(&mut Cursor::new(&data))
.unwrap();
}
#[test]
fn list_of_byte_arrays() {
// BaseNbt { name: m"", tag: NbtCompound { values: [(m"",
// List(List([List::ByteArray([])])))] } }
let data = [10, 0, 0, 9, 0, 0, 9, 0, 0, 0, 1, 7, 0, 0, 0, 0, 0];
super::read(&mut Cursor::new(&data)).unwrap();
super::NbtValidator::new()
.read(&mut Cursor::new(&data))
.unwrap();
}
#[test]
@ -283,6 +319,8 @@ mod tests {
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
let mut decoded_src = Vec::new();
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
super::read(&mut Cursor::new(&decoded_src)).unwrap();
super::NbtValidator::new()
.read(&mut Cursor::new(&decoded_src))
.unwrap();
}
}