mirror of
https://github.com/GeyserMC/MCProtocolLib.git
synced 2024-11-23 23:58:05 -05:00
Merge packetlib w/ commit history
This commit is contained in:
commit
910a0009b9
49 changed files with 3815 additions and 1 deletions
27
Jenkinsfile
vendored
27
Jenkinsfile
vendored
|
@ -23,6 +23,31 @@ pipeline {
|
|||
when {
|
||||
branch "master"
|
||||
}
|
||||
pipeline {
|
||||
agent any
|
||||
tools {
|
||||
maven 'Maven 3'
|
||||
jdk 'Java 8'
|
||||
}
|
||||
options {
|
||||
buildDiscarder(logRotator(artifactNumToKeepStr: '20'))
|
||||
}
|
||||
stages {
|
||||
stage ('Build') {
|
||||
steps {
|
||||
sh 'mvn clean package'
|
||||
}
|
||||
post {
|
||||
success {
|
||||
archiveArtifacts artifacts: 'target/*.jar', excludes: 'target/*-sources.jar', fingerprint: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage ('Deploy') {
|
||||
when {
|
||||
branch "master"
|
||||
}
|
||||
|
||||
steps {
|
||||
rtMavenDeployer(
|
||||
|
@ -50,4 +75,4 @@ pipeline {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
32
example/com/github/steveice10/packetlib/test/PingPacket.java
Normal file
32
example/com/github/steveice10/packetlib/test/PingPacket.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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
31
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
}
|
106
src/main/java/com/github/steveice10/packetlib/ProxyInfo.java
Normal file
106
src/main/java/com/github/steveice10/packetlib/ProxyInfo.java
Normal 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;
|
||||
}
|
||||
}
|
156
src/main/java/com/github/steveice10/packetlib/Server.java
Normal file
156
src/main/java/com/github/steveice10/packetlib/Server.java
Normal 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);
|
||||
}
|
258
src/main/java/com/github/steveice10/packetlib/Session.java
Normal file
258
src/main/java/com/github/steveice10/packetlib/Session.java
Normal 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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
184
src/main/java/com/github/steveice10/packetlib/tcp/TcpServer.java
Normal file
184
src/main/java/com/github/steveice10/packetlib/tcp/TcpServer.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue