Merge packetlib w/ commit history

This commit is contained in:
Kas-tle 2023-03-09 21:38:07 +00:00 committed by GitHub
commit 910a0009b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 3815 additions and 1 deletions

25
Jenkinsfile vendored
View file

@ -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(

View file

@ -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());
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}

31
pom.xml
View file

@ -135,6 +135,37 @@
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.66.Final</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-haproxy</artifactId>
<version>4.1.66.Final</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.netty.incubator</groupId>
<artifactId>netty-incubator-transport-native-io_uring</artifactId>
<version>0.0.8.Final</version>
<classifier>linux-x86_64</classifier>
</dependency>
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<version>4.3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-int-object-maps</artifactId>
<version>8.5.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>

View file

@ -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<Session> sessions = new ArrayList<>();
private final Map<String, Object> flags = new HashMap<>();
private final List<ServerListener> 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<String, Object> getGlobalFlags() {
return Collections.unmodifiableMap(this.flags);
}
@Override
public boolean hasGlobalFlag(String key) {
return this.flags.containsKey(key);
}
@Override
public <T> T getGlobalFlag(String key) {
return this.getGlobalFlag(key, null);
}
@SuppressWarnings("unchecked")
@Override
public <T> 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<ServerListener> 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<Session> 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);
}

View file

@ -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() {
}
}

View file

@ -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;
}
}

View file

@ -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<String, Object> 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 <T> 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> 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 <T> 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> 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<ServerListener> 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<Session> 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);
}

View file

@ -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<String, Object> 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 <T> 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> 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 <T> 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> 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<SessionListener> 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);
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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 <T> the packet type
*/
public class PacketDefinition<T extends Packet, H extends PacketCodecHelper> {
private final int id;
private final Class<T> packetClass;
private final PacketSerializer<T, H> serializer;
public PacketDefinition(final int id, final Class<T> packetClass, final PacketSerializer<T, H> 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<T> getPacketClass() {
return this.packetClass;
}
/**
* Returns the {@link PacketSerializer} of the packet.
*
* @return the packet serializer of the packet
*/
public PacketSerializer<T, H> getSerializer() {
return this.serializer;
}
public T newInstance(ByteBuf buf, H helper) throws IOException {
return this.serializer.deserialize(buf, helper, this);
}
}

View file

@ -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<T extends Packet, H extends PacketCodecHelper> {
void serialize(ByteBuf buf, H helper, T packet) throws IOException;
T deserialize(ByteBuf buf, H helper, PacketDefinition<T, H> definition) throws IOException;
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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) {
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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 <T> 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 extends Packet> 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);
}
}

View file

@ -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) {
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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;
}
}
}

View file

@ -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<BufferedPacket, PacketCodecHelper> {
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<BufferedPacket, PacketCodecHelper> definition) {
byte[] array = new byte[buf.readableBytes()];
buf.readBytes(array);
return new BufferedPacket(definition.getPacketClass(), array);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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<PacketDefinition<? extends Packet, ?>> serverbound = new Int2ObjectOpenHashMap<>();
private final Int2ObjectMap<PacketDefinition<? extends Packet, ?>> clientbound = new Int2ObjectOpenHashMap<>();
private final Map<Class<? extends Packet>, Integer> clientboundIds = new IdentityHashMap<>();
private final Map<Class<? extends Packet>, 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 <T extends Packet, H extends PacketCodecHelper> void register(int id, Class<T> packet, PacketSerializer<T, H> 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 <T extends Packet, H extends PacketCodecHelper> void registerServerbound(int id, Class<T> packet, PacketSerializer<T, H> 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 <T extends Packet, H extends PacketCodecHelper> void registerClientbound(int id, Class<T> packet, PacketSerializer<T, H> 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 <H extends PacketCodecHelper> 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 <H extends PacketCodecHelper> 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;
}
}

View file

@ -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<Channel>() {
@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<DnsResponse, InetSocketAddress> 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;
}
}
}

View file

@ -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<Packet> {
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<Object> out) throws Exception {
int initial = buf.readerIndex();
PacketProtocol packetProtocol = this.session.getPacketProtocol();
PacketCodecHelper codecHelper = this.session.getCodecHelper();
try {
int id = packetProtocol.getPacketHeader().readPacketId(buf, codecHelper);
if (id == -1) {
buf.readerIndex(initial);
return;
}
Packet packet = this.client ? packetProtocol.createClientboundPacket(id, buf, codecHelper) : packetProtocol.createServerboundPacket(id, buf, codecHelper);
if (buf.readableBytes() > 0) {
throw new IllegalStateException("Packet \"" + packet.getClass().getSimpleName() + "\" not fully read.");
}
out.add(packet);
} catch (Throwable t) {
// Advance buffer to end to make sure remaining data in this packet is skipped.
buf.readerIndex(buf.readerIndex() + buf.readableBytes());
PacketErrorEvent e = new PacketErrorEvent(this.session, t);
this.session.callEvent(e);
if (!e.shouldSuppress()) {
throw t;
}
}
}
}

View file

@ -0,0 +1,85 @@
package com.github.steveice10.packetlib.tcp;
import com.github.steveice10.packetlib.Session;
import com.github.steveice10.packetlib.codec.PacketCodecHelper;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageCodec;
import io.netty.handler.codec.DecoderException;
import java.util.List;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
public class TcpPacketCompression extends ByteToMessageCodec<ByteBuf> {
private static final int MAX_COMPRESSED_SIZE = 2097152;
private final Session session;
private final Deflater deflater = new Deflater();
private final Inflater inflater = new Inflater();
private final byte[] buf = new byte[8192];
private final boolean validateDecompression;
public TcpPacketCompression(Session session, boolean validateDecompression) {
this.session = session;
this.validateDecompression = validateDecompression;
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
super.handlerRemoved(ctx);
this.deflater.end();
this.inflater.end();
}
@Override
public void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception {
int readable = in.readableBytes();
if(readable < this.session.getCompressionThreshold()) {
this.session.getCodecHelper().writeVarInt(out, 0);
out.writeBytes(in);
} else {
byte[] bytes = new byte[readable];
in.readBytes(bytes);
this.session.getCodecHelper().writeVarInt(out, bytes.length);
this.deflater.setInput(bytes, 0, readable);
this.deflater.finish();
while(!this.deflater.finished()) {
int length = this.deflater.deflate(this.buf);
out.writeBytes(this.buf, 0, length);
}
this.deflater.reset();
}
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
if(buf.readableBytes() != 0) {
int size = this.session.getCodecHelper().readVarInt(buf);
if(size == 0) {
out.add(buf.readBytes(buf.readableBytes()));
} else {
if (validateDecompression) { // This is sectioned off as of at least Java Edition 1.18
if (size < this.session.getCompressionThreshold()) {
throw new DecoderException("Badly compressed packet: size of " + size + " is below threshold of " + this.session.getCompressionThreshold() + ".");
}
if (size > MAX_COMPRESSED_SIZE) {
throw new DecoderException("Badly compressed packet: size of " + size + " is larger than protocol maximum of " + MAX_COMPRESSED_SIZE + ".");
}
}
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
this.inflater.setInput(bytes);
byte[] inflated = new byte[size];
this.inflater.inflate(inflated);
out.add(Unpooled.wrappedBuffer(inflated));
this.inflater.reset();
}
}
}
}

View file

@ -0,0 +1,49 @@
package com.github.steveice10.packetlib.tcp;
import com.github.steveice10.packetlib.crypt.PacketEncryption;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageCodec;
import java.util.List;
public class TcpPacketEncryptor extends ByteToMessageCodec<ByteBuf> {
private final PacketEncryption encryption;
private byte[] decryptedArray = new byte[0];
private byte[] encryptedArray = new byte[0];
public TcpPacketEncryptor(PacketEncryption encryption) {
this.encryption = encryption;
}
@Override
public void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception {
int length = in.readableBytes();
byte[] bytes = this.getBytes(in);
int outLength = this.encryption.getEncryptOutputSize(length);
if( this.encryptedArray.length < outLength) {
this.encryptedArray = new byte[outLength];
}
out.writeBytes(this.encryptedArray, 0, this.encryption.encrypt(bytes, 0, length, this.encryptedArray, 0));
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
int length = buf.readableBytes();
byte[] bytes = this.getBytes(buf);
ByteBuf result = ctx.alloc().heapBuffer(this.encryption.getDecryptOutputSize(length));
result.writerIndex(this.encryption.decrypt(bytes, 0, length, result.array(), result.arrayOffset()));
out.add(result);
}
private byte[] getBytes(ByteBuf buf) {
int length = buf.readableBytes();
if (this.decryptedArray.length < length) {
this.decryptedArray = new byte[length];
}
buf.readBytes(this.decryptedArray, 0, length);
return this.decryptedArray;
}
}

