diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/ICEServerSet.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/ICEServerSet.java new file mode 100644 index 0000000..675e892 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/ICEServerSet.java @@ -0,0 +1,32 @@ +package net.lax1dude.eaglercraft.sp.relay.pkt; + +public class ICEServerSet { + + public static enum RelayType { + STUN, TURN; + } + + public static class RelayServer { + + public final RelayType type; + public final String address; + public final String username; + public final String password; + + protected RelayServer(RelayType type, String address, String username, String password) { + this.type = type; + this.address = address; + this.username = username; + this.password = password; + } + + protected RelayServer(RelayType type, String address) { + this.type = type; + this.address = address; + this.username = null; + this.password = null; + } + + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket.java new file mode 100644 index 0000000..22f6f8d --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket.java @@ -0,0 +1,144 @@ +package net.lax1dude.eaglercraft.sp.relay.pkt; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; + +public class IPacket { + + private static final Map> definedPacketClasses = new HashMap(); + private static final Map,Integer> definedPacketIds = new HashMap(); + + private static void register(int id, Class clazz) { + definedPacketClasses.put(id, clazz); + definedPacketIds.put(clazz, id); + } + + static { + register(0x00, IPacket00Handshake.class); + register(0x01, IPacket01ICEServers.class); + register(0x02, IPacket02NewClient.class); + register(0x03, IPacket03ICECandidate.class); + register(0x04, IPacket04Description.class); + register(0x05, IPacket05ClientSuccess.class); + register(0x06, IPacket06ClientFailure.class); + register(0x07, IPacket07LocalWorlds.class); + register(0x69, IPacket69Pong.class); + register(0xFE, IPacketFEDisconnectClient.class); + register(0xFF, IPacketFFErrorCode.class); + } + + public static IPacket readPacket(DataInputStream input) throws IOException { + int i = input.read(); + try { + Class clazz = definedPacketClasses.get(i); + if(clazz == null) { + throw new IOException("Unknown packet type: " + i); + } + IPacket pkt = clazz.newInstance(); + pkt.read(input); + return pkt; + } catch (InstantiationException | IllegalAccessException e) { + throw new IOException("Unknown packet type: " + i); + } + } + + public static byte[] writePacket(IPacket packet) throws IOException { + Integer i = definedPacketIds.get(packet.getClass()); + if(i != null) { + int len = packet.packetLength(); + ByteArrayOutputStream bao = len == -1 ? new ByteArrayOutputStream() : + new ByteArrayOutputStream(len + 1); + bao.write(i); + packet.write(new DataOutputStream(bao)); + byte[] ret = bao.toByteArray(); + if(len != -1 && ret.length != len + 1) { + System.err.println("writePacket buffer for packet " + packet.getClass().getSimpleName() + " " + + (len + 1 < ret.length ? "overflowed" : "underflowed") + " by " + (len + 1 < ret.length ? + ret.length - len - 1 : len + 1 - ret.length) + " bytes"); + } + return ret; + }else { + throw new IOException("Unknown packet type: " + packet.getClass().getSimpleName()); + } + } + + public void read(DataInputStream input) throws IOException { + } + + public void write(DataOutputStream output) throws IOException { + } + + public int packetLength() { + return -1; + } + + public static String readASCII(InputStream is, int len) throws IOException { + char[] ret = new char[len]; + for(int i = 0; i < len; ++i) { + int j = is.read(); + if(j < 0) { + return null; + } + ret[i] = (char)is.read(); + } + return new String(ret); + } + + public static void writeASCII(OutputStream is, String txt) throws IOException { + for(int i = 0, l = txt.length(); i < l; ++i) { + is.write((int)txt.charAt(i)); + } + } + + public static String readASCII8(InputStream is) throws IOException { + int i = is.read(); + if(i < 0) { + return null; + }else { + return readASCII(is, i); + } + } + + public static void writeASCII8(OutputStream is, String txt) throws IOException { + if(txt == null) { + is.write(0); + }else { + int l = txt.length(); + is.write(l); + for(int i = 0; i < l; ++i) { + is.write((int)txt.charAt(i)); + } + } + } + + public static String readASCII16(InputStream is) throws IOException { + int hi = is.read(); + int lo = is.read(); + if(hi < 0 || lo < 0) { + return null; + }else { + return readASCII(is, (hi << 8) | lo); + } + } + + public static void writeASCII16(OutputStream is, String txt) throws IOException { + if(txt == null) { + is.write(0); + is.write(0); + }else { + int l = txt.length(); + is.write((l >> 8) & 0xFF); + is.write(l & 0xFF); + for(int i = 0; i < l; ++i) { + is.write((int)txt.charAt(i)); + } + } + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket00Handshake.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket00Handshake.java new file mode 100644 index 0000000..3702881 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket00Handshake.java @@ -0,0 +1,41 @@ +package net.lax1dude.eaglercraft.sp.relay.pkt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class IPacket00Handshake extends IPacket { + + public int connectionType = 0; + public int connectionVersion = 1; + public String connectionCode = null; + + public IPacket00Handshake() { + } + + public IPacket00Handshake(int connectionType, int connectionVersion, + String connectionCode) { + this.connectionType = connectionType; + this.connectionVersion = connectionVersion; + this.connectionCode = connectionCode; + } + + @Override + public void read(DataInputStream input) throws IOException { + connectionType = input.read(); + connectionVersion = input.read(); + connectionCode = IPacket.readASCII8(input); + } + + @Override + public void write(DataOutputStream output) throws IOException { + output.write(connectionType); + IPacket.writeASCII8(output, connectionCode); + } + + @Override + public int packetLength() { + return 1 + 1 + (connectionCode != null ? 1 + connectionCode.length() : 0); + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket01ICEServers.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket01ICEServers.java new file mode 100644 index 0000000..6fbb5c1 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket01ICEServers.java @@ -0,0 +1,40 @@ +package net.lax1dude.eaglercraft.sp.relay.pkt; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +public class IPacket01ICEServers extends IPacket { + + public final Collection servers; + + public IPacket01ICEServers() { + servers = new ArrayList(); + } + + public void read(DataInputStream input) throws IOException { + servers.clear(); + int l = input.readUnsignedShort(); + for(int i = 0; i < l; ++i) { + char type = (char)input.read(); + if(type == 'S') { + servers.add(new ICEServerSet.RelayServer( + ICEServerSet.RelayType.STUN, + readASCII16(input), + null, null + )); + }else if(type == 'T') { + servers.add(new ICEServerSet.RelayServer( + ICEServerSet.RelayType.TURN, + readASCII16(input), + readASCII8(input), + readASCII8(input) + )); + }else { + throw new IOException("Unknown/Unsupported Relay Type: '" + type + "'"); + } + } + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket02NewClient.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket02NewClient.java new file mode 100644 index 0000000..4fa2763 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket02NewClient.java @@ -0,0 +1,17 @@ +package net.lax1dude.eaglercraft.sp.relay.pkt; + +import java.io.DataInputStream; +import java.io.IOException; + +public class IPacket02NewClient extends IPacket { + + public String clientId; + + public IPacket02NewClient(String clientId) { + } + + public void read(DataInputStream input) throws IOException { + clientId = readASCII8(input); + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket03ICECandidate.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket03ICECandidate.java new file mode 100644 index 0000000..239a27f --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket03ICECandidate.java @@ -0,0 +1,34 @@ +package net.lax1dude.eaglercraft.sp.relay.pkt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class IPacket03ICECandidate extends IPacket { + + public String peerId; + public String candidate; + + public IPacket03ICECandidate(String peerId, String desc) { + this.peerId = peerId; + this.candidate = desc; + } + + public IPacket03ICECandidate() { + } + + public void read(DataInputStream input) throws IOException { + peerId = readASCII8(input); + candidate = readASCII16(input); + } + + public void write(DataOutputStream output) throws IOException { + writeASCII8(output, peerId); + writeASCII16(output, candidate); + } + + public int packetLength() { + return 1 + peerId.length() + 2 + candidate.length(); + } + +} \ No newline at end of file diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket04Description.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket04Description.java new file mode 100644 index 0000000..8693c7b --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket04Description.java @@ -0,0 +1,34 @@ +package net.lax1dude.eaglercraft.sp.relay.pkt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class IPacket04Description extends IPacket { + + public String peerId; + public String description; + + public IPacket04Description(String peerId, String desc) { + this.peerId = peerId; + this.description = desc; + } + + public IPacket04Description() { + } + + public void read(DataInputStream input) throws IOException { + peerId = readASCII8(input); + description = readASCII16(input); + } + + public void write(DataOutputStream output) throws IOException { + writeASCII8(output, peerId); + writeASCII16(output, description); + } + + public int packetLength() { + return 1 + peerId.length() + 2 + description.length(); + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket05ClientSuccess.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket05ClientSuccess.java new file mode 100644 index 0000000..a215a1a --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket05ClientSuccess.java @@ -0,0 +1,30 @@ +package net.lax1dude.eaglercraft.sp.relay.pkt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class IPacket05ClientSuccess extends IPacket { + + public String clientId; + + public IPacket05ClientSuccess() { + } + + public IPacket05ClientSuccess(String clientId) { + this.clientId = clientId; + } + + public void read(DataInputStream input) throws IOException { + clientId = readASCII8(input); + } + + public void write(DataOutputStream output) throws IOException { + writeASCII8(output, clientId); + } + + public int packetLength() { + return 1 + clientId.length(); + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket06ClientFailure.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket06ClientFailure.java new file mode 100644 index 0000000..fceae93 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket06ClientFailure.java @@ -0,0 +1,30 @@ +package net.lax1dude.eaglercraft.sp.relay.pkt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class IPacket06ClientFailure extends IPacket { + + public String clientId; + + public IPacket06ClientFailure() { + } + + public IPacket06ClientFailure(String clientId) { + this.clientId = clientId; + } + + public void read(DataInputStream input) throws IOException { + clientId = readASCII8(input); + } + + public void write(DataOutputStream output) throws IOException { + writeASCII8(output, clientId); + } + + public int packetLength() { + return 1 + clientId.length(); + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket07LocalWorlds.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket07LocalWorlds.java new file mode 100644 index 0000000..25f0e4a --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket07LocalWorlds.java @@ -0,0 +1,35 @@ +package net.lax1dude.eaglercraft.sp.relay.pkt; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class IPacket07LocalWorlds extends IPacket { + + public static class LocalWorld { + + public final String worldName; + public final String worldCode; + + public LocalWorld(String worldName, String worldCode) { + this.worldName = worldName; + this.worldCode = worldCode; + } + + } + + public final List worldsList; + + public IPacket07LocalWorlds() { + this.worldsList = new ArrayList(); + } + + public void read(DataInputStream input) throws IOException { + int l = input.read(); + for(int i = 0; i < l; ++i) { + worldsList.add(new LocalWorld(readASCII8(input), readASCII8(input))); + } + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket69Pong.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket69Pong.java new file mode 100644 index 0000000..bdb2cd7 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacket69Pong.java @@ -0,0 +1,27 @@ +package net.lax1dude.eaglercraft.sp.relay.pkt; + +import java.io.DataInputStream; +import java.io.IOException; + +public class IPacket69Pong extends IPacket { + + public int protcolVersion; + public String comment; + public String brand; + + public IPacket69Pong(int protcolVersion, String comment, String brand) { + if(comment.length() > 255) { + comment = comment.substring(0, 256); + } + this.protcolVersion = protcolVersion; + this.comment = comment; + this.brand = brand; + } + + public void read(DataInputStream output) throws IOException { + protcolVersion = output.read(); + comment = readASCII8(output); + brand = readASCII8(output); + } + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacketFEDisconnectClient.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacketFEDisconnectClient.java new file mode 100644 index 0000000..34d6163 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacketFEDisconnectClient.java @@ -0,0 +1,51 @@ +package net.lax1dude.eaglercraft.sp.relay.pkt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; + +public class IPacketFEDisconnectClient extends IPacket { + + public static final int TYPE_FINISHED_SUCCESS = 0x00; + public static final int TYPE_FINISHED_FAILED = 0x01; + public static final int TYPE_TIMEOUT = 0x02; + public static final int TYPE_INVALID_OPERATION = 0x03; + public static final int TYPE_INTERNAL_ERROR = 0x04; + public static final int TYPE_UNKNOWN = 0xFF; + + public String clientId; + public int code; + public String reason; + + public IPacketFEDisconnectClient() { + } + + public IPacketFEDisconnectClient(String clientId, int code, String reason) { + this.clientId = clientId; + this.code = code; + this.reason = reason; + } + + public void read(DataInputStream input) throws IOException { + clientId = readASCII8(input); + code = input.read(); + reason = readASCII16(input); + } + + public void write(DataOutputStream output) throws IOException { + writeASCII8(output, clientId); + output.write(code); + writeASCII16(output, reason); + } + + public int packetLength() { + return -1; + } + + public static final ByteBuffer ratelimitPacketTooMany = ByteBuffer.wrap(new byte[] { (byte)0xFC, (byte)0x00 }); + public static final ByteBuffer ratelimitPacketBlock = ByteBuffer.wrap(new byte[] { (byte)0xFC, (byte)0x01 }); + public static final ByteBuffer ratelimitPacketBlockLock = ByteBuffer.wrap(new byte[] { (byte)0xFC, (byte)0x02 }); + public static final ByteBuffer ratelimitPacketLocked = ByteBuffer.wrap(new byte[] { (byte)0xFC, (byte)0x03 }); + +} diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacketFFErrorCode.java b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacketFFErrorCode.java new file mode 100644 index 0000000..8effe08 --- /dev/null +++ b/src/main/java/net/lax1dude/eaglercraft/sp/relay/pkt/IPacketFFErrorCode.java @@ -0,0 +1,46 @@ +package net.lax1dude.eaglercraft.sp.relay.pkt; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class IPacketFFErrorCode extends IPacket { + + public static final int TYPE_INTERNAL_ERROR = 0x00; + public static final int TYPE_PROTOCOL_VERSION = 0x01; + public static final int TYPE_INVALID_PACKET = 0x02; + public static final int TYPE_ILLEGAL_OPERATION = 0x03; + public static final int TYPE_CODE_LENGTH = 0x04; + public static final int TYPE_INCORRECT_CODE = 0x05; + public static final int TYPE_SERVER_DISCONNECTED = 0x06; + public static final int TYPE_UNKNOWN_CLIENT = 0x07; + + public int code; + public String desc; + + public IPacketFFErrorCode() { + } + + public IPacketFFErrorCode(int code, String desc) { + this.code = code; + this.desc = desc; + } + + @Override + public void read(DataInputStream input) throws IOException { + code = input.read(); + desc = readASCII16(input); + } + + @Override + public void write(DataOutputStream input) throws IOException { + input.write(code); + writeASCII16(input, desc); + } + + @Override + public int packetLength() { + return 1 + 2 + desc.length(); + } + +}