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:
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
@@ -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;
|
||||||
|
|||||||
-6
@@ -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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -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);
|
||||||
|
|||||||
+19
-2
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-9
@@ -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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+97
-29
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user