Move valence_nbt to main Valence repo. (#97)

This also adds another check in CI
This commit is contained in:
Ryan Johnson 2022-10-01 14:18:42 -07:00 committed by GitHub
parent ef64296159
commit 9c62bc1b90
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 1618 additions and 1 deletions

View file

@ -32,3 +32,5 @@ jobs:
run: cargo +stable clippy --no-deps --all-features --all-targets -- -D warnings
- name: Run tests
run: cargo +stable test --all-features --all-targets
- name: Run valence_nbt tests without preserve_order
run: cargo +stable test -p valence_nbt

View file

@ -69,7 +69,7 @@ rayon = "1.5.3"
num = "0.4.0"
[workspace]
members = ["packet_inspector"]
members = ["valence_nbt", "packet_inspector"]
[profile.dev.package."*"]
opt-level = 3

21
valence_nbt/Cargo.toml Normal file
View file

@ -0,0 +1,21 @@
[package]
name = "valence_nbt"
description = "A library for Minecraft's Named Binary Tag (NBT) format."
documentation = "https://docs.rs/valence_nbt/"
repository = "https://github.com/valence-rs/valence_nbt"
readme = "README.md"
license = "MIT"
keywords = ["nbt", "minecraft", "serialization"]
version = "0.1.2"
authors = ["Ryan Johnson <ryanj00a@gmail.com>"]
edition = "2021"
[dependencies]
byteorder = "1.4.3"
cesu8 = "1.1.0"
indexmap = { version = "1.9.1", optional = true }
zerocopy = "0.6.1"
[features]
# When enabled, the order of fields in compounds are preserved.
preserve_order = ["dep:indexmap"]

8
valence_nbt/README.md Normal file
View file

@ -0,0 +1,8 @@
# valence_nbt
A library for encoding and decoding Minecraft's [Named Binary Tag] (NBT) format.
See the [documentation] for more information.
[Named Binary Tag]: https://minecraft.fandom.com/wiki/NBT_format
[documentation]: https://docs.rs/valence_nbt/

413
valence_nbt/src/compound.rs Normal file
View file

@ -0,0 +1,413 @@
use std::borrow::Borrow;
use std::hash::Hash;
use std::iter::FusedIterator;
use std::ops::{Index, IndexMut};
use crate::Value;
/// A map type with [`String`] keys and [`Value`] values.
#[derive(Clone, PartialEq, Default, Debug)]
pub struct Compound {
map: Map,
}
#[cfg(not(feature = "preserve_order"))]
type Map = std::collections::HashMap<String, Value>;
#[cfg(feature = "preserve_order")]
type Map = indexmap::IndexMap<String, Value>;
impl Compound {
pub fn new() -> Self {
Self { map: Map::new() }
}
pub fn with_capacity(cap: usize) -> Self {
Self {
map: Map::with_capacity(cap),
}
}
pub fn clear(&mut self) {
self.map.clear();
}
pub fn get<Q>(&self, k: &Q) -> Option<&Value>
where
String: Borrow<Q>,
Q: ?Sized + Eq + Ord + Hash,
{
self.map.get(k)
}
pub fn contains_key<Q>(&self, k: &Q) -> bool
where
String: Borrow<Q>,
Q: ?Sized + Eq + Ord + Hash,
{
self.map.contains_key(k)
}
pub fn get_mut<Q>(&mut self, k: &Q) -> Option<&mut Value>
where
String: Borrow<Q>,
Q: ?Sized + Eq + Ord + Hash,
{
self.map.get_mut(k)
}
pub fn get_key_value<Q>(&self, k: &Q) -> Option<(&String, &Value)>
where
String: Borrow<Q>,
Q: ?Sized + Eq + Ord + Hash,
{
self.map.get_key_value(k)
}
pub fn insert<K, V>(&mut self, k: K, v: V) -> Option<Value>
where
K: Into<String>,
V: Into<Value>,
{
self.map.insert(k.into(), v.into())
}
pub fn remove<Q>(&mut self, k: &Q) -> Option<Value>
where
String: Borrow<Q>,
Q: ?Sized + Eq + Ord + Hash,
{
self.map.remove(k)
}
pub fn remove_entry<Q>(&mut self, k: &Q) -> Option<(String, Value)>
where
String: Borrow<Q>,
Q: ?Sized + Eq + Ord + Hash,
{
self.map.remove_entry(k)
}
// TODO: append function for potential BTreeMap usage?
pub fn entry<K>(&mut self, k: K) -> Entry
where
K: Into<String>,
{
#[cfg(not(feature = "preserve_order"))]
use std::collections::hash_map::Entry as EntryImpl;
#[cfg(feature = "preserve_order")]
use indexmap::map::Entry as EntryImpl;
match self.map.entry(k.into()) {
EntryImpl::Vacant(ve) => Entry::Vacant(VacantEntry { ve }),
EntryImpl::Occupied(oe) => Entry::Occupied(OccupiedEntry { oe }),
}
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn iter(&self) -> Iter {
Iter {
iter: self.map.iter(),
}
}
pub fn iter_mut(&mut self) -> IterMut {
IterMut {
iter: self.map.iter_mut(),
}
}
pub fn keys(&self) -> Keys {
Keys {
iter: self.map.keys(),
}
}
pub fn values(&self) -> Values {
Values {
iter: self.map.values(),
}
}
pub fn values_mut(&mut self) -> ValuesMut {
ValuesMut {
iter: self.map.values_mut(),
}
}
pub fn retain<F>(&mut self, f: F)
where
F: FnMut(&String, &mut Value) -> bool,
{
self.map.retain(f)
}
}
impl Extend<(String, Value)> for Compound {
fn extend<T>(&mut self, iter: T)
where
T: IntoIterator<Item = (String, Value)>,
{
self.map.extend(iter)
}
}
impl FromIterator<(String, Value)> for Compound {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = (String, Value)>,
{
Self {
map: Map::from_iter(iter),
}
}
}
pub enum Entry<'a> {
Vacant(VacantEntry<'a>),
Occupied(OccupiedEntry<'a>),
}
impl<'a> Entry<'a> {
pub fn key(&self) -> &String {
match self {
Entry::Vacant(ve) => ve.key(),
Entry::Occupied(oe) => oe.key(),
}
}
pub fn or_insert(self, default: impl Into<Value>) -> &'a mut Value {
match self {
Entry::Vacant(ve) => ve.insert(default),
Entry::Occupied(oe) => oe.into_mut(),
}
}
pub fn or_insert_with<F, V>(self, default: F) -> &'a mut Value
where
F: FnOnce() -> V,
V: Into<Value>,
{
match self {
Entry::Vacant(ve) => ve.insert(default()),
Entry::Occupied(oe) => oe.into_mut(),
}
}
pub fn and_modify<F>(self, f: F) -> Self
where
F: FnOnce(&mut Value),
{
match self {
Entry::Vacant(ve) => Entry::Vacant(ve),
Entry::Occupied(mut oe) => {
f(oe.get_mut());
Entry::Occupied(oe)
}
}
}
}
pub struct VacantEntry<'a> {
#[cfg(not(feature = "preserve_order"))]
ve: std::collections::hash_map::VacantEntry<'a, String, Value>,
#[cfg(feature = "preserve_order")]
ve: indexmap::map::VacantEntry<'a, String, Value>,
}
impl<'a> VacantEntry<'a> {
pub fn key(&self) -> &String {
self.ve.key()
}
pub fn insert(self, v: impl Into<Value>) -> &'a mut Value {
self.ve.insert(v.into())
}
}
pub struct OccupiedEntry<'a> {
#[cfg(not(feature = "preserve_order"))]
oe: std::collections::hash_map::OccupiedEntry<'a, String, Value>,
#[cfg(feature = "preserve_order")]
oe: indexmap::map::OccupiedEntry<'a, String, Value>,
}
impl<'a> OccupiedEntry<'a> {
pub fn key(&self) -> &String {
self.oe.key()
}
pub fn get(&self) -> &Value {
self.oe.get()
}
pub fn get_mut(&mut self) -> &mut Value {
self.oe.get_mut()
}
pub fn into_mut(self) -> &'a mut Value {
self.oe.into_mut()
}
pub fn insert(&mut self, v: impl Into<Value>) -> Value {
self.oe.insert(v.into())
}
pub fn remove(self) -> Value {
self.oe.remove()
}
}
impl<Q> Index<&'_ Q> for Compound
where
String: Borrow<Q>,
Q: ?Sized + Eq + Ord + Hash,
{
type Output = Value;
fn index(&self, index: &Q) -> &Self::Output {
self.map.index(index)
}
}
impl<Q> IndexMut<&'_ Q> for Compound
where
String: Borrow<Q>,
Q: ?Sized + Eq + Ord + Hash,
{
fn index_mut(&mut self, index: &Q) -> &mut Self::Output {
self.map.get_mut(index).expect("no entry found for key")
}
}
macro_rules! impl_iterator_traits {
(($name:ident $($generics:tt)*) => $item:ty) => {
impl $($generics)* Iterator for $name $($generics)* {
type Item = $item;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
#[cfg(feature = "preserve_order")]
impl $($generics)* DoubleEndedIterator for $name $($generics)* {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.iter.next_back()
}
}
impl $($generics)* ExactSizeIterator for $name $($generics)* {
#[inline]
fn len(&self) -> usize {
self.iter.len()
}
}
impl $($generics)* FusedIterator for $name $($generics)* {}
}
}
impl<'a> IntoIterator for &'a Compound {
type Item = (&'a String, &'a Value);
type IntoIter = Iter<'a>;
fn into_iter(self) -> Self::IntoIter {
Iter {
iter: self.map.iter(),
}
}
}
#[derive(Clone)]
pub struct Iter<'a> {
#[cfg(not(feature = "preserve_order"))]
iter: std::collections::hash_map::Iter<'a, String, Value>,
#[cfg(feature = "preserve_order")]
iter: indexmap::map::Iter<'a, String, Value>,
}
impl_iterator_traits!((Iter<'a>) => (&'a String, &'a Value));
impl<'a> IntoIterator for &'a mut Compound {
type Item = (&'a String, &'a mut Value);
type IntoIter = IterMut<'a>;
fn into_iter(self) -> Self::IntoIter {
IterMut {
iter: self.map.iter_mut(),
}
}
}
pub struct IterMut<'a> {
#[cfg(not(feature = "preserve_order"))]
iter: std::collections::hash_map::IterMut<'a, String, Value>,
#[cfg(feature = "preserve_order")]
iter: indexmap::map::IterMut<'a, String, Value>,
}
impl_iterator_traits!((IterMut<'a>) => (&'a String, &'a mut Value));
impl IntoIterator for Compound {
type Item = (String, Value);
type IntoIter = IntoIter;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
iter: self.map.into_iter(),
}
}
}
pub struct IntoIter {
#[cfg(not(feature = "preserve_order"))]
iter: std::collections::hash_map::IntoIter<String, Value>,
#[cfg(feature = "preserve_order")]
iter: indexmap::map::IntoIter<String, Value>,
}
impl_iterator_traits!((IntoIter) => (String, Value));
#[derive(Clone)]
pub struct Keys<'a> {
#[cfg(not(feature = "preserve_order"))]
iter: std::collections::hash_map::Keys<'a, String, Value>,
#[cfg(feature = "preserve_order")]
iter: indexmap::map::Keys<'a, String, Value>,
}
impl_iterator_traits!((Keys<'a>) => &'a String);
#[derive(Clone)]
pub struct Values<'a> {
#[cfg(not(feature = "preserve_order"))]
iter: std::collections::hash_map::Values<'a, String, Value>,
#[cfg(feature = "preserve_order")]
iter: indexmap::map::Values<'a, String, Value>,
}
impl_iterator_traits!((Values<'a>) => &'a Value);
pub struct ValuesMut<'a> {
#[cfg(not(feature = "preserve_order"))]
iter: std::collections::hash_map::ValuesMut<'a, String, Value>,
#[cfg(feature = "preserve_order")]
iter: indexmap::map::ValuesMut<'a, String, Value>,
}
impl_iterator_traits!((ValuesMut<'a>) => &'a mut Value);

59
valence_nbt/src/error.rs Normal file
View file

@ -0,0 +1,59 @@
use std::error::Error as StdError;
use std::fmt::{Display, Formatter};
use std::io;
/// Errors that can occur when encoding or decoding.
#[derive(Debug)]
pub struct Error {
/// Box this to keep the size of `Result<T, Error>` small.
cause: Box<Cause>,
}
#[derive(Debug)]
enum Cause {
Io(io::Error),
Owned(Box<str>),
Static(&'static str),
}
impl Error {
pub(crate) fn new_owned(msg: impl Into<Box<str>>) -> Self {
Self {
cause: Box::new(Cause::Owned(msg.into())),
}
}
pub(crate) fn new_static(msg: &'static str) -> Self {
Self {
cause: Box::new(Cause::Static(msg)),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &*self.cause {
Cause::Io(e) => e.fmt(f),
Cause::Owned(msg) => write!(f, "{msg}"),
Cause::Static(msg) => write!(f, "{msg}"),
}
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match &*self.cause {
Cause::Io(e) => Some(e),
Cause::Owned(_) => None,
Cause::Static(_) => None,
}
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Self {
cause: Box::new(Cause::Io(e)),
}
}
}

View file

@ -0,0 +1,299 @@
use std::mem;
use byteorder::{BigEndian, ReadBytesExt};
use cesu8::Cesu8DecodingError;
use crate::tag::Tag;
use crate::{Compound, Error, List, Result, Value, MAX_DEPTH};
/// Decodes uncompressed NBT binary data from the provided slice.
///
/// The string returned is the name of the root compound.
pub fn from_binary_slice(slice: &mut &[u8]) -> Result<(Compound, String)> {
let mut state = DecodeState { slice, depth: 0 };
let root_tag = state.read_tag()?;
if root_tag != Tag::Compound {
return Err(Error::new_owned(format!(
"expected root tag for compound (got {root_tag})",
)));
}
let root_name = state.read_string()?;
let root = state.read_compound()?;
debug_assert_eq!(state.depth, 0);
Ok((root, root_name))
}
struct DecodeState<'a, 'b> {
slice: &'a mut &'b [u8],
/// Current recursion depth.
depth: usize,
}
impl DecodeState<'_, '_> {
#[inline]
fn check_depth<T>(&mut self, f: impl FnOnce(&mut Self) -> Result<T>) -> Result<T> {
if self.depth >= MAX_DEPTH {
return Err(Error::new_static("reached maximum recursion depth"));
}
self.depth += 1;
let res = f(self);
self.depth -= 1;
res
}
fn read_tag(&mut self) -> Result<Tag> {
match self.slice.read_u8()? {
0 => Ok(Tag::End),
1 => Ok(Tag::Byte),
2 => Ok(Tag::Short),
3 => Ok(Tag::Int),
4 => Ok(Tag::Long),
5 => Ok(Tag::Float),
6 => Ok(Tag::Double),
7 => Ok(Tag::ByteArray),
8 => Ok(Tag::String),
9 => Ok(Tag::List),
10 => Ok(Tag::Compound),
11 => Ok(Tag::IntArray),
12 => Ok(Tag::LongArray),
byte => Err(Error::new_owned(format!("invalid tag byte of {byte:#x}"))),
}
}
fn read_value(&mut self, tag: Tag) -> Result<Value> {
match tag {
Tag::End => unreachable!("illegal TAG_End argument"),
Tag::Byte => Ok(self.read_byte()?.into()),
Tag::Short => Ok(self.read_short()?.into()),
Tag::Int => Ok(self.read_int()?.into()),
Tag::Long => Ok(self.read_long()?.into()),
Tag::Float => Ok(self.read_float()?.into()),
Tag::Double => Ok(self.read_double()?.into()),
Tag::ByteArray => Ok(self.read_byte_array()?.into()),
Tag::String => Ok(self.read_string()?.into()),
Tag::List => self.check_depth(|st| Ok(st.read_any_list()?.into())),
Tag::Compound => self.check_depth(|st| Ok(st.read_compound()?.into())),
Tag::IntArray => Ok(self.read_int_array()?.into()),
Tag::LongArray => Ok(self.read_long_array()?.into()),
}
}
fn read_byte(&mut self) -> Result<i8> {
Ok(self.slice.read_i8()?)
}
fn read_short(&mut self) -> Result<i16> {
Ok(self.slice.read_i16::<BigEndian>()?)
}
fn read_int(&mut self) -> Result<i32> {
Ok(self.slice.read_i32::<BigEndian>()?)
}
fn read_long(&mut self) -> Result<i64> {
Ok(self.slice.read_i64::<BigEndian>()?)
}
fn read_float(&mut self) -> Result<f32> {
Ok(self.slice.read_f32::<BigEndian>()?)
}
fn read_double(&mut self) -> Result<f64> {
Ok(self.slice.read_f64::<BigEndian>()?)
}
fn read_byte_array(&mut self) -> Result<Vec<i8>> {
let len = self.slice.read_i32::<BigEndian>()?;
if len.is_negative() {
return Err(Error::new_owned(format!(
"negative byte array length of {len}"
)));
}
if len as usize > self.slice.len() {
return Err(Error::new_owned(format!(
"byte array length of {len} exceeds remainder of input"
)));
}
let (left, right) = self.slice.split_at(len as usize);
let array = left.iter().map(|b| *b as i8).collect();
*self.slice = right;
Ok(array)
}
fn read_string(&mut self) -> Result<String> {
let len = self.slice.read_u16::<BigEndian>()?.into();
if len > self.slice.len() {
return Err(Error::new_owned(format!(
"string of length {len} exceeds remainder of input"
)));
}
let (left, right) = self.slice.split_at(len);
match cesu8::from_java_cesu8(left) {
Ok(cow) => {
*self.slice = right;
Ok(cow.into())
}
Err(Cesu8DecodingError) => {
Err(Error::new_static("could not convert CESU-8 data to UTF-8"))
}
}
}
fn read_any_list(&mut self) -> Result<List> {
match self.read_tag()? {
Tag::End => match self.read_int()? {
0 => Ok(List::Byte(Vec::new())),
len => Err(Error::new_owned(format!(
"TAG_End list with nonzero length of {len}"
))),
},
Tag::Byte => Ok(self
.read_list::<_, _, 1>(Tag::Byte, |st| st.read_byte())?
.into()),
Tag::Short => Ok(self
.read_list::<_, _, 2>(Tag::Short, |st| st.read_short())?
.into()),
Tag::Int => Ok(self
.read_list::<_, _, 4>(Tag::Int, |st| st.read_int())?
.into()),
Tag::Long => Ok(self
.read_list::<_, _, 8>(Tag::Long, |st| st.read_long())?
.into()),
Tag::Float => Ok(self
.read_list::<_, _, 4>(Tag::Float, |st| st.read_float())?
.into()),
Tag::Double => Ok(self
.read_list::<_, _, 8>(Tag::Double, |st| st.read_double())?
.into()),
Tag::ByteArray => Ok(self
.read_list::<_, _, 4>(Tag::ByteArray, |st| st.read_byte_array())?
.into()),
Tag::String => Ok(self
.read_list::<_, _, 2>(Tag::String, |st| st.read_string())?
.into()),
Tag::List => self.check_depth(|st| {
Ok(st
.read_list::<_, _, 5>(Tag::List, |st| st.read_any_list())?
.into())
}),
Tag::Compound => self.check_depth(|st| {
Ok(st
.read_list::<_, _, 1>(Tag::Compound, |st| st.read_compound())?
.into())
}),
Tag::IntArray => Ok(self
.read_list::<_, _, 4>(Tag::IntArray, |st| st.read_int_array())?
.into()),
Tag::LongArray => Ok(self
.read_list::<_, _, 4>(Tag::LongArray, |st| st.read_long_array())?
.into()),
}
}
/// Assumes the element tag has already been read.
///
/// `MIN_ELEM_SIZE` is the minimum size of the list element when encoded.
fn read_list<T, F, const MIN_ELEM_SIZE: usize>(
&mut self,
elem_type: Tag,
mut read_elem: F,
) -> Result<Vec<T>>
where
F: FnMut(&mut Self) -> Result<T>,
{
let len = self.read_int()?;
if len.is_negative() {
return Err(Error::new_owned(format!(
"negative {elem_type} list length of {len}",
)));
}
// Ensure we don't reserve more than the maximum amount of memory required given
// the size of the remaining input.
if len as u64 * MIN_ELEM_SIZE as u64 > self.slice.len() as u64 {
return Err(Error::new_owned(format!(
"{elem_type} list of length {len} exceeds remainder of input"
)));
}
let mut list = Vec::with_capacity(len as usize);
for _ in 0..len {
list.push(read_elem(self)?);
}
Ok(list)
}
fn read_compound(&mut self) -> Result<Compound> {
let mut compound = Compound::new();
loop {
let tag = self.read_tag()?;
if tag == Tag::End {
return Ok(compound);
}
compound.insert(self.read_string()?, self.read_value(tag)?);
}
}
fn read_int_array(&mut self) -> Result<Vec<i32>> {
let len = self.read_int()?;
if len.is_negative() {
return Err(Error::new_owned(format!(
"negative int array length of {len}",
)));
}
if len as u64 * mem::size_of::<i32>() as u64 > self.slice.len() as u64 {
return Err(Error::new_owned(format!(
"int array of length {len} exceeds remainder of input"
)));
}
let mut array = Vec::with_capacity(len as usize);
for _ in 0..len {
array.push(self.read_int()?);
}
Ok(array)
}
fn read_long_array(&mut self) -> Result<Vec<i64>> {
let len = self.read_int()?;
if len.is_negative() {
return Err(Error::new_owned(format!(
"negative long array length of {len}",
)));
}
if len as u64 * mem::size_of::<i64>() as u64 > self.slice.len() as u64 {
return Err(Error::new_owned(format!(
"long array of length {len} exceeds remainder of input"
)));
}
let mut array = Vec::with_capacity(len as usize);
for _ in 0..len {
array.push(self.read_long()?);
}
Ok(array)
}
}

118
valence_nbt/src/lib.rs Normal file
View file

@ -0,0 +1,118 @@
//! A library for encoding and decoding Minecraft's [Named Binary Tag] (NBT)
//! format.
//!
//! [Named Binary Tag]: https://minecraft.fandom.com/wiki/NBT_format
//!
//! # Examples
//!
//! Encode NBT data to its binary form. We are using the [`compound!`] macro to
//! conveniently construct [`Compound`] values.
//!
//! ```rust
//! use valence_nbt::{compound, to_binary_writer, List};
//!
//! let c = compound! {
//! "byte" => 5_i8,
//! "string" => "hello",
//! "list_of_float" => List::Float(vec![
//! 3.1415,
//! 2.7182,
//! 1.4142
//! ]),
//! };
//!
//! let mut buf = Vec::new();
//!
//! to_binary_writer(&mut buf, &c, "").unwrap();
//! ```
//!
//! Decode NBT data from its binary form.
//!
//! ```rust
//! use valence_nbt::{compound, from_binary_slice};
//!
//! let some_bytes = [10, 0, 0, 3, 0, 3, 105, 110, 116, 0, 0, 222, 173, 0];
//!
//! let expected_value = compound! {
//! "int" => 0xdead
//! };
//!
//! let (nbt, root_name) = from_binary_slice(&mut some_bytes.as_slice()).unwrap();
//!
//! assert_eq!(nbt, expected_value);
//! assert_eq!(root_name, "");
//! ```
//!
//! # Features
//!
//! - `preserve_order`: Causes the order of fields in [`Compound`]s to be
//! preserved during insertion and deletion at a slight cost to performance.
//! The iterators on `Compound` can then implement [`DoubleEndedIterator`].
#![deny(unsafe_code)]
pub use compound::Compound;
pub use error::Error;
pub use from_binary_slice::from_binary_slice;
pub use to_binary_writer::to_binary_writer;
pub use value::{List, Value};
pub mod compound;
mod error;
mod from_binary_slice;
mod to_binary_writer;
pub mod value;
mod tag;
#[cfg(test)]
mod tests;
/// Maximum recursion depth to prevent overflowing the call stack.
const MAX_DEPTH: usize = 512;
type Result<T> = std::result::Result<T, Error>;
/// A convenience macro for constructing [`Compound`]s.
///
/// Key expressions must implement `Into<String>` while value expressions must
/// implement `Into<Value>`.
///
/// # Examples
///
/// ```
/// use valence_nbt::{compound, List};
///
/// let c = compound! {
/// "byte" => 123_i8,
/// "list_of_int" => List::Int(vec![3, -7, 5]),
/// "list_of_string" => List::String(vec![
/// "foo".to_owned(),
/// "bar".to_owned(),
/// "baz".to_owned()
/// ]),
/// "string" => "aé日",
/// "compound" => compound! {
/// "foo" => 1,
/// "bar" => 2,
/// "baz" => 3,
/// },
/// "int_array" => vec![5, -9, i32::MIN, 0, i32::MAX],
/// "byte_array" => vec![0_i8, 2, 3],
/// "long_array" => vec![123_i64, 456, 789],
/// };
///
/// println!("{c:?}");
/// ```
#[macro_export]
macro_rules! compound {
($($key:expr => $value:expr),* $(,)?) => {
<$crate::Compound as ::std::iter::FromIterator<(::std::string::String, $crate::Value)>>::from_iter([
$(
(
::std::convert::Into::<::std::string::String>::into($key),
::std::convert::Into::<$crate::Value>::into($value)
),
)*
])
}
}

65
valence_nbt/src/tag.rs Normal file
View file

@ -0,0 +1,65 @@
use std::fmt;
use std::fmt::Formatter;
use crate::Value;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Tag {
// Variant order is significant!
End,
Byte,
Short,
Int,
Long,
Float,
Double,
ByteArray,
String,
List,
Compound,
IntArray,
LongArray,
}
impl Tag {
pub fn element_type(value: &Value) -> Self {
match value {
Value::Byte(_) => Tag::Byte,
Value::Short(_) => Tag::Short,
Value::Int(_) => Tag::Int,
Value::Long(_) => Tag::Long,
Value::Float(_) => Tag::Float,
Value::Double(_) => Tag::Double,
Value::ByteArray(_) => Tag::ByteArray,
Value::String(_) => Tag::String,
Value::List(_) => Tag::List,
Value::Compound(_) => Tag::Compound,
Value::IntArray(_) => Tag::IntArray,
Value::LongArray(_) => Tag::LongArray,
}
}
pub const fn name(self) -> &'static str {
match self {
Tag::End => "end",
Tag::Byte => "byte",
Tag::Short => "short",
Tag::Int => "int",
Tag::Long => "long",
Tag::Float => "float",
Tag::Double => "double",
Tag::ByteArray => "byte array",
Tag::String => "string",
Tag::List => "list",
Tag::Compound => "compound",
Tag::IntArray => "int array",
Tag::LongArray => "long array",
}
}
}
impl fmt::Display for Tag {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}

166
valence_nbt/src/tests.rs Normal file
View file

@ -0,0 +1,166 @@
use std::mem;
use crate::tag::Tag;
use crate::{compound, from_binary_slice, to_binary_writer, Compound, List, Value};
const ROOT_NAME: &str = "The root name‽";
#[test]
fn round_trip() {
let mut buf = Vec::new();
let compound = example_compound();
to_binary_writer(&mut buf, &compound, ROOT_NAME).unwrap();
println!("{buf:?}");
let (decoded, root_name) = from_binary_slice(&mut buf.as_slice()).unwrap();
assert_eq!(root_name, ROOT_NAME);
assert_eq!(compound, decoded);
}
#[test]
fn check_min_sizes() {
fn check(min_val: Value, expected_size: usize) {
/// TAG_Compound + root name + field tag + field name + TAG_End
const COMPOUND_OVERHEAD: usize = 1 + 2 + 1 + 2 + 1;
let dbg = format!("{min_val:?}");
let mut buf = Vec::new();
to_binary_writer(&mut buf, &compound!("" => min_val), "").unwrap();
assert_eq!(
expected_size,
buf.len() - COMPOUND_OVERHEAD,
"size mismatch for {dbg}"
);
}
check(Value::Byte(0), 1);
check(Value::Short(0), 2);
check(Value::Int(0), 4);
check(Value::Long(0), 8);
check(Value::Float(0.0), 4);
check(Value::Double(0.0), 8);
check(Value::ByteArray([].into()), 4);
check(Value::String("".into()), 2);
check(Value::List(Vec::<i32>::new().into()), 5);
check(Value::Compound(compound!()), 1);
check(Value::IntArray([].into()), 4);
check(Value::LongArray([].into()), 4);
}
#[test]
fn deeply_nested_compound_encode() {
let mut c = compound!("" => 111_i8);
for _ in 0..10_000 {
c = compound!("" => c);
}
// Should not overflow the stack
let _ = to_binary_writer(&mut Vec::new(), &c, ROOT_NAME);
// Don"t overflow the stack while dropping.
mem::forget(c);
}
#[test]
fn deeply_nested_compound_decode() {
let mut buf = vec![Tag::Compound as u8, 0, 0]; // Root compound
let n = 10_000;
for _ in 0..n {
buf.extend([Tag::Compound as u8, 0, 0]);
}
buf.extend((0..n).map(|_| Tag::End as u8));
buf.push(Tag::End as u8); // End root compound
// Should not overflow the stack
let _ = from_binary_slice(&mut buf.as_slice());
}
#[test]
fn deeply_nested_list_encode() {
let mut l = List::Byte(Vec::new());
for _ in 0..10_000 {
l = List::List(vec![l]);
}
let c = compound!("" => l);
// Should not panic
let _ = to_binary_writer(&mut Vec::new(), &c, ROOT_NAME);
// Don"t overflow the stack while dropping.
mem::forget(c);
}
#[test]
fn deeply_nested_list_decode() {
// Root compound with one field.
let mut buf = vec![Tag::Compound as u8, 0, 0, Tag::List as u8, 0, 0];
let n = 10_000;
for _ in 0..n - 1 {
buf.extend([Tag::List as u8, 0, 0, 0, 1]); // List of list
}
// Last list is an empty list of bytes.
buf.extend([Tag::Byte as u8, 0, 0, 0, 0]);
buf.push(Tag::End as u8); // End root compound
// Should not overflow the stack
let _ = from_binary_slice(&mut buf.as_slice());
}
#[cfg(feature = "preserve_order")]
#[test]
fn preserves_order() {
let letters = ["g", "b", "d", "e", "h", "z", "m", "a", "q"];
let mut c = Compound::new();
for l in letters {
c.insert(l, 0_i8);
}
for (k, l) in c.keys().zip(letters) {
assert_eq!(k, l);
}
}
fn example_compound() -> Compound {
fn inner() -> Compound {
compound! {
"int" => i32::MIN,
"long" => i64::MAX,
"float" => 1e10_f32,
"double" => f64::INFINITY,
}
}
compound! {
"byte" => 123_i8,
"list_of_int" => List::Int(vec![3, -7, 5]),
"list_of_string" => List::String(vec![
"foo".to_owned(),
"bar".to_owned(),
"baz".to_owned()
]),
"string" => "aé日",
"compound" => inner(),
"list_of_compound" => List::Compound(vec![
inner(),
inner(),
inner(),
]),
"int_array" => vec![5, -9, i32::MIN, 0, i32::MAX],
"byte_array" => vec![0_i8, 2, 3],
"long_array" => vec![123_i64, 456, 789],
}
}

View file

@ -0,0 +1,230 @@
use std::io::Write;
use byteorder::{BigEndian, WriteBytesExt};
use zerocopy::AsBytes;
use crate::tag::Tag;
use crate::{Compound, Error, List, Result, Value, MAX_DEPTH};
/// Encodes uncompressed NBT binary data to the provided writer.
///
/// Only compounds are permitted at the top level. This is why the function
/// accepts a [`Compound`] reference rather than a [`Value`].
///
/// Additionally, the root compound can be given a name. Typically the empty
/// string `""` is used.
pub fn to_binary_writer<W: Write>(writer: W, compound: &Compound, root_name: &str) -> Result<()> {
let mut state = EncodeState { writer, depth: 0 };
state.write_tag(Tag::Compound)?;
state.write_string(root_name)?;
state.write_compound(compound)?;
debug_assert_eq!(state.depth, 0);
Ok(())
}
struct EncodeState<W> {
writer: W,
/// Current recursion depth.
depth: usize,
}
impl<W: Write> EncodeState<W> {
#[inline]
fn check_depth<T>(&mut self, f: impl FnOnce(&mut Self) -> Result<T>) -> Result<T> {
if self.depth >= MAX_DEPTH {
return Err(Error::new_static("reached maximum recursion depth"));
}
self.depth += 1;
let res = f(self);
self.depth -= 1;
res
}
fn write_tag(&mut self, tag: Tag) -> Result<()> {
Ok(self.writer.write_u8(tag as u8)?)
}
fn write_value(&mut self, v: &Value) -> Result<()> {
match v {
Value::Byte(b) => self.write_byte(*b),
Value::Short(s) => self.write_short(*s),
Value::Int(i) => self.write_int(*i),
Value::Long(l) => self.write_long(*l),
Value::Float(f) => self.write_float(*f),
Value::Double(d) => self.write_double(*d),
Value::ByteArray(ba) => self.write_byte_array(ba),
Value::String(s) => self.write_string(s),
Value::List(l) => self.check_depth(|st| st.write_any_list(l)),
Value::Compound(c) => self.check_depth(|st| st.write_compound(c)),
Value::IntArray(ia) => self.write_int_array(ia),
Value::LongArray(la) => self.write_long_array(la),
}
}
fn write_byte(&mut self, byte: i8) -> Result<()> {
Ok(self.writer.write_i8(byte)?)
}
fn write_short(&mut self, short: i16) -> Result<()> {
Ok(self.writer.write_i16::<BigEndian>(short)?)
}
fn write_int(&mut self, int: i32) -> Result<()> {
Ok(self.writer.write_i32::<BigEndian>(int)?)
}
fn write_long(&mut self, long: i64) -> Result<()> {
Ok(self.writer.write_i64::<BigEndian>(long)?)
}
fn write_float(&mut self, float: f32) -> Result<()> {
Ok(self.writer.write_f32::<BigEndian>(float)?)
}
fn write_double(&mut self, double: f64) -> Result<()> {
Ok(self.writer.write_f64::<BigEndian>(double)?)
}
fn write_byte_array(&mut self, bytes: &[i8]) -> Result<()> {
match bytes.len().try_into() {
Ok(len) => self.write_int(len)?,
Err(_) => {
return Err(Error::new_owned(format!(
"byte array of length {} exceeds maximum of i32::MAX",
bytes.len(),
)))
}
}
Ok(self.writer.write_all(bytes.as_bytes())?)
}
fn write_string(&mut self, s: &str) -> Result<()> {
let s = cesu8::to_java_cesu8(s);
match s.len().try_into() {
Ok(len) => self.writer.write_u16::<BigEndian>(len)?,
Err(_) => {
return Err(Error::new_owned(format!(
"string of length {} exceeds maximum of u16::MAX",
s.len()
)))
}
}
Ok(self.writer.write_all(&s)?)
}
fn write_any_list(&mut self, list: &List) -> Result<()> {
match list {
List::Byte(bl) => {
self.write_tag(Tag::Byte)?;
match bl.len().try_into() {
Ok(len) => self.write_int(len)?,
Err(_) => {
return Err(Error::new_owned(format!(
"byte list of length {} exceeds maximum of i32::MAX",
bl.len(),
)))
}
}
Ok(self.writer.write_all(bl.as_bytes())?)
}
List::Short(sl) => self.write_list(sl, Tag::Short, |st, s| st.write_short(*s)),
List::Int(il) => self.write_list(il, Tag::Int, |st, i| st.write_int(*i)),
List::Long(ll) => self.write_list(ll, Tag::Long, |st, l| st.write_long(*l)),
List::Float(fl) => self.write_list(fl, Tag::Float, |st, f| st.write_float(*f)),
List::Double(dl) => self.write_list(dl, Tag::Double, |st, d| st.write_double(*d)),
List::ByteArray(bal) => {
self.write_list(bal, Tag::ByteArray, |st, ba| st.write_byte_array(ba))
}
List::String(sl) => self.write_list(sl, Tag::String, |st, s| st.write_string(s)),
List::List(ll) => {
self.check_depth(|st| st.write_list(ll, Tag::List, |st, l| st.write_any_list(l)))
}
List::Compound(cl) => self
.check_depth(|st| st.write_list(cl, Tag::Compound, |st, c| st.write_compound(c))),
List::IntArray(ial) => {
self.write_list(ial, Tag::IntArray, |st, ia| st.write_int_array(ia))
}
List::LongArray(lal) => {
self.write_list(lal, Tag::LongArray, |st, la| st.write_long_array(la))
}
}
}
fn write_list<T, F>(&mut self, list: &Vec<T>, elem_type: Tag, mut write_elem: F) -> Result<()>
where
F: FnMut(&mut Self, &T) -> Result<()>,
{
self.write_tag(elem_type)?;
match list.len().try_into() {
Ok(len) => self.writer.write_i32::<BigEndian>(len)?,
Err(_) => {
return Err(Error::new_owned(format!(
"{elem_type} list of length {} exceeds maximum of i32::MAX",
list.len(),
)))
}
}
for elem in list {
write_elem(self, elem)?;
}
Ok(())
}
fn write_compound(&mut self, c: &Compound) -> Result<()> {
for (k, v) in c.iter() {
self.write_tag(Tag::element_type(v))?;
self.write_string(k)?;
self.write_value(v)?;
}
self.write_tag(Tag::End)?;
Ok(())
}
fn write_int_array(&mut self, ia: &[i32]) -> Result<()> {
match ia.len().try_into() {
Ok(len) => self.write_int(len)?,
Err(_) => {
return Err(Error::new_owned(format!(
"int array of length {} exceeds maximum of i32::MAX",
ia.len(),
)))
}
}
for i in ia {
self.write_int(*i)?;
}
Ok(())
}
fn write_long_array(&mut self, la: &[i64]) -> Result<()> {
match la.len().try_into() {
Ok(len) => self.write_int(len)?,
Err(_) => {
return Err(Error::new_owned(format!(
"long array of length {} exceeds maximum of i32::MAX",
la.len(),
)))
}
}
for l in la {
self.write_long(*l)?;
}
Ok(())
}
}

236
valence_nbt/src/value.rs Normal file
View file

@ -0,0 +1,236 @@
use std::borrow::Cow;
use crate::Compound;
/// Represents an arbitrary NBT value.
#[derive(Clone, PartialEq, Debug)]
pub enum Value {
Byte(i8),
Short(i16),
Int(i32),
Long(i64),
Float(f32),
Double(f64),
ByteArray(Vec<i8>),
String(String),
List(List),
Compound(Compound),
IntArray(Vec<i32>),
LongArray(Vec<i64>),
}
/// An NBT list value.
///
/// NBT lists are homogeneous, meaning each list element must be of the same
/// type. This is opposed to a format like JSON where lists can be
/// heterogeneous. Here is a JSON list that would be illegal in NBT:
///
/// ```json
/// [42, "hello", {}]
/// ```
///
/// Every possible element type has its own variant in this enum. As a result,
/// heterogeneous lists are unrepresentable.
#[derive(Clone, PartialEq, Debug)]
pub enum List {
Byte(Vec<i8>),
Short(Vec<i16>),
Int(Vec<i32>),
Long(Vec<i64>),
Float(Vec<f32>),
Double(Vec<f64>),
ByteArray(Vec<Vec<i8>>),
String(Vec<String>),
List(Vec<List>),
Compound(Vec<Compound>),
IntArray(Vec<Vec<i32>>),
LongArray(Vec<Vec<i64>>),
}
impl List {
/// Returns the length of this list.
pub fn len(&self) -> usize {
match self {
List::Byte(l) => l.len(),
List::Short(l) => l.len(),
List::Int(l) => l.len(),
List::Long(l) => l.len(),
List::Float(l) => l.len(),
List::Double(l) => l.len(),
List::ByteArray(l) => l.len(),
List::String(l) => l.len(),
List::List(l) => l.len(),
List::Compound(l) => l.len(),
List::IntArray(l) => l.len(),
List::LongArray(l) => l.len(),
}
}
/// Returns `true` if this list has no elements. `false` otherwise.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl From<i8> for Value {
fn from(v: i8) -> Self {
Self::Byte(v)
}
}
/// Bools are usually represented as `0` or `1` bytes in NBT.
impl From<bool> for Value {
fn from(b: bool) -> Self {
Value::Byte(b as _)
}
}
impl From<i16> for Value {
fn from(v: i16) -> Self {
Self::Short(v)
}
}
impl From<i32> for Value {
fn from(v: i32) -> Self {
Self::Int(v)
}
}
impl From<i64> for Value {
fn from(v: i64) -> Self {
Self::Long(v)
}
}
impl From<f32> for Value {
fn from(v: f32) -> Self {
Self::Float(v)
}
}
impl From<f64> for Value {
fn from(v: f64) -> Self {
Self::Double(v)
}
}
impl From<Vec<i8>> for Value {
fn from(v: Vec<i8>) -> Self {
Self::ByteArray(v)
}
}
impl From<String> for Value {
fn from(v: String) -> Self {
Self::String(v)
}
}
impl<'a> From<&'a str> for Value {
fn from(v: &'a str) -> Self {
Self::String(v.to_owned())
}
}
impl<'a> From<Cow<'a, str>> for Value {
fn from(v: Cow<'a, str>) -> Self {
Self::String(v.into_owned())
}
}
impl From<List> for Value {
fn from(v: List) -> Self {
Self::List(v)
}
}
impl From<Compound> for Value {
fn from(v: Compound) -> Self {
Self::Compound(v)
}
}
impl From<Vec<i32>> for Value {
fn from(v: Vec<i32>) -> Self {
Self::IntArray(v)
}
}
impl From<Vec<i64>> for Value {
fn from(v: Vec<i64>) -> Self {
Self::LongArray(v)
}
}
impl From<Vec<i8>> for List {
fn from(v: Vec<i8>) -> Self {
List::Byte(v)
}
}
impl From<Vec<i16>> for List {
fn from(v: Vec<i16>) -> Self {
List::Short(v)
}
}
impl From<Vec<i32>> for List {
fn from(v: Vec<i32>) -> Self {
List::Int(v)
}
}
impl From<Vec<i64>> for List {
fn from(v: Vec<i64>) -> Self {
List::Long(v)
}
}
impl From<Vec<f32>> for List {
fn from(v: Vec<f32>) -> Self {
List::Float(v)
}
}
impl From<Vec<f64>> for List {
fn from(v: Vec<f64>) -> Self {
List::Double(v)
}
}
impl From<Vec<Vec<i8>>> for List {
fn from(v: Vec<Vec<i8>>) -> Self {
List::ByteArray(v)
}
}
impl From<Vec<String>> for List {
fn from(v: Vec<String>) -> Self {
List::String(v)
}
}
impl From<Vec<List>> for List {
fn from(v: Vec<List>) -> Self {
List::List(v)
}
}
impl From<Vec<Compound>> for List {
fn from(v: Vec<Compound>) -> Self {
List::Compound(v)
}
}
impl From<Vec<Vec<i32>>> for List {
fn from(v: Vec<Vec<i32>>) -> Self {
List::IntArray(v)
}
}
impl From<Vec<Vec<i64>>> for List {
fn from(v: Vec<Vec<i64>>) -> Self {
List::LongArray(v)
}
}