View file

@ -0,0 +1,55 @@
package com.github.steveice10.packetlib.tcp;
import com.github.steveice10.packetlib.Session;
import com.github.steveice10.packetlib.codec.PacketCodecHelper;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageCodec;
import io.netty.handler.codec.CorruptedFrameException;
import java.util.List;
public class TcpPacketSizer extends ByteToMessageCodec<ByteBuf> {
private final Session session;
private final int size;
public TcpPacketSizer(Session session, int size) {
this.session = session;
this.size = size;
}
@Override
public void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception {
int length = in.readableBytes();
out.ensureWritable(this.session.getPacketProtocol().getPacketHeader().getLengthSize(length) + length);
this.session.getPacketProtocol().getPacketHeader().writeLength(out, this.session.getCodecHelper(), length);
out.writeBytes(in);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
buf.markReaderIndex();
byte[] lengthBytes = new byte[size];
for (int index = 0; index < lengthBytes.length; index++) {
if (!buf.isReadable()) {
buf.resetReaderIndex();
return;
}
lengthBytes[index] = buf.readByte();
if ((this.session.getPacketProtocol().getPacketHeader().isLengthVariable() && lengthBytes[index] >= 0) || index == size - 1) {
int length = this.session.getPacketProtocol().getPacketHeader().readLength(Unpooled.wrappedBuffer(lengthBytes), this.session.getCodecHelper(), buf.readableBytes());
if (buf.readableBytes() < length) {
buf.resetReaderIndex();
return;
}
out.add(buf.readBytes(length));
return;
}
}
throw new CorruptedFrameException("Length is too long.");
}
}

View file

