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.

This commit is contained in:
Manevolent
2018-07-28 13:32:01 -04:00
parent ab4cb12010
commit c121202735
15 changed files with 406 additions and 75 deletions
@@ -29,6 +29,8 @@ public interface Command {
*/ */
default boolean willExpectResponse() { return false; } default boolean willExpectResponse() { return false; }
void add(CommandParameter parameter);
/** /**
* Builds the command to a netowrk-issueable string. * Builds the command to a netowrk-issueable string.
* The implementation currently defaults to the basic command format for TS3: * The implementation currently defaults to the basic command format for TS3:
@@ -1,11 +1,34 @@
package com.github.manevolent.ts3j.command; 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.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 { public final class CommandHandler implements CommandProcessor {
private final Map<String, CommandProcessor> commandProcessors = new HashMap<>();
protected CommandHandler() {
}
@Override @Override
public void process(AbstractTeamspeakClientSocket socket, ComplexCommand complexCommand) public void process(AbstractTeamspeakClientSocket socket, ComplexCommand complexCommand)
throws CommandProcessException { 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;
} }
} }
@@ -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);
}
}
}
@@ -1,12 +1,17 @@
package com.github.manevolent.ts3j.command.response; package com.github.manevolent.ts3j.command.response;
import com.github.manevolent.ts3j.command.Command; import com.github.manevolent.ts3j.command.Command;
import com.github.manevolent.ts3j.command.ComplexCommand;
import java.util.concurrent.TimeoutException;
public class CommandResponse { public class CommandResponse {
private final Object returnLock = new Object(); private final Object returnLock = new Object();
private final Command command; private final Command command;
private final int returnCode; private final int returnCode;
private long dispatchedTime; private long dispatchedTime;
private ComplexCommand returnedCommand;
public CommandResponse(Command command, int returnCode) { public CommandResponse(Command command, int returnCode) {
this.command = command; this.command = command;
@@ -21,20 +26,29 @@ public class CommandResponse {
return returnCode; return returnCode;
} }
public CommandResponse join() throws InterruptedException { public ComplexCommand getResponse()
throws InterruptedException, TimeoutException {
return getResponse(Integer.MAX_VALUE);
}
public ComplexCommand getResponse(int millis)
throws InterruptedException, TimeoutException {
if (returnCode < 0) return null;
synchronized (returnLock) { synchronized (returnLock) {
returnLock.wait(); long waited, toWait;
while (getReturnedCommand() == null) {
waited = System.currentTimeMillis() - getDispatchedTime();
toWait = millis - waited;
if (toWait <= 0) throw new TimeoutException();
returnLock.wait(toWait);
} }
return this; return getReturnedCommand();
} }
public CommandResponse join(int millis) throws InterruptedException {
synchronized (returnLock) {
returnLock.wait(millis);
}
return this;
} }
public long getDispatchedTime() { public long getDispatchedTime() {
@@ -44,4 +58,19 @@ public class CommandResponse {
public void setDispatchedTime(long dispatchedTime) { public void setDispatchedTime(long dispatchedTime) {
this.dispatchedTime = 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();
}
}
} }
@@ -29,7 +29,7 @@ public abstract class PacketBody {
public abstract int getSize(); public abstract int getSize();
public void setHeaderValues(PacketHeader header) { public void setHeaderValues(PacketHeader header) {
// Do nothing
} }
/** /**
@@ -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();
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -3,12 +3,12 @@ package com.github.manevolent.ts3j.protocol.packet;
public enum PacketBodyType { public enum PacketBodyType {
VOICE(0x0, null, null, false, false, false, false, false), VOICE(0x0, null, null, false, false, false, false, false),
VOICE_WHISPER(0x1, 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), PONG(0x5, PacketBody5Pong.class, null, false, false, false, false, false),
PING(0x4, null, PONG, 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(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), 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); COMMAND(0x2, PacketBody2Command.class, ACK, true, true, true, true, true);
private final int index; private final int index;
@@ -1,15 +1,9 @@
package com.github.manevolent.ts3j.protocol.packet.handler.local; 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 com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
public class LocalClientHandlerConnected extends LocalClientHandlerFull { public class LocalClientHandlerConnected extends LocalClientHandlerFull {
public LocalClientHandlerConnected(LocalTeamspeakClientSocket client) { public LocalClientHandlerConnected(LocalTeamspeakClientSocket client) {
super(client); super(client);
} }
@Override
protected void handleCommand(ComplexCommand command) {
}
} }
@@ -1,5 +1,6 @@
package com.github.manevolent.ts3j.protocol.packet.handler.local; 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.SimpleCommand;
import com.github.manevolent.ts3j.command.part.CommandSingleParameter; import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
import com.github.manevolent.ts3j.protocol.Packet; 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); getClient().setState(ClientConnectionState.RETRIEVING_DATA);
} else if (command.getName().equals("error")) { } else if (command.getName().equals("error")) {
getClient().setState(ClientConnectionState.DISCONNECTED); getClient().setState(ClientConnectionState.DISCONNECTED);
@@ -5,6 +5,7 @@ import com.github.manevolent.ts3j.command.CommandProcessor;
import com.github.manevolent.ts3j.command.ComplexCommand; import com.github.manevolent.ts3j.command.ComplexCommand;
import com.github.manevolent.ts3j.protocol.Packet; import com.github.manevolent.ts3j.protocol.Packet;
import com.github.manevolent.ts3j.protocol.packet.PacketBody2Command; 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 com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
import java.io.IOException; import java.io.IOException;
@@ -22,11 +23,27 @@ public abstract class LocalClientHandlerFull extends LocalClientHandler {
@Override @Override
public void handlePacket(Packet packet) throws IOException, TimeoutException { public void handlePacket(Packet packet) throws IOException, TimeoutException {
if (packet.getBody() instanceof PacketBody2Command) switch (packet.getBody().getType()) {
case COMMAND:
try { try {
handleCommand(ComplexCommand.parse(packet.getRole(), ((PacketBody2Command) packet.getBody()).getText())); handleCommand(ComplexCommand.parse(
packet.getRole(),
((PacketBody2Command) packet.getBody()).getText()
));
} catch (CommandProcessException e) { } catch (CommandProcessException e) {
throw new IOException(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;
}
} }
} }
@@ -1,18 +1,9 @@
package com.github.manevolent.ts3j.protocol.packet.handler.local; 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 com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class LocalClientHandlerRetrievingData extends LocalClientHandlerFull { public class LocalClientHandlerRetrievingData extends LocalClientHandlerFull {
public LocalClientHandlerRetrievingData(LocalTeamspeakClientSocket client) { public LocalClientHandlerRetrievingData(LocalTeamspeakClientSocket client) {
super(client); super(client);
} }
@Override
protected void handleCommand(ComplexCommand command) {
}
} }
@@ -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.Command;
import com.github.manevolent.ts3j.command.CommandProcessor; 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.command.response.CommandResponse;
import com.github.manevolent.ts3j.identity.Identity; import com.github.manevolent.ts3j.identity.Identity;
import com.github.manevolent.ts3j.protocol.NetworkPacket; 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.QuickLZ;
import com.github.manevolent.ts3j.util.Ts3Crypt; import com.github.manevolent.ts3j.util.Ts3Crypt;
import com.github.manevolent.ts3j.util.Ts3Debugging; import com.github.manevolent.ts3j.util.Ts3Debugging;
import javafx.util.Pair;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
@@ -41,6 +43,7 @@ public abstract class AbstractTeamspeakClientSocket
private final Map<PacketBodyType, Reassembly> reassemblyQueue = new ConcurrentHashMap<>(); private final Map<PacketBodyType, Reassembly> reassemblyQueue = new ConcurrentHashMap<>();
private final Map<String, Object> clientOptions = new ConcurrentHashMap<>(); private final Map<String, Object> clientOptions = new ConcurrentHashMap<>();
private final Map<Integer, PacketResponse> sendQueue = new ConcurrentHashMap<>(); private final Map<Integer, PacketResponse> sendQueue = new ConcurrentHashMap<>();
private final Map<PacketBodyType, PacketCounter> sendCounter = new ConcurrentHashMap<>();
private CommandProcessor commandProcessor; private CommandProcessor commandProcessor;
@@ -52,6 +55,7 @@ public abstract class AbstractTeamspeakClientSocket
private int generationId = 0; private int generationId = 0;
private int clientId = 0; private int clientId = 0;
private int packetId = 0; private int packetId = 0;
private int commandReturnCode = 0;
private final NetworkReader networkReader = new NetworkReader(); private final NetworkReader networkReader = new NetworkReader();
private final NetworkHandler networkHandler = new NetworkHandler(); private final NetworkHandler networkHandler = new NetworkHandler();
@@ -73,6 +77,13 @@ public abstract class AbstractTeamspeakClientSocket
for (PacketBodyType bodyType : PacketBodyType.values()) for (PacketBodyType bodyType : PacketBodyType.values())
if (bodyType.isSplittable()) if (bodyType.isSplittable())
reassemblyQueue.put(bodyType, new Reassembly()); 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() { protected void start() {
@@ -169,14 +180,13 @@ public abstract class AbstractTeamspeakClientSocket
CommandResponse response; CommandResponse response;
if (command.willExpectResponse()) { if (command.willExpectResponse()) {
//command.appendParameter(new CommandSingleParameter("return_code", returnCodeIndex)); int returnCode = commandReturnCode++;
response = new CommandResponse(command, 0); // TODO response = new CommandResponse(command, returnCode++);
command.add(new CommandSingleParameter("return_code", Integer.toString(returnCode)));
} else { } else {
response = new CommandResponse(command, -1); response = new CommandResponse(command, -1);
} }
String commandString = command.toString();
response.setDispatchedTime(System.currentTimeMillis()); response.setDispatchedTime(System.currentTimeMillis());
writePacket(new PacketBody2Command(ProtocolRole.CLIENT, command)); writePacket(new PacketBody2Command(ProtocolRole.CLIENT, command));
@@ -218,18 +228,12 @@ public abstract class AbstractTeamspeakClientSocket
// Set header values // Set header values
packet.getBody().setHeaderValues(packet.getHeader()); packet.getBody().setHeaderValues(packet.getHeader());
if (packet.getHeader().getType() != PacketBodyType.INIT1 && packet.getHeader().getType() != PacketBodyType.ACK) { Pair<Integer, Integer> counterResult = sendCounter.get(packet.getHeader().getType()).next();
packetId++;
packetId = packetId & 0x0000FFFF;
if (packetId == 0) generationId++;
if (packet.getHeader() instanceof ClientPacketHeader) if (packet.getHeader() instanceof ClientPacketHeader)
((ClientPacketHeader) packet.getHeader()).setClientId(clientId); ((ClientPacketHeader) packet.getHeader()).setClientId(clientId);
packet.getHeader().setGeneration(generationId); packet.getHeader().setPacketId(counterResult.getKey());
packet.getHeader().setPacketId(packetId); packet.getHeader().setGeneration(counterResult.getValue());
}
switch (packet.getHeader().getType()) { switch (packet.getHeader().getType()) {
case INIT1: case INIT1:
@@ -248,6 +252,7 @@ public abstract class AbstractTeamspeakClientSocket
ByteBuffer outputBuffer = ByteBuffer.allocate(totalSize); ByteBuffer outputBuffer = ByteBuffer.allocate(totalSize);
packet.getBody().write(outputBuffer); packet.getBody().write(outputBuffer);
if (packet.getHeader().getType().isCompressible()) {
byte[] compressed = QuickLZ.compress(outputBuffer.array(), 1); byte[] compressed = QuickLZ.compress(outputBuffer.array(), 1);
packet.getHeader().setPacketFlag(HeaderFlag.COMPRESSED, true); packet.getHeader().setPacketFlag(HeaderFlag.COMPRESSED, true);
packet.setBody(new PacketBodyCompressed(packet.getHeader().getType(), packet.getRole(), compressed)); packet.setBody(new PacketBodyCompressed(packet.getHeader().getType(), packet.getRole(), compressed));
@@ -256,9 +261,10 @@ public abstract class AbstractTeamspeakClientSocket
writePacketIntl(packet); writePacketIntl(packet);
return; return;
} }
}
if (!packet.getHeader().getType().isSplittable()) 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;) { for (int offs = 0; offs < totalSize;) {
int flush = Math.min(500 - packet.getHeader().getSize(), totalSize - offs); int flush = Math.min(500 - packet.getHeader().getSize(), totalSize - offs);
@@ -367,9 +373,7 @@ public abstract class AbstractTeamspeakClientSocket
} }
} }
private Packet readPacketIntl() throws IOException { private Packet readPacketIntl(NetworkPacket networkPacket) throws IOException {
NetworkPacket networkPacket = readNetworkPacket();
if (networkPacket.getHeader() == null) throw new NullPointerException("header"); if (networkPacket.getHeader() == null) throw new NullPointerException("header");
Packet packet = new Packet(networkPacket.getHeader().getRole()); Packet packet = new Packet(networkPacket.getHeader().getRole());
@@ -386,7 +390,7 @@ public abstract class AbstractTeamspeakClientSocket
networkPacket.getHeader().getType().canEncrypt(); networkPacket.getHeader().getType().canEncrypt();
if (networkPacket.getHeader().getType().mustEncrypt() && !encrypted) 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 packetBuffer =
(ByteBuffer) networkPacket.getBuffer().position(networkPacket.getHeader().getSize()); (ByteBuffer) networkPacket.getBuffer().position(networkPacket.getHeader().getSize());
@@ -437,7 +441,7 @@ public abstract class AbstractTeamspeakClientSocket
@Override @Override
public Packet readPacket() throws IOException, TimeoutException { public Packet readPacket() throws IOException, TimeoutException {
while (isReading()) { while (isReading()) {
Packet packet = readPacketIntl(); Packet packet = readPacketIntl(readNetworkPacket());
// Find if the packet must be acknowledged // Find if the packet must be acknowledged
PacketBodyType ackType = packet.getHeader().getType().getAcknolwedgedBy(); PacketBodyType ackType = packet.getHeader().getType().getAcknolwedgedBy();
@@ -446,6 +450,12 @@ public abstract class AbstractTeamspeakClientSocket
case ACK: case ACK:
writePacket(new PacketBody6Ack(getRole().getOut(), packet.getHeader().getPacketId())); writePacket(new PacketBody6Ack(getRole().getOut(), packet.getHeader().getPacketId()));
break; 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,17 +466,25 @@ public abstract class AbstractTeamspeakClientSocket
} }
// Find if the packet is itself an acknowledgement // Find if the packet is itself an acknowledgement
boolean handle;
PacketResponse response;
switch (packet.getHeader().getType()) { switch (packet.getHeader().getType()) {
case ACK: case ACK:
int packetId = ((PacketBody6Ack)packet.getBody()).getPacketId(); response = sendQueue.get(((PacketBody6Ack)packet.getBody()).getPacketId());
PacketResponse response = sendQueue.get(packetId); handle = false;
if (response != null) {
response.acknowledge(packet);
}
break; break;
case ACK_LOW: case ACK_LOW:
response = sendQueue.get(((PacketBody7AckLow)packet.getBody()).getPacketId());
handle = false;
break; break;
default: default:
handle = true;
response = null;
}
if (response != null) {
response.acknowledge(packet);
} else if (handle) {
return packet; return packet;
} }
} }
@@ -672,6 +690,51 @@ public abstract class AbstractTeamspeakClientSocket
} }
} }
private interface PacketCounter {
Pair<Integer, Integer> next();
int getGenerationId();
}
private class PacketCounterZero implements PacketCounter {
@Override
public Pair<Integer, Integer> 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<Integer, Integer> next() {
synchronized (sendLock) {
packetId++;
if (packetId >= 65536) {
packetId = 0;
generationId++;
}
return new Pair<>(packetId, generationId);
}
}
}
private class Reassembly { private class Reassembly {
private final Map<Integer, Packet> queue = new LinkedHashMap<>(); private final Map<Integer, Packet> queue = new LinkedHashMap<>();
private boolean state = false; 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(); int totalLength = reassemblyList.stream().mapToInt(x -> x.getBody().getSize()).sum() + lastFragment.getBody().getSize();
if (totalLength < 0) throw new IllegalArgumentException("reassembly too small: " + totalLength); 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); ByteBuffer reassemblyBuffer = ByteBuffer.allocate(totalLength);
reassembledPacket.getHeader().setPacketFlag(HeaderFlag.FRAGMENTED, false);
for (Packet old : reassemblyList) for (Packet old : reassemblyList)
old.writeBody(reassemblyBuffer); old.writeBody(reassemblyBuffer);
Packet firstPacket = reassemblyList.get(0); if (firstPacket.getHeader().getPacketFlag(HeaderFlag.COMPRESSED))
Packet reassembledPacket = new Packet(firstPacket.getRole()); reassemblyBuffer = ByteBuffer.wrap(QuickLZ.decompress(reassemblyBuffer.array()));
reassembledPacket.setHeader(firstPacket.getHeader());
reassembledPacket.getHeader().setPacketFlag(HeaderFlag.FRAGMENTED, false); reassembledPacket.readBody(reassemblyBuffer);
reassembledPacket.readBody((ByteBuffer) reassemblyBuffer.position(0));
return reassembledPacket; return reassembledPacket;
} }