#3803: Add NBT module

This commit is contained in:
Outfluencer
2025-05-31 10:16:29 +10:00
committed by md_5
parent bec329352d
commit 41e49dad6b
39 changed files with 1514 additions and 154 deletions

View File

@@ -0,0 +1,52 @@
package net.md_5.bungee.nbt;
import com.google.common.base.Preconditions;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.limit.NBTLimiter;
import net.md_5.bungee.nbt.type.CompoundTag;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class NamedTag implements Tag
{
private String name;
private TypedTag tag;
/**
* Reads the data of the {@link DataInput} and parses it into this
* {@link NamedTag}.
*
* @param input input to read from
* @param limiter limitation of the read data
*/
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
byte type = input.readByte();
name = CompoundTag.readString( input, limiter );
tag = Tag.readById( type, input, limiter );
}
/**
* Write this {@link NamedTag} into a {@link DataOutput}.
*
* @param output the output to write to
*/
@Override
public void write(DataOutput output) throws IOException
{
Preconditions.checkNotNull( name, "name cannot be null" );
Preconditions.checkNotNull( tag, "tag cannot be null" );
output.writeByte( tag.getId() );
CompoundTag.writeString( name, output );
tag.write( output );
}
}

View File

@@ -0,0 +1,131 @@
package net.md_5.bungee.nbt;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.function.Supplier;
import net.md_5.bungee.nbt.exception.NBTFormatException;
import net.md_5.bungee.nbt.limit.NBTLimiter;
import net.md_5.bungee.nbt.type.ByteArrayTag;
import net.md_5.bungee.nbt.type.ByteTag;
import net.md_5.bungee.nbt.type.CompoundTag;
import net.md_5.bungee.nbt.type.DoubleTag;
import net.md_5.bungee.nbt.type.EndTag;
import net.md_5.bungee.nbt.type.FloatTag;
import net.md_5.bungee.nbt.type.IntArrayTag;
import net.md_5.bungee.nbt.type.IntTag;
import net.md_5.bungee.nbt.type.ListTag;
import net.md_5.bungee.nbt.type.LongArrayTag;
import net.md_5.bungee.nbt.type.LongTag;
import net.md_5.bungee.nbt.type.ShortTag;
import net.md_5.bungee.nbt.type.StringTag;
public interface Tag
{
int OBJECT_HEADER = 8;
int ARRAY_HEADER = 12;
int STRING_SIZE = 28;
int OBJECT_REFERENCE = 4;
Supplier<? extends TypedTag>[] CONSTRUCTORS = new Supplier[]
{
EndTag::new,
ByteTag::new,
ShortTag::new,
IntTag::new,
LongTag::new,
FloatTag::new,
DoubleTag::new,
ByteArrayTag::new,
StringTag::new,
ListTag::new,
CompoundTag::new,
IntArrayTag::new,
LongArrayTag::new
};
byte END = 0;
byte BYTE = 1;
byte SHORT = 2;
byte INT = 3;
byte LONG = 4;
byte FLOAT = 5;
byte DOUBLE = 6;
byte BYTE_ARRAY = 7;
byte STRING = 8;
byte LIST = 9;
byte COMPOUND = 10;
byte INT_ARRAY = 11;
byte LONG_ARRAY = 12;
/**
* Reads the data into this tag.
*
* @param input the input to read from
* @param limiter the limiter for this read operation
* @throws IOException if an exception occurs during io operations
*/
void read(DataInput input, NBTLimiter limiter) throws IOException;
/**
* Writes this tag into a {@link DataOutput}.
*
* @param output the output to write to
* @throws IOException if an exception occurs during io operations
*/
void write(DataOutput output) throws IOException;
/**
* Reads the data of the {@link DataInput} and parses it into a {@link Tag}.
*
* @param id the nbt type
* @param input input to read from
* @param limiter limitation of the read data
* @return the initialized {@link Tag}
* @throws IOException if an exception occurs during io operations
*/
static TypedTag readById(byte id, DataInput input, NBTLimiter limiter) throws IOException
{
if ( id < END || id > LONG_ARRAY )
{
throw new NBTFormatException( "Invalid tag id: " + id );
}
TypedTag tag = CONSTRUCTORS[id].get();
tag.read( input, limiter );
return tag;
}
static NamedTag readNamedTag(DataInput input, NBTLimiter limiter) throws IOException
{
NamedTag namedTag = new NamedTag();
namedTag.read( input, limiter );
return namedTag;
}
static byte[] toByteArray(TypedTag tag) throws IOException
{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream( byteArrayOutputStream );
dataOutputStream.writeByte( tag.getId() );
tag.write( dataOutputStream );
return byteArrayOutputStream.toByteArray();
}
static TypedTag fromByteArray(byte[] data) throws IOException
{
return fromByteArray( data, NBTLimiter.unlimitedSize() );
}
static TypedTag fromByteArray(byte[] data, NBTLimiter limiter) throws IOException
{
DataInputStream stream = new DataInputStream( new ByteArrayInputStream( data ) );
byte type = stream.readByte();
return readById( type, stream, limiter );
}
}