@ -0,0 +1,184 @@
package com.github.steveice10.packetlib.tcp;
import com.github.steveice10.packetlib.AbstractServer;
import com.github.steveice10.packetlib.BuiltinFlags;
import com.github.steveice10.packetlib.helper.TransportHelper;
import com.github.steveice10.packetlib.packet.PacketProtocol;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
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.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.*;
import io.netty.channel.kqueue.KQueueEventLoopGroup;
import io.netty.channel.kqueue.KQueueServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.incubator.channel.uring.IOUringEventLoopGroup;
import io.netty.incubator.channel.uring.IOUringServerSocketChannel;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import java.net.InetSocketAddress;
import java.util.function.Supplier;
public class TcpServer extends AbstractServer {
private EventLoopGroup group;
private Class<? extends ServerSocketChannel> serverSocketChannel;
private Channel channel;
public TcpServer(String host, int port, Supplier<? extends PacketProtocol> protocol) {
super(host, port, protocol);
}
@Override
public boolean isListening() {
return this.channel != null && this.channel.isOpen();
}
@Override
public void bindImpl(boolean wait, final Runnable callback) {
if(this.group != null || this.channel != null) {
return;
}
switch (TransportHelper.determineTransportMethod()) {
case IO_URING:
this.group = new IOUringEventLoopGroup();
this.serverSocketChannel = IOUringServerSocketChannel.class;
break;
case EPOLL:
this.group = new EpollEventLoopGroup();
this.serverSocketChannel = EpollServerSocketChannel.class;
break;
case KQUEUE:
this.group = new KQueueEventLoopGroup();
this.serverSocketChannel = KQueueServerSocketChannel.class;
break;
case NIO:
this.group = new NioEventLoopGroup();
this.serverSocketChannel = NioServerSocketChannel.class;
break;
}
ChannelFuture future = new ServerBootstrap().channel(this.serverSocketChannel).childHandler(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel channel) {
InetSocketAddress address = (InetSocketAddress) channel.remoteAddress();
PacketProtocol protocol = createPacketProtocol();
TcpSession session = new TcpServerSession(address.getHostName(), address.getPort(), protocol, TcpServer.this);
session.getPacketProtocol().newServerSession(TcpServer.this, session);
channel.config().setOption(ChannelOption.IP_TOS, 0x18);
try {
channel.config().setOption(ChannelOption.TCP_NODELAY, true);
} catch (ChannelException ignored) {
}
ChannelPipeline pipeline = channel.pipeline();
session.refreshReadTimeoutHandler(channel);
session.refreshWriteTimeoutHandler(channel);
int size = protocol.getPacketHeader().getLengthSize();
if (size > 0) {
pipeline.addLast("sizer", new TcpPacketSizer(session, size));
}
pipeline.addLast("codec", new TcpPacketCodec(session, false));
pipeline.addLast("manager", session);
}
}).group(this.group).localAddress(this.getHost(), this.getPort()).bind();
if(wait) {
try {
future.sync();
} catch(InterruptedException e) {
}
channel = future.channel();
if(callback != null) {
callback.run();
}
} else {
future.addListener((ChannelFutureListener) future1 -> {
if(future1.isSuccess()) {
channel = future1.channel();
if(callback != null) {
callback.run();
}
} else {
System.err.println("[ERROR] Failed to asynchronously bind connection listener.");
if(future1.cause() != null) {
future1.cause().printStackTrace();
}
}
});
}
}
@Override
public void closeImpl(boolean wait, final Runnable callback) {
if(this.channel != null) {
if(this.channel.isOpen()) {
ChannelFuture future = this.channel.close();
if(wait) {
try {
future.sync();
} catch(InterruptedException e) {
}
if(callback != null) {
callback.run();
}
} else {
future.addListener((ChannelFutureListener) future1 -> {
if(future1.isSuccess()) {
if(callback != null) {
callback.run();
}
} else {
System.err.println("[ERROR] Failed to asynchronously close connection listener.");
if(future1.cause() != null) {
future1.cause().printStackTrace();
}
}
});
}
}
this.channel = null;
}
if(this.group != null) {
Future<?> future = this.group.shutdownGracefully();
if(wait) {
try {
future.sync();
} catch(InterruptedException e) {
}
} else {
future.addListener(new GenericFutureListener() {
@Override
public void operationComplete(Future future) {
if(!future.isSuccess() && getGlobalFlag(BuiltinFlags.PRINT_DEBUG, false)) {
System.err.println("[ERROR] Failed to asynchronously close connection listener.");
if(future.cause() != null) {
future.cause().printStackTrace();
}
}
}
});
}
this.group = null;
}
}
}

View file

@ -0,0 +1,65 @@
package com.github.steveice10.packetlib.tcp;
import com.github.steveice10.packetlib.codec.PacketCodecHelper;
import com.github.steveice10.packetlib.packet.PacketProtocol;
import io.netty.channel.ChannelHandlerContext;
import java.util.HashMap;
import java.util.Map;
public class TcpServerSession extends TcpSession {
private final TcpServer server;
private final PacketCodecHelper codecHelper;
public TcpServerSession(String host, int port, PacketProtocol protocol, TcpServer server) {
super(host, port, protocol);
this.server = server;
this.codecHelper = protocol.createHelper();
}
@Override
public PacketCodecHelper getCodecHelper() {
return this.codecHelper;
}
@Override
public Map<String, Object> getFlags() {
Map<String, Object> ret = new HashMap<>();
ret.putAll(this.server.getGlobalFlags());
ret.putAll(super.getFlags());
return ret;
}
@Override
public boolean hasFlag(String key) {
if(super.hasFlag(key)) {
return true;
}
return this.server.hasGlobalFlag(key);
}
@Override
public <T> T getFlag(String key, T def) {
T ret = super.getFlag(key, null);
if(ret != null) {
return ret;
}
return this.server.getGlobalFlag(key, def);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
this.server.addSession(this);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
this.server.removeSession(this);
}
}

View file

