ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java

1030 lines
38 KiB
Java

/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2012 Kristian S.
* Stangeland
* <p>
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later
* version.
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
* <p>
* You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.comphenix.protocol.events;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.cloning.SerializableCloner;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BlockPosition;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.ComponentConverter;
import com.comphenix.protocol.wrappers.Either;
import com.comphenix.protocol.wrappers.EnumWrappers;
import com.comphenix.protocol.wrappers.EnumWrappers.Direction;
import com.comphenix.protocol.wrappers.EnumWrappers.EntityUseAction;
import com.comphenix.protocol.wrappers.EnumWrappers.Hand;
import com.comphenix.protocol.wrappers.EnumWrappers.SoundCategory;
import com.comphenix.protocol.wrappers.MovingObjectPositionBlock;
import com.comphenix.protocol.wrappers.Pair;
import com.comphenix.protocol.wrappers.WrappedBlockData;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry;
import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject;
import com.comphenix.protocol.wrappers.WrappedEnumEntityUseAction;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedRegistry;
import com.comphenix.protocol.wrappers.WrappedSaltedSignature;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.collect.Lists;
import io.netty.buffer.ByteBuf;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.hover.content.Text;
import net.minecraft.core.IRegistry;
import net.minecraft.network.protocol.game.PacketPlayOutGameStateChange;
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes;
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes.AttributeSnapshot;
import net.minecraft.resources.MinecraftKey;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectList;
import net.minecraft.world.entity.ai.attributes.AttributeBase;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.animal.CatVariant;
import net.minecraft.world.entity.animal.FrogVariant;
import org.apache.commons.lang.SerializationUtils;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static com.comphenix.protocol.utility.TestUtils.assertItemCollectionsEqual;
import static com.comphenix.protocol.utility.TestUtils.assertItemsEqual;
import static com.comphenix.protocol.utility.TestUtils.equivalentItem;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class PacketContainerTest {
private static BaseComponent[] TEST_COMPONENT;
// Helper converters
private final EquivalentConverter<WrappedDataWatcher> watchConvert = BukkitConverters.getDataWatcherConverter();
private final EquivalentConverter<ItemStack> itemConvert = BukkitConverters.getItemStackConverter();
@BeforeAll
public static void initializeBukkit() {
BukkitInitialization.initializeAll();
TEST_COMPONENT = new ComponentBuilder("Hit or miss?")
.event(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://reddit.com"))
.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("The \"front page\" of the internet")))
.append("I guess they never miss, huh?")
.create();
}
private <T> void testPrimitive(StructureModifier<T> modifier, int index, T initialValue, T testValue) {
// Check initial value
assertEquals(initialValue, modifier.read(index));
// Test assignment
modifier.write(index, testValue);
assertEquals(testValue, modifier.read(0));
}
private <T> void testObjectArray(StructureModifier<T[]> modifier, int index, T[] initialValue, T[] testValue) {
// Check initial value
assertEquals(modifier.read(index).length, initialValue.length);
modifier.writeDefaults();
// Test initial
assertArrayEquals(initialValue, modifier.read(index));
// Test assignment
modifier.write(index, testValue);
assertArrayEquals(testValue, modifier.read(0));
}
@Test
public void testGetByteArrays() {
// Contains a byte array we will test
PacketContainer customPayload = new PacketContainer(PacketType.Login.Client.ENCRYPTION_BEGIN);
StructureModifier<byte[]> bytes = customPayload.getByteArrays();
byte[] testArray = new byte[]{1, 2, 3};
// It's NULL at first
// assertEquals(null, bytes.read(0));
customPayload.getModifier().writeDefaults();
// Then it should create an empty array
assertArrayEquals(new byte[0], bytes.read(0));
// Check and see if we can write to it
bytes.write(0, testArray);
assertArrayEquals(testArray, bytes.read(0));
}
@Test
public void testGetBytes() {
PacketContainer spawnMob = new PacketContainer(PacketType.Play.Server.NAMED_ENTITY_SPAWN);
this.testPrimitive(spawnMob.getBytes(), 0, (byte) 0, (byte) 1);
}
@Test
public void testGetShorts() {
PacketContainer itemData = new PacketContainer(PacketType.Play.Server.REL_ENTITY_MOVE);
this.testPrimitive(itemData.getShorts(), 0, (short) 0, (short) 1);
}
@Test
public void testGetIntegers() {
PacketContainer updateSign = new PacketContainer(PacketType.Play.Client.CLOSE_WINDOW);
this.testPrimitive(updateSign.getIntegers(), 0, 0, 1);
}
@Test
public void testGetLongs() {
PacketContainer updateTime = new PacketContainer(PacketType.Play.Server.UPDATE_TIME);
this.testPrimitive(updateTime.getLongs(), 0, (long) 0, (long) 1);
}
@Test
public void testGetFloat() {
PacketContainer explosion = new PacketContainer(PacketType.Play.Server.EXPLOSION);
this.testPrimitive(explosion.getFloat(), 0, (float) 0, (float) 0.8);
}
@Test
public void testGetDoubles() {
PacketContainer explosion = new PacketContainer(PacketType.Play.Server.EXPLOSION);
this.testPrimitive(explosion.getDoubles(), 0, (double) 0, 0.8);
}
@Test
public void testGetStrings() {
PacketContainer explosion = new PacketContainer(PacketType.Play.Client.CHAT);
this.testPrimitive(explosion.getStrings(), 0, "", "hello");
}
@Test
public void testGetStringArrays() {
PacketContainer packet = new PacketContainer(PacketType.Play.Client.UPDATE_SIGN);
this.testObjectArray(packet.getStringArrays(), 0,
new String[]{"", "", "", ""},
new String[]{"hello", "world"}
);
}
@Test
public void testGetIntegerArrays() {
// Contains a byte array we will test
PacketContainer packet = new PacketContainer(PacketType.Play.Server.MOUNT);
StructureModifier<int[]> integers = packet.getIntegerArrays();
int[] testArray = new int[]{1, 2, 3};
assertArrayEquals(new int[0], integers.read(0));
integers.write(0, testArray);
assertArrayEquals(testArray, integers.read(0));
}
@Test
public void testGetItemModifier() {
PacketContainer windowClick = new PacketContainer(PacketType.Play.Client.WINDOW_CLICK);
ItemStack item = this.itemWithData();
StructureModifier<ItemStack> items = windowClick.getItemModifier();
// assertNull(items.read(0));
// Insert the item and check if it's there
items.write(0, item);
assertTrue(equivalentItem(item, items.read(0)), "Item " + item + " != " + items.read(0));
}
private ItemStack itemWithData() {
ItemStack item = new ItemStack(Material.GREEN_WOOL, 1);
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(ChatColor.GREEN + "Green Wool");
meta.setLore(Lists.newArrayList(ChatColor.WHITE + "This is lore."));
item.setItemMeta(meta);
return item;
}
@Test
public void testGetItemListModifier() {
PacketContainer windowItems = new PacketContainer(PacketType.Play.Server.WINDOW_ITEMS);
StructureModifier<List<ItemStack>> itemAccess = windowItems.getItemListModifier();
List<ItemStack> items = new ArrayList<>();
items.add(this.itemWithData());
items.add(new ItemStack(Material.DIAMOND_AXE));
assertEquals(itemAccess.read(0).size(), 0);
// Insert and check that it was succesful
itemAccess.write(0, items);
// Read back array
List<ItemStack> comparison = itemAccess.read(0);
assertItemCollectionsEqual(items, comparison);
}
@Test
public void testGetNbtModifier() {
PacketContainer updateTileEntity = new PacketContainer(PacketType.Play.Server.TILE_ENTITY_DATA);
NbtCompound compound = NbtFactory.ofCompound("test");
compound.put("test", "name");
compound.put(NbtFactory.ofList("ages", 1, 2, 3));
updateTileEntity.getNbtModifier().write(0, compound);
NbtCompound result = (NbtCompound) updateTileEntity.getNbtModifier().read(0);
assertEquals(compound.getString("test"), result.getString("test"));
assertEquals(compound.getList("ages"), result.getList("ages"));
}
// TODO They removed DataWatchers from packets, it's all entity metadata packets now
/* @Test
public void testGetDataWatcherModifier() {
PacketContainer mobSpawnPacket = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
StructureModifier<WrappedDataWatcher> watcherAccessor = mobSpawnPacket.getDataWatcherModifier();
WrappedDataWatcher dataWatcher = new WrappedDataWatcher();
dataWatcher.setObject(new WrappedDataWatcherObject(1, Registry.get(Byte.class)), (byte) 1);
dataWatcher.setObject(new WrappedDataWatcherObject(2, Registry.get(String.class)), "Lorem");
dataWatcher.setObject(new WrappedDataWatcherObject(3, Registry.get(Boolean.class)), true);
dataWatcher.setObject(new WrappedDataWatcherObject(4, Registry.getUUIDSerializer(true)), Optional.of(UUID.randomUUID()));
assertNull(watcherAccessor.read(0));
// Insert and read back
watcherAccessor.write(0, dataWatcher);
assertEquals(dataWatcher, watcherAccessor.read(0));
} */
// Unfortunately, it might be too difficult to mock this one
//
// @Test
// public void testGetEntityModifier() { }
// No packet expose this type directly.
//
// @Test
// public void testGetPositionModifier() { }
@Test
public void testEntityTypeModifier() {
PacketContainer packet = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY);
packet.getEntityTypeModifier().write(0, EntityType.ARROW);
assertEquals(packet.getEntityTypeModifier().read(0), EntityType.ARROW);
}
@Test
public void testGetPositionCollectionModifier() {
PacketContainer explosionPacket = new PacketContainer(PacketType.Play.Server.EXPLOSION);
StructureModifier<List<BlockPosition>> positionAccessor = explosionPacket.getBlockPositionCollectionModifier();
assertEquals(positionAccessor.read(0).size(), 0);
List<BlockPosition> positions = new ArrayList<>();
positions.add(new BlockPosition(1, 2, 3));
positions.add(new BlockPosition(3, 4, 5));
// Insert and read back
positionAccessor.write(0, positions);
List<BlockPosition> cloned = positionAccessor.read(0);
assertEquals(positions, cloned);
}
@Test
public void testGetWatchableCollectionModifier() {
PacketContainer entityMetadata = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
StructureModifier<List<WrappedWatchableObject>> watchableAccessor =
entityMetadata.getWatchableCollectionModifier();
assertNull(watchableAccessor.read(0));
WrappedDataWatcher watcher = new WrappedDataWatcher();
watcher.setObject(0, Registry.get(String.class), "Test");
watcher.setObject(1, Registry.get(Byte.class), (byte) 21);
List<WrappedWatchableObject> list = watcher.getWatchableObjects();
// Insert and read back
watchableAccessor.write(0, list);
assertEquals(list, watchableAccessor.read(0));
// Put it into a new data watcher
WrappedDataWatcher newWatcher = new WrappedDataWatcher(watchableAccessor.read(0));
assertEquals(newWatcher.getWatchableObjects(), list);
}
@Test
public void testGameProfiles() {
PacketContainer spawnEntity = new PacketContainer(PacketType.Login.Server.SUCCESS);
WrappedGameProfile profile = new WrappedGameProfile(UUID.fromString("d7047a08-3150-4aa8-a2f2-7c1e2b17e298"),
"name");
spawnEntity.getGameProfiles().write(0, profile);
assertEquals(profile, spawnEntity.getGameProfiles().read(0));
}
@Test
public void testChatComponents() {
PacketContainer chatPacket = new PacketContainer(PacketType.Login.Server.DISCONNECT);
chatPacket.getChatComponents().write(0,
WrappedChatComponent.fromChatMessage("You shall not " + ChatColor.ITALIC + "pass!")[0]);
assertEquals("{\"extra\":[{\"text\":\"You shall not \"},{\"italic\":true,\"text\":\"pass!\"}],\"text\":\"\"}",
chatPacket.getChatComponents().read(0).getJson());
}
@Test
public void testSerialization() {
PacketContainer useItem = new PacketContainer(PacketType.Play.Client.USE_ITEM);
useItem.getMovingBlockPositions().write(0, new MovingObjectPositionBlock(
new BlockPosition(0, 1, 0),
new Vector(0, 1, 0),
Direction.DOWN,
false));
useItem.getHands().write(0, Hand.MAIN_HAND);
useItem.getIntegers().write(0, 5);
useItem.getLongs().write(0, System.currentTimeMillis());
PacketContainer copy = (PacketContainer) SerializationUtils.clone(useItem);
assertEquals(PacketType.Play.Client.USE_ITEM, copy.getType());
assertEquals(Hand.MAIN_HAND, copy.getHands().read(0));
assertEquals(5, copy.getIntegers().read(0));
MovingObjectPositionBlock pos = copy.getMovingBlockPositions().read(0);
assertEquals(1, pos.getBlockPosition().getY());
assertEquals(Direction.DOWN, pos.getDirection());
assertFalse(pos.isInsideBlock());
}
@Test
public void testBigPacketSerialization() {
PacketContainer payload = new PacketContainer(PacketType.Play.Server.CUSTOM_PAYLOAD);
payload.getMinecraftKeys().write(0, new com.comphenix.protocol.wrappers.MinecraftKey("test"));
byte[] randomData = new byte[8192];
ThreadLocalRandom.current().nextBytes(randomData);
ByteBuf serializer = (ByteBuf) MinecraftReflection.createPacketDataSerializer(randomData.length);
serializer.writeBytes(randomData);
payload.getModifier().withType(MinecraftReflection.getPacketDataSerializerClass()).write(0, serializer);
PacketContainer cloned = SerializableCloner.clone(payload);
com.comphenix.protocol.wrappers.MinecraftKey clonedKey = cloned.getMinecraftKeys().read(0);
byte[] clonedData = new byte[randomData.length];
ByteBuf clonedBuffer = (ByteBuf) cloned.getModifier()
.withType(MinecraftReflection.getPacketDataSerializerClass())
.read(0);
clonedBuffer.readBytes(clonedData);
assertEquals("minecraft:test", clonedKey.getFullKey());
assertArrayEquals(randomData, clonedData);
}
@Test
public void testIntList() {
PacketContainer destroy = new PacketContainer(PacketType.Play.Server.ENTITY_DESTROY);
destroy.getIntLists().write(0, new ArrayList<Integer>() {{
this.add(420);
this.add(69);
}});
List<Integer> back = destroy.getIntLists().read(0);
assertEquals(back.size(), 2);
assertEquals((int) back.get(0), 420);
assertEquals((int) back.get(1), 69);
}
@Test
public void testAttributeList() {
PacketContainer attribute = new PacketContainer(PacketType.Play.Server.UPDATE_ATTRIBUTES);
attribute.getIntegers().write(0, 123); // Entity ID
// Initialize some test data
List<AttributeModifier> modifiers = Lists.newArrayList(
new AttributeModifier(UUID.randomUUID(), "Unknown synced attribute modifier", 10,
AttributeModifier.Operation.a));
// Obtain an AttributeSnapshot instance. This is complicated by the fact that AttributeSnapshots
// are inner classes (which is ultimately pointless because AttributeSnapshots don't access any
// members of the packet itself)
PacketPlayOutUpdateAttributes packet = (PacketPlayOutUpdateAttributes) attribute.getHandle();
AttributeBase base = IRegistry.ak.a(MinecraftKey.a("generic.max_health"));
AttributeSnapshot snapshot = new AttributeSnapshot(base, 20.0D, modifiers);
attribute.getSpecificModifier(List.class).write(0, Lists.newArrayList(snapshot));
PacketContainer cloned = attribute.deepClone();
AttributeSnapshot
clonedSnapshot = (AttributeSnapshot) cloned.getSpecificModifier(List.class).read(0).get(0);
// Compare the fields, because apparently the packet is a field in AttributeSnapshot
for (Field field : AttributeSnapshot.class.getDeclaredFields()) {
try {
// Skip the packet
if (field.getType().equals(packet.getClass())) {
continue;
}
field.setAccessible(true);
this.testEquality(field.get(snapshot), field.get(clonedSnapshot));
} catch (AssertionError e) {
throw e;
} catch (Throwable ex) {
ex.printStackTrace();
}
}
}
@Test
public void testBlocks() {
PacketContainer blockAction = new PacketContainer(PacketType.Play.Server.BLOCK_ACTION);
blockAction.getBlocks().write(0, Material.STONE);
assertEquals(Material.STONE, blockAction.getBlocks().read(0));
}
@Test
public void testBlockData() {
PacketContainer blockChange = new PacketContainer(PacketType.Play.Server.BLOCK_CHANGE);
Material material = Material.GLOWSTONE;
WrappedBlockData data = WrappedBlockData.createData(material);
blockChange.getBlockData().write(0, data);
WrappedBlockData read = blockChange.getBlockData().read(0);
assertEquals(material, read.getType());
}
@Test
@SuppressWarnings("deprecation")
public void testPotionEffect() {
PotionEffect effect = new PotionEffect(PotionEffectType.FIRE_RESISTANCE, 20 * 60, 1);
MobEffect mobEffect = new MobEffect(MobEffectList.a(effect.getType().getId()), effect.getDuration(),
effect.getAmplifier(), effect.isAmbient(),
effect.hasParticles());
int entityId = 42;
// The constructor we want to call
PacketConstructor creator = PacketConstructor.DEFAULT.withPacket(
PacketType.Play.Server.ENTITY_EFFECT, new Class<?>[]{int.class, MobEffect.class});
PacketContainer packet = creator.createPacket(entityId, mobEffect);
assertEquals(entityId, packet.getIntegers().read(0));
assertEquals(effect.getAmplifier(), (byte) packet.getBytes().read(0));
assertEquals(effect.getDuration(), packet.getIntegers().read(1));
WrappedRegistry registry = WrappedRegistry.getRegistry(MinecraftReflection.getMobEffectListClass());
Object effectList = assertInstanceOf(MobEffectList.class, packet.getStructures().read(0).getHandle());
assertEquals(effect.getType().getId(), registry.getId(effectList));
int e = 0;
if (effect.isAmbient()) {
e |= 1;
}
if (effect.hasParticles()) {
e |= 2;
}
if (effect.hasIcon()) {
e |= 4;
}
assertEquals(e, (byte) packet.getBytes().read(1));
}
@Test
public void testPlayerAction() {
PacketContainer container = new PacketContainer(PacketType.Play.Client.ENTITY_ACTION);
// no change across nms versions
container.getPlayerActions().write(0, EnumWrappers.PlayerAction.OPEN_INVENTORY);
assertEquals(container.getPlayerActions().read(0), EnumWrappers.PlayerAction.OPEN_INVENTORY);
// changed in 1.15
container.getPlayerActions().write(0, EnumWrappers.PlayerAction.START_SNEAKING);
assertEquals(container.getPlayerActions().read(0), EnumWrappers.PlayerAction.START_SNEAKING);
}
@Test
public void testMobEffectList() {
PacketContainer container = new PacketContainer(PacketType.Play.Server.REMOVE_ENTITY_EFFECT);
container.getEffectTypes().write(0, PotionEffectType.GLOWING);
assertEquals(container.getEffectTypes().read(0), PotionEffectType.GLOWING);
}
@Test
public void testSoundCategory() {
PacketContainer container = new PacketContainer(PacketType.Play.Server.NAMED_SOUND_EFFECT);
container.getSoundCategories().write(0, SoundCategory.PLAYERS);
assertEquals(SoundCategory.PLAYERS, container.getSoundCategories().read(0));
}
@Test
public void testSoundEffects() {
PacketContainer container = new PacketContainer(PacketType.Play.Server.NAMED_SOUND_EFFECT);
container.getSoundEffects().write(0, Sound.ENTITY_CAT_HISS);
assertEquals(container.getSoundEffects().read(0), Sound.ENTITY_CAT_HISS);
}
// @Test
public void testGenericEnums() {
PacketContainer container = new PacketContainer(PacketType.Play.Server.BOSS);
container.getEnumModifier(Action.class, 1).write(0, Action.UPDATE_PCT);
// assertEquals(container.getEnumModifier(Action.class, PacketPlayOutBoss.d.class).read(0), Action.UPDATE_PCT);
}
@Test
public void testInternalStructures() {
PacketContainer container = new PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM);
Optional<InternalStructure> optStruct = container.getOptionalStructures().read(0);
assertTrue(optStruct.isPresent());
InternalStructure struct = optStruct.get();
struct.getChatComponents().write(0, WrappedChatComponent.fromText("hi there"));
container.getOptionalStructures().write(0, Optional.of(struct));
optStruct = container.getOptionalStructures().read(0);
assertTrue(optStruct.isPresent());
struct = optStruct.get();
this.testEquality(
struct.getChatComponents().read(0),
WrappedChatComponent.fromText("hi there")
);
}
// @Test
public void testDimensions() {
// TODO this won't work in testing, but hopefully will in live
PacketContainer container = new PacketContainer(PacketType.Play.Server.RESPAWN);
container.getDimensions().write(0, 1);
assertEquals((Object) 1, container.getDimensions().read(0));
}
@Test
public void testEntityEquipment() {
PacketContainer container = new PacketContainer(PacketType.Play.Server.ENTITY_EQUIPMENT);
List<Pair<EnumWrappers.ItemSlot, ItemStack>> data = new ArrayList<>();
data.add(new Pair<>(EnumWrappers.ItemSlot.CHEST, new ItemStack(Material.NETHERITE_CHESTPLATE)));
data.add(new Pair<>(EnumWrappers.ItemSlot.LEGS, new ItemStack(Material.GOLDEN_LEGGINGS)));
container.getSlotStackPairLists().write(0, data);
List<Pair<EnumWrappers.ItemSlot, ItemStack>> written = container.getSlotStackPairLists().read(0);
assertEquals(data, written);
}
@Test
public void testMovingBlockPos() {
PacketContainer container = new PacketContainer(PacketType.Play.Client.USE_ITEM);
Vector vector = new Vector(0, 1, 2);
BlockPosition position = new BlockPosition(3, 4, 5);
EnumWrappers.Direction direction = EnumWrappers.Direction.DOWN;
MovingObjectPositionBlock movingPos = new MovingObjectPositionBlock(position, vector, direction, true);
container.getMovingBlockPositions().write(0, movingPos);
MovingObjectPositionBlock back = container.getMovingBlockPositions().read(0);
assertEquals(back.getPosVector(), vector);
assertEquals(back.getBlockPosition(), position);
assertEquals(back.getDirection(), direction);
assertTrue(back.isInsideBlock());
}
@Test
public void testMultiBlockChange() {
PacketContainer packet = new PacketContainer(PacketType.Play.Server.MULTI_BLOCK_CHANGE);
packet.getShortArrays().writeSafely(0, new short[]{420, 69});
assertArrayEquals(new short[]{420, 69}, packet.getShortArrays().readSafely(0));
packet.getBlockDataArrays().writeSafely(0, new WrappedBlockData[]{
WrappedBlockData.createData(Material.IRON_BARS),
WrappedBlockData.createData(Material.IRON_BLOCK)
});
assertArrayEquals(new WrappedBlockData[]{
WrappedBlockData.createData(Material.IRON_BARS),
WrappedBlockData.createData(Material.IRON_BLOCK)
}, packet.getBlockDataArrays().readSafely(0));
packet.getSectionPositions().writeSafely(0, new BlockPosition(42, 43, 44));
assertEquals(new BlockPosition(42, 43, 44), packet.getSectionPositions().readSafely(0));
PacketContainer clone = packet.deepClone();
assertNotSame(clone, packet);
}
@Test
public void testGameStateChange() {
PacketContainer packet = new PacketContainer(PacketType.Play.Server.GAME_STATE_CHANGE);
assertEquals(0, (int) packet.getGameStateIDs().read(0));
packet.getGameStateIDs().write(0, 2);
assertEquals(2, (int) packet.getGameStateIDs().read(0));
}
@Test
public void testUseEntity() {
PacketContainer packet = new PacketContainer(PacketType.Play.Client.USE_ENTITY);
WrappedEnumEntityUseAction action;
WrappedEnumEntityUseAction clone;
// test attack
packet.getEnumEntityUseActions().write(0, WrappedEnumEntityUseAction.attack());
action = packet.getEnumEntityUseActions().read(0);
// attack's handle should always be the same
assertEquals(WrappedEnumEntityUseAction.attack(), action);
assertEquals(EntityUseAction.ATTACK, action.getAction());
// hand & position should not be available
assertThrows(IllegalArgumentException.class, action::getHand);
assertThrows(IllegalArgumentException.class, action::getPosition);
// test cloning
clone = action.deepClone();
assertSame(WrappedEnumEntityUseAction.attack(), clone);
// test interact
packet.getEnumEntityUseActions().write(0, WrappedEnumEntityUseAction.interact(Hand.OFF_HAND));
action = packet.getEnumEntityUseActions().read(0);
assertEquals(EntityUseAction.INTERACT, action.getAction());
assertEquals(Hand.OFF_HAND, action.getHand());
// position should not be available
assertThrows(IllegalArgumentException.class, action::getPosition);
// test cloning
clone = action.deepClone();
assertEquals(EntityUseAction.INTERACT, clone.getAction());
assertEquals(Hand.OFF_HAND, clone.getHand());
// test interact_at
Vector position = new Vector(1, 199, 4);
packet.getEnumEntityUseActions().write(0, WrappedEnumEntityUseAction.interactAt(Hand.MAIN_HAND, position));
action = packet.getEnumEntityUseActions().read(0);
assertEquals(EntityUseAction.INTERACT_AT, action.getAction());
assertEquals(Hand.MAIN_HAND, action.getHand());
assertEquals(position, action.getPosition());
// test cloning
clone = action.deepClone();
assertEquals(EntityUseAction.INTERACT_AT, clone.getAction());
assertEquals(Hand.MAIN_HAND, clone.getHand());
assertEquals(position, clone.getPosition());
}
@Test
public void testSetSimulationDistance() {
// first packet which is a record - set will fail if we missed something during patching
PacketContainer container = new PacketContainer(PacketType.Play.Server.UPDATE_SIMULATION_DISTANCE);
container.getIntegers().write(0, 1234);
assertEquals(1234, (int) container.getIntegers().read(0));
}
@Test
public void testMapChunk() {
// this is a special case as we are generating a data serializer class (we only need to construct the packet)
PacketContainer container = new PacketContainer(PacketType.Play.Server.MAP_CHUNK);
// check if we can read an nbt compound from the class
assertTrue(container.getStructures().read(0).getNbtModifier().optionRead(0).isPresent());
}
@Test
public void testComponentArrays() {
PacketContainer signChange = new PacketContainer(PacketType.Play.Server.TILE_ENTITY_DATA);
WrappedChatComponent[] components = new WrappedChatComponent[]{
WrappedChatComponent.fromText("hello world"), WrappedChatComponent.fromText(""),
WrappedChatComponent.fromText(""), WrappedChatComponent.fromText("")
};
signChange.getChatComponentArrays().write(0, components);
WrappedChatComponent[] back = signChange.getChatComponentArrays().read(0);
assertArrayEquals(components, back);
}
@Test
public void testLoginSignatureNonce() {
PacketContainer encryptionStart = new PacketContainer(PacketType.Login.Client.ENCRYPTION_BEGIN);
encryptionStart.getByteArrays().write(0, new byte[]{1, 2, 3});
byte[] nonce = {4, 5, 6};
encryptionStart.getLoginSignatures().write(0, Either.left(nonce));
byte[] read = encryptionStart.getLoginSignatures().read(0).left().get();
assertArrayEquals(nonce, read);
}
@Test
public void testLoginSignatureSigned() {
PacketContainer encryptionStart = new PacketContainer(PacketType.Login.Client.ENCRYPTION_BEGIN);
encryptionStart.getByteArrays().write(0, new byte[]{1, 2, 3});
byte[] signature = new byte[512];
long salt = 124L;
encryptionStart.getLoginSignatures().write(0, Either.right(new WrappedSaltedSignature(salt, signature)));
WrappedSaltedSignature read = encryptionStart.getLoginSignatures().read(0).right().get();
assertEquals(salt, read.getSalt());
assertArrayEquals(signature, read.getSignature());
}
// TODO: fix this this at some point
/*
@Test
public void testSignedChatMessage() {
PacketContainer chatPacket = new PacketContainer(PacketType.Play.Client.CHAT);
byte[] signature = new byte[512];
long salt = 124L;
WrappedSaltedSignature wrappedSignature = new WrappedSaltedSignature(salt, signature);
chatPacket.getSignatures().write(0, wrappedSignature);
WrappedSaltedSignature read = chatPacket.getSignatures().read(0);
assertEquals(salt, read.getSalt());
assertArrayEquals(signature, read.getSignature());
}*/
private void assertPacketsEqualAndSerializable(PacketContainer constructed, PacketContainer cloned) {
StructureModifier<Object> firstMod = constructed.getModifier(), secondMod = cloned.getModifier();
assertEquals(firstMod.size(), secondMod.size());
if (PacketType.Status.Server.SERVER_INFO.equals(constructed.getType())) {
assertArrayEquals(SerializationUtils.serialize(constructed), SerializationUtils.serialize(cloned));
} else {
// Make sure all the fields are equivalent
for (int i = 0; i < firstMod.size(); i++) {
if (firstMod.getField(i).getType().isArray()) {
assertArrayEquals(this.getArray(firstMod.read(i)), this.getArray(secondMod.read(i)));
} else {
this.testEquality(firstMod.read(i), secondMod.read(i));
}
}
}
Object buffer = MinecraftReflection.createPacketDataSerializer(0);
MinecraftMethods.getPacketWriteByteBufMethod().invoke(cloned.getHandle(), buffer);
}
@Test
public void testCloning() {
// Try constructing all the packets
for (PacketType type : PacketType.values()) {
// TODO: try to support chat - for now chat contains to many sub classes to properly clone it
if (type.isDeprecated() || !type.isSupported() || type.name().contains("CUSTOM_PAYLOAD") || type.name().contains("CHAT")) {
continue;
}
try {
PacketContainer constructed = new PacketContainer(type);
// Initialize default values
constructed.getModifier().writeDefaults();
// Make sure watchable collections can be cloned
if (type == PacketType.Play.Server.ENTITY_METADATA) {
constructed.getWatchableCollectionModifier().write(0, Lists.newArrayList(
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.get(Byte.class)),
(byte) 1),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.get(Integer.class)),
1),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.get(Float.class)),
1F),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.get(String.class)),
"String"),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.get(Boolean.class)),
true),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.getChatComponentSerializer(true)),
Optional.of(ComponentConverter.fromBaseComponent(TEST_COMPONENT).getHandle())),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.getItemStackSerializer(false)),
BukkitConverters.getItemStackConverter().getGeneric(new ItemStack(Material.WOODEN_AXE))),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.get(CatVariant.class)),
CatVariant.a),
new WrappedWatchableObject(
new WrappedDataWatcherObject(0, Registry.get(FrogVariant.class)),
FrogVariant.c)
));
} else if (type == PacketType.Play.Server.CHAT) {
constructed.getChatComponents().write(0, ComponentConverter.fromBaseComponent(TEST_COMPONENT));
} else if (type == PacketType.Play.Server.REMOVE_ENTITY_EFFECT || type == PacketType.Play.Server.ENTITY_EFFECT) {
constructed.getEffectTypes().write(0, PotionEffectType.GLOWING);
} else if (type == PacketType.Play.Server.GAME_STATE_CHANGE) {
constructed.getStructures().write(
0,
InternalStructure.getConverter().getSpecific(PacketPlayOutGameStateChange.a));
} else if (type == PacketType.Play.Client.USE_ITEM || type == PacketType.Play.Client.BLOCK_PLACE) {
constructed.getLongs().write(0, 0L); // timestamp of the packet, not sent over the network
}
// gives some indication which cloning process fails as the checks itself are happening outside this method
System.out.println("Cloning " + type);
// Clone the packet all three ways
PacketContainer shallowCloned = constructed.shallowClone();
this.assertPacketsEqualAndSerializable(constructed, shallowCloned);
PacketContainer deepCloned = constructed.deepClone();
this.assertPacketsEqualAndSerializable(constructed, deepCloned);
PacketContainer serializedCloned = SerializableCloner.clone(constructed);
if (type == PacketType.Play.Client.USE_ITEM || type == PacketType.Play.Client.BLOCK_PLACE) {
// shit fix - but what are we supposed to do :/
serializedCloned.getLongs().write(0, 0L);
}
this.assertPacketsEqualAndSerializable(constructed, serializedCloned);
} catch (Exception ex) {
Assertions.fail("Unable to clone " + type, ex);
}
}
}
// Convert to objects that support equals()
private void testEquality(Object a, Object b) {
if (a == null) {
if (b == null) {
return;
} else {
throw new AssertionError("a was null, but b was not");
}
} else if (b == null) {
throw new AssertionError("a was not null, but b was null");
}
if (a instanceof Optional) {
if (b instanceof Optional) {
this.testEquality(((Optional<?>) a).orElse(null), ((Optional<?>) b).orElse(null));
return;
} else {
throw new AssertionError("a was optional, but b was not");
}
}
if (a.equals(b) || Objects.equals(a, b) || this.stringEquality(a, b)) {
return;
}
if (MinecraftReflection.isDataWatcher(a)) {
a = this.watchConvert.getSpecific(a);
b = this.watchConvert.getSpecific(b);
} else if (MinecraftReflection.isItemStack(a)) {
a = this.itemConvert.getSpecific(a);
b = this.itemConvert.getSpecific(b);
}
if (a instanceof ItemStack && b instanceof ItemStack) {
assertItemsEqual((ItemStack) a, (ItemStack) b);
return;
}
if (a instanceof List<?>) {
if (b instanceof List<?>) {
List<?> listA = (List<?>) a;
List<?> listB = (List<?>) b;
assertEquals(listA.size(), listB.size());
for (int i = 0; i < listA.size(); i++) {
this.testEquality(listA.get(i), listB.get(i));
}
return;
} else {
throw new AssertionError("a was a list, but b was not");
}
}
if (a.getClass().isArray()) {
if (b.getClass().isArray()) {
int arrayLengthA = Array.getLength(a);
int arrayLengthB = Array.getLength(b);
assertEquals(arrayLengthA, arrayLengthB);
for (int i = 0; i < arrayLengthA; i++) {
Object elementA = Array.get(a, i);
Object elementB = Array.get(b, i);
testEquality(elementA, elementB);
}
return;
} else {
throw new AssertionError("a was an array, but b was not");
}
}
if (!a.getClass().isAssignableFrom(b.getClass())) {
assertEquals(a, b);
return;
}
Set<Field> fields = FuzzyReflection.fromObject(a, true).getFields();
for (Field field : fields) {
if (!Modifier.isStatic(field.getModifiers())) {
FieldAccessor accessor = Accessors.getFieldAccessor(field);
testEquality(accessor.get(a), accessor.get(b));
}
}
}
private boolean stringEquality(Object a, Object b) {
try {
return a.toString().equals(b.toString());
} catch (Exception ex) {
// internal null pointers, usually
return false;
}
}
/**
* Get the underlying array as an object array.
*
* @param val - array wrapped as an Object.
* @return An object array.
*/
private Object[] getArray(Object val) {
if (val instanceof Object[]) {
return (Object[]) val;
}
if (val == null) {
return null;
}
int arrlength = Array.getLength(val);
Object[] outputArray = new Object[arrlength];
for (int i = 0; i < arrlength; ++i) {
outputArray[i] = Array.get(val, i);
}
return outputArray;
}
/**
* Actions from the outbound Boss packet. Used for testing generic enums.
*
* @author dmulloy2
*/
public enum Action {
ADD,
REMOVE,
UPDATE_PCT,
UPDATE_NAME,
UPDATE_STYLE,
UPDATE_PROPERTIES
}
}