diff --git a/Jenkinsfile b/Jenkinsfile
index be600fb83..22658a3da 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -23,6 +23,31 @@ pipeline {
when {
branch "master"
}
+pipeline {
+ agent any
+ tools {
+ maven 'Maven 3'
+ jdk 'Java 8'
+ }
+ options {
+ buildDiscarder(logRotator(artifactNumToKeepStr: '20'))
+ }
+ stages {
+ stage ('Build') {
+ steps {
+ sh 'mvn clean package'
+ }
+ post {
+ success {
+ archiveArtifacts artifacts: 'target/*.jar', excludes: 'target/*-sources.jar', fingerprint: true
+ }
+ }
+ }
+
+ stage ('Deploy') {
+ when {
+ branch "master"
+ }
steps {
rtMavenDeployer(
@@ -50,4 +75,4 @@ pipeline {
}
}
}
-}
+}
\ No newline at end of file
diff --git a/example/com/github/steveice10/packetlib/test/ClientSessionListener.java b/example/com/github/steveice10/packetlib/test/ClientSessionListener.java
new file mode 100644
index 000000000..39ef0b387
--- /dev/null
+++ b/example/com/github/steveice10/packetlib/test/ClientSessionListener.java
@@ -0,0 +1,50 @@
+package com.github.steveice10.packetlib.test;
+
+import com.github.steveice10.packetlib.Session;
+import com.github.steveice10.packetlib.event.session.ConnectedEvent;
+import com.github.steveice10.packetlib.event.session.DisconnectedEvent;
+import com.github.steveice10.packetlib.event.session.DisconnectingEvent;
+import com.github.steveice10.packetlib.event.session.SessionAdapter;
+import com.github.steveice10.packetlib.packet.Packet;
+
+public class ClientSessionListener extends SessionAdapter {
+ @Override
+ public void packetReceived(Session session, Packet packet) {
+ if (packet instanceof PingPacket) {
+ String id = ((PingPacket) packet).getPingId();
+
+ System.out.println("CLIENT Received: " + id);
+
+ if (id.equals("hello")) {
+ session.send(new PingPacket("exit"));
+ } else if (id.equals("exit")) {
+ session.disconnect("Finished");
+ }
+ }
+ }
+
+ @Override
+ public void packetSent(Session session, Packet packet) {
+ if (packet instanceof PingPacket) {
+ System.out.println("CLIENT Sent: " + ((PingPacket) packet).getPingId());
+ }
+ }
+
+ @Override
+ public void connected(ConnectedEvent event) {
+ System.out.println("CLIENT Connected");
+
+ event.getSession().enableEncryption(((TestProtocol) event.getSession().getPacketProtocol()).getEncryption());
+ event.getSession().send(new PingPacket("hello"));
+ }
+
+ @Override
+ public void disconnecting(DisconnectingEvent event) {
+ System.out.println("CLIENT Disconnecting: " + event.getReason());
+ }
+
+ @Override
+ public void disconnected(DisconnectedEvent event) {
+ System.out.println("CLIENT Disconnected: " + event.getReason());
+ }
+}
diff --git a/example/com/github/steveice10/packetlib/test/PingPacket.java b/example/com/github/steveice10/packetlib/test/PingPacket.java
new file mode 100644
index 000000000..6c0d7e16c
--- /dev/null
+++ b/example/com/github/steveice10/packetlib/test/PingPacket.java
@@ -0,0 +1,32 @@
+package com.github.steveice10.packetlib.test;
+
+import com.github.steveice10.packetlib.codec.PacketCodecHelper;
+import com.github.steveice10.packetlib.packet.Packet;
+
+import java.io.IOException;
+
+public class PingPacket implements Packet {
+ private final String id;
+
+ public PingPacket(ByteBuf buf, PacketCodecHelper codecHelper) throws IOException {
+ this.id = codecHelper.readString(buf);
+ }
+
+ public PingPacket(String id) {
+ this.id = id;
+ }
+
+ public String getPingId() {
+ return this.id;
+ }
+
+ @Override
+ public void write(ByteBuf buf, PacketCodecHelper codecHelper) throws IOException {
+ codecHelper.writeString(buf, this.id);
+ }
+
+ @Override
+ public boolean isPriority() {
+ return false;
+ }
+}
diff --git a/example/com/github/steveice10/packetlib/test/PingServerTest.java b/example/com/github/steveice10/packetlib/test/PingServerTest.java
new file mode 100644
index 000000000..6ee4f0c32
--- /dev/null
+++ b/example/com/github/steveice10/packetlib/test/PingServerTest.java
@@ -0,0 +1,31 @@
+package com.github.steveice10.packetlib.test;
+
+import com.github.steveice10.packetlib.Server;
+import com.github.steveice10.packetlib.Session;
+import com.github.steveice10.packetlib.tcp.TcpClientSession;
+import com.github.steveice10.packetlib.tcp.TcpServer;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import java.security.NoSuchAlgorithmException;
+
+public class PingServerTest {
+ public static void main(String[] args) {
+ SecretKey key;
+ try {
+ KeyGenerator gen = KeyGenerator.getInstance("AES");
+ gen.init(128);
+ key = gen.generateKey();
+ } catch(NoSuchAlgorithmException e) {
+ System.err.println("AES algorithm not supported, exiting...");
+ return;
+ }
+
+ Server server = new TcpServer("127.0.0.1", 25565, TestProtocol::new);
+ server.addListener(new ServerListener(key));
+ server.bind();
+
+ Session client = new TcpClientSession("127.0.0.1", 25565, new TestProtocol(key));
+ client.connect();
+ }
+}
diff --git a/example/com/github/steveice10/packetlib/test/ServerListener.java b/example/com/github/steveice10/packetlib/test/ServerListener.java
new file mode 100644
index 000000000..34911bfcf
--- /dev/null
+++ b/example/com/github/steveice10/packetlib/test/ServerListener.java
@@ -0,0 +1,46 @@
+package com.github.steveice10.packetlib.test;
+
+import com.github.steveice10.packetlib.event.server.ServerAdapter;
+import com.github.steveice10.packetlib.event.server.ServerBoundEvent;
+import com.github.steveice10.packetlib.event.server.ServerClosedEvent;
+import com.github.steveice10.packetlib.event.server.ServerClosingEvent;
+import com.github.steveice10.packetlib.event.server.SessionAddedEvent;
+import com.github.steveice10.packetlib.event.server.SessionRemovedEvent;
+
+import javax.crypto.SecretKey;
+
+public class ServerListener extends ServerAdapter {
+ private SecretKey key;
+
+ public ServerListener(SecretKey key) {
+ this.key = key;
+ }
+
+ @Override
+ public void serverBound(ServerBoundEvent event) {
+ System.out.println("SERVER Bound: " + event.getServer().getHost() + ":" + event.getServer().getPort());
+ }
+
+ @Override
+ public void serverClosing(ServerClosingEvent event) {
+ System.out.println("CLOSING SERVER...");
+ }
+
+ @Override
+ public void serverClosed(ServerClosedEvent event) {
+ System.out.println("SERVER CLOSED");
+ }
+
+ @Override
+ public void sessionAdded(SessionAddedEvent event) {
+ System.out.println("SERVER Session Added: " + event.getSession().getHost() + ":" + event.getSession().getPort());
+ ((TestProtocol) event.getSession().getPacketProtocol()).setSecretKey(this.key);
+ event.getSession().enableEncryption(((TestProtocol) event.getSession().getPacketProtocol()).getEncryption());
+ }
+
+ @Override
+ public void sessionRemoved(SessionRemovedEvent event) {
+ System.out.println("SERVER Session Removed: " + event.getSession().getHost() + ":" + event.getSession().getPort());
+ event.getServer().close(false);
+ }
+}
diff --git a/example/com/github/steveice10/packetlib/test/ServerSessionListener.java b/example/com/github/steveice10/packetlib/test/ServerSessionListener.java
new file mode 100644
index 000000000..81a44cdef
--- /dev/null
+++ b/example/com/github/steveice10/packetlib/test/ServerSessionListener.java
@@ -0,0 +1,40 @@
+package com.github.steveice10.packetlib.test;
+
+import com.github.steveice10.packetlib.Session;
+import com.github.steveice10.packetlib.event.session.ConnectedEvent;
+import com.github.steveice10.packetlib.event.session.DisconnectedEvent;
+import com.github.steveice10.packetlib.event.session.DisconnectingEvent;
+import com.github.steveice10.packetlib.event.session.SessionAdapter;
+import com.github.steveice10.packetlib.packet.Packet;
+
+public class ServerSessionListener extends SessionAdapter {
+ @Override
+ public void packetReceived(Session session, Packet packet) {
+ if (packet instanceof PingPacket) {
+ System.out.println("SERVER Received: " + ((PingPacket) packet).getPingId());
+ session.send(packet);
+ }
+ }
+
+ @Override
+ public void packetSent(Session session, Packet packet) {
+ if (packet instanceof PingPacket) {
+ System.out.println("SERVER Sent: " + ((PingPacket) packet).getPingId());
+ }
+ }
+
+ @Override
+ public void connected(ConnectedEvent event) {
+ System.out.println("SERVER Connected");
+ }
+
+ @Override
+ public void disconnecting(DisconnectingEvent event) {
+ System.out.println("SERVER Disconnecting: " + event.getReason());
+ }
+
+ @Override
+ public void disconnected(DisconnectedEvent event) {
+ System.out.println("SERVER Disconnected: " + event.getReason());
+ }
+}
diff --git a/example/com/github/steveice10/packetlib/test/TestProtocol.java b/example/com/github/steveice10/packetlib/test/TestProtocol.java
new file mode 100644
index 000000000..ff7e8cbbd
--- /dev/null
+++ b/example/com/github/steveice10/packetlib/test/TestProtocol.java
@@ -0,0 +1,62 @@
+package com.github.steveice10.packetlib.test;
+
+import com.github.steveice10.packetlib.Server;
+import com.github.steveice10.packetlib.Session;
+import com.github.steveice10.packetlib.crypt.AESEncryption;
+import com.github.steveice10.packetlib.crypt.PacketEncryption;
+import com.github.steveice10.packetlib.packet.DefaultPacketHeader;
+import com.github.steveice10.packetlib.packet.PacketHeader;
+import com.github.steveice10.packetlib.packet.PacketProtocol;
+
+import javax.crypto.SecretKey;
+import java.security.GeneralSecurityException;
+
+public class TestProtocol extends PacketProtocol {
+ private final PacketHeader header = new DefaultPacketHeader();
+ private AESEncryption encrypt;
+
+ @SuppressWarnings("unused")
+ public TestProtocol() {
+ }
+
+ public TestProtocol(SecretKey key) {
+ this.setSecretKey(key);
+ }
+
+ public PacketCodecHelper createHelper() {
+ return new BasePacketCodecHelper();
+ }
+
+ public void setSecretKey(SecretKey key) {
+ this.register(0, PingPacket.class, PingPacket::new);
+ try {
+ this.encrypt = new AESEncryption(key);
+ } catch(GeneralSecurityException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public String getSRVRecordPrefix() {
+ return "_test";
+ }
+
+ @Override
+ public PacketHeader getPacketHeader() {
+ return this.header;
+ }
+
+ public PacketEncryption getEncryption() {
+ return this.encrypt;
+ }
+
+ @Override
+ public void newClientSession(Session session) {
+ session.addListener(new ClientSessionListener());
+ }
+
+ @Override
+ public void newServerSession(Server server, Session session) {
+ session.addListener(new ServerSessionListener());
+ }
+}
diff --git a/pom.xml b/pom.xml
index 2e855a102..30d8a35da 100644
--- a/pom.xml
+++ b/pom.xml
@@ -87,12 +87,6 @@
1.4
compile
-
- com.github.steveice10
- packetlib
- 3.0.1
- compile
-
com.github.GeyserMC
mcauthlib
@@ -135,6 +129,37 @@
4.13.1
test
+
+ io.netty
+ netty-all
+ 4.1.66.Final
+ compile
+
+
+ io.netty
+ netty-codec-haproxy
+ 4.1.66.Final
+ compile
+ true
+
+
+ io.netty.incubator
+ netty-incubator-transport-native-io_uring
+ 0.0.8.Final
+ linux-x86_64
+
+
+ com.github.spotbugs
+ spotbugs-annotations
+ 4.3.0
+ provided
+
+
+ com.nukkitx.fastutil
+ fastutil-int-object-maps
+ 8.5.2
+ compile
+
diff --git a/src/main/java/com/github/steveice10/packetlib/AbstractServer.java b/src/main/java/com/github/steveice10/packetlib/AbstractServer.java
new file mode 100644
index 000000000..781b95179
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/AbstractServer.java
@@ -0,0 +1,171 @@
+package com.github.steveice10.packetlib;
+
+import com.github.steveice10.packetlib.event.server.*;
+import com.github.steveice10.packetlib.packet.PacketProtocol;
+
+import java.util.*;
+import java.util.function.Supplier;
+
+public abstract class AbstractServer implements Server {
+ private final String host;
+ private final int port;
+ private final Supplier extends PacketProtocol> protocolSupplier;
+
+ private final List sessions = new ArrayList<>();
+
+ private final Map flags = new HashMap<>();
+ private final List listeners = new ArrayList<>();
+
+ public AbstractServer(String host, int port, Supplier extends PacketProtocol> protocolSupplier) {
+ this.host = host;
+ this.port = port;
+ this.protocolSupplier = protocolSupplier;
+ }
+
+ @Override
+ public String getHost() {
+ return this.host;
+ }
+
+ @Override
+ public int getPort() {
+ return this.port;
+ }
+
+ @Override
+ public Supplier extends PacketProtocol> getPacketProtocol() {
+ return this.protocolSupplier;
+ }
+
+ protected PacketProtocol createPacketProtocol() {
+ return this.protocolSupplier.get();
+ }
+
+ @Override
+ public Map getGlobalFlags() {
+ return Collections.unmodifiableMap(this.flags);
+ }
+
+ @Override
+ public boolean hasGlobalFlag(String key) {
+ return this.flags.containsKey(key);
+ }
+
+ @Override
+ public T getGlobalFlag(String key) {
+ return this.getGlobalFlag(key, null);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T getGlobalFlag(String key, T def) {
+ Object value = this.flags.get(key);
+ if(value == null) {
+ return def;
+ }
+
+ try {
+ return (T) value;
+ } catch(ClassCastException e) {
+ throw new IllegalStateException("Tried to get flag \"" + key + "\" as the wrong type. Actual type: " + value.getClass().getName());
+ }
+ }
+
+ @Override
+ public void setGlobalFlag(String key, Object value) {
+ this.flags.put(key, value);
+ }
+
+ @Override
+ public List getListeners() {
+ return Collections.unmodifiableList(this.listeners);
+ }
+
+ @Override
+ public void addListener(ServerListener listener) {
+ this.listeners.add(listener);
+ }
+
+ @Override
+ public void removeListener(ServerListener listener) {
+ this.listeners.remove(listener);
+ }
+
+ protected void callEvent(ServerEvent event) {
+ for(ServerListener listener : this.listeners) {
+ event.call(listener);
+ }
+ }
+
+ @Override
+ public List getSessions() {
+ return new ArrayList<>(this.sessions);
+ }
+
+ public void addSession(Session session) {
+ this.sessions.add(session);
+ this.callEvent(new SessionAddedEvent(this, session));
+ }
+
+ public void removeSession(Session session) {
+ this.sessions.remove(session);
+ if(session.isConnected()) {
+ session.disconnect("Connection closed.");
+ }
+
+ this.callEvent(new SessionRemovedEvent(this, session));
+ }
+
+ @Override
+ public AbstractServer bind() {
+ return this.bind(true);
+ }
+
+ @Override
+ public AbstractServer bind(boolean wait) {
+ return this.bind(wait, null);
+ }
+
+ @Override
+ public AbstractServer bind(boolean wait, Runnable callback) {
+ this.bindImpl(wait, () -> {
+ callEvent(new ServerBoundEvent(AbstractServer.this));
+ if(callback != null) {
+ callback.run();
+ }
+ });
+
+ return this;
+ }
+
+ protected abstract void bindImpl(boolean wait, Runnable callback);
+
+ @Override
+ public void close() {
+ this.close(true);
+ }
+
+ @Override
+ public void close(boolean wait) {
+ this.close(wait, null);
+ }
+
+ @Override
+ public void close(boolean wait, Runnable callback) {
+ this.callEvent(new ServerClosingEvent(this));
+ for(Session session : this.getSessions()) {
+ if(session.isConnected()) {
+ session.disconnect("Server closed.");
+ }
+ }
+
+ this.closeImpl(wait, () -> {
+ callEvent(new ServerClosedEvent(AbstractServer.this));
+ if(callback != null) {
+ callback.run();
+ }
+ });
+ }
+
+ protected abstract void closeImpl(boolean wait, Runnable callback);
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/BuiltinFlags.java b/src/main/java/com/github/steveice10/packetlib/BuiltinFlags.java
new file mode 100644
index 000000000..0080943f1
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/BuiltinFlags.java
@@ -0,0 +1,23 @@
+package com.github.steveice10.packetlib;
+
+/**
+ * Built-in PacketLib session flags.
+ */
+public class BuiltinFlags {
+ /**
+ * When set to true, enables printing internal debug messages.
+ */
+ public static final String PRINT_DEBUG = "print-packetlib-debug";
+
+ public static final String ENABLE_CLIENT_PROXY_PROTOCOL = "enable-client-proxy-protocol";
+
+ public static final String CLIENT_PROXIED_ADDRESS = "client-proxied-address";
+
+ /**
+ * When set to false, an SRV record resolve is not attempted.
+ */
+ public static final String ATTEMPT_SRV_RESOLVE = "attempt-srv-resolve";
+
+ private BuiltinFlags() {
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/ProxyInfo.java b/src/main/java/com/github/steveice10/packetlib/ProxyInfo.java
new file mode 100644
index 000000000..90e6544f3
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/ProxyInfo.java
@@ -0,0 +1,106 @@
+package com.github.steveice10.packetlib;
+
+import java.net.SocketAddress;
+
+/**
+ * Information describing a network proxy.
+ */
+public class ProxyInfo {
+ private Type type;
+ private SocketAddress address;
+ private boolean authenticated;
+ private String username;
+ private String password;
+
+ /**
+ * Creates a new unauthenticated ProxyInfo instance.
+ *
+ * @param type Type of proxy.
+ * @param address Network address of the proxy.
+ */
+ public ProxyInfo(Type type, SocketAddress address) {
+ this.type = type;
+ this.address = address;
+ this.authenticated = false;
+ }
+
+ /**
+ * Creates a new authenticated ProxyInfo instance.
+ *
+ * @param type Type of proxy.
+ * @param address Network address of the proxy.
+ * @param username Username to authenticate with.
+ * @param password Password to authenticate with.
+ */
+ public ProxyInfo(Type type, SocketAddress address, String username, String password) {
+ this(type, address);
+ this.authenticated = true;
+ this.username = username;
+ this.password = password;
+ }
+
+ /**
+ * Gets the proxy's type.
+ *
+ * @return The proxy's type.
+ */
+ public Type getType() {
+ return this.type;
+ }
+
+ /**
+ * Gets the proxy's network address.
+ *
+ * @return The proxy's network address.
+ */
+ public SocketAddress getAddress() {
+ return this.address;
+ }
+
+ /**
+ * Gets whether the proxy is authenticated with.
+ *
+ * @return Whether to authenticate with the proxy.
+ */
+ public boolean isAuthenticated() {
+ return this.authenticated;
+ }
+
+ /**
+ * Gets the proxy's authentication username.
+ *
+ * @return The username to authenticate with.
+ */
+ public String getUsername() {
+ return this.username;
+ }
+
+ /**
+ * Gets the proxy's authentication password.
+ *
+ * @return The password to authenticate with.
+ */
+ public String getPassword() {
+ return this.password;
+ }
+
+ /**
+ * Supported proxy types.
+ */
+ public enum Type {
+ /**
+ * HTTP proxy.
+ */
+ HTTP,
+
+ /**
+ * SOCKS4 proxy.
+ */
+ SOCKS4,
+
+ /**
+ * SOCKS5 proxy.
+ */
+ SOCKS5;
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/Server.java b/src/main/java/com/github/steveice10/packetlib/Server.java
new file mode 100644
index 000000000..3891d8198
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/Server.java
@@ -0,0 +1,156 @@
+package com.github.steveice10.packetlib;
+
+import com.github.steveice10.packetlib.event.server.ServerListener;
+import com.github.steveice10.packetlib.packet.PacketProtocol;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * Listens for new sessions to connect.
+ */
+public interface Server {
+ /**
+ * Gets the host the session is listening on.
+ *
+ * @return The listening host.
+ */
+ String getHost();
+
+ /**
+ * Gets the port the session is listening on.
+ *
+ * @return The listening port.
+ */
+ int getPort();
+
+ /**
+ * Gets the packet protocol of the server.
+ *
+ * @return The server's packet protocol.
+ */
+ Supplier extends PacketProtocol> getPacketProtocol();
+
+ /**
+ * Returns true if the listener is listening.
+ *
+ * @return True if the listener is listening.
+ */
+ boolean isListening();
+
+ /**
+ * Gets this server's set flags.
+ *
+ * @return This server's flags.
+ */
+ Map getGlobalFlags();
+
+ /**
+ * Checks whether this server has a flag set.
+ *
+ * @param key Key of the flag to check for.
+ * @return Whether this server has a flag set.
+ */
+ boolean hasGlobalFlag(String key);
+
+ /**
+ * Gets the value of the given flag as an instance of the given type.
+ *
+ * @param Type of the flag.
+ * @param key Key of the flag.
+ * @return Value of the flag.
+ * @throws IllegalStateException If the flag's value isn't of the required type.
+ */
+ T getGlobalFlag(String key);
+
+ /**
+ * Gets the value of the given flag as an instance of the given type.
+ * If the flag is not set, the specified default value will be returned.
+ *
+ * @param Type of the flag.
+ * @param key Key of the flag.
+ * @param def Default value of the flag.
+ * @return Value of the flag.
+ * @throws IllegalStateException If the flag's value isn't of the required type.
+ */
+ @SuppressWarnings("unchecked")
+ T getGlobalFlag(String key, T def);
+
+ /**
+ * Sets the value of a flag. The flag will be used in sessions if a session does
+ * not contain a value for the flag.
+ *
+ * @param key Key of the flag.
+ * @param value Value to set the flag to.
+ */
+ void setGlobalFlag(String key, Object value);
+
+ /**
+ * Gets the listeners listening on this session.
+ *
+ * @return This server's listeners.
+ */
+ List getListeners();
+
+ /**
+ * Adds a listener to this server.
+ *
+ * @param listener Listener to add.
+ */
+ void addListener(ServerListener listener);
+
+ /**
+ * Removes a listener from this server.
+ *
+ * @param listener Listener to remove.
+ */
+ void removeListener(ServerListener listener);
+
+ /**
+ * Gets all sessions belonging to this server.
+ *
+ * @return Sessions belonging to this server.
+ */
+ List getSessions();
+
+ /**
+ * Binds the listener to its host and port.
+ */
+ AbstractServer bind();
+
+ /**
+ * Binds the listener to its host and port.
+ *
+ * @param wait Whether to wait for the listener to finish binding.
+ */
+ AbstractServer bind(boolean wait);
+
+ /**
+ * Binds the listener to its host and port.
+ *
+ * @param wait Whether to wait for the listener to finish binding.
+ * @param callback Callback to call when the listener has finished binding.
+ */
+ AbstractServer bind(boolean wait, Runnable callback);
+
+ /**
+ * Closes the listener.
+ */
+ void close();
+
+ /**
+ * Closes the listener.
+ *
+ * @param wait Whether to wait for the listener to finish closing.
+ */
+ void close(boolean wait);
+
+ /**
+ * Closes the listener.
+ *
+ * @param wait Whether to wait for the listener to finish closing.
+ * @param callback Callback to call when the listener has finished closing.
+ */
+ void close(boolean wait, Runnable callback);
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/Session.java b/src/main/java/com/github/steveice10/packetlib/Session.java
new file mode 100644
index 000000000..1627c4553
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/Session.java
@@ -0,0 +1,258 @@
+package com.github.steveice10.packetlib;
+
+import com.github.steveice10.packetlib.codec.PacketCodecHelper;
+import com.github.steveice10.packetlib.crypt.PacketEncryption;
+import com.github.steveice10.packetlib.event.session.SessionEvent;
+import com.github.steveice10.packetlib.event.session.SessionListener;
+import com.github.steveice10.packetlib.packet.Packet;
+import com.github.steveice10.packetlib.packet.PacketProtocol;
+
+import java.net.SocketAddress;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A network session.
+ */
+public interface Session {
+
+ /**
+ * Connects this session to its host and port.
+ */
+ public void connect();
+
+ /**
+ * Connects this session to its host and port.
+ *
+ * @param wait Whether to wait for the connection to be established before returning.
+ */
+ public void connect(boolean wait);
+
+ /**
+ * Gets the host the session is connected to.
+ *
+ * @return The connected host.
+ */
+ public String getHost();
+
+ /**
+ * Gets the port the session is connected to.
+ *
+ * @return The connected port.
+ */
+ public int getPort();
+
+ /**
+ * Gets the local address of the session.
+ *
+ * @return The local address, or null if the session is not connected.
+ */
+ public SocketAddress getLocalAddress();
+
+ /**
+ * Gets the remote address of the session.
+ *
+ * @return The remote address, or null if the session is not connected.
+ */
+ public SocketAddress getRemoteAddress();
+
+ /**
+ * Gets the packet protocol of the session.
+ *
+ * @return The session's packet protocol.
+ */
+ public PacketProtocol getPacketProtocol();
+
+ /**
+ * Gets the session's {@link PacketCodecHelper}.
+ *
+ * @return The session's packet codec helper.
+ */
+ PacketCodecHelper getCodecHelper();
+
+ /**
+ * Gets this session's set flags. If this session belongs to a server, the server's
+ * flags will be included in the results.
+ *
+ * @return This session's flags.
+ */
+ public Map getFlags();
+
+ /**
+ * Checks whether this session has a flag set. If this session belongs to a server,
+ * the server's flags will also be checked.
+ *
+ * @param key Key of the flag to check for.
+ * @return Whether this session has a flag set.
+ */
+ public boolean hasFlag(String key);
+
+ /**
+ * Gets the value of the given flag as an instance of the given type. If this
+ * session belongs to a server, the server's flags will be checked for the flag
+ * as well.
+ *
+ * @param Type of the flag.
+ * @param key Key of the flag.
+ * @return Value of the flag.
+ * @throws IllegalStateException If the flag's value isn't of the required type.
+ */
+ public T getFlag(String key);
+
+ /**
+ * Gets the value of the given flag as an instance of the given type. If this
+ * session belongs to a server, the server's flags will be checked for the flag
+ * as well. If the flag is not set, the specified default value will be returned.
+ *
+ * @param Type of the flag.
+ * @param key Key of the flag.
+ * @param def Default value of the flag.
+ * @return Value of the flag.
+ * @throws IllegalStateException If the flag's value isn't of the required type.
+ */
+ public T getFlag(String key, T def);
+
+ /**
+ * Sets the value of a flag. This does not change a server's flags if this session
+ * belongs to a server.
+ *
+ * @param key Key of the flag.
+ * @param value Value to set the flag to.
+ */
+ public void setFlag(String key, Object value);
+
+ /**
+ * Gets the listeners listening on this session.
+ *
+ * @return This session's listeners.
+ */
+ public List getListeners();
+
+ /**
+ * Adds a listener to this session.
+ *
+ * @param listener Listener to add.
+ */
+ public void addListener(SessionListener listener);
+
+ /**
+ * Removes a listener from this session.
+ *
+ * @param listener Listener to remove.
+ */
+ public void removeListener(SessionListener listener);
+
+ /**
+ * Calls an event on the listeners of this session.
+ *
+ * @param event Event to call.
+ */
+ void callEvent(SessionEvent event);
+
+ /**
+ * Notifies all listeners that a packet was just received.
+ *
+ * @param packet Packet to notify.
+ */
+ void callPacketReceived(Packet packet);
+
+ /**
+ * Notifies all listeners that a packet was just sent.
+ *
+ * @param packet Packet to notify.
+ */
+ void callPacketSent(Packet packet);
+
+ /**
+ * Gets the compression packet length threshold for this session (-1 = disabled).
+ *
+ * @return This session's compression threshold.
+ */
+ int getCompressionThreshold();
+
+ /**
+ * Sets the compression packet length threshold for this session (-1 = disabled).
+ *
+ * @param threshold The new compression threshold.
+ * @param validateDecompression whether to validate that the decompression fits within size checks.
+ */
+ void setCompressionThreshold(int threshold, boolean validateDecompression);
+
+ /**
+ * Enables encryption for this session.
+ *
+ * @param encryption the encryption to encrypt with
+ */
+ void enableEncryption(PacketEncryption encryption);
+
+ /**
+ * Gets the connect timeout for this session in seconds.
+ *
+ * @return The session's connect timeout.
+ */
+ public int getConnectTimeout();
+
+ /**
+ * Sets the connect timeout for this session in seconds.
+ *
+ * @param timeout Connect timeout to set.
+ */
+ public void setConnectTimeout(int timeout);
+
+ /**
+ * Gets the read timeout for this session in seconds.
+ *
+ * @return The session's read timeout.
+ */
+ public int getReadTimeout();
+
+ /**
+ * Sets the read timeout for this session in seconds.
+ *
+ * @param timeout Read timeout to set.
+ */
+ public void setReadTimeout(int timeout);
+
+ /**
+ * Gets the write timeout for this session in seconds.
+ *
+ * @return The session's write timeout.
+ */
+ public int getWriteTimeout();
+
+ /**
+ * Sets the write timeout for this session in seconds.
+ *
+ * @param timeout Write timeout to set.
+ */
+ public void setWriteTimeout(int timeout);
+
+ /**
+ * Returns true if the session is connected.
+ *
+ * @return True if the session is connected.
+ */
+ public boolean isConnected();
+
+ /**
+ * Sends a packet.
+ *
+ * @param packet Packet to send.
+ */
+ public void send(Packet packet);
+
+ /**
+ * Disconnects the session.
+ *
+ * @param reason Reason for disconnecting.
+ */
+ public void disconnect(String reason);
+
+ /**
+ * Disconnects the session.
+ *
+ * @param reason Reason for disconnecting.
+ * @param cause Throwable responsible for disconnecting.
+ */
+ public void disconnect(String reason, Throwable cause);
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/codec/BasePacketCodecHelper.java b/src/main/java/com/github/steveice10/packetlib/codec/BasePacketCodecHelper.java
new file mode 100644
index 000000000..544e098c8
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/codec/BasePacketCodecHelper.java
@@ -0,0 +1,166 @@
+package com.github.steveice10.packetlib.codec;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+
+import java.nio.charset.StandardCharsets;
+
+public class BasePacketCodecHelper implements PacketCodecHelper {
+
+ @Override
+ public void writeVarInt(ByteBuf buf, int value) {
+ this.writeVarLong(buf, value & 0xFFFFFFFFL);
+ }
+
+ @Override
+ public int readVarInt(ByteBuf buf) {
+ int value = 0;
+ int size = 0;
+ int b;
+ while (((b = buf.readByte()) & 0x80) == 0x80) {
+ value |= (b & 0x7F) << (size++ * 7);
+ if (size > 5) {
+ throw new IllegalArgumentException("VarInt too long (length must be <= 5)");
+ }
+ }
+
+ return value | ((b & 0x7F) << (size * 7));
+ }
+
+ // Based off of Andrew Steinborn's blog post:
+ // https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
+ @Override
+ public void writeVarLong(ByteBuf buf, long value) {
+ // Peel the one and two byte count cases explicitly as they are the most common VarInt sizes
+ // that the server will write, to improve inlining.
+ if ((value & ~0x7FL) == 0) {
+ buf.writeByte((byte) value);
+ } else if ((value & ~0x3FFFL) == 0) {
+ int w = (int) ((value & 0x7FL | 0x80L) << 8 |
+ (value >>> 7));
+ buf.writeShort(w);
+ } else {
+ writeVarLongFull(buf, value);
+ }
+ }
+
+ private static void writeVarLongFull(ByteBuf buf, long value) {
+ if ((value & ~0x7FL) == 0) {
+ buf.writeByte((byte) value);
+ } else if ((value & ~0x3FFFL) == 0) {
+ int w = (int) ((value & 0x7FL | 0x80L) << 8 |
+ (value >>> 7));
+ buf.writeShort(w);
+ } else if ((value & ~0x1FFFFFL) == 0) {
+ int w = (int) ((value & 0x7FL | 0x80L) << 16 |
+ ((value >>> 7) & 0x7FL | 0x80L) << 8 |
+ (value >>> 14));
+ buf.writeMedium(w);
+ } else if ((value & ~0xFFFFFFFL) == 0) {
+ int w = (int) ((value & 0x7F | 0x80) << 24 |
+ (((value >>> 7) & 0x7F | 0x80) << 16) |
+ ((value >>> 14) & 0x7F | 0x80) << 8 |
+ (value >>> 21));
+ buf.writeInt(w);
+ } else if ((value & ~0x7FFFFFFFFL) == 0) {
+ int w = (int) ((value & 0x7F | 0x80) << 24 |
+ ((value >>> 7) & 0x7F | 0x80) << 16 |
+ ((value >>> 14) & 0x7F | 0x80) << 8 |
+ ((value >>> 21) & 0x7F | 0x80));
+ buf.writeInt(w);
+ buf.writeByte((int) (value >>> 28));
+ } else if ((value & ~0x3FFFFFFFFFFL) == 0) {
+ int w = (int) ((value & 0x7F | 0x80) << 24 |
+ ((value >>> 7) & 0x7F | 0x80) << 16 |
+ ((value >>> 14) & 0x7F | 0x80) << 8 |
+ ((value >>> 21) & 0x7F | 0x80));
+ int w2 = (int) (((value >>> 28) & 0x7FL | 0x80L) << 8 |
+ (value >>> 35));
+ buf.writeInt(w);
+ buf.writeShort(w2);
+ } else if ((value & ~0x1FFFFFFFFFFFFL) == 0) {
+ int w = (int) ((value & 0x7F | 0x80) << 24 |
+ ((value >>> 7) & 0x7F | 0x80) << 16 |
+ ((value >>> 14) & 0x7F | 0x80) << 8 |
+ ((value >>> 21) & 0x7F | 0x80));
+ int w2 = (int) ((((value >>> 28) & 0x7FL | 0x80L) << 16 |
+ ((value >>> 35) & 0x7FL | 0x80L) << 8) |
+ (value >>> 42));
+ buf.writeInt(w);
+ buf.writeMedium(w2);
+ } else if ((value & ~0xFFFFFFFFFFFFFFL) == 0) {
+ long w = (value & 0x7F | 0x80) << 56 |
+ ((value >>> 7) & 0x7F | 0x80) << 48 |
+ ((value >>> 14) & 0x7F | 0x80) << 40 |
+ ((value >>> 21) & 0x7F | 0x80) << 32 |
+ ((value >>> 28) & 0x7FL | 0x80L) << 24 |
+ ((value >>> 35) & 0x7FL | 0x80L) << 16 |
+ ((value >>> 42) & 0x7FL | 0x80L) << 8 |
+ (value >>> 49);
+ buf.writeLong(w);
+ } else if ((value & ~0x7FFFFFFFFFFFFFFFL) == 0) {
+ long w = (value & 0x7F | 0x80) << 56 |
+ ((value >>> 7) & 0x7F | 0x80) << 48 |
+ ((value >>> 14) & 0x7F | 0x80) << 40 |
+ ((value >>> 21) & 0x7F | 0x80) << 32 |
+ ((value >>> 28) & 0x7FL | 0x80L) << 24 |
+ ((value >>> 35) & 0x7FL | 0x80L) << 16 |
+ ((value >>> 42) & 0x7FL | 0x80L) << 8 |
+ (value >>> 49);
+ buf.writeLong(w);
+ buf.writeByte((byte) (value >>> 56));
+ } else {
+ long w = (value & 0x7F | 0x80) << 56 |
+ ((value >>> 7) & 0x7F | 0x80) << 48 |
+ ((value >>> 14) & 0x7F | 0x80) << 40 |
+ ((value >>> 21) & 0x7F | 0x80) << 32 |
+ ((value >>> 28) & 0x7FL | 0x80L) << 24 |
+ ((value >>> 35) & 0x7FL | 0x80L) << 16 |
+ ((value >>> 42) & 0x7FL | 0x80L) << 8 |
+ (value >>> 49);
+ int w2 = (int) (((value >>> 56) & 0x7FL | 0x80L) << 8 |
+ (value >>> 63));
+ buf.writeLong(w);
+ buf.writeShort(w2);
+ }
+ }
+
+ @Override
+ public long readVarLong(ByteBuf buf) {
+ int value = 0;
+ int size = 0;
+ int b;
+ while (((b = buf.readByte()) & 0x80) == 0x80) {
+ value |= (b & 0x7F) << (size++ * 7);
+ if (size > 10) {
+ throw new IllegalArgumentException("VarLong too long (length must be <= 10)");
+ }
+ }
+
+ return value | ((b & 0x7FL) << (size * 7));
+ }
+
+ public String readString(ByteBuf buf) {
+ return this.readString(buf, Short.MAX_VALUE);
+ }
+
+ @Override
+ public String readString(ByteBuf buf, int maxLength) {
+ int length = this.readVarInt(buf);
+ if (length > maxLength * 3) {
+ throw new IllegalArgumentException("String buffer is longer than maximum allowed length");
+ }
+ String string = (String) buf.readCharSequence(length, StandardCharsets.UTF_8);
+ if (string.length() > maxLength) {
+ throw new IllegalArgumentException("String is longer than maximum allowed length");
+ }
+
+ return string;
+ }
+
+ @Override
+ public void writeString(ByteBuf buf, String value) {
+ this.writeVarInt(buf, ByteBufUtil.utf8Bytes(value));
+ buf.writeCharSequence(value, StandardCharsets.UTF_8);
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/codec/PacketCodecHelper.java b/src/main/java/com/github/steveice10/packetlib/codec/PacketCodecHelper.java
new file mode 100644
index 000000000..2fdc67c0f
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/codec/PacketCodecHelper.java
@@ -0,0 +1,20 @@
+package com.github.steveice10.packetlib.codec;
+
+import io.netty.buffer.ByteBuf;
+
+public interface PacketCodecHelper {
+
+ void writeVarInt(ByteBuf buf, int value);
+
+ int readVarInt(ByteBuf buf);
+
+ void writeVarLong(ByteBuf buf, long value);
+
+ long readVarLong(ByteBuf buf);
+
+ String readString(ByteBuf buf);
+
+ String readString(ByteBuf buf, int maxLength);
+
+ void writeString(ByteBuf buf, String value);
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/codec/PacketDefinition.java b/src/main/java/com/github/steveice10/packetlib/codec/PacketDefinition.java
new file mode 100644
index 000000000..fccc0b480
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/codec/PacketDefinition.java
@@ -0,0 +1,56 @@
+package com.github.steveice10.packetlib.codec;
+
+import com.github.steveice10.packetlib.packet.Packet;
+import io.netty.buffer.ByteBuf;
+
+import java.io.IOException;
+
+/**
+ * Represents a definition of a packet with various
+ * information about it, such as it's id, class and
+ * factory for construction.
+ *
+ * @param the packet type
+ */
+public class PacketDefinition {
+ private final int id;
+ private final Class packetClass;
+ private final PacketSerializer serializer;
+
+ public PacketDefinition(final int id, final Class packetClass, final PacketSerializer serializer) {
+ this.id = id;
+ this.packetClass = packetClass;
+ this.serializer = serializer;
+ }
+
+ /**
+ * Returns the id of the packet.
+ *
+ * @return the id of the packet
+ */
+ public int getId() {
+ return this.id;
+ }
+
+ /**
+ * Returns the class of the packet.
+ *
+ * @return the class of the packet
+ */
+ public Class getPacketClass() {
+ return this.packetClass;
+ }
+
+ /**
+ * Returns the {@link PacketSerializer} of the packet.
+ *
+ * @return the packet serializer of the packet
+ */
+ public PacketSerializer getSerializer() {
+ return this.serializer;
+ }
+
+ public T newInstance(ByteBuf buf, H helper) throws IOException {
+ return this.serializer.deserialize(buf, helper, this);
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/codec/PacketSerializer.java b/src/main/java/com/github/steveice10/packetlib/codec/PacketSerializer.java
new file mode 100644
index 000000000..5d70e73df
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/codec/PacketSerializer.java
@@ -0,0 +1,13 @@
+package com.github.steveice10.packetlib.codec;
+
+import com.github.steveice10.packetlib.packet.Packet;
+import io.netty.buffer.ByteBuf;
+
+import java.io.IOException;
+
+public interface PacketSerializer {
+
+ void serialize(ByteBuf buf, H helper, T packet) throws IOException;
+
+ T deserialize(ByteBuf buf, H helper, PacketDefinition definition) throws IOException;
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/crypt/AESEncryption.java b/src/main/java/com/github/steveice10/packetlib/crypt/AESEncryption.java
new file mode 100644
index 000000000..7dc26a90e
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/crypt/AESEncryption.java
@@ -0,0 +1,47 @@
+package com.github.steveice10.packetlib.crypt;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+
+/**
+ * An encryption implementation using "AES/CFB8/NoPadding" encryption.
+ */
+public class AESEncryption implements PacketEncryption {
+ private Cipher inCipher;
+ private Cipher outCipher;
+
+ /**
+ * Creates a new AESEncryption instance.
+ *
+ * @param key Key to use when encrypting/decrypting data.
+ * @throws GeneralSecurityException If a security error occurs.
+ */
+ public AESEncryption(Key key) throws GeneralSecurityException {
+ this.inCipher = Cipher.getInstance("AES/CFB8/NoPadding");
+ this.inCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(key.getEncoded()));
+ this.outCipher = Cipher.getInstance("AES/CFB8/NoPadding");
+ this.outCipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(key.getEncoded()));
+ }
+
+ @Override
+ public int getDecryptOutputSize(int length) {
+ return this.inCipher.getOutputSize(length);
+ }
+
+ @Override
+ public int getEncryptOutputSize(int length) {
+ return this.outCipher.getOutputSize(length);
+ }
+
+ @Override
+ public int decrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset) throws Exception {
+ return this.inCipher.update(input, inputOffset, inputLength, output, outputOffset);
+ }
+
+ @Override
+ public int encrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset) throws Exception {
+ return this.outCipher.update(input, inputOffset, inputLength, output, outputOffset);
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/crypt/PacketEncryption.java b/src/main/java/com/github/steveice10/packetlib/crypt/PacketEncryption.java
new file mode 100644
index 000000000..371ca2403
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/crypt/PacketEncryption.java
@@ -0,0 +1,48 @@
+package com.github.steveice10.packetlib.crypt;
+
+/**
+ * An interface for encrypting packets.
+ */
+public interface PacketEncryption {
+ /**
+ * Gets the output size from decrypting.
+ *
+ * @param length Length of the data being decrypted.
+ * @return The output size from decrypting.
+ */
+ public int getDecryptOutputSize(int length);
+
+ /**
+ * Gets the output size from encrypting.
+ *
+ * @param length Length of the data being encrypted.
+ * @return The output size from encrypting.
+ */
+ public int getEncryptOutputSize(int length);
+
+ /**
+ * Decrypts the given data.
+ *
+ * @param input Input data to decrypt.
+ * @param inputOffset Offset of the data to start decrypting at.
+ * @param inputLength Length of the data to be decrypted.
+ * @param output Array to output decrypted data to.
+ * @param outputOffset Offset of the output array to start at.
+ * @return The number of bytes stored in the output array.
+ * @throws Exception If an error occurs.
+ */
+ public int decrypt(byte input[], int inputOffset, int inputLength, byte output[], int outputOffset) throws Exception;
+
+ /**
+ * Encrypts the given data.
+ *
+ * @param input Input data to encrypt.
+ * @param inputOffset Offset of the data to start encrypting at.
+ * @param inputLength Length of the data to be encrypted.
+ * @param output Array to output encrypted data to.
+ * @param outputOffset Offset of the output array to start at.
+ * @return The number of bytes stored in the output array.
+ * @throws Exception If an error occurs.
+ */
+ public int encrypt(byte input[], int inputOffset, int inputLength, byte output[], int outputOffset) throws Exception;
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/server/ServerAdapter.java b/src/main/java/com/github/steveice10/packetlib/event/server/ServerAdapter.java
new file mode 100644
index 000000000..64c99bc36
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/server/ServerAdapter.java
@@ -0,0 +1,26 @@
+package com.github.steveice10.packetlib.event.server;
+
+/**
+ * An adapter for picking server events to listen for.
+ */
+public class ServerAdapter implements ServerListener {
+ @Override
+ public void serverBound(ServerBoundEvent event) {
+ }
+
+ @Override
+ public void serverClosing(ServerClosingEvent event) {
+ }
+
+ @Override
+ public void serverClosed(ServerClosedEvent event) {
+ }
+
+ @Override
+ public void sessionAdded(SessionAddedEvent event) {
+ }
+
+ @Override
+ public void sessionRemoved(SessionRemovedEvent event) {
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/server/ServerBoundEvent.java b/src/main/java/com/github/steveice10/packetlib/event/server/ServerBoundEvent.java
new file mode 100644
index 000000000..54cb07a82
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/server/ServerBoundEvent.java
@@ -0,0 +1,33 @@
+package com.github.steveice10.packetlib.event.server;
+
+import com.github.steveice10.packetlib.Server;
+
+/**
+ * Called when the server is bound to its host and port.
+ */
+public class ServerBoundEvent implements ServerEvent {
+ private Server server;
+
+ /**
+ * Creates a new ServerBoundEvent instance.
+ *
+ * @param server Server being bound.
+ */
+ public ServerBoundEvent(Server server) {
+ this.server = server;
+ }
+
+ /**
+ * Gets the server involved in this event.
+ *
+ * @return The event's server.
+ */
+ public Server getServer() {
+ return this.server;
+ }
+
+ @Override
+ public void call(ServerListener listener) {
+ listener.serverBound(this);
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/server/ServerClosedEvent.java b/src/main/java/com/github/steveice10/packetlib/event/server/ServerClosedEvent.java
new file mode 100644
index 000000000..c6251c2fa
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/server/ServerClosedEvent.java
@@ -0,0 +1,33 @@
+package com.github.steveice10.packetlib.event.server;
+
+import com.github.steveice10.packetlib.Server;
+
+/**
+ * Called when the server is closed.
+ */
+public class ServerClosedEvent implements ServerEvent {
+ private Server server;
+
+ /**
+ * Creates a new ServerClosedEvent instance.
+ *
+ * @param server Server being closed.
+ */
+ public ServerClosedEvent(Server server) {
+ this.server = server;
+ }
+
+ /**
+ * Gets the server involved in this event.
+ *
+ * @return The event's server.
+ */
+ public Server getServer() {
+ return this.server;
+ }
+
+ @Override
+ public void call(ServerListener listener) {
+ listener.serverClosed(this);
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/server/ServerClosingEvent.java b/src/main/java/com/github/steveice10/packetlib/event/server/ServerClosingEvent.java
new file mode 100644
index 000000000..ebcb6d140
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/server/ServerClosingEvent.java
@@ -0,0 +1,33 @@
+package com.github.steveice10.packetlib.event.server;
+
+import com.github.steveice10.packetlib.Server;
+
+/**
+ * Called when the server is about to close.
+ */
+public class ServerClosingEvent implements ServerEvent {
+ private Server server;
+
+ /**
+ * Creates a new ServerClosingEvent instance.
+ *
+ * @param server Server being closed.
+ */
+ public ServerClosingEvent(Server server) {
+ this.server = server;
+ }
+
+ /**
+ * Gets the server involved in this event.
+ *
+ * @return The event's server.
+ */
+ public Server getServer() {
+ return this.server;
+ }
+
+ @Override
+ public void call(ServerListener listener) {
+ listener.serverClosing(this);
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/server/ServerEvent.java b/src/main/java/com/github/steveice10/packetlib/event/server/ServerEvent.java
new file mode 100644
index 000000000..2037d5bbf
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/server/ServerEvent.java
@@ -0,0 +1,13 @@
+package com.github.steveice10.packetlib.event.server;
+
+/**
+ * An event relating to servers.
+ */
+public interface ServerEvent {
+ /**
+ * Calls the event.
+ *
+ * @param listener Listener to call the event on.
+ */
+ public void call(ServerListener listener);
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/server/ServerListener.java b/src/main/java/com/github/steveice10/packetlib/event/server/ServerListener.java
new file mode 100644
index 000000000..6c32331f1
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/server/ServerListener.java
@@ -0,0 +1,41 @@
+package com.github.steveice10.packetlib.event.server;
+
+/**
+ * A listener for listening to server events.
+ */
+public interface ServerListener {
+ /**
+ * Called when a server is bound to its host and port.
+ *
+ * @param event Data relating to the event.
+ */
+ public void serverBound(ServerBoundEvent event);
+
+ /**
+ * Called when a server is about to close.
+ *
+ * @param event Data relating to the event.
+ */
+ public void serverClosing(ServerClosingEvent event);
+
+ /**
+ * Called when a server is closed.
+ *
+ * @param event Data relating to the event.
+ */
+ public void serverClosed(ServerClosedEvent event);
+
+ /**
+ * Called when a session is added to the server.
+ *
+ * @param event Data relating to the event.
+ */
+ public void sessionAdded(SessionAddedEvent event);
+
+ /**
+ * Called when a session is removed and disconnected from the server.
+ *
+ * @param event Data relating to the event.
+ */
+ public void sessionRemoved(SessionRemovedEvent event);
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/server/SessionAddedEvent.java b/src/main/java/com/github/steveice10/packetlib/event/server/SessionAddedEvent.java
new file mode 100644
index 000000000..3adb49c0f
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/server/SessionAddedEvent.java
@@ -0,0 +1,46 @@
+package com.github.steveice10.packetlib.event.server;
+
+import com.github.steveice10.packetlib.Server;
+import com.github.steveice10.packetlib.Session;
+
+/**
+ * Called when a session is added to the server.
+ */
+public class SessionAddedEvent implements ServerEvent {
+ private Server server;
+ private Session session;
+
+ /**
+ * Creates a new SessionAddedEvent instance.
+ *
+ * @param server Server the session is being added to.
+ * @param session Session being added.
+ */
+ public SessionAddedEvent(Server server, Session session) {
+ this.server = server;
+ this.session = session;
+ }
+
+ /**
+ * Gets the server involved in this event.
+ *
+ * @return The event's server.
+ */
+ public Server getServer() {
+ return this.server;
+ }
+
+ /**
+ * Gets the session involved in this event.
+ *
+ * @return The event's session.
+ */
+ public Session getSession() {
+ return this.session;
+ }
+
+ @Override
+ public void call(ServerListener listener) {
+ listener.sessionAdded(this);
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/server/SessionRemovedEvent.java b/src/main/java/com/github/steveice10/packetlib/event/server/SessionRemovedEvent.java
new file mode 100644
index 000000000..8bb49bc09
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/server/SessionRemovedEvent.java
@@ -0,0 +1,46 @@
+package com.github.steveice10.packetlib.event.server;
+
+import com.github.steveice10.packetlib.Server;
+import com.github.steveice10.packetlib.Session;
+
+/**
+ * Called when a session is removed and disconnected from the server.
+ */
+public class SessionRemovedEvent implements ServerEvent {
+ private Server server;
+ private Session session;
+
+ /**
+ * Creates a new SessionRemovedEvent instance.
+ *
+ * @param server Server the session is being removed from.
+ * @param session Session being removed.
+ */
+ public SessionRemovedEvent(Server server, Session session) {
+ this.server = server;
+ this.session = session;
+ }
+
+ /**
+ * Gets the server involved in this event.
+ *
+ * @return The event's server.
+ */
+ public Server getServer() {
+ return this.server;
+ }
+
+ /**
+ * Gets the session involved in this event.
+ *
+ * @return The event's session.
+ */
+ public Session getSession() {
+ return this.session;
+ }
+
+ @Override
+ public void call(ServerListener listener) {
+ listener.sessionRemoved(this);
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/session/ConnectedEvent.java b/src/main/java/com/github/steveice10/packetlib/event/session/ConnectedEvent.java
new file mode 100644
index 000000000..1e312e707
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/session/ConnectedEvent.java
@@ -0,0 +1,33 @@
+package com.github.steveice10.packetlib.event.session;
+
+import com.github.steveice10.packetlib.Session;
+
+/**
+ * Called when the session connects.
+ */
+public class ConnectedEvent implements SessionEvent {
+ private Session session;
+
+ /**
+ * Creates a new ConnectedEvent instance.
+ *
+ * @param session Session being connected.
+ */
+ public ConnectedEvent(Session session) {
+ this.session = session;
+ }
+
+ /**
+ * Gets the session involved in this event.
+ *
+ * @return The event's session.
+ */
+ public Session getSession() {
+ return this.session;
+ }
+
+ @Override
+ public void call(SessionListener listener) {
+ listener.connected(this);
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/session/DisconnectedEvent.java b/src/main/java/com/github/steveice10/packetlib/event/session/DisconnectedEvent.java
new file mode 100644
index 000000000..463ed0424
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/session/DisconnectedEvent.java
@@ -0,0 +1,67 @@
+package com.github.steveice10.packetlib.event.session;
+
+import com.github.steveice10.packetlib.Session;
+
+/**
+ * Called when the session is disconnected.
+ */
+public class DisconnectedEvent implements SessionEvent {
+ private Session session;
+ private String reason;
+ private Throwable cause;
+
+ /**
+ * Creates a new DisconnectedEvent instance.
+ *
+ * @param session Session being disconnected.
+ * @param reason Reason for the session to disconnect.
+ */
+ public DisconnectedEvent(Session session, String reason) {
+ this(session, reason, null);
+ }
+
+ /**
+ * Creates a new DisconnectedEvent instance.
+ *
+ * @param session Session being disconnected.
+ * @param reason Reason for the session to disconnect.
+ * @param cause Throwable that caused the disconnect.
+ */
+ public DisconnectedEvent(Session session, String reason, Throwable cause) {
+ this.session = session;
+ this.reason = reason;
+ this.cause = cause;
+ }
+
+ /**
+ * Gets the session involved in this event.
+ *
+ * @return The event's session.
+ */
+ public Session getSession() {
+ return this.session;
+ }
+
+ /**
+ * Gets the reason given for the session disconnecting.
+ *
+ * @return The event's reason.
+ */
+ public String getReason() {
+ return this.reason;
+ }
+
+ /**
+ * Gets the Throwable responsible for the session disconnecting.
+ *
+ * @return The Throwable responsible for the disconnect, or null if the disconnect was not caused by a Throwable.
+ */
+ public Throwable getCause() {
+ return this.cause;
+ }
+
+ @Override
+ public void call(SessionListener listener) {
+ listener.disconnected(this);
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/session/DisconnectingEvent.java b/src/main/java/com/github/steveice10/packetlib/event/session/DisconnectingEvent.java
new file mode 100644
index 000000000..0c9929e4e
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/session/DisconnectingEvent.java
@@ -0,0 +1,67 @@
+package com.github.steveice10.packetlib.event.session;
+
+import com.github.steveice10.packetlib.Session;
+
+/**
+ * Called when the session is about to disconnect.
+ */
+public class DisconnectingEvent implements SessionEvent {
+ private Session session;
+ private String reason;
+ private Throwable cause;
+
+ /**
+ * Creates a new DisconnectingEvent instance.
+ *
+ * @param session Session being disconnected.
+ * @param reason Reason for the session to disconnect.
+ */
+ public DisconnectingEvent(Session session, String reason) {
+ this(session, reason, null);
+ }
+
+ /**
+ * Creates a new DisconnectingEvent instance.
+ *
+ * @param session Session being disconnected.
+ * @param reason Reason for the session to disconnect.
+ * @param cause Throwable that caused the disconnect.
+ */
+ public DisconnectingEvent(Session session, String reason, Throwable cause) {
+ this.session = session;
+ this.reason = reason;
+ this.cause = cause;
+ }
+
+ /**
+ * Gets the session involved in this event.
+ *
+ * @return The event's session.
+ */
+ public Session getSession() {
+ return this.session;
+ }
+
+ /**
+ * Gets the reason given for the session disconnecting.
+ *
+ * @return The event's reason.
+ */
+ public String getReason() {
+ return this.reason;
+ }
+
+ /**
+ * Gets the Throwable responsible for the session disconnecting.
+ *
+ * @return The Throwable responsible for the disconnect, or null if the disconnect was not caused by a Throwable.
+ */
+ public Throwable getCause() {
+ return this.cause;
+ }
+
+ @Override
+ public void call(SessionListener listener) {
+ listener.disconnecting(this);
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/session/PacketErrorEvent.java b/src/main/java/com/github/steveice10/packetlib/event/session/PacketErrorEvent.java
new file mode 100644
index 000000000..735ca58be
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/session/PacketErrorEvent.java
@@ -0,0 +1,68 @@
+package com.github.steveice10.packetlib.event.session;
+
+import com.github.steveice10.packetlib.Session;
+
+/**
+ * Called when a session encounters an error while reading or writing packet data.
+ */
+public class PacketErrorEvent implements SessionEvent {
+ private Session session;
+ private Throwable cause;
+ private boolean suppress = false;
+
+ /**
+ * Creates a new SessionErrorEvent instance.
+ *
+ * @param session Session that the error came from.
+ * @param cause Cause of the error.
+ */
+ public PacketErrorEvent(Session session, Throwable cause) {
+ this.session = session;
+ this.cause = cause;
+ }
+
+ /**
+ * Gets the session involved in this event.
+ *
+ * @return The event's session.
+ */
+ public Session getSession() {
+ return this.session;
+ }
+
+ /**
+ * Gets the Throwable responsible for the error.
+ *
+ * @return The Throwable responsible for the error.
+ */
+ public Throwable getCause() {
+ return this.cause;
+ }
+
+ /**
+ * Gets whether the error should be suppressed. If the error is not suppressed,
+ * it will be passed on through internal error handling and disconnect the session.
+ *
+ * The default value is false.
+ *
+ * @return Whether the error should be suppressed.
+ */
+ public boolean shouldSuppress() {
+ return this.suppress;
+ }
+
+ /**
+ * Sets whether the error should be suppressed. If the error is not suppressed,
+ * it will be passed on through internal error handling and disconnect the session.
+ *
+ * @param suppress Whether the error should be suppressed.
+ */
+ public void setSuppress(boolean suppress) {
+ this.suppress = suppress;
+ }
+
+ @Override
+ public void call(SessionListener listener) {
+ listener.packetError(this);
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/session/PacketSendingEvent.java b/src/main/java/com/github/steveice10/packetlib/event/session/PacketSendingEvent.java
new file mode 100644
index 000000000..c5814a4fb
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/session/PacketSendingEvent.java
@@ -0,0 +1,81 @@
+package com.github.steveice10.packetlib.event.session;
+
+import com.github.steveice10.packetlib.Session;
+import com.github.steveice10.packetlib.packet.Packet;
+
+/**
+ * Called when the session is sending a packet.
+ */
+public class PacketSendingEvent implements SessionEvent {
+ private Session session;
+ private Packet packet;
+ private boolean cancelled = false;
+
+ /**
+ * Creates a new PacketSendingEvent instance.
+ *
+ * @param session Session sending the packet.
+ * @param packet Packet being sent.
+ */
+ public PacketSendingEvent(Session session, Packet packet) {
+ this.session = session;
+ this.packet = packet;
+ }
+
+ /**
+ * Gets the session involved in this event.
+ *
+ * @return The event's session.
+ */
+ public Session getSession() {
+ return this.session;
+ }
+
+ /**
+ * Gets the packet involved in this event as the required type.
+ *
+ * @param Type of the packet.
+ * @return The event's packet as the required type.
+ * @throws IllegalStateException If the packet's value isn't of the required type.
+ */
+ @SuppressWarnings("unchecked")
+ public T getPacket() {
+ try {
+ return (T) this.packet;
+ } catch(ClassCastException e) {
+ throw new IllegalStateException("Tried to get packet as the wrong type. Actual type: " + this.packet.getClass().getName());
+ }
+ }
+
+ /**
+ * Sets the packet that should be sent as a result of this event.
+ *
+ * @param packet The packet to send.
+ */
+ public void setPacket(Packet packet) {
+ this.packet = packet;
+ }
+
+ /**
+ * Gets whether the event has been cancelled.
+ *
+ * @return Whether the event has been cancelled.
+ */
+ public boolean isCancelled() {
+ return this.cancelled;
+ }
+
+ /**
+ * Sets whether the event should be cancelled.
+ *
+ * @param cancelled Whether the event should be cancelled.
+ */
+ public void setCancelled(boolean cancelled) {
+ this.cancelled = cancelled;
+ }
+
+ @Override
+ public void call(SessionListener listener) {
+ listener.packetSending(this);
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/session/SessionAdapter.java b/src/main/java/com/github/steveice10/packetlib/event/session/SessionAdapter.java
new file mode 100644
index 000000000..83de67b3f
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/session/SessionAdapter.java
@@ -0,0 +1,37 @@
+package com.github.steveice10.packetlib.event.session;
+
+import com.github.steveice10.packetlib.Session;
+import com.github.steveice10.packetlib.packet.Packet;
+
+/**
+ * An adapter for picking session events to listen for.
+ */
+public class SessionAdapter implements SessionListener {
+ @Override
+ public void packetReceived(Session session, Packet packet) {
+ }
+
+ @Override
+ public void packetSending(PacketSendingEvent event) {
+ }
+
+ @Override
+ public void packetSent(Session session, Packet packet) {
+ }
+
+ @Override
+ public void packetError(PacketErrorEvent event) {
+ }
+
+ @Override
+ public void connected(ConnectedEvent event) {
+ }
+
+ @Override
+ public void disconnecting(DisconnectingEvent event) {
+ }
+
+ @Override
+ public void disconnected(DisconnectedEvent event) {
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/session/SessionEvent.java b/src/main/java/com/github/steveice10/packetlib/event/session/SessionEvent.java
new file mode 100644
index 000000000..bcd2b7275
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/session/SessionEvent.java
@@ -0,0 +1,13 @@
+package com.github.steveice10.packetlib.event.session;
+
+/**
+ * An event relating to sessions.
+ */
+public interface SessionEvent {
+ /**
+ * Calls the event.
+ *
+ * @param listener Listener to call the event on.
+ */
+ public void call(SessionListener listener);
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/event/session/SessionListener.java b/src/main/java/com/github/steveice10/packetlib/event/session/SessionListener.java
new file mode 100644
index 000000000..6ffd3f0c0
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/event/session/SessionListener.java
@@ -0,0 +1,58 @@
+package com.github.steveice10.packetlib.event.session;
+
+import com.github.steveice10.packetlib.Session;
+import com.github.steveice10.packetlib.packet.Packet;
+
+/**
+ * A listener for listening to session events.
+ */
+public interface SessionListener {
+ /**
+ * Called when a session receives a packet.
+ *
+ * @param packet the packet that was just received.
+ */
+ void packetReceived(Session session, Packet packet);
+
+ /**
+ * Called when a session is sending a packet.
+ *
+ * @param event Data relating to the event.
+ */
+ public void packetSending(PacketSendingEvent event);
+
+ /**
+ * Called when a session sends a packet.
+ *
+ * @param packet Packet just sent.
+ */
+ void packetSent(Session session, Packet packet);
+
+ /**
+ * Called when a session encounters an error while reading or writing packet data.
+ *
+ * @param event Data relating to the event.
+ */
+ public void packetError(PacketErrorEvent event);
+
+ /**
+ * Called when a session connects.
+ *
+ * @param event Data relating to the event.
+ */
+ public void connected(ConnectedEvent event);
+
+ /**
+ * Called when a session is about to disconnect.
+ *
+ * @param event Data relating to the event.
+ */
+ public void disconnecting(DisconnectingEvent event);
+
+ /**
+ * Called when a session is disconnected.
+ *
+ * @param event Data relating to the event.
+ */
+ public void disconnected(DisconnectedEvent event);
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/helper/TransportHelper.java b/src/main/java/com/github/steveice10/packetlib/helper/TransportHelper.java
new file mode 100644
index 000000000..87a75bfaa
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/helper/TransportHelper.java
@@ -0,0 +1,30 @@
+package com.github.steveice10.packetlib.helper;
+
+import io.netty.channel.epoll.Epoll;
+import io.netty.channel.kqueue.KQueue;
+import io.netty.incubator.channel.uring.IOUring;
+
+public class TransportHelper {
+ public enum TransportMethod {
+ NIO, EPOLL, KQUEUE, IO_URING
+ }
+
+ public static TransportMethod determineTransportMethod() {
+ if (isClassAvailable("io.netty.incubator.channel.uring.IOUring") && IOUring.isAvailable()) return TransportMethod.IO_URING;
+ if (isClassAvailable("io.netty.channel.epoll.Epoll") && Epoll.isAvailable()) return TransportMethod.EPOLL;
+ if (isClassAvailable("io.netty.channel.kqueue.KQueue") && KQueue.isAvailable()) return TransportMethod.KQUEUE;
+ return TransportMethod.NIO;
+ }
+
+ /**
+ * Used so implementations can opt to remove these dependencies if so desired
+ */
+ private static boolean isClassAvailable(String className) {
+ try {
+ Class.forName(className);
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/packet/BufferedPacket.java b/src/main/java/com/github/steveice10/packetlib/packet/BufferedPacket.java
new file mode 100644
index 000000000..9aea7ee37
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/packet/BufferedPacket.java
@@ -0,0 +1,38 @@
+package com.github.steveice10.packetlib.packet;
+
+import com.github.steveice10.packetlib.codec.PacketCodecHelper;
+import com.github.steveice10.packetlib.codec.PacketDefinition;
+import com.github.steveice10.packetlib.codec.PacketSerializer;
+import io.netty.buffer.ByteBuf;
+
+public class BufferedPacket implements Packet, PacketSerializer {
+ private final Class extends Packet> packetClass;
+ private final byte[] buf;
+
+ public BufferedPacket(Class extends Packet> packetClass, byte[] buf) {
+ this.packetClass = packetClass;
+ this.buf = buf;
+ }
+
+ public Class extends Packet> getPacketClass() {
+ return packetClass;
+ }
+
+ @Override
+ public boolean isPriority() {
+ return true;
+ }
+
+ @Override
+ public void serialize(ByteBuf buf, PacketCodecHelper helper, BufferedPacket packet) {
+ buf.writeBytes(this.buf);
+ }
+
+ @Override
+ public BufferedPacket deserialize(ByteBuf buf, PacketCodecHelper helper, PacketDefinition definition) {
+ byte[] array = new byte[buf.readableBytes()];
+ buf.readBytes(array);
+
+ return new BufferedPacket(definition.getPacketClass(), array);
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/packet/DefaultPacketHeader.java b/src/main/java/com/github/steveice10/packetlib/packet/DefaultPacketHeader.java
new file mode 100644
index 000000000..f72186803
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/packet/DefaultPacketHeader.java
@@ -0,0 +1,56 @@
+package com.github.steveice10.packetlib.packet;
+
+import com.github.steveice10.packetlib.codec.PacketCodecHelper;
+import io.netty.buffer.ByteBuf;
+
+import java.io.IOException;
+
+/**
+ * The default packet header, using a varint packet length and id.
+ */
+public class DefaultPacketHeader implements PacketHeader {
+ @Override
+ public boolean isLengthVariable() {
+ return true;
+ }
+
+ @Override
+ public int getLengthSize() {
+ return 5;
+ }
+
+ @Override
+ public int getLengthSize(int length) {
+ if((length & -128) == 0) {
+ return 1;
+ } else if((length & -16384) == 0) {
+ return 2;
+ } else if((length & -2097152) == 0) {
+ return 3;
+ } else if((length & -268435456) == 0) {
+ return 4;
+ } else {
+ return 5;
+ }
+ }
+
+ @Override
+ public int readLength(ByteBuf buf, PacketCodecHelper codecHelper, int available) throws IOException {
+ return codecHelper.readVarInt(buf);
+ }
+
+ @Override
+ public void writeLength(ByteBuf buf, PacketCodecHelper codecHelper, int length) throws IOException {
+ codecHelper.writeVarInt(buf, length);
+ }
+
+ @Override
+ public int readPacketId(ByteBuf buf, PacketCodecHelper codecHelper) throws IOException {
+ return codecHelper.readVarInt(buf);
+ }
+
+ @Override
+ public void writePacketId(ByteBuf buf, PacketCodecHelper codecHelper, int packetId) throws IOException {
+ codecHelper.writeVarInt(buf, packetId);
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/packet/Packet.java b/src/main/java/com/github/steveice10/packetlib/packet/Packet.java
new file mode 100644
index 000000000..0850f2914
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/packet/Packet.java
@@ -0,0 +1,20 @@
+package com.github.steveice10.packetlib.packet;
+
+import io.netty.buffer.ByteBuf;
+
+/**
+ * A network packet. Any given packet must have a constructor that takes in a {@link ByteBuf}.
+ */
+public interface Packet {
+
+ /**
+ * Gets whether the packet has handling priority.
+ * If the result is true, the packet will be handled immediately after being
+ * decoded.
+ *
+ * @return Whether the packet has priority.
+ */
+ default boolean isPriority() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/packet/PacketHeader.java b/src/main/java/com/github/steveice10/packetlib/packet/PacketHeader.java
new file mode 100644
index 000000000..c2f51144f
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/packet/PacketHeader.java
@@ -0,0 +1,74 @@
+package com.github.steveice10.packetlib.packet;
+
+import com.github.steveice10.packetlib.codec.PacketCodecHelper;
+import io.netty.buffer.ByteBuf;
+
+import java.io.IOException;
+
+/**
+ * The header of a protocol's packets.
+ */
+public interface PacketHeader {
+ /**
+ * Gets whether the header's length value can vary in size.
+ *
+ * @return Whether the header's length value can vary in size.
+ */
+ public boolean isLengthVariable();
+
+ /**
+ * Gets the size of the header's length value.
+ *
+ * @return The length value's size.
+ */
+ public int getLengthSize();
+
+ /**
+ * Gets the size of the header's length value.
+ *
+ * @param length Length value to get the size of.
+ * @return The length value's size.
+ */
+ public int getLengthSize(int length);
+
+ /**
+ * Reads the length of a packet from the given input.
+ *
+ * @param buf Buffer to read from.
+ * @param codecHelper The codec helper.
+ * @param available Number of packet bytes available after the length.
+ * @return The resulting packet length.
+ * @throws java.io.IOException If an I/O error occurs.
+ */
+ public int readLength(ByteBuf buf, PacketCodecHelper codecHelper, int available) throws IOException;
+
+ /**
+ * Writes the length of a packet to the given output.
+ *
+ * @param buf Buffer to write to.
+ * @param codecHelper The codec helper.
+ * @param length Length to write.
+ * @throws java.io.IOException If an I/O error occurs.
+ */
+ public void writeLength(ByteBuf buf, PacketCodecHelper codecHelper, int length) throws IOException;
+
+ /**
+ * Reads the ID of a packet from the given input.
+ *
+ * @param buf Buffer to read from.
+ * @param codecHelper The codec helper.
+ * @return The resulting packet ID, or -1 if the packet should not be read yet.
+ * @throws java.io.IOException If an I/O error occurs.
+ */
+ public int readPacketId(ByteBuf buf, PacketCodecHelper codecHelper) throws IOException;
+
+ /**
+ * Writes the ID of a packet to the given output.
+ *
+ * @param buf Buffer to write to.
+ * @param codecHelper The codec helper.
+ * @param packetId Packet ID to write.
+ * @throws java.io.IOException If an I/O error occurs.
+ */
+ public void writePacketId(ByteBuf buf, PacketCodecHelper codecHelper, int packetId) throws IOException;
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/packet/PacketProtocol.java b/src/main/java/com/github/steveice10/packetlib/packet/PacketProtocol.java
new file mode 100644
index 000000000..131f0d0ef
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/packet/PacketProtocol.java
@@ -0,0 +1,303 @@
+package com.github.steveice10.packetlib.packet;
+
+import com.github.steveice10.packetlib.Server;
+import com.github.steveice10.packetlib.Session;
+import com.github.steveice10.packetlib.codec.PacketCodecHelper;
+import com.github.steveice10.packetlib.codec.PacketDefinition;
+import com.github.steveice10.packetlib.codec.PacketSerializer;
+import io.netty.buffer.ByteBuf;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * A protocol for packet sending and receiving.
+ * All implementations must have a constructor that takes in a {@link ByteBuf}.
+ */
+public abstract class PacketProtocol {
+ private final Int2ObjectMap> serverbound = new Int2ObjectOpenHashMap<>();
+ private final Int2ObjectMap> clientbound = new Int2ObjectOpenHashMap<>();
+
+ private final Map, Integer> clientboundIds = new IdentityHashMap<>();
+ private final Map, Integer> serverboundIds = new IdentityHashMap<>();
+
+ /**
+ * Gets the prefix used when locating SRV records for this protocol.
+ *
+ * @return The protocol's SRV record prefix.
+ */
+ public abstract String getSRVRecordPrefix();
+
+ /**
+ * Gets the packet header of this protocol.
+ *
+ * @return The protocol's packet header.
+ */
+ public abstract PacketHeader getPacketHeader();
+
+ /**
+ * Creates a new {@link PacketCodecHelper} that can be used
+ * for each session.
+ *
+ * @return A new {@link PacketCodecHelper}.
+ */
+ public abstract PacketCodecHelper createHelper();
+
+ /**
+ * Called when a client session is created with this protocol.
+ *
+ * @param session The created session.
+ */
+ public abstract void newClientSession(Session session);
+
+ /**
+ * Called when a server session is created with this protocol.
+ *
+ * @param server The server that the session belongs to.
+ * @param session The created session.
+ */
+ public abstract void newServerSession(Server server, Session session);
+
+ /**
+ * Clears all currently registered packets.
+ */
+ public final void clearPackets() {
+ this.serverbound.clear();
+ this.clientbound.clear();
+ this.clientboundIds.clear();
+ this.serverboundIds.clear();
+ }
+
+ /**
+ * Registers a packet to this protocol as both serverbound and clientbound.
+ *
+ * @param id Id to register the packet to.
+ * @param packet Packet to register.
+ * @param serializer The packet serializer.
+ * @throws IllegalArgumentException If the packet fails a test creation when being registered as serverbound.
+ */
+ public final void register(int id, Class packet, PacketSerializer serializer) {
+ this.registerServerbound(id, packet, serializer);
+ this.registerClientbound(id, packet, serializer);
+ }
+
+ /**
+ * Registers a packet to this protocol as both serverbound and clientbound.
+ *
+ * @param definition The packet definition.
+ * @throws IllegalArgumentException If the packet fails a test creation when being registered as serverbound.
+ */
+ public final void register(PacketDefinition extends Packet, ?> definition) {
+ this.registerServerbound(definition);
+ this.registerClientbound(definition);
+ }
+
+ /**
+ * Registers a serverbound packet to this protocol.
+ *
+ * @param id Id to register the packet to.
+ * @param packet Packet to register.
+ * @param serializer The packet serializer.
+ * @throws IllegalArgumentException If the packet fails a test creation.
+ */
+ public final void registerServerbound(int id, Class packet, PacketSerializer serializer) {
+ this.registerServerbound(new PacketDefinition<>(id, packet, serializer));
+ }
+
+ /**
+ * Registers a serverbound packet to this protocol.
+ *
+ * @param definition The packet definition.
+ */
+ public final void registerServerbound(PacketDefinition extends Packet, ?> definition) {
+ this.serverbound.put(definition.getId(), definition);
+ this.serverboundIds.put(definition.getPacketClass(), definition.getId());
+ }
+
+ /**
+ * Registers a clientbound packet to this protocol.
+ *
+ * @param id Id to register the packet to.
+ * @param packet Packet to register.
+ * @param serializer The packet serializer.
+ */
+ public final void registerClientbound(int id, Class packet, PacketSerializer serializer) {
+ this.registerClientbound(new PacketDefinition<>(id, packet, serializer));
+ }
+
+ /**
+ * Registers a clientbound packet to this protocol.
+ *
+ * @param definition The packet definition.
+ */
+ public final void registerClientbound(PacketDefinition extends Packet, ?> definition) {
+ this.clientbound.put(definition.getId(), definition);
+ this.clientboundIds.put(definition.getPacketClass(), definition.getId());
+ }
+
+ /**
+ * Creates a new instance of a clientbound packet with the given id and read the clientbound input.
+ *
+ * @param id Id of the packet to create.
+ * @param buf The buffer to read the packet from.
+ * @param codecHelper The codec helper.
+ * @return The created packet.
+ * @throws IOException if there was an IO error whilst reading the packet.
+ * @throws IllegalArgumentException If the packet ID is not registered.
+ */
+ @SuppressWarnings("unchecked")
+ public Packet createClientboundPacket(int id, ByteBuf buf, H codecHelper) throws IOException {
+ PacketDefinition, H> definition = (PacketDefinition, H>) this.clientbound.get(id);
+ if (definition == null) {
+ throw new IllegalArgumentException("Invalid packet id: " + id);
+ }
+
+ return definition.newInstance(buf, codecHelper);
+ }
+
+ /**
+ * Gets the registered id of a clientbound packet class.
+ *
+ * @param packetClass Class of the packet to get the id for.
+ * @return The packet's registered id.
+ * @throws IllegalArgumentException If the packet is not registered.
+ */
+ public int getClientboundId(Class extends Packet> packetClass) {
+ Integer packetId = this.clientboundIds.get(packetClass);
+ if(packetId == null) {
+ throw new IllegalArgumentException("Unregistered clientbound packet class: " + packetClass.getName());
+ }
+
+ return packetId;
+ }
+
+ /**
+ * Gets the registered id of a clientbound {@link Packet} instance.
+ *
+ * @param packet Instance of {@link Packet} to get the id for.
+ * @return The packet's registered id.
+ * @throws IllegalArgumentException If the packet is not registered.
+ */
+ public int getClientboundId(Packet packet) {
+ if (packet instanceof BufferedPacket) {
+ return getClientboundId(((BufferedPacket) packet).getPacketClass());
+ }
+
+ return getClientboundId(packet.getClass());
+ }
+
+ /**
+ * Gets the packet class for a packet id.
+ * @param id The packet id.
+ * @return The registered packet's class
+ * @throws IllegalArgumentException If the packet ID is not registered.
+ */
+ public Class extends Packet> getClientboundClass(int id) {
+ PacketDefinition, ?> definition = this.clientbound.get(id);
+ if (definition == null) {
+ throw new IllegalArgumentException("Invalid packet id: " + id);
+ }
+
+ return definition.getPacketClass();
+ }
+
+ /**
+ * Creates a new instance of a serverbound packet with the given id and read the serverbound input.
+ *
+ * @param id Id of the packet to create.
+ * @param buf The buffer to read the packet from.
+ * @param codecHelper The codec helper.
+ * @return The created packet.
+ * @throws IOException if there was an IO error whilst reading the packet.
+ * @throws IllegalArgumentException If the packet ID is not registered.
+ */
+ @SuppressWarnings("unchecked")
+ public Packet createServerboundPacket(int id, ByteBuf buf, H codecHelper) throws IOException {
+ PacketDefinition, H> definition = (PacketDefinition, H>) this.serverbound.get(id);
+ if (definition == null) {
+ throw new IllegalArgumentException("Invalid packet id: " + id);
+ }
+
+ return definition.newInstance(buf, codecHelper);
+ }
+
+ /**
+ * Gets the registered id of a serverbound packet class.
+ *
+ * @param packetClass Class of the packet to get the id for.
+ * @return The packet's registered id.
+ * @throws IllegalArgumentException If the packet is not registered.
+ */
+ public int getServerboundId(Class extends Packet> packetClass) {
+ Integer packetId = this.serverboundIds.get(packetClass);
+ if(packetId == null) {
+ throw new IllegalArgumentException("Unregistered serverbound packet class: " + packetClass.getName());
+ }
+
+ return packetId;
+ }
+
+ /**
+ * Gets the registered id of a serverbound {@link Packet} instance.
+ *
+ * @param packet Instance of {@link Packet} to get the id for.
+ * @return The packet's registered id.
+ * @throws IllegalArgumentException If the packet is not registered.
+ */
+ public int getServerboundId(Packet packet) {
+ if (packet instanceof BufferedPacket) {
+ return getServerboundId(((BufferedPacket) packet).getPacketClass());
+ }
+
+ return getServerboundId(packet.getClass());
+ }
+
+ /**
+ * Gets the packet class for a packet id.
+ * @param id The packet id.
+ * @return The registered packet's class
+ * @throws IllegalArgumentException If the packet ID is not registered.
+ */
+ public Class extends Packet> getServerboundClass(int id) {
+ PacketDefinition, ?> definition = this.serverbound.get(id);
+ if (definition == null) {
+ throw new IllegalArgumentException("Invalid packet id: " + id);
+ }
+
+ return definition.getPacketClass();
+ }
+
+ /**
+ * Gets the serverbound packet definition for the given packet id.
+ *
+ * @param id The packet id.
+ * @return The registered packet's class
+ */
+ public PacketDefinition, ?> getServerboundDefinition(int id) {
+ PacketDefinition, ?> definition = this.serverbound.get(id);
+ if (definition == null) {
+ throw new IllegalArgumentException("Invalid packet id: " + id);
+ }
+
+ return definition;
+ }
+
+ /**
+ * Gets the clientbound packet definition for the given packet id.
+ *
+ * @param id The packet id.
+ * @return The registered packet's class
+ */
+ public PacketDefinition, ?> getClientboundDefinition(int id) {
+ PacketDefinition, ?> definition = this.clientbound.get(id);
+ if (definition == null) {
+ throw new IllegalArgumentException("Invalid packet id: " + id);
+ }
+
+ return definition;
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/tcp/TcpClientSession.java b/src/main/java/com/github/steveice10/packetlib/tcp/TcpClientSession.java
new file mode 100644
index 000000000..fee4aa936
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/tcp/TcpClientSession.java
@@ -0,0 +1,319 @@
+package com.github.steveice10.packetlib.tcp;
+
+import com.github.steveice10.packetlib.BuiltinFlags;
+import com.github.steveice10.packetlib.ProxyInfo;
+import com.github.steveice10.packetlib.codec.PacketCodecHelper;
+import com.github.steveice10.packetlib.helper.TransportHelper;
+import com.github.steveice10.packetlib.packet.PacketProtocol;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.AddressedEnvelope;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.epoll.EpollDatagramChannel;
+import io.netty.channel.epoll.EpollEventLoopGroup;
+import io.netty.channel.epoll.EpollSocketChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.DatagramChannel;
+import io.netty.channel.*;
+import io.netty.channel.kqueue.KQueueDatagramChannel;
+import io.netty.channel.kqueue.KQueueEventLoopGroup;
+import io.netty.channel.kqueue.KQueueSocketChannel;
+import io.netty.channel.socket.nio.NioDatagramChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.dns.DefaultDnsQuestion;
+import io.netty.handler.codec.dns.DefaultDnsRawRecord;
+import io.netty.handler.codec.dns.DefaultDnsRecordDecoder;
+import io.netty.handler.codec.dns.DnsRecordType;
+import io.netty.handler.codec.dns.DnsResponse;
+import io.netty.handler.codec.dns.DnsSection;
+import io.netty.handler.codec.haproxy.HAProxyCommand;
+import io.netty.handler.codec.haproxy.HAProxyMessage;
+import io.netty.handler.codec.haproxy.HAProxyMessageEncoder;
+import io.netty.handler.codec.haproxy.HAProxyProtocolVersion;
+import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol;
+import io.netty.handler.proxy.HttpProxyHandler;
+import io.netty.handler.proxy.Socks4ProxyHandler;
+import io.netty.handler.proxy.Socks5ProxyHandler;
+import io.netty.incubator.channel.uring.IOUringDatagramChannel;
+import io.netty.incubator.channel.uring.IOUringEventLoopGroup;
+import io.netty.incubator.channel.uring.IOUringSocketChannel;
+import io.netty.resolver.dns.DnsNameResolver;
+import io.netty.resolver.dns.DnsNameResolverBuilder;
+import java.net.*;
+
+public class TcpClientSession extends TcpSession {
+ private static final String IP_REGEX = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b";
+ private static Class extends Channel> CHANNEL_CLASS;
+ private static Class extends DatagramChannel> DATAGRAM_CHANNEL_CLASS;
+ private static EventLoopGroup EVENT_LOOP_GROUP;
+
+ private final String bindAddress;
+ private final int bindPort;
+ private final ProxyInfo proxy;
+ private final PacketCodecHelper codecHelper;
+
+ public TcpClientSession(String host, int port, PacketProtocol protocol) {
+ this(host, port, protocol, null);
+ }
+
+ public TcpClientSession(String host, int port, PacketProtocol protocol, ProxyInfo proxy) {
+ this(host, port, "0.0.0.0", 0, protocol, proxy);
+ }
+
+ public TcpClientSession(String host, int port, String bindAddress, int bindPort, PacketProtocol protocol) {
+ this(host, port, bindAddress, bindPort, protocol, null);
+ }
+
+ public TcpClientSession(String host, int port, String bindAddress, int bindPort, PacketProtocol protocol, ProxyInfo proxy) {
+ super(host, port, protocol);
+ this.bindAddress = bindAddress;
+ this.bindPort = bindPort;
+ this.proxy = proxy;
+ this.codecHelper = protocol.createHelper();
+ }
+
+ @Override
+ public void connect(boolean wait) {
+ if(this.disconnected) {
+ throw new IllegalStateException("Session has already been disconnected.");
+ }
+
+ boolean debug = getFlag(BuiltinFlags.PRINT_DEBUG, false);
+
+ if (CHANNEL_CLASS == null) {
+ createTcpEventLoopGroup();
+ }
+
+ try {
+ final Bootstrap bootstrap = new Bootstrap();
+ bootstrap.channel(CHANNEL_CLASS);
+ bootstrap.handler(new ChannelInitializer() {
+ @Override
+ public void initChannel(Channel channel) {
+ PacketProtocol protocol = getPacketProtocol();
+ protocol.newClientSession(TcpClientSession.this);
+
+ channel.config().setOption(ChannelOption.IP_TOS, 0x18);
+ try {
+ channel.config().setOption(ChannelOption.TCP_NODELAY, true);
+ } catch (ChannelException e) {
+ if(debug) {
+ System.out.println("Exception while trying to set TCP_NODELAY");
+ e.printStackTrace();
+ }
+ }
+
+ ChannelPipeline pipeline = channel.pipeline();
+
+ refreshReadTimeoutHandler(channel);
+ refreshWriteTimeoutHandler(channel);
+
+ addProxy(pipeline);
+
+ int size = protocol.getPacketHeader().getLengthSize();
+ if (size > 0) {
+ pipeline.addLast("sizer", new TcpPacketSizer(TcpClientSession.this, size));
+ }
+
+ pipeline.addLast("codec", new TcpPacketCodec(TcpClientSession.this, true));
+ pipeline.addLast("manager", TcpClientSession.this);
+
+ addHAProxySupport(pipeline);
+ }
+ }).group(EVENT_LOOP_GROUP).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout() * 1000);
+
+ InetSocketAddress remoteAddress = resolveAddress();
+ bootstrap.remoteAddress(remoteAddress);
+ bootstrap.localAddress(bindAddress, bindPort);
+
+ ChannelFuture future = bootstrap.connect();
+ if (wait) {
+ future.sync();
+ }
+
+ future.addListener((futureListener) -> {
+ if (!futureListener.isSuccess()) {
+ exceptionCaught(null, futureListener.cause());
+ }
+ });
+ } catch(Throwable t) {
+ exceptionCaught(null, t);
+ }
+ }
+
+ @Override
+ public PacketCodecHelper getCodecHelper() {
+ return this.codecHelper;
+ }
+
+ private InetSocketAddress resolveAddress() {
+ boolean debug = getFlag(BuiltinFlags.PRINT_DEBUG, false);
+
+ String name = this.getPacketProtocol().getSRVRecordPrefix() + "._tcp." + this.getHost();
+ if (debug) {
+ System.out.println("[PacketLib] Attempting SRV lookup for \"" + name + "\".");
+ }
+
+ if(getFlag(BuiltinFlags.ATTEMPT_SRV_RESOLVE, true) && (!this.host.matches(IP_REGEX) && !this.host.equalsIgnoreCase("localhost"))) {
+ DnsNameResolver resolver = null;
+ AddressedEnvelope envelope = null;
+ try {
+ resolver = new DnsNameResolverBuilder(EVENT_LOOP_GROUP.next())
+ .channelType(DATAGRAM_CHANNEL_CLASS)
+ .build();
+ envelope = resolver.query(new DefaultDnsQuestion(name, DnsRecordType.SRV)).get();
+
+ DnsResponse response = envelope.content();
+ if (response.count(DnsSection.ANSWER) > 0) {
+ DefaultDnsRawRecord record = response.recordAt(DnsSection.ANSWER, 0);
+ if (record.type() == DnsRecordType.SRV) {
+ ByteBuf buf = record.content();
+ buf.skipBytes(4); // Skip priority and weight.
+
+ int port = buf.readUnsignedShort();
+ String host = DefaultDnsRecordDecoder.decodeName(buf);
+ if (host.endsWith(".")) {
+ host = host.substring(0, host.length() - 1);
+ }
+
+ if(debug) {
+ System.out.println("[PacketLib] Found SRV record containing \"" + host + ":" + port + "\".");
+ }
+
+ this.host = host;
+ this.port = port;
+ } else if (debug) {
+ System.out.println("[PacketLib] Received non-SRV record in response.");
+ }
+ } else if (debug) {
+ System.out.println("[PacketLib] No SRV record found.");
+ }
+ } catch(Exception e) {
+ if (debug) {
+ System.out.println("[PacketLib] Failed to resolve SRV record.");
+ e.printStackTrace();
+ }
+ } finally {
+ if (envelope != null) {
+ envelope.release();
+ }
+
+ if (resolver != null) {
+ resolver.close();
+ }
+ }
+ } else if(debug) {
+ System.out.println("[PacketLib] Not resolving SRV record for " + this.host);
+ }
+
+ // Resolve host here
+ try {
+ InetAddress resolved = InetAddress.getByName(getHost());
+ if (debug) {
+ System.out.printf("[PacketLib] Resolved %s -> %s%n", getHost(), resolved.getHostAddress());
+ }
+ return new InetSocketAddress(resolved, getPort());
+ } catch (UnknownHostException e) {
+ if (debug) {
+ System.out.println("[PacketLib] Failed to resolve host, letting Netty do it instead.");
+ e.printStackTrace();
+ }
+ return InetSocketAddress.createUnresolved(getHost(), getPort());
+ }
+ }
+
+ private void addProxy(ChannelPipeline pipeline) {
+ if(proxy != null) {
+ switch(proxy.getType()) {
+ case HTTP:
+ if (proxy.isAuthenticated()) {
+ pipeline.addFirst("proxy", new HttpProxyHandler(proxy.getAddress(), proxy.getUsername(), proxy.getPassword()));
+ } else {
+ pipeline.addFirst("proxy", new HttpProxyHandler(proxy.getAddress()));
+ }
+
+ break;
+ case SOCKS4:
+ if (proxy.isAuthenticated()) {
+ pipeline.addFirst("proxy", new Socks4ProxyHandler(proxy.getAddress(), proxy.getUsername()));
+ } else {
+ pipeline.addFirst("proxy", new Socks4ProxyHandler(proxy.getAddress()));
+ }
+
+ break;
+ case SOCKS5:
+ if (proxy.isAuthenticated()) {
+ pipeline.addFirst("proxy", new Socks5ProxyHandler(proxy.getAddress(), proxy.getUsername(), proxy.getPassword()));
+ } else {
+ pipeline.addFirst("proxy", new Socks5ProxyHandler(proxy.getAddress()));
+ }
+
+ break;
+ default:
+ throw new UnsupportedOperationException("Unsupported proxy type: " + proxy.getType());
+ }
+ }
+ }
+
+ private void addHAProxySupport(ChannelPipeline pipeline) {
+ InetSocketAddress clientAddress = getFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS);
+ if (getFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, false) && clientAddress != null) {
+ pipeline.addFirst("proxy-protocol-packet-sender", new ChannelInboundHandlerAdapter() {
+ @Override
+ public void channelActive(ChannelHandlerContext ctx) throws Exception {
+ HAProxyProxiedProtocol proxiedProtocol = clientAddress.getAddress() instanceof Inet4Address ? HAProxyProxiedProtocol.TCP4 : HAProxyProxiedProtocol.TCP6;
+ InetSocketAddress remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress();
+ ctx.channel().writeAndFlush(new HAProxyMessage(
+ HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, proxiedProtocol,
+ clientAddress.getAddress().getHostAddress(), remoteAddress.getAddress().getHostAddress(),
+ clientAddress.getPort(), remoteAddress.getPort()
+ ));
+ ctx.pipeline().remove(this);
+ ctx.pipeline().remove("proxy-protocol-encoder");
+ super.channelActive(ctx);
+ }
+ });
+ pipeline.addFirst("proxy-protocol-encoder", HAProxyMessageEncoder.INSTANCE);
+ }
+ }
+
+ @Override
+ public void disconnect(String reason, Throwable cause) {
+ super.disconnect(reason, cause);
+ }
+
+ private static void createTcpEventLoopGroup() {
+ if (CHANNEL_CLASS != null) {
+ return;
+ }
+
+ switch (TransportHelper.determineTransportMethod()) {
+ case IO_URING:
+ EVENT_LOOP_GROUP = new IOUringEventLoopGroup();
+ CHANNEL_CLASS = IOUringSocketChannel.class;
+ DATAGRAM_CHANNEL_CLASS = IOUringDatagramChannel.class;
+ break;
+ case EPOLL:
+ EVENT_LOOP_GROUP = new EpollEventLoopGroup();
+ CHANNEL_CLASS = EpollSocketChannel.class;
+ DATAGRAM_CHANNEL_CLASS = EpollDatagramChannel.class;
+ break;
+ case KQUEUE:
+ EVENT_LOOP_GROUP = new KQueueEventLoopGroup();
+ CHANNEL_CLASS = KQueueSocketChannel.class;
+ DATAGRAM_CHANNEL_CLASS = KQueueDatagramChannel.class;
+ break;
+ case NIO:
+ EVENT_LOOP_GROUP = new NioEventLoopGroup();
+ CHANNEL_CLASS = NioSocketChannel.class;
+ DATAGRAM_CHANNEL_CLASS = NioDatagramChannel.class;
+ break;
+ }
+ }
+}
diff --git a/src/main/java/com/github/steveice10/packetlib/tcp/TcpPacketCodec.java b/src/main/java/com/github/steveice10/packetlib/tcp/TcpPacketCodec.java
new file mode 100644
index 000000000..4384e94f2
--- /dev/null
+++ b/src/main/java/com/github/steveice10/packetlib/tcp/TcpPacketCodec.java
@@ -0,0 +1,80 @@
+package com.github.steveice10.packetlib.tcp;
+
+import com.github.steveice10.packetlib.Session;
+import com.github.steveice10.packetlib.codec.PacketCodecHelper;
+import com.github.steveice10.packetlib.codec.PacketDefinition;
+import com.github.steveice10.packetlib.event.session.PacketErrorEvent;
+import com.github.steveice10.packetlib.packet.Packet;
+import com.github.steveice10.packetlib.packet.PacketProtocol;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageCodec;
+
+import java.util.List;
+
+public class TcpPacketCodec extends ByteToMessageCodec {
+ private final Session session;
+ private final boolean client;
+
+ public TcpPacketCodec(Session session, boolean client) {
+ this.session = session;
+ this.client = client;
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ @Override
+ public void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf buf) throws Exception {
+ int initial = buf.writerIndex();
+
+ PacketProtocol packetProtocol = this.session.getPacketProtocol();
+ PacketCodecHelper codecHelper = this.session.getCodecHelper();
+ try {
+ int packetId = this.client ? packetProtocol.getServerboundId(packet) : packetProtocol.getClientboundId(packet);
+ PacketDefinition definition = this.client ? packetProtocol.getServerboundDefinition(packetId) : packetProtocol.getClientboundDefinition(packetId);
+
+ packetProtocol.getPacketHeader().writePacketId(buf, codecHelper, packetId);
+ definition.getSerializer().serialize(buf, codecHelper, packet);
+ } catch (Throwable t) {
+ // Reset writer index to make sure incomplete data is not written out.
+ buf.writerIndex(initial);
+
+ PacketErrorEvent e = new PacketErrorEvent(this.session, t);
+ this.session.callEvent(e);
+ if (!e.shouldSuppress()) {
+ throw t;
+ }
+ }
+ }
+
+ @Override
+ protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List