View File

@@ -0,0 +1,12 @@
package net.md_5.bungee.nbt;
public interface TypedTag extends Tag
{
/**
* Gets the id of this tag's type.
*
* @return the id related to this tag's type
*/
byte getId();
}

View File

@@ -0,0 +1,10 @@
package net.md_5.bungee.nbt.exception;
public class NBTException extends RuntimeException
{
public NBTException(String message)
{
super( message );
}
}

View File

@@ -0,0 +1,10 @@
package net.md_5.bungee.nbt.exception;
public class NBTFormatException extends NBTException
{
public NBTFormatException(String message)
{
super( message );
}
}

View File

@@ -0,0 +1,10 @@
package net.md_5.bungee.nbt.exception;
public class NBTLimitException extends NBTException
{
public NBTLimitException(String message)
{
super( message );
}
}

View File

@@ -0,0 +1,66 @@
package net.md_5.bungee.nbt.limit;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.nbt.exception.NBTLimitException;
@RequiredArgsConstructor
public class NBTLimiter
{
private static final int MAX_STACK_DEPTH = 512;
//
private final long maxBytes;
private final int maxDepth;
public static NBTLimiter unlimitedSize()
{
return new NBTLimiter( Long.MAX_VALUE, MAX_STACK_DEPTH );
}
public NBTLimiter(long maxBytes)
{
this( maxBytes, MAX_STACK_DEPTH );
}
private long usedBytes;
private int depth;
public void countBytes(long amount)
{
if ( amount < 0 )
{
throw new NBTLimitException( "NBT limiter tried to count negative byte amount" );
}
if ( ( usedBytes = Math.addExact( usedBytes, amount ) ) > maxBytes )
{
throw new NBTLimitException( "NBT tag is to big, bytes > " + maxBytes );
}
}
public void countBytes(long amount, long factor)
{
if ( amount < 0 || factor < 0 )
{
throw new NBTLimitException( "NBT limiter tried to count negative byte amount" );
}
countBytes( Math.multiplyExact( amount, factor ) );
}
public void push()
{
if ( ( depth = Math.addExact( depth, 1 ) ) > maxDepth )
{
throw new NBTLimitException( "NBT tag is to complex, depth > " + maxDepth );
}
}
public void pop()
{
if ( --depth < 0 )
{
throw new NBTLimitException( "NBT limiter tried to pop depth 0" );
}
}
}

View File

@@ -0,0 +1,44 @@
package net.md_5.bungee.nbt.type;
import com.google.common.base.Preconditions;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ByteArrayTag implements TypedTag
{
private byte[] value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + ARRAY_HEADER + Integer.BYTES );
int length = input.readInt();
limiter.countBytes( length, Byte.BYTES );
input.readFully( value = new byte[ length ] );
}
@Override
public void write(DataOutput output) throws IOException
{
Preconditions.checkNotNull( value, "byte array value cannot be null" );
output.writeInt( value.length );
output.write( value );
}
@Override
public byte getId()
{
return Tag.BYTE_ARRAY;
}
}

View File

@@ -0,0 +1,39 @@
package net.md_5.bungee.nbt.type;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ByteTag implements TypedTag
{
private byte value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + Byte.BYTES );
value = input.readByte();
}
@Override
public void write(DataOutput output) throws IOException
{
output.write( value );
}
@Override
public byte getId()
{
return Tag.BYTE;
}
}

View File

