From 602ed04deec0a55381c3bdb0163fa3045d360104 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 21 Feb 2021 22:43:12 -0500 Subject: [PATCH] Started work on revamped protocol registry --- .../packet/DensePacketRegistryMap.java | 100 ++++++++++++++++++ .../packet/EmptyPacketRegistryMap.java | 28 +++++ .../packet/PacketRegistryBuilder.java | 70 ++++++++++++ .../registry/packet/PacketRegistryMap.java | 12 +++ .../packet/RegularPacketRegistryMap.java | 66 ++++++++++++ .../registry/protocol/ProtocolRegistry.java | 9 ++ .../protocol/SimpleProtocolRegistry.java | 32 ++++++ .../VersionSpecificProtocolRegistry.java | 42 ++++++++ .../registry/state/ProtocolStates.java | 57 ++++++++++ .../registry/state/Version172To176.java | 39 +++++++ .../registry/version/GenericVersions.java | 5 + 11 files changed, 460 insertions(+) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/DensePacketRegistryMap.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/EmptyPacketRegistryMap.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/PacketRegistryBuilder.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/PacketRegistryMap.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/RegularPacketRegistryMap.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/network/registry/protocol/ProtocolRegistry.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/network/registry/protocol/SimpleProtocolRegistry.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/network/registry/protocol/VersionSpecificProtocolRegistry.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/network/registry/state/ProtocolStates.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/network/registry/state/Version172To176.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/network/registry/version/GenericVersions.java diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/DensePacketRegistryMap.java b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/DensePacketRegistryMap.java new file mode 100644 index 000000000..4c8ecbe77 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/DensePacketRegistryMap.java @@ -0,0 +1,100 @@ +package com.velocitypowered.proxy.network.registry.packet; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.network.packet.Packet; +import com.velocitypowered.proxy.network.packet.PacketReader; +import com.velocitypowered.proxy.network.packet.PacketWriter; +import com.velocitypowered.proxy.network.registry.packet.PacketRegistryBuilder.PacketMapping; +import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Provides a packet registry map that is "dense", ideal for registries that are tightly packed + * together by ID. Lookups for readers are very fast (O(1)) and for writers uses an embedded + * open-addressing, probing hash map to conserve memory. + */ +public class DensePacketRegistryMap implements PacketRegistryMap { + + private final PacketReader[] readersById; + private final PacketWriter[] writersByClass; + private final Class[] classesById; + + public DensePacketRegistryMap(Int2ObjectMap> mappings) { + int size = mappings.keySet().stream().mapToInt(x -> x).max().orElse(0) + 1; + + this.readersById = new PacketReader[size]; + this.writersByClass = new PacketWriter[size * 2]; + this.classesById = new Class[size * 2]; + + for (PacketMapping value : mappings.values()) { + this.readersById[value.id] = value.reader; + this.place(value.packetClass, value.writer); + } + } + + private void place(Class key, PacketWriter value) { + int bucket = findEmpty(key); + this.writersByClass[bucket] = value; + this.classesById[bucket] = key; + } + + private int findEmpty(Class key) { + int start = key.hashCode() % this.classesById.length; + int index = start; + + for (;;) { + if (this.classesById[index] == null || this.classesById[index].equals(key)) { + // It's available, so no chance that this value exists anywhere in the map. + return index; + } + + if ((index = (index + 1) % this.classesById.length) == start) { + return -1; + } + } + } + + private int index(Class key) { + int start = key.hashCode() % this.classesById.length; + int index = start; + + for (;;) { + if (this.classesById[index] == null) { + // It's available, so no chance that this value exists anywhere in the map. + return -1; + } + if (key.equals(this.classesById[index])) { + return index; + } + + // Conflict, keep probing ... + if ((index = (index + 1) % this.classesById.length) == start) { + return -1; + } + } + } + + @Override + public @Nullable Packet readPacket(int id, ByteBuf buf, ProtocolVersion version) { + if (id < 0 || id >= this.readersById.length) { + return null; + } + + return this.readersById[id].read(buf, version); + } + + @Override + public

void writePacket(P packet, ByteBuf buf, ProtocolVersion version) { + int id = this.index(packet.getClass()); + if (id != -1) { + this.writersByClass[id].write(buf, packet, version); + } else { + throw new IllegalArgumentException(String.format( + "Unable to find id for packet of type %s in protocol %s", + packet.getClass().getName(), version + )); + } + + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/EmptyPacketRegistryMap.java b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/EmptyPacketRegistryMap.java new file mode 100644 index 000000000..35443f210 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/EmptyPacketRegistryMap.java @@ -0,0 +1,28 @@ +package com.velocitypowered.proxy.network.registry.packet; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.network.packet.Packet; +import io.netty.buffer.ByteBuf; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class EmptyPacketRegistryMap implements PacketRegistryMap { + + public static final EmptyPacketRegistryMap INSTANCE = new EmptyPacketRegistryMap(); + + private EmptyPacketRegistryMap() { + + } + + @Override + public @Nullable Packet readPacket(int id, ByteBuf buf, ProtocolVersion version) { + return null; + } + + @Override + public

void writePacket(P packet, ByteBuf buf, ProtocolVersion version) { + throw new IllegalArgumentException(String.format( + "Unable to find id for packet of type %s in protocol %s", + packet.getClass().getName(), version + )); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/PacketRegistryBuilder.java b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/PacketRegistryBuilder.java new file mode 100644 index 000000000..5de1b3482 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/PacketRegistryBuilder.java @@ -0,0 +1,70 @@ +package com.velocitypowered.proxy.network.registry.packet; + +import com.velocitypowered.proxy.network.packet.Packet; +import com.velocitypowered.proxy.network.packet.PacketReader; +import com.velocitypowered.proxy.network.packet.PacketWriter; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class PacketRegistryBuilder { + + private final Int2ObjectMap> mappings; + private boolean dense = false; + + public PacketRegistryBuilder() { + this.mappings = new Int2ObjectOpenHashMap<>(); + } + + public PacketRegistryBuilder( + Int2ObjectMap> mappings, boolean dense) { + this.mappings = new Int2ObjectOpenHashMap<>(mappings); + this.dense = dense; + } + + public

PacketRegistryBuilder register(int id, Class

packetClass, + PacketWriter

writer) { + mappings.put(id, new PacketMapping(id, packetClass, writer, null)); + return this; + } + + public

PacketRegistryBuilder register(int id, Class

packetClass, + PacketWriter

writer, PacketReader

reader) { + mappings.put(id, new PacketMapping(id, packetClass, writer, reader)); + return this; + } + + public PacketRegistryBuilder dense() { + this.dense = true; + return this; + } + + public PacketRegistryBuilder copy() { + return new PacketRegistryBuilder(this.mappings, this.dense); + } + + public PacketRegistryMap build() { + if (this.dense) { + return new DensePacketRegistryMap(mappings); + } else { + return new RegularPacketRegistryMap(mappings); + } + } + + static final class PacketMapping

{ + + int id; + final Class

packetClass; + final PacketWriter

writer; + final @Nullable PacketReader

reader; + + PacketMapping(int id, Class

packetClass, + PacketWriter

writer, + @Nullable PacketReader

reader) { + this.id = id; + this.packetClass = packetClass; + this.writer = writer; + this.reader = reader; + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/PacketRegistryMap.java b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/PacketRegistryMap.java new file mode 100644 index 000000000..d635abc09 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/PacketRegistryMap.java @@ -0,0 +1,12 @@ +package com.velocitypowered.proxy.network.registry.packet; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.network.packet.Packet; +import io.netty.buffer.ByteBuf; +import org.checkerframework.checker.nullness.qual.Nullable; + +public interface PacketRegistryMap { + @Nullable Packet readPacket(final int id, ByteBuf buf, ProtocolVersion version); + +

void writePacket(P packet, ByteBuf buf, ProtocolVersion version); +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/RegularPacketRegistryMap.java b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/RegularPacketRegistryMap.java new file mode 100644 index 000000000..f62fb894d --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/packet/RegularPacketRegistryMap.java @@ -0,0 +1,66 @@ +package com.velocitypowered.proxy.network.registry.packet; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.network.packet.Packet; +import com.velocitypowered.proxy.network.packet.PacketReader; +import com.velocitypowered.proxy.network.packet.PacketWriter; +import com.velocitypowered.proxy.network.registry.packet.PacketRegistryBuilder.PacketMapping; +import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * The canonical implementation of the packet registry map. + */ +public class RegularPacketRegistryMap implements PacketRegistryMap { + + private final Int2ObjectMap> readersById; + private final Int2ObjectMap> writersById; + private final Object2IntMap> classesById; + + public RegularPacketRegistryMap(Int2ObjectMap> mappings) { + int size = mappings.size(); + + this.readersById = new Int2ObjectOpenHashMap<>(size); + this.writersById = new Int2ObjectOpenHashMap<>(size); + this.classesById = new Object2IntOpenHashMap<>(size); + this.classesById.defaultReturnValue(Integer.MIN_VALUE); + + for (PacketMapping value : mappings.values()) { + if (value.reader != null) { + this.readersById.put(value.id, value.reader); + } + + this.writersById.put(value.id, value.writer); + this.classesById.put(value.packetClass, value.id); + } + } + + @Override + public @Nullable Packet readPacket(int id, ByteBuf buf, ProtocolVersion version) { + PacketReader reader = this.readersById.get(id); + if (reader == null) { + return null; + } + return reader.read(buf, version); + } + + @Override + public

void writePacket(P packet, ByteBuf buf, ProtocolVersion version) { + int packetId = this.classesById.getInt(packet.getClass()); + if (packetId == Integer.MIN_VALUE) { + throw new IllegalArgumentException(String.format( + "Unable to find id for packet of type %s in protocol %s", + packet.getClass().getName(), version + )); + } + + PacketWriter writer = this.writersById.get(packetId); + assert writer != null; + writer.write(buf, packet, version); + + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/registry/protocol/ProtocolRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/protocol/ProtocolRegistry.java new file mode 100644 index 000000000..8ebe4886e --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/protocol/ProtocolRegistry.java @@ -0,0 +1,9 @@ +package com.velocitypowered.proxy.network.registry.protocol; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.network.packet.PacketDirection; +import com.velocitypowered.proxy.network.registry.packet.PacketRegistryMap; + +public interface ProtocolRegistry { + PacketRegistryMap lookup(PacketDirection direction, ProtocolVersion version); +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/registry/protocol/SimpleProtocolRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/protocol/SimpleProtocolRegistry.java new file mode 100644 index 000000000..83862361d --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/protocol/SimpleProtocolRegistry.java @@ -0,0 +1,32 @@ +package com.velocitypowered.proxy.network.registry.protocol; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.network.packet.PacketDirection; +import com.velocitypowered.proxy.network.registry.packet.PacketRegistryMap; + +/** + * A flat protocol registry that does not care about the protocol version. + */ +public class SimpleProtocolRegistry implements ProtocolRegistry { + + private final PacketRegistryMap serverbound; + private final PacketRegistryMap clientbound; + + public SimpleProtocolRegistry( + PacketRegistryMap serverbound, + PacketRegistryMap clientbound) { + this.serverbound = serverbound; + this.clientbound = clientbound; + } + + @Override + public PacketRegistryMap lookup(PacketDirection direction, ProtocolVersion version) { + if (direction == PacketDirection.SERVERBOUND) { + return this.serverbound; + } else if (direction == PacketDirection.CLIENTBOUND) { + return this.clientbound; + } else { + throw new NullPointerException("direction"); + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/registry/protocol/VersionSpecificProtocolRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/protocol/VersionSpecificProtocolRegistry.java new file mode 100644 index 000000000..7497c7e7c --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/protocol/VersionSpecificProtocolRegistry.java @@ -0,0 +1,42 @@ +package com.velocitypowered.proxy.network.registry.protocol; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.network.packet.PacketDirection; +import com.velocitypowered.proxy.network.registry.packet.PacketRegistryMap; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.Map; + +/** + * A version-aware protocol registry. + */ +public class VersionSpecificProtocolRegistry implements ProtocolRegistry { + + private final Map serverboundByVersion; + private final Map clientboundByVersion; + + public VersionSpecificProtocolRegistry() { + this.serverboundByVersion = new EnumMap<>(ProtocolVersion.class); + this.clientboundByVersion = new EnumMap<>(ProtocolVersion.class); + } + + public VersionSpecificProtocolRegistry register(ProtocolVersion min, ProtocolVersion max, + PacketRegistryMap serverbound, PacketRegistryMap clientbound) { + for (ProtocolVersion version : EnumSet.range(min, max)) { + this.serverboundByVersion.put(version, serverbound); + this.clientboundByVersion.put(version, clientbound); + } + return this; + } + + @Override + public PacketRegistryMap lookup(PacketDirection direction, ProtocolVersion version) { + if (direction == PacketDirection.SERVERBOUND) { + return this.serverboundByVersion.get(version); + } else if (direction == PacketDirection.CLIENTBOUND) { + return this.clientboundByVersion.get(version); + } else { + throw new NullPointerException("direction"); + } + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/registry/state/ProtocolStates.java b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/state/ProtocolStates.java new file mode 100644 index 000000000..1a9a5b813 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/state/ProtocolStates.java @@ -0,0 +1,57 @@ +package com.velocitypowered.proxy.network.registry.state; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.network.packet.clientbound.ClientboundStatusPingPacket; +import com.velocitypowered.proxy.network.packet.clientbound.ClientboundStatusResponsePacket; +import com.velocitypowered.proxy.network.packet.serverbound.ServerboundHandshakePacket; +import com.velocitypowered.proxy.network.packet.serverbound.ServerboundStatusPingPacket; +import com.velocitypowered.proxy.network.packet.serverbound.ServerboundStatusRequestPacket; +import com.velocitypowered.proxy.network.registry.packet.EmptyPacketRegistryMap; +import com.velocitypowered.proxy.network.registry.packet.PacketRegistryBuilder; +import com.velocitypowered.proxy.network.registry.protocol.ProtocolRegistry; +import com.velocitypowered.proxy.network.registry.protocol.SimpleProtocolRegistry; +import com.velocitypowered.proxy.network.registry.protocol.VersionSpecificProtocolRegistry; + +public class ProtocolStates { + public static final ProtocolRegistry HANDSHAKE; + public static final ProtocolRegistry STATUS; + public static final ProtocolRegistry LOGIN; + + static { + HANDSHAKE = new SimpleProtocolRegistry( + new PacketRegistryBuilder() + .dense() + .register(0x00, ServerboundHandshakePacket.class, ServerboundHandshakePacket.ENCODER, + ServerboundHandshakePacket.DECODER) + .build(), + EmptyPacketRegistryMap.INSTANCE); + + STATUS = new SimpleProtocolRegistry( + new PacketRegistryBuilder() + .dense() + .register(0x00, ServerboundStatusRequestPacket.class, + ServerboundStatusRequestPacket.ENCODER, + ServerboundStatusRequestPacket.DECODER) + .register(0x01, ServerboundStatusPingPacket.class, + ServerboundStatusPingPacket.ENCODER, + ServerboundStatusPingPacket.DECODER) + .build(), + new PacketRegistryBuilder() + .dense() + .register(0x00, ClientboundStatusResponsePacket.class, + ClientboundStatusResponsePacket.ENCODER, + ClientboundStatusResponsePacket.DECODER) + .register(0x01, ClientboundStatusPingPacket.class, + ClientboundStatusPingPacket.ENCODER, + ClientboundStatusPingPacket.DECODER) + .build()); + + LOGIN = new VersionSpecificProtocolRegistry() + .register(ProtocolVersion.MINECRAFT_1_7_2, ProtocolVersion.MINECRAFT_1_8, + Version172To176.SERVERBOUND_LOGIN, Version172To176.CLIENTBOUND_LOGIN); + } + + private ProtocolStates() { + throw new AssertionError(); + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/registry/state/Version172To176.java b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/state/Version172To176.java new file mode 100644 index 000000000..bfe95825f --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/state/Version172To176.java @@ -0,0 +1,39 @@ +package com.velocitypowered.proxy.network.registry.state; + +import com.velocitypowered.proxy.network.packet.clientbound.ClientboundDisconnectPacket; +import com.velocitypowered.proxy.network.packet.clientbound.ClientboundEncryptionRequestPacket; +import com.velocitypowered.proxy.network.packet.clientbound.ClientboundServerLoginSuccessPacket; +import com.velocitypowered.proxy.network.packet.serverbound.ServerboundEncryptionResponsePacket; +import com.velocitypowered.proxy.network.packet.serverbound.ServerboundServerLoginPacket; +import com.velocitypowered.proxy.network.packet.serverbound.ServerboundTabCompleteRequestPacket; +import com.velocitypowered.proxy.network.registry.packet.PacketRegistryBuilder; +import com.velocitypowered.proxy.network.registry.packet.PacketRegistryMap; + +public class Version172To176 { + private Version172To176() { + throw new AssertionError(); + } + + public static final PacketRegistryMap SERVERBOUND_LOGIN = new PacketRegistryBuilder() + .dense() + .register(0x00, ServerboundServerLoginPacket.class, ServerboundServerLoginPacket.ENCODER, + ServerboundServerLoginPacket.DECODER) + .register(0x01, ServerboundEncryptionResponsePacket.class, + ServerboundEncryptionResponsePacket.ENCODER, ServerboundEncryptionResponsePacket.DECODER) + .build(); + + public static final PacketRegistryMap CLIENTBOUND_LOGIN = new PacketRegistryBuilder() + .dense() + .register(0x00, ClientboundDisconnectPacket.class, ClientboundDisconnectPacket.ENCODER, + ClientboundDisconnectPacket.DECODER) + .register(0x01, ClientboundEncryptionRequestPacket.class, + ClientboundEncryptionRequestPacket.ENCODER, ClientboundEncryptionRequestPacket.DECODER) + .register(0x02, ClientboundServerLoginSuccessPacket.class, + ClientboundServerLoginSuccessPacket.ENCODER, ClientboundServerLoginSuccessPacket.DECODER) + .build(); + + public static final PacketRegistryMap SERVERBOUND_PLAY = new PacketRegistryBuilder() + .register(0x14, ServerboundTabCompleteRequestPacket.class, + ServerboundTabCompleteRequestPacket.ENCODER, ServerboundTabCompleteRequestPacket.DECODER) + .build(); +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/registry/version/GenericVersions.java b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/version/GenericVersions.java new file mode 100644 index 000000000..9069f39a1 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/registry/version/GenericVersions.java @@ -0,0 +1,5 @@ +package com.velocitypowered.proxy.network.registry.version; + +public class GenericVersions { + +}