@ -0,0 +1,380 @@
package com.github.steveice10.packetlib.tcp;
import com.github.steveice10.packetlib.Session;
import com.github.steveice10.packetlib.crypt.PacketEncryption;
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.PacketSendingEvent;
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 io.netty.channel.*;
import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutException;
import io.netty.handler.timeout.WriteTimeoutHandler;
import javax.annotation.Nullable;
import java.net.ConnectException;
import java.net.SocketAddress;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
public abstract class TcpSession extends SimpleChannelInboundHandler<Packet> implements Session {
/**
* Controls whether non-priority packets are handled in a separate event loop
*/
public static boolean USE_EVENT_LOOP_FOR_PACKETS = true;
private static EventLoopGroup PACKET_EVENT_LOOP;
protected String host;
protected int port;
private final PacketProtocol protocol;
private final EventLoop eventLoop = createEventLoop();
private int compressionThreshold = -1;
private int connectTimeout = 30;
private int readTimeout = 30;
private int writeTimeout = 0;
private final Map<String, Object> flags = new HashMap<>();
private final List<SessionListener> listeners = new CopyOnWriteArrayList<>();
private Channel channel;
protected boolean disconnected = false;
public TcpSession(String host, int port, PacketProtocol protocol) {
this.host = host;
this.port = port;
this.protocol = protocol;
}
@Override
public void connect() {
this.connect(true);
}
@Override
public void connect(boolean wait) {
}
@Override
public String getHost() {
return this.host;
}
@Override
public int getPort() {
return this.port;
}
@Override
public SocketAddress getLocalAddress() {
return this.channel != null ? this.channel.localAddress() : null;
}
@Override
public SocketAddress getRemoteAddress() {
return this.channel != null ? this.channel.remoteAddress() : null;
}
@Override
public PacketProtocol getPacketProtocol() {
return this.protocol;
}
@Override
public Map<String, Object> getFlags() {
return Collections.unmodifiableMap(this.flags);
}
@Override
public boolean hasFlag(String key) {
return this.flags.containsKey(key);
}
@Override
public <T> T getFlag(String key) {
return this.getFlag(key, null);
}
@SuppressWarnings("unchecked")
@Override
public <T> T getFlag(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 setFlag(String key, Object value) {
this.flags.put(key, value);
}
@Override
public List<SessionListener> getListeners() {
return Collections.unmodifiableList(this.listeners);
}
@Override
public void addListener(SessionListener listener) {
this.listeners.add(listener);
}
@Override
public void removeListener(SessionListener listener) {
this.listeners.remove(listener);
}
@Override
public void callEvent(SessionEvent event) {
try {
for (SessionListener listener : this.listeners) {
event.call(listener);
}
} catch (Throwable t) {
exceptionCaught(null, t);
}
}
@Override
public void callPacketReceived(Packet packet) {
try {
for (SessionListener listener : this.listeners) {
listener.packetReceived(this, packet);
}
} catch (Throwable t) {
exceptionCaught(null, t);
}
}
@Override
public void callPacketSent(Packet packet) {
try {
for (SessionListener listener : this.listeners) {
listener.packetSent(this, packet);
}
} catch (Throwable t) {
exceptionCaught(null, t);
}
}
@Override
public int getCompressionThreshold() {
return this.compressionThreshold;
}
@Override
public void setCompressionThreshold(int threshold, boolean validateDecompression) {
this.compressionThreshold = threshold;
if (this.channel != null) {
if (this.compressionThreshold >= 0) {
if (this.channel.pipeline().get("compression") == null) {
this.channel.pipeline().addBefore("codec", "compression", new TcpPacketCompression(this, validateDecompression));
}
} else if (this.channel.pipeline().get("compression") != null) {
this.channel.pipeline().remove("compression");
}
}
}
@Override
public void enableEncryption(PacketEncryption encryption) {
if (channel == null) {
throw new IllegalStateException("Connect the client before initializing encryption!");
}
channel.pipeline().addBefore("sizer", "encryption", new TcpPacketEncryptor(encryption));
}
@Override
public int getConnectTimeout() {
return this.connectTimeout;
}
@Override
public void setConnectTimeout(int timeout) {
this.connectTimeout = timeout;
}
@Override
public int getReadTimeout() {
return this.readTimeout;
}
@Override
public void setReadTimeout(int timeout) {
this.readTimeout = timeout;
this.refreshReadTimeoutHandler();
}
@Override
public int getWriteTimeout() {
return this.writeTimeout;
}
@Override
public void setWriteTimeout(int timeout) {
this.writeTimeout = timeout;
this.refreshWriteTimeoutHandler();
}
@Override
public boolean isConnected() {
return this.channel != null && this.channel.isOpen() && !this.disconnected;
}
@Override
public void send(Packet packet) {
if(this.channel == null) {
return;
}
PacketSendingEvent sendingEvent = new PacketSendingEvent(this, packet);
this.callEvent(sendingEvent);
if (!sendingEvent.isCancelled()) {
final Packet toSend = sendingEvent.getPacket();
this.channel.writeAndFlush(toSend).addListener((ChannelFutureListener) future -> {
if(future.isSuccess()) {
callPacketSent(toSend);
} else {
exceptionCaught(null, future.cause());
}
});
}
}
@Override
public void disconnect(String reason) {
this.disconnect(reason, null);
}
@Override
public void disconnect(final String reason, final Throwable cause) {
if (this.disconnected) {
return;
}
this.disconnected = true;
if (this.channel != null && this.channel.isOpen()) {
this.callEvent(new DisconnectingEvent(this, reason, cause));
this.channel.flush().close().addListener((ChannelFutureListener) future ->
callEvent(new DisconnectedEvent(TcpSession.this,
reason != null ? reason : "Connection closed.", cause)));
} else {
this.callEvent(new DisconnectedEvent(this, reason != null ? reason : "Connection closed.", cause));
}
}
private @Nullable EventLoop createEventLoop() {
if (!USE_EVENT_LOOP_FOR_PACKETS) {
return null;
}
if (PACKET_EVENT_LOOP == null) {
PACKET_EVENT_LOOP = new DefaultEventLoopGroup();
}
return PACKET_EVENT_LOOP.next();
}
public Channel getChannel() {
return this.channel;
}
protected void refreshReadTimeoutHandler() {
this.refreshReadTimeoutHandler(this.channel);
}
protected void refreshReadTimeoutHandler(Channel channel) {
if (channel != null) {
if (this.readTimeout <= 0) {
if (channel.pipeline().get("readTimeout") != null) {
channel.pipeline().remove("readTimeout");
}
} else {
if (channel.pipeline().get("readTimeout") == null) {
channel.pipeline().addFirst("readTimeout", new ReadTimeoutHandler(this.readTimeout));
} else {
channel.pipeline().replace("readTimeout", "readTimeout", new ReadTimeoutHandler(this.readTimeout));
}
}
}
}
protected void refreshWriteTimeoutHandler() {
this.refreshWriteTimeoutHandler(this.channel);
}
protected void refreshWriteTimeoutHandler(Channel channel) {
if (channel != null) {
if (this.writeTimeout <= 0) {
if (channel.pipeline().get("writeTimeout") != null) {
channel.pipeline().remove("writeTimeout");
}
} else {
if (channel.pipeline().get("writeTimeout") == null) {
channel.pipeline().addFirst("writeTimeout", new WriteTimeoutHandler(this.writeTimeout));
} else {
channel.pipeline().replace("writeTimeout", "writeTimeout", new WriteTimeoutHandler(this.writeTimeout));
}
}
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
if (this.disconnected || this.channel != null) {
ctx.channel().close();
return;
}
this.channel = ctx.channel();
this.callEvent(new ConnectedEvent(this));
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel() == this.channel) {
this.disconnect("Connection closed.");
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
String message;
if (cause instanceof ConnectTimeoutException || (cause instanceof ConnectException && cause.getMessage().contains("connection timed out"))) {
message = "Connection timed out.";
} else if (cause instanceof ReadTimeoutException) {
message = "Read timed out.";
} else if (cause instanceof WriteTimeoutException) {
message = "Write timed out.";
} else {
message = cause.toString();
}
this.disconnect(message, cause);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Packet packet) {
if (!packet.isPriority() && eventLoop != null) {
eventLoop.execute(() -> this.callPacketReceived(packet));
} else {
this.callPacketReceived(packet);
}
}
}