@@ -0,0 +1,99 @@
package net.md_5.bungee.nbt.type;
import com.google.common.base.Preconditions;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.exception.NBTFormatException;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CompoundTag implements TypedTag
{
private static final int MAP_SIZE_IN_BYTES = 48;
private static final int MAP_ENTRY_SIZE_IN_BYTES = 32;
//
private Map<String, TypedTag> value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.push();
limiter.countBytes( MAP_SIZE_IN_BYTES );
Map<String, TypedTag> map = new LinkedHashMap<>();
for ( byte type; ( type = input.readByte() ) != Tag.END; )
{
String name = readString( input, limiter );
TypedTag tag = Tag.readById( type, input, limiter );
if ( map.put( name, tag ) == null )
{
limiter.countBytes( MAP_ENTRY_SIZE_IN_BYTES + OBJECT_REFERENCE );
}
}
limiter.pop();
value = map;
}
@Override
public void write(DataOutput output) throws IOException
{
Preconditions.checkNotNull( value, "compound tag map cannot be null" );
for ( Map.Entry<String, TypedTag> entry : value.entrySet() )
{
String name = entry.getKey();
TypedTag tag = entry.getValue();
output.writeByte( tag.getId() );
if ( tag.getId() == Tag.END )
{
throw new NBTFormatException( "invalid end tag in compound tag" );
}
writeString( name, output );
tag.write( output );
}
output.writeByte( 0 );
}
@Override
public byte getId()
{
return Tag.COMPOUND;
}
public static String readString(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( STRING_SIZE );
String string = input.readUTF();
limiter.countBytes( string.length(), Character.BYTES );
return string;
}
public static void writeString(String string, DataOutput output) throws IOException
{
output.writeUTF( string );
}
public TypedTag get(String key)
{
return value.get( key );
}
public void put(String key, TypedTag tag)
{
value.put( key, tag );
}
public int size()
{
return value.size();
}
}

View File

@@ -0,0 +1,39 @@
package net.md_5.bungee.nbt.type;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DoubleTag implements TypedTag
{
private double value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + Double.BYTES );
value = input.readDouble();
}
@Override
public void write(DataOutput output) throws IOException
{
output.writeDouble( value );
}
@Override
public byte getId()
{
return Tag.DOUBLE;
}
}

View File

@@ -0,0 +1,34 @@
package net.md_5.bungee.nbt.type;
import java.io.DataInput;
import java.io.DataOutput;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
public class EndTag implements TypedTag
{
public static final EndTag INSTANCE = new EndTag();
@Override
public void read(DataInput input, NBTLimiter limiter)
{
limiter.countBytes( OBJECT_HEADER );
}
@Override
public void write(DataOutput output)
{
}
@Override
public byte getId()
{
return Tag.END;
}
}

View File

@@ -0,0 +1,39 @@
package net.md_5.bungee.nbt.type;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class FloatTag implements TypedTag
{
private float value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + Float.BYTES );
value = input.readFloat();
}
@Override
public void write(DataOutput output) throws IOException
{
output.writeFloat( value );
}
@Override
public byte getId()
{
return Tag.FLOAT;
}
}

View File

@@ -0,0 +1,52 @@
package net.md_5.bungee.nbt.type;
import com.google.common.base.Preconditions;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class IntArrayTag implements TypedTag
{
private int[] value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + ARRAY_HEADER + Integer.BYTES );
int length = input.readInt();
limiter.countBytes( length, Integer.BYTES );
int[] data = new int[ length ];
for ( int i = 0; i < length; i++ )
{
data[i] = input.readInt();
}
value = data;
}
@Override
public void write(DataOutput output) throws IOException
{
Preconditions.checkNotNull( value, "int array value cannot be null" );
output.writeInt( value.length );
for ( int i : value )
{
output.writeInt( i );
}
}
@Override
public byte getId()
{
return Tag.INT_ARRAY;
}
}

View File

@@ -0,0 +1,39 @@
package net.md_5.bungee.nbt.type;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class IntTag implements TypedTag
{
private int value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + Integer.BYTES );
value = input.readInt();
}
@Override
public void write(DataOutput output) throws IOException
{
output.writeInt( value );
}
@Override
public byte getId()
{
return Tag.INT;
}
}

View File

