From c121202735efad41469e1a0409838c1799dca9ad Mon Sep 17 00:00:00 2001 From: Manevolent Date: Sat, 28 Jul 2018 13:32:01 -0400 Subject: [PATCH] We can now connect to servers, get the welcome message, handle pings, properly decompress fragments, etc. I have also added CommandLow/AckLow support. We need to implement data objects now to recognize command objects, and then implement the voice protocol. --- .../manevolent/ts3j/command/Command.java | 2 + .../ts3j/command/CommandHandler.java | 23 +++ .../client/ClientInitServerProcessor.java | 25 +++ .../command/response/CommandResponse.java | 51 +++++-- .../ts3j/protocol/packet/PacketBody.java | 2 +- .../packet/PacketBody3CommandLow.java | 53 +++++++ .../ts3j/protocol/packet/PacketBody4Ping.java | 33 ++++ .../ts3j/protocol/packet/PacketBody5Pong.java | 52 +++++++ .../protocol/packet/PacketBody7AckLow.java | 42 +++++ .../ts3j/protocol/packet/PacketBodyType.java | 8 +- .../local/LocalClientHandlerConnected.java | 6 - .../local/LocalClientHandlerConnecting.java | 2 + .../handler/local/LocalClientHandlerFull.java | 29 +++- .../LocalClientHandlerRetrievingData.java | 9 -- .../client/AbstractTeamspeakClientSocket.java | 144 +++++++++++++----- 15 files changed, 406 insertions(+), 75 deletions(-) create mode 100644 src/main/java/com/github/manevolent/ts3j/command/client/ClientInitServerProcessor.java create mode 100644 src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody3CommandLow.java create mode 100644 src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody4Ping.java create mode 100644 src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody5Pong.java create mode 100644 src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody7AckLow.java diff --git a/src/main/java/com/github/manevolent/ts3j/command/Command.java b/src/main/java/com/github/manevolent/ts3j/command/Command.java index ae9436e..033d50b 100644 --- a/src/main/java/com/github/manevolent/ts3j/command/Command.java +++ b/src/main/java/com/github/manevolent/ts3j/command/Command.java @@ -29,6 +29,8 @@ public interface Command { */ default boolean willExpectResponse() { return false; } + void add(CommandParameter parameter); + /** * Builds the command to a netowrk-issueable string. * The implementation currently defaults to the basic command format for TS3: diff --git a/src/main/java/com/github/manevolent/ts3j/command/CommandHandler.java b/src/main/java/com/github/manevolent/ts3j/command/CommandHandler.java index b6f0a67..b4ff205 100644 --- a/src/main/java/com/github/manevolent/ts3j/command/CommandHandler.java +++ b/src/main/java/com/github/manevolent/ts3j/command/CommandHandler.java @@ -1,11 +1,34 @@ package com.github.manevolent.ts3j.command; +import com.github.manevolent.ts3j.command.client.ClientInitServerProcessor; import com.github.manevolent.ts3j.protocol.socket.client.AbstractTeamspeakClientSocket; +import com.github.manevolent.ts3j.util.Ts3Debugging; + +import java.util.HashMap; +import java.util.Map; public final class CommandHandler implements CommandProcessor { + private final Map commandProcessors = new HashMap<>(); + + protected CommandHandler() { + + } + @Override public void process(AbstractTeamspeakClientSocket socket, ComplexCommand complexCommand) throws CommandProcessException { + Ts3Debugging.debug("[COMMAND] " + complexCommand.build()); + CommandProcessor processor = commandProcessors.get(complexCommand.getName().toLowerCase()); + if (processor != null) + processor.process(socket, complexCommand); + } + + public static CommandHandler createLocalClient() { + CommandHandler handler = new CommandHandler(); + + handler.commandProcessors.put("initserver", new ClientInitServerProcessor()); + + return handler; } } diff --git a/src/main/java/com/github/manevolent/ts3j/command/client/ClientInitServerProcessor.java b/src/main/java/com/github/manevolent/ts3j/command/client/ClientInitServerProcessor.java new file mode 100644 index 0000000..567d859 --- /dev/null +++ b/src/main/java/com/github/manevolent/ts3j/command/client/ClientInitServerProcessor.java @@ -0,0 +1,25 @@ +package com.github.manevolent.ts3j.command.client; + +import com.github.manevolent.ts3j.command.CommandProcessException; +import com.github.manevolent.ts3j.command.CommandProcessor; +import com.github.manevolent.ts3j.command.ComplexCommand; +import com.github.manevolent.ts3j.command.SimpleCommand; +import com.github.manevolent.ts3j.protocol.client.ClientConnectionState; +import com.github.manevolent.ts3j.protocol.socket.client.AbstractTeamspeakClientSocket; +import com.github.manevolent.ts3j.util.Ts3Debugging; + +public class ClientInitServerProcessor implements CommandProcessor { + @Override + public void process(AbstractTeamspeakClientSocket socket, ComplexCommand complexCommand) throws CommandProcessException { + SimpleCommand command = complexCommand.simplify(); + + Ts3Debugging.debug(command.get("virtualserver_welcomemessage").getValue()); + + if (socket.getState() == ClientConnectionState.RETRIEVING_DATA) + try { + socket.setState(ClientConnectionState.CONNECTED); + } catch (Exception e) { + throw new CommandProcessException(e); + } + } +} diff --git a/src/main/java/com/github/manevolent/ts3j/command/response/CommandResponse.java b/src/main/java/com/github/manevolent/ts3j/command/response/CommandResponse.java index 3d21a87..60bfced 100644 --- a/src/main/java/com/github/manevolent/ts3j/command/response/CommandResponse.java +++ b/src/main/java/com/github/manevolent/ts3j/command/response/CommandResponse.java @@ -1,12 +1,17 @@ package com.github.manevolent.ts3j.command.response; import com.github.manevolent.ts3j.command.Command; +import com.github.manevolent.ts3j.command.ComplexCommand; + +import java.util.concurrent.TimeoutException; public class CommandResponse { private final Object returnLock = new Object(); private final Command command; private final int returnCode; + private long dispatchedTime; + private ComplexCommand returnedCommand; public CommandResponse(Command command, int returnCode) { this.command = command; @@ -21,20 +26,29 @@ public class CommandResponse { return returnCode; } - public CommandResponse join() throws InterruptedException { - synchronized (returnLock) { - returnLock.wait(); - } - - return this; + public ComplexCommand getResponse() + throws InterruptedException, TimeoutException { + return getResponse(Integer.MAX_VALUE); } - public CommandResponse join(int millis) throws InterruptedException { - synchronized (returnLock) { - returnLock.wait(millis); - } + public ComplexCommand getResponse(int millis) + throws InterruptedException, TimeoutException { + if (returnCode < 0) return null; - return this; + synchronized (returnLock) { + long waited, toWait; + + while (getReturnedCommand() == null) { + waited = System.currentTimeMillis() - getDispatchedTime(); + toWait = millis - waited; + + if (toWait <= 0) throw new TimeoutException(); + + returnLock.wait(toWait); + } + + return getReturnedCommand(); + } } public long getDispatchedTime() { @@ -44,4 +58,19 @@ public class CommandResponse { public void setDispatchedTime(long dispatchedTime) { this.dispatchedTime = dispatchedTime; } + + public ComplexCommand getReturnedCommand() { + return returnedCommand; + } + + public void setReturnedCommand(ComplexCommand returnedCommand) { + synchronized (returnLock) { + if (this.returnCode < 0 ) throw new IllegalArgumentException("not expecting return command"); + if (this.returnedCommand != null) throw new IllegalArgumentException("already set return command"); + + this.returnedCommand = returnedCommand; + + returnLock.notifyAll(); + } + } } diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody.java index f238cf6..9b8055f 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody.java @@ -29,7 +29,7 @@ public abstract class PacketBody { public abstract int getSize(); public void setHeaderValues(PacketHeader header) { - // Do nothing + } /** diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody3CommandLow.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody3CommandLow.java new file mode 100644 index 0000000..24cc04c --- /dev/null +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody3CommandLow.java @@ -0,0 +1,53 @@ +package com.github.manevolent.ts3j.protocol.packet; + +import com.github.manevolent.ts3j.command.Command; +import com.github.manevolent.ts3j.command.ComplexCommand; +import com.github.manevolent.ts3j.protocol.ProtocolRole; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +public class PacketBody3CommandLow extends PacketBody { + private String text; + + public PacketBody3CommandLow(ProtocolRole role) { + super(PacketBodyType.COMMAND_LOW, role); + } + + public PacketBody3CommandLow(ProtocolRole role, String command) { + this(role); + + this.text = command; + } + + public PacketBody3CommandLow(ProtocolRole role, Command command) { + this(role, command.build()); + } + + public String getText() { + return text; + } + + public ComplexCommand parse() { + return ComplexCommand.parse(getRole(), getText()); + } + + public void setText(String text) { + this.text = text; + } + + @Override + public void read(ByteBuffer buffer) { + text = Charset.forName("UTF8").decode(buffer).toString(); + } + + @Override + public void write(ByteBuffer buffer) { + buffer.put(text.getBytes(Charset.forName("UTF8"))); + } + + @Override + public int getSize() { + return text.length(); + } +} diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody4Ping.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody4Ping.java new file mode 100644 index 0000000..d7619e4 --- /dev/null +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody4Ping.java @@ -0,0 +1,33 @@ +package com.github.manevolent.ts3j.protocol.packet; + +import com.github.manevolent.ts3j.protocol.ProtocolRole; +import com.github.manevolent.ts3j.protocol.header.HeaderFlag; +import com.github.manevolent.ts3j.protocol.header.PacketHeader; + +import java.nio.ByteBuffer; + +public class PacketBody4Ping extends PacketBody { + public PacketBody4Ping(ProtocolRole role) { + super(PacketBodyType.PING, role); + } + + @Override + public void setHeaderValues(PacketHeader header) { + header.setPacketFlag(HeaderFlag.UNENCRYPTED, true); + } + + @Override + public void read(ByteBuffer buffer) { + + } + + @Override + public void write(ByteBuffer buffer) { + + } + + @Override + public int getSize() { + return 0; + } +} diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody5Pong.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody5Pong.java new file mode 100644 index 0000000..1a75fd0 --- /dev/null +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody5Pong.java @@ -0,0 +1,52 @@ +package com.github.manevolent.ts3j.protocol.packet; + +import com.github.manevolent.ts3j.protocol.ProtocolRole; +import com.github.manevolent.ts3j.protocol.header.HeaderFlag; +import com.github.manevolent.ts3j.protocol.header.PacketHeader; + +import java.nio.ByteBuffer; + +public class PacketBody5Pong extends PacketBody { + private int packetId; + + public PacketBody5Pong(ProtocolRole role) { + super(PacketBodyType.PONG, role); + } + + public PacketBody5Pong(ProtocolRole role, int packetId) { + this(role); + + setPacketId(packetId); + } + + @Override + public void setHeaderValues(PacketHeader header) { + header.setPacketFlag(HeaderFlag.UNENCRYPTED, true); + } + + @Override + public void read(ByteBuffer buffer) { + packetId = buffer.getShort() & 0x000000FFFFF; + } + + @Override + public void write(ByteBuffer buffer) { + buffer.putShort((short) (packetId)); + } + + @Override + public int getSize() { + return 2; + } + + public int getPacketId() { + return packetId; + } + + public void setPacketId(int packetId) { + if (packetId > 65535 || packetId < 0) + throw new IllegalArgumentException("packetId out of bounds: " + packetId); + + this.packetId = packetId; + } +} diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody7AckLow.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody7AckLow.java new file mode 100644 index 0000000..bca994c --- /dev/null +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBody7AckLow.java @@ -0,0 +1,42 @@ +package com.github.manevolent.ts3j.protocol.packet; + +import com.github.manevolent.ts3j.protocol.ProtocolRole; + +import java.nio.ByteBuffer; + +public class PacketBody7AckLow extends PacketBody { + private int packetId; + + public PacketBody7AckLow(ProtocolRole role) { + super(PacketBodyType.ACK_LOW, role); + } + + public PacketBody7AckLow(ProtocolRole role, int packetId) { + this(role); + + setPacketId(packetId); + } + + public int getPacketId() { + return packetId; + } + + public void setPacketId(int packetId) { + this.packetId = packetId; + } + + @Override + public void read(ByteBuffer buffer) { + packetId = buffer.getShort() & 0x0000FFFF; + } + + @Override + public void write(ByteBuffer buffer) { + buffer.putShort((short) (getPacketId() & 0xFFFFFF)); + } + + @Override + public int getSize() { + return 2; + } +} diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBodyType.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBodyType.java index 2ccadc7..69ec83e 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBodyType.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/PacketBodyType.java @@ -3,12 +3,12 @@ package com.github.manevolent.ts3j.protocol.packet; public enum PacketBodyType { VOICE(0x0, null, null, false, false, false, false, false), VOICE_WHISPER(0x1, null, null, false, false, false, false, false), - PONG(0x5, null, null, false, false, false, false, false), - PING(0x4, null, PONG, false, false, false, false, false), + PONG(0x5, PacketBody5Pong.class, null, false, false, false, false, false), + PING(0x4, PacketBody4Ping.class, PONG, false, false, false, false, false), ACK(0x6, PacketBody6Ack.class, null, true, true, false, false, true), - ACK_LOW(0x7, null, null, true, true, false, false, true), + ACK_LOW(0x7, PacketBody7AckLow.class, null, true, true, false, false, true), INIT1(0x8, PacketBody8Init1.class, null, false, true, false, false, false), - COMMAND_LOW(0x3, null, ACK_LOW, true, true, true, true, true), + COMMAND_LOW(0x3, PacketBody3CommandLow.class, ACK_LOW, true, true, true, true, true), COMMAND(0x2, PacketBody2Command.class, ACK, true, true, true, true, true); private final int index; diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnected.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnected.java index 0a59ff6..4aafcac 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnected.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnected.java @@ -1,15 +1,9 @@ package com.github.manevolent.ts3j.protocol.packet.handler.local; -import com.github.manevolent.ts3j.command.ComplexCommand; import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket; public class LocalClientHandlerConnected extends LocalClientHandlerFull { public LocalClientHandlerConnected(LocalTeamspeakClientSocket client) { super(client); } - - @Override - protected void handleCommand(ComplexCommand command) { - - } } diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnecting.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnecting.java index a50b1e6..2d49b41 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnecting.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerConnecting.java @@ -1,5 +1,6 @@ package com.github.manevolent.ts3j.protocol.packet.handler.local; +import com.github.manevolent.ts3j.command.CommandHandler; import com.github.manevolent.ts3j.command.SimpleCommand; import com.github.manevolent.ts3j.command.part.CommandSingleParameter; import com.github.manevolent.ts3j.protocol.Packet; @@ -228,6 +229,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { ) ); + getClient().setCommandProcessor(CommandHandler.createLocalClient()); getClient().setState(ClientConnectionState.RETRIEVING_DATA); } else if (command.getName().equals("error")) { getClient().setState(ClientConnectionState.DISCONNECTED); diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerFull.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerFull.java index c5dc4d2..7565330 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerFull.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerFull.java @@ -5,6 +5,7 @@ import com.github.manevolent.ts3j.command.CommandProcessor; import com.github.manevolent.ts3j.command.ComplexCommand; import com.github.manevolent.ts3j.protocol.Packet; import com.github.manevolent.ts3j.protocol.packet.PacketBody2Command; +import com.github.manevolent.ts3j.protocol.packet.PacketBody3CommandLow; import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket; import java.io.IOException; @@ -22,11 +23,27 @@ public abstract class LocalClientHandlerFull extends LocalClientHandler { @Override public void handlePacket(Packet packet) throws IOException, TimeoutException { - if (packet.getBody() instanceof PacketBody2Command) - try { - handleCommand(ComplexCommand.parse(packet.getRole(), ((PacketBody2Command) packet.getBody()).getText())); - } catch (CommandProcessException e) { - throw new IOException(e); - } + switch (packet.getBody().getType()) { + case COMMAND: + try { + handleCommand(ComplexCommand.parse( + packet.getRole(), + ((PacketBody2Command) packet.getBody()).getText() + )); + } catch (CommandProcessException e) { + throw new IOException(e); + } + break; + case COMMAND_LOW: + try { + handleCommand(ComplexCommand.parse( + packet.getRole(), + ((PacketBody3CommandLow) packet.getBody()).getText() + )); + } catch (CommandProcessException e) { + throw new IOException(e); + } + break; + } } } diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerRetrievingData.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerRetrievingData.java index fbbdf47..dd6a037 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerRetrievingData.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/local/LocalClientHandlerRetrievingData.java @@ -1,18 +1,9 @@ package com.github.manevolent.ts3j.protocol.packet.handler.local; -import com.github.manevolent.ts3j.command.ComplexCommand; import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket; -import java.io.IOException; -import java.util.concurrent.TimeoutException; - public class LocalClientHandlerRetrievingData extends LocalClientHandlerFull { public LocalClientHandlerRetrievingData(LocalTeamspeakClientSocket client) { super(client); } - - @Override - protected void handleCommand(ComplexCommand command) { - - } } diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java index eb1f51f..7ac4b32 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java @@ -2,6 +2,7 @@ package com.github.manevolent.ts3j.protocol.socket.client; import com.github.manevolent.ts3j.command.Command; import com.github.manevolent.ts3j.command.CommandProcessor; +import com.github.manevolent.ts3j.command.part.CommandSingleParameter; import com.github.manevolent.ts3j.command.response.CommandResponse; import com.github.manevolent.ts3j.identity.Identity; import com.github.manevolent.ts3j.protocol.NetworkPacket; @@ -21,6 +22,7 @@ import com.github.manevolent.ts3j.protocol.socket.AbstractTeamspeakSocket; import com.github.manevolent.ts3j.util.QuickLZ; import com.github.manevolent.ts3j.util.Ts3Crypt; import com.github.manevolent.ts3j.util.Ts3Debugging; +import javafx.util.Pair; import java.io.IOException; import java.net.DatagramPacket; @@ -41,6 +43,7 @@ public abstract class AbstractTeamspeakClientSocket private final Map reassemblyQueue = new ConcurrentHashMap<>(); private final Map clientOptions = new ConcurrentHashMap<>(); private final Map sendQueue = new ConcurrentHashMap<>(); + private final Map sendCounter = new ConcurrentHashMap<>(); private CommandProcessor commandProcessor; @@ -52,6 +55,7 @@ public abstract class AbstractTeamspeakClientSocket private int generationId = 0; private int clientId = 0; private int packetId = 0; + private int commandReturnCode = 0; private final NetworkReader networkReader = new NetworkReader(); private final NetworkHandler networkHandler = new NetworkHandler(); @@ -73,6 +77,13 @@ public abstract class AbstractTeamspeakClientSocket for (PacketBodyType bodyType : PacketBodyType.values()) if (bodyType.isSplittable()) reassemblyQueue.put(bodyType, new Reassembly()); + + // Initialize counters + for (PacketBodyType bodyType : PacketBodyType.values()) + if (bodyType == PacketBodyType.INIT1) + sendCounter.put(PacketBodyType.INIT1, new PacketCounterZero()); + else + sendCounter.put(bodyType, new PacketCounterFull()); } protected void start() { @@ -169,14 +180,13 @@ public abstract class AbstractTeamspeakClientSocket CommandResponse response; if (command.willExpectResponse()) { - //command.appendParameter(new CommandSingleParameter("return_code", returnCodeIndex)); - response = new CommandResponse(command, 0); // TODO + int returnCode = commandReturnCode++; + response = new CommandResponse(command, returnCode++); + command.add(new CommandSingleParameter("return_code", Integer.toString(returnCode))); } else { response = new CommandResponse(command, -1); } - String commandString = command.toString(); - response.setDispatchedTime(System.currentTimeMillis()); writePacket(new PacketBody2Command(ProtocolRole.CLIENT, command)); @@ -218,18 +228,12 @@ public abstract class AbstractTeamspeakClientSocket // Set header values packet.getBody().setHeaderValues(packet.getHeader()); - if (packet.getHeader().getType() != PacketBodyType.INIT1 && packet.getHeader().getType() != PacketBodyType.ACK) { - packetId++; - packetId = packetId & 0x0000FFFF; + Pair counterResult = sendCounter.get(packet.getHeader().getType()).next(); + if (packet.getHeader() instanceof ClientPacketHeader) + ((ClientPacketHeader) packet.getHeader()).setClientId(clientId); - if (packetId == 0) generationId++; - - if (packet.getHeader() instanceof ClientPacketHeader) - ((ClientPacketHeader) packet.getHeader()).setClientId(clientId); - - packet.getHeader().setGeneration(generationId); - packet.getHeader().setPacketId(packetId); - } + packet.getHeader().setPacketId(counterResult.getKey()); + packet.getHeader().setGeneration(counterResult.getValue()); switch (packet.getHeader().getType()) { case INIT1: @@ -248,17 +252,19 @@ public abstract class AbstractTeamspeakClientSocket ByteBuffer outputBuffer = ByteBuffer.allocate(totalSize); packet.getBody().write(outputBuffer); - byte[] compressed = QuickLZ.compress(outputBuffer.array(), 1); - packet.getHeader().setPacketFlag(HeaderFlag.COMPRESSED, true); - packet.setBody(new PacketBodyCompressed(packet.getHeader().getType(), packet.getRole(), compressed)); + if (packet.getHeader().getType().isCompressible()) { + byte[] compressed = QuickLZ.compress(outputBuffer.array(), 1); + packet.getHeader().setPacketFlag(HeaderFlag.COMPRESSED, true); + packet.setBody(new PacketBodyCompressed(packet.getHeader().getType(), packet.getRole(), compressed)); - if (packet.getSize() <= 500) { - writePacketIntl(packet); - return; + if (packet.getSize() <= 500) { + writePacketIntl(packet); + return; + } } if (!packet.getHeader().getType().isSplittable()) - throw new IllegalArgumentException("packet too large: " + packet.getSize()); + throw new IllegalArgumentException("packet too large: " + packet.getSize() + " (cannot split)"); for (int offs = 0; offs < totalSize;) { int flush = Math.min(500 - packet.getHeader().getSize(), totalSize - offs); @@ -367,9 +373,7 @@ public abstract class AbstractTeamspeakClientSocket } } - private Packet readPacketIntl() throws IOException { - NetworkPacket networkPacket = readNetworkPacket(); - + private Packet readPacketIntl(NetworkPacket networkPacket) throws IOException { if (networkPacket.getHeader() == null) throw new NullPointerException("header"); Packet packet = new Packet(networkPacket.getHeader().getRole()); @@ -386,7 +390,7 @@ public abstract class AbstractTeamspeakClientSocket networkPacket.getHeader().getType().canEncrypt(); if (networkPacket.getHeader().getType().mustEncrypt() && !encrypted) - throw new IllegalArgumentException("packet is unencrypted, but must encrypt"); + throw new IllegalArgumentException("packet is unencrypted, but must be encrypted"); ByteBuffer packetBuffer = (ByteBuffer) networkPacket.getBuffer().position(networkPacket.getHeader().getSize()); @@ -437,7 +441,7 @@ public abstract class AbstractTeamspeakClientSocket @Override public Packet readPacket() throws IOException, TimeoutException { while (isReading()) { - Packet packet = readPacketIntl(); + Packet packet = readPacketIntl(readNetworkPacket()); // Find if the packet must be acknowledged PacketBodyType ackType = packet.getHeader().getType().getAcknolwedgedBy(); @@ -446,6 +450,12 @@ public abstract class AbstractTeamspeakClientSocket case ACK: writePacket(new PacketBody6Ack(getRole().getOut(), packet.getHeader().getPacketId())); break; + case ACK_LOW: + writePacket(new PacketBody7AckLow(getRole().getOut(), packet.getHeader().getPacketId())); + break; + case PONG: + writePacket(new PacketBody5Pong(getRole().getOut(), packet.getHeader().getPacketId())); + continue; // Don't pass pings to the parent } } @@ -456,18 +466,26 @@ public abstract class AbstractTeamspeakClientSocket } // Find if the packet is itself an acknowledgement + boolean handle; + PacketResponse response; switch (packet.getHeader().getType()) { case ACK: - int packetId = ((PacketBody6Ack)packet.getBody()).getPacketId(); - PacketResponse response = sendQueue.get(packetId); - if (response != null) { - response.acknowledge(packet); - } + response = sendQueue.get(((PacketBody6Ack)packet.getBody()).getPacketId()); + handle = false; break; case ACK_LOW: + response = sendQueue.get(((PacketBody7AckLow)packet.getBody()).getPacketId()); + handle = false; break; default: - return packet; + handle = true; + response = null; + } + + if (response != null) { + response.acknowledge(packet); + } else if (handle) { + return packet; } } @@ -672,6 +690,51 @@ public abstract class AbstractTeamspeakClientSocket } } + private interface PacketCounter { + Pair next(); + int getGenerationId(); + } + + private class PacketCounterZero implements PacketCounter { + @Override + public Pair next() { + return new Pair<>(0,0); + } + + @Override + public int getGenerationId() { + return 0; + } + } + + private class PacketCounterFull implements PacketCounter { + private final Object sendLock = new Object(); + private int packetId = 0; + private int generationId = 0; + + public int getPacketId() { + return packetId; + } + + public int getGenerationId() { + return generationId; + } + + @Override + public Pair next() { + synchronized (sendLock) { + packetId++; + + if (packetId >= 65536) { + packetId = 0; + generationId++; + } + + return new Pair<>(packetId, generationId); + } + } + } + private class Reassembly { private final Map queue = new LinkedHashMap<>(); private boolean state = false; @@ -698,16 +761,21 @@ public abstract class AbstractTeamspeakClientSocket int totalLength = reassemblyList.stream().mapToInt(x -> x.getBody().getSize()).sum() + lastFragment.getBody().getSize(); if (totalLength < 0) throw new IllegalArgumentException("reassembly too small: " + totalLength); + Packet firstPacket = reassemblyList.get(0); + Packet reassembledPacket = new Packet(firstPacket.getRole()); + reassembledPacket.setHeader(firstPacket.getHeader()); + ByteBuffer reassemblyBuffer = ByteBuffer.allocate(totalLength); + reassembledPacket.getHeader().setPacketFlag(HeaderFlag.FRAGMENTED, false); + for (Packet old : reassemblyList) old.writeBody(reassemblyBuffer); - Packet firstPacket = reassemblyList.get(0); - Packet reassembledPacket = new Packet(firstPacket.getRole()); - reassembledPacket.setHeader(firstPacket.getHeader()); - reassembledPacket.getHeader().setPacketFlag(HeaderFlag.FRAGMENTED, false); - reassembledPacket.readBody((ByteBuffer) reassemblyBuffer.position(0)); + if (firstPacket.getHeader().getPacketFlag(HeaderFlag.COMPRESSED)) + reassemblyBuffer = ByteBuffer.wrap(QuickLZ.decompress(reassemblyBuffer.array())); + + reassembledPacket.readBody(reassemblyBuffer); return reassembledPacket; }