@@ -0,0 +1,87 @@
package net.md_5.bungee.nbt.type;
import com.google.common.base.Preconditions;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.exception.NBTFormatException;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ListTag implements TypedTag
{
public static final int LIST_HEADER = 12;
//
private List<TypedTag> value;
private byte listType;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.push();
limiter.countBytes( OBJECT_HEADER + LIST_HEADER + ARRAY_HEADER + Byte.BYTES + Integer.BYTES );
listType = input.readByte();
int length = input.readInt();
if ( listType == Tag.END && length > 0 )
{
throw new NBTFormatException( "Missing type in ListTag" );
}
limiter.countBytes( length, OBJECT_REFERENCE );
List<TypedTag> tagList = new ArrayList<>( length );
for ( int i = 0; i < length; i++ )
{
tagList.add( Tag.readById( listType, input, limiter ) );
}
limiter.pop();
value = tagList;
}
@Override
public void write(DataOutput output) throws IOException
{
Preconditions.checkNotNull( value, "list value cannot be null" );
if ( listType == Tag.END && !value.isEmpty() )
{
throw new NBTFormatException( "Missing type in ListTag" );
}
output.writeByte( listType );
output.writeInt( value.size() );
for ( TypedTag tag : value )
{
if ( tag.getId() != listType )
{
throw new NBTFormatException( "ListTag type mismatch" );
}
tag.write( output );
}
}
@Override
public byte getId()
{
return Tag.LIST;
}
public TypedTag get(int index)
{
return value.get( index );
}
public int size()
{
return value.size();
}
}

View File

@@ -0,0 +1,53 @@
package net.md_5.bungee.nbt.type;
import com.google.common.base.Preconditions;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LongArrayTag implements TypedTag
{
private long[] value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + ARRAY_HEADER + Integer.BYTES );
int length = input.readInt();
limiter.countBytes( length, Long.BYTES );
long[] data = new long[ length ];
for ( int i = 0; i < length; i++ )
{
data[i] = input.readLong();
}
value = data;
}
@Override
public void write(DataOutput output) throws IOException
{
Preconditions.checkNotNull( value, "long array value cannot be null" );
output.writeInt( value.length );
for ( long i : value )
{
output.writeLong( i );
}
}
@Override
public byte getId()
{
return Tag.LONG_ARRAY;
}
}

View File

@@ -0,0 +1,39 @@
package net.md_5.bungee.nbt.type;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LongTag implements TypedTag
{
private long value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + Long.BYTES );
value = input.readLong();
}
@Override
public void write(DataOutput output) throws IOException
{
output.writeLong( value );
}
@Override
public byte getId()
{
return Tag.LONG;
}
}

View File

@@ -0,0 +1,39 @@
package net.md_5.bungee.nbt.type;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ShortTag implements TypedTag
{
private short value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + Short.BYTES );
value = input.readShort();
}
@Override
public void write(DataOutput output) throws IOException
{
output.writeShort( value );
}
@Override
public byte getId()
{
return Tag.SHORT;
}
}

View File

@@ -0,0 +1,44 @@
package net.md_5.bungee.nbt.type;
import com.google.common.base.Preconditions;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.md_5.bungee.nbt.Tag;
import net.md_5.bungee.nbt.TypedTag;
import net.md_5.bungee.nbt.limit.NBTLimiter;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StringTag implements TypedTag
{
private String value;
@Override
public void read(DataInput input, NBTLimiter limiter) throws IOException
{
limiter.countBytes( OBJECT_HEADER + STRING_SIZE );
String string = input.readUTF();
limiter.countBytes( string.length(), Character.BYTES );
value = string;
}
@Override
public void write(DataOutput output) throws IOException
{
Preconditions.checkNotNull( value, "string value cannot be null" );
output.writeUTF( value );
}
@Override
public byte getId()
{
return Tag.STRING;
}
}

View File

@@ -0,0 +1,53 @@
package net.md_5.bungee.nbt;
import static org.junit.jupiter.api.Assertions.*;
import java.util.ArrayList;
import net.md_5.bungee.nbt.exception.NBTLimitException;
import net.md_5.bungee.nbt.limit.NBTLimiter;
import net.md_5.bungee.nbt.type.ByteArrayTag;
import net.md_5.bungee.nbt.type.ListTag;
import org.junit.jupiter.api.Test;
public class NBTLimiterTest
{
@Test
public void testNbtLimiter()
{
assertThrows( NBTLimitException.class, () ->
{
ByteArrayTag byteArrayTag = new ByteArrayTag( new byte[ 1000 ] );
byte[] arr = Tag.toByteArray( byteArrayTag );
Tag.fromByteArray( arr, new NBTLimiter( 100, 1 ) );
} );
assertDoesNotThrow( () ->
{
ByteArrayTag byteArrayTag = new ByteArrayTag( new byte[ 1000 ] );
byte[] arr = Tag.toByteArray( byteArrayTag );
Tag.fromByteArray( arr, new NBTLimiter( 99999999, 1 ) );
} );
}
@Test
public void testDepth()
{
assertThrows( NBTLimitException.class, () ->
{
ListTag root = new ListTag( new ArrayList<>(), Tag.LIST );
root.getValue().add( new ListTag( new ArrayList<>(), Tag.LIST ) );
byte[] arr = Tag.toByteArray( root );
Tag.fromByteArray( arr, new NBTLimiter( 100, 1 ) );
} );
assertDoesNotThrow( () ->
{
ListTag root = new ListTag( new ArrayList<>(), Tag.LIST );
root.getValue().add( new ListTag( new ArrayList<>(), Tag.LIST ) );
byte[] arr = Tag.toByteArray( root );
Tag.fromByteArray( arr, new NBTLimiter( 100, 2 ) );
} );
}
}

View File

@@ -0,0 +1,247 @@
package net.md_5.bungee.nbt;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import net.md_5.bungee.nbt.exception.NBTFormatException;
import net.md_5.bungee.nbt.type.ByteArrayTag;
import net.md_5.bungee.nbt.type.ByteTag;
import net.md_5.bungee.nbt.type.CompoundTag;
import net.md_5.bungee.nbt.type.DoubleTag;
import net.md_5.bungee.nbt.type.EndTag;
import net.md_5.bungee.nbt.type.FloatTag;
import net.md_5.bungee.nbt.type.IntArrayTag;
import net.md_5.bungee.nbt.type.IntTag;
import net.md_5.bungee.nbt.type.ListTag;
import net.md_5.bungee.nbt.type.LongArrayTag;
import net.md_5.bungee.nbt.type.LongTag;
import net.md_5.bungee.nbt.type.ShortTag;
import net.md_5.bungee.nbt.type.StringTag;
import org.junit.jupiter.api.Test;
public class NBTTagTest
{
@Test
public void testByteTag() throws IOException
{
byte[] tests = new byte[]
{
0, Byte.MAX_VALUE, Byte.MIN_VALUE
};
for ( byte value : tests )
{
ByteTag byteTag = new ByteTag( value );
byte[] deserialized = Tag.toByteArray( byteTag );
ByteTag reSerialized = (ByteTag) Tag.fromByteArray( deserialized );
assertEquals( byteTag, reSerialized );
}
}
@Test
public void testShortTag() throws IOException
{
short[] tests = new short[]
{
0, Short.MAX_VALUE, Short.MIN_VALUE
};
for ( short value : tests )
{
ShortTag tag = new ShortTag( value );
byte[] deserialized = Tag.toByteArray( tag );
ShortTag reSerialized = (ShortTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
}
@Test
public void testIntTag() throws IOException
{
int[] tests = new int[]
{
0, Integer.MAX_VALUE, Integer.MIN_VALUE
};
for ( int value : tests )
{
IntTag tag = new IntTag( value );
byte[] deserialized = Tag.toByteArray( tag );
IntTag reSerialized = (IntTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
}
@Test
public void testLongTag() throws IOException
{
long[] tests = new long[]
{
0, Long.MAX_VALUE, Long.MIN_VALUE
};
for ( long value : tests )
{
LongTag tag = new LongTag( value );
byte[] deserialized = Tag.toByteArray( tag );
LongTag reSerialized = (LongTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
}
@Test
public void testDoubleTag() throws IOException
{
double[] tests = new double[]
{
0, Double.MAX_VALUE, Double.MIN_VALUE, Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY
};
for ( double value : tests )
{
DoubleTag tag = new DoubleTag( value );
byte[] deserialized = Tag.toByteArray( tag );
DoubleTag reSerialized = (DoubleTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
}
@Test
public void testFloatTag() throws IOException
{
float[] tests = new float[]
{
0, Float.MAX_VALUE, Float.MIN_VALUE, Float.NaN, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY
};
for ( float value : tests )
{
FloatTag tag = new FloatTag( value );
byte[] deserialized = Tag.toByteArray( tag );
FloatTag reSerialized = (FloatTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
}
@Test
public void testStringTag() throws IOException
{
String[] tests = new String[]
{
"Outfluencer", "", String.valueOf( System.currentTimeMillis() ), "BungeeCord", new Object().toString()
};
for ( String value : tests )
{
StringTag tag = new StringTag( value );
byte[] deserialized = Tag.toByteArray( tag );
StringTag reSerialized = (StringTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
}
@Test
public void testByteArrayTag() throws IOException
{
byte[] value = new byte[ 1024 ];
ThreadLocalRandom.current().nextBytes( value );
ByteArrayTag tag = new ByteArrayTag( value );
byte[] deserialized = Tag.toByteArray( tag );
ByteArrayTag reSerialized = (ByteArrayTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
value = new byte[ 0 ];
ThreadLocalRandom.current().nextBytes( value );
tag = new ByteArrayTag( value );
deserialized = Tag.toByteArray( tag );
reSerialized = (ByteArrayTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
@Test
public void testIntArrayTag() throws IOException
{
int[] value = new int[ 1024 ];
for ( int i = 0; i < value.length; i++ )
{
value[i] = ThreadLocalRandom.current().nextInt();
}
IntArrayTag tag = new IntArrayTag( value );
byte[] deserialized = Tag.toByteArray( tag );
IntArrayTag reSerialized = (IntArrayTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
value = new int[ 0 ];
tag = new IntArrayTag( value );
deserialized = Tag.toByteArray( tag );
reSerialized = (IntArrayTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
@Test
public void testLongArrayTag() throws IOException
{
long[] value = new long[ 1024 ];
for ( int i = 0; i < value.length; i++ )
{
value[i] = ThreadLocalRandom.current().nextLong();
}
LongArrayTag tag = new LongArrayTag( value );
byte[] deserialized = Tag.toByteArray( tag );
LongArrayTag reSerialized = (LongArrayTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
value = new long[ 0 ];
tag = new LongArrayTag( value );
deserialized = Tag.toByteArray( tag );
reSerialized = (LongArrayTag) Tag.fromByteArray( deserialized );
assertEquals( tag, reSerialized );
}
@Test
public void testListTag() throws IOException
{
List<TypedTag> tags = new ArrayList<>();
for ( int i = 0; i < 100; i++ )
{
tags.add( new IntTag( i ) );
}
ListTag listTag = new ListTag( tags, Tag.INT );
byte[] deserialized = Tag.toByteArray( listTag );
ListTag reSerialized = (ListTag) Tag.fromByteArray( deserialized );
assertEquals( reSerialized.getValue(), tags );
List<TypedTag> tags2 = new ArrayList<>();
for ( int i = 0; i < 100; i++ )
{
tags2.add( new IntTag( i ) );
}
tags2.add( new ByteTag( Byte.MIN_VALUE ) );
assertThrows( NBTFormatException.class, () -> Tag.toByteArray( new ListTag( tags2, Tag.INT ) ) );
assertThrows( NBTFormatException.class, () -> Tag.toByteArray( new ListTag( Collections.singletonList( new EndTag() ), Tag.END ) ) );
}
@Test
public void testCompoundTag() throws IOException
{
Map<String, TypedTag> map = new HashMap<>();
for ( int i = 0; i < 100; i++ )
{
map.put( "" + i, new IntTag( i ) );
map.put( "a" + i, new ByteTag( (byte) i ) );
map.put( "b" + i, new ShortTag( (short) i ) );
map.put( "c" + i, new LongTag( i ) );
map.put( "f" + i, new FloatTag( i ) );
map.put( "d" + i, new DoubleTag( i ) );
}
CompoundTag compoundTag = new CompoundTag( map );
byte[] deserialized = Tag.toByteArray( compoundTag );
CompoundTag reSerialized = (CompoundTag) Tag.fromByteArray( deserialized );
assertEquals( reSerialized, compoundTag );
Map<String, TypedTag> map2 = new LinkedHashMap<>();
map2.put( "", new EndTag() );
CompoundTag compoundTag2 = new CompoundTag( map2 );
assertThrows( NBTFormatException.class, () -> Tag.toByteArray( compoundTag2 ) );
}
}

View File

@@ -0,0 +1,30 @@
package net.md_5.bungee.nbt;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Objects;
import net.md_5.bungee.nbt.limit.NBTLimiter;
import net.md_5.bungee.nbt.type.CompoundTag;
import org.junit.jupiter.api.Test;
public class PlayerDataTest
{
@Test
public void testPlayerData() throws URISyntaxException, IOException
{
ClassLoader classLoader = PlayerDataTest.class.getClassLoader();
File file = new File( Objects.requireNonNull( classLoader.getResource( "playerdata.nbt" ) ).toURI() );
FileInputStream fileInputStream = new FileInputStream( file );
DataInputStream dis = new DataInputStream( fileInputStream );
NamedTag namedTag = new NamedTag();
namedTag.read( dis, NBTLimiter.unlimitedSize() );
assertInstanceOf( CompoundTag.class, namedTag.getTag() );
assertEquals( namedTag.getName(), "" );
}
}

Binary file not shown.