Huge refactor
This commit is contained in:
@@ -116,7 +116,7 @@ for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
standard defined by a recognized standards buffer, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
|
||||
@@ -35,5 +35,11 @@
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>1.60</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.github.manevolent.ts3j.protocol;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.packet.Packet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface Endpoint {
|
||||
|
||||
SocketRole getRole();
|
||||
|
||||
void send(Packet packet) throws IOException;
|
||||
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package com.github.manevolent.ts3j.protocol;
|
||||
|
||||
import com.github.manevolent.ts3j.identity.LocalIdentity;
|
||||
import com.github.manevolent.ts3j.protocol.header.HeaderFlag;
|
||||
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketType;
|
||||
import com.github.manevolent.ts3j.protocol.packet.channel.PacketChannel;
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.PacketHandler;
|
||||
import com.github.manevolent.ts3j.protocol.socket.LocalTeamspeakSocket;
|
||||
import com.github.manevolent.ts3j.protocol.packet.channel.ReassemblyQueue;
|
||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
|
||||
public abstract class LocalEndpoint implements Endpoint {
|
||||
private final LocalTeamspeakSocket socket;
|
||||
|
||||
private LocalIdentity localIdentity = null;
|
||||
|
||||
private final ReassemblyQueue reassemblyQueue = new ReassemblyQueue();
|
||||
|
||||
private final Object runLock = new Object();
|
||||
private Thread receiverThread;
|
||||
private boolean running = false;
|
||||
|
||||
protected LocalEndpoint(LocalTeamspeakSocket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
protected LocalEndpoint(SocketRole socketRole) throws SocketException {
|
||||
this.socket = new LocalTeamspeakSocket(socketRole);
|
||||
}
|
||||
|
||||
public InetSocketAddress getLocalAddress() {
|
||||
return getSocket().getLocalAddress();
|
||||
}
|
||||
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
return getSocket().getLocalAddress();
|
||||
}
|
||||
|
||||
protected final LocalTeamspeakSocket getSocket() {
|
||||
return socket;
|
||||
}
|
||||
|
||||
protected abstract void acknowledge(NetworkPacket packet, PacketType ackType) throws IOException;
|
||||
protected abstract PacketChannel getChannel(NetworkPacket packet);
|
||||
|
||||
public void setRunning(boolean running) {
|
||||
if (this.running != running) {
|
||||
this.running = running;
|
||||
|
||||
if (running) {
|
||||
Ts3Logging.debug("Starting network receiver thread...");
|
||||
|
||||
receiverThread = new Thread(new PacketReceiver());
|
||||
receiverThread.setDaemon(true);
|
||||
receiverThread.start();
|
||||
} else {
|
||||
if (receiverThread != null) {
|
||||
receiverThread.interrupt();
|
||||
receiverThread = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketRole getRole() {
|
||||
return socket.getSocketRole();
|
||||
}
|
||||
|
||||
private NetworkPacket receive() throws IOException {
|
||||
return socket.receive();
|
||||
}
|
||||
|
||||
public LocalIdentity getLocalIdentity() {
|
||||
return localIdentity;
|
||||
}
|
||||
|
||||
public void setLocalIdentity(LocalIdentity localIdentity) {
|
||||
this.localIdentity = localIdentity;
|
||||
}
|
||||
|
||||
private class PacketReceiver implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
Ts3Logging.debug("Entering packet receiver");
|
||||
|
||||
while (running) {
|
||||
try {
|
||||
NetworkPacket packet = receive();
|
||||
PacketChannel channel = getChannel(packet);
|
||||
if (channel == null) continue;
|
||||
|
||||
channel.insert(packet);
|
||||
|
||||
if (packet.getHeader().getType().getAcknolwedgedBy() != null)
|
||||
acknowledge(packet, packet.getHeader().getType().getAcknolwedgedBy());
|
||||
|
||||
PacketHandler handler = channel.getHandler();
|
||||
if (handler != null) {
|
||||
while ((packet = channel.next()) != null) {
|
||||
handler.handlePacket(packet);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof InterruptedException) continue;
|
||||
|
||||
Ts3Logging.debug("Problem handling packet", e);
|
||||
}
|
||||
}
|
||||
|
||||
Ts3Logging.debug("Leaving packet receiver");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,127 +1,42 @@
|
||||
package com.github.manevolent.ts3j.protocol;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||
import com.github.manevolent.ts3j.protocol.packet.Packet;
|
||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||
|
||||
import java.net.DatagramPacket;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Represents a fully contained network packet, traveling in or out.
|
||||
*/
|
||||
public final class NetworkPacket {
|
||||
private final ProtocolRole role;
|
||||
private final DatagramPacket datagram;
|
||||
private final PacketHeader header;
|
||||
private final ByteBuffer buffer;
|
||||
|
||||
private PacketHeader header;
|
||||
private Packet packet;
|
||||
|
||||
public NetworkPacket(ProtocolRole role) {
|
||||
if (role == null) throw new NullPointerException("role");
|
||||
|
||||
this.role = role;
|
||||
public NetworkPacket(DatagramPacket datagram, PacketHeader header, ByteBuffer buffer) {
|
||||
this.datagram = datagram;
|
||||
this.header = header;
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
public ProtocolRole getRole() {
|
||||
return role;
|
||||
/**
|
||||
* Gets the original packet datagram.
|
||||
* @return Packet datagram.
|
||||
*/
|
||||
public DatagramPacket getDatagram() {
|
||||
return datagram;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the received header.
|
||||
* @return Packet header.
|
||||
*/
|
||||
public PacketHeader getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public void setHeader(PacketHeader header) {
|
||||
if (header == null) throw new NullPointerException("header");
|
||||
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
public Packet getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
public void setPacket(Packet packet) {
|
||||
if (packet == null) throw new NullPointerException("packet");
|
||||
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the existing data out to the specified byte buffer
|
||||
* @param buffer Buffer to write to
|
||||
* Gets the original packet buffer.
|
||||
* @return Packet buffer.
|
||||
*/
|
||||
public ByteBuffer write(ByteBuffer buffer) {
|
||||
writeHeader(buffer);
|
||||
writeBody(buffer);
|
||||
|
||||
public ByteBuffer getBuffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public ByteBuffer writeHeader(ByteBuffer buffer) {
|
||||
if (getHeader() == null) throw new NullPointerException("header");
|
||||
|
||||
getHeader().setType(getPacket().getType());
|
||||
|
||||
getHeader().write(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public ByteBuffer writeBody(ByteBuffer buffer) {
|
||||
if (getPacket() == null) throw new NullPointerException("packet");
|
||||
|
||||
getPacket().write(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads data from the specified byte buffer, filling in the packet body as needed.
|
||||
* @param buffer Buffer to read from.
|
||||
*/
|
||||
public ByteBuffer read(ByteBuffer buffer) {
|
||||
readHeader(buffer);
|
||||
readBody(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public ByteBuffer readHeader(ByteBuffer buffer) {
|
||||
if (getHeader() == null) {
|
||||
try {
|
||||
setHeader(getRole().getHeaderClass().newInstance());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
getHeader().read(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public ByteBuffer readBody(ByteBuffer buffer) {
|
||||
if (getPacket() == null) {
|
||||
try {
|
||||
setPacket(
|
||||
getHeader().getType().getPacketClass()
|
||||
.getConstructor(ProtocolRole.class)
|
||||
.newInstance(getHeader().getRole())
|
||||
);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
getPacket().read(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of this packet. This method is generally only used when writing a fully defined packet.
|
||||
* @return packet size.
|
||||
*/
|
||||
public int getSize() {
|
||||
return getHeader().getSize() + getPacket().getSize();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.github.manevolent.ts3j.protocol;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketBody;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Represents a fully contained network packet, traveling in or out.
|
||||
*/
|
||||
public final class Packet {
|
||||
private final ProtocolRole role;
|
||||
|
||||
private PacketHeader header;
|
||||
private PacketBody body;
|
||||
|
||||
public Packet(ProtocolRole role) {
|
||||
if (role == null) throw new NullPointerException("role");
|
||||
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public ProtocolRole getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public PacketHeader getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public void setHeader(PacketHeader header) {
|
||||
if (header == null) throw new NullPointerException("header");
|
||||
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
public PacketBody getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public void setBody(PacketBody packetBody) {
|
||||
if (packetBody == null) throw new NullPointerException("packet");
|
||||
|
||||
this.body = packetBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the existing data out to the specified byte buffer
|
||||
* @param buffer Buffer to write to
|
||||
*/
|
||||
public ByteBuffer write(ByteBuffer buffer) {
|
||||
writeHeader(buffer);
|
||||
writeBody(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public ByteBuffer writeHeader(ByteBuffer buffer) {
|
||||
if (getHeader() == null) throw new NullPointerException("header");
|
||||
|
||||
if (getBody() != null)
|
||||
getHeader().setType(getBody().getType());
|
||||
|
||||
getHeader().write(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public ByteBuffer writeBody(ByteBuffer buffer) {
|
||||
if (getBody() == null) throw new NullPointerException("packet");
|
||||
|
||||
getBody().write(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads data from the specified byte buffer, filling in the packet body as needed.
|
||||
* @param buffer Buffer to read from.
|
||||
*/
|
||||
public ByteBuffer read(ByteBuffer buffer) {
|
||||
readHeader(buffer);
|
||||
readBody(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public ByteBuffer readHeader(ByteBuffer buffer) {
|
||||
if (getHeader() == null) {
|
||||
try {
|
||||
setHeader(getRole().getHeaderClass().newInstance());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
getHeader().read(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public ByteBuffer readBody(ByteBuffer buffer) {
|
||||
if (getBody() == null) {
|
||||
try {
|
||||
setBody(
|
||||
getHeader().getType().getPacketClass()
|
||||
.getConstructor(ProtocolRole.class)
|
||||
.newInstance(getHeader().getRole())
|
||||
);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
getBody().read(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of this packet. This method is generally only used when writing a fully defined packet.
|
||||
* @return packet size.
|
||||
*/
|
||||
public int getSize() {
|
||||
return (getHeader() == null ? 0 : getHeader().getSize()) + (getBody() == null ? 0 : getBody().getSize());
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.github.manevolent.ts3j.protocol;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public abstract class RemoteEndpoint implements Endpoint {
|
||||
public abstract InetSocketAddress getRemoteAddress();
|
||||
}
|
||||
+14
-9
@@ -1,29 +1,34 @@
|
||||
|
||||
package com.github.manevolent.ts3j.protocol.client;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.client.LocalClientHandler;
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.client.LocalClientHandlerConnected;
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.client.LocalClientHandlerConnecting;
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.client.LocalClientHandlerDisconnected;
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.PacketHandler;
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.local.LocalClientHandler;
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.local.LocalClientHandlerConnected;
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.local.LocalClientHandlerConnecting;
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.local.LocalClientHandlerDisconnected;
|
||||
import com.github.manevolent.ts3j.protocol.socket.client.AbstractTeamspeakClientSocket;
|
||||
import com.github.manevolent.ts3j.protocol.socket.client.TeamspeakClientSocket;
|
||||
import org.apache.commons.lang.reflect.ConstructorUtils;
|
||||
|
||||
public enum ClientConnectionState {
|
||||
CONNECTING(LocalClientHandlerConnecting.class),
|
||||
NEGOTIATING(LocalClientHandlerConnecting.class),
|
||||
CONNECTED(LocalClientHandlerConnected.class),
|
||||
DISCONNECTED(LocalClientHandlerDisconnected.class);
|
||||
|
||||
private final Class<? extends LocalClientHandler> clazz;
|
||||
private final Class<? extends PacketHandler> clazz;
|
||||
|
||||
ClientConnectionState(Class<? extends LocalClientHandler> clazz) {
|
||||
ClientConnectionState(Class<? extends PacketHandler> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
public Class<? extends LocalClientHandler> getHandlerClass() {
|
||||
public Class<? extends PacketHandler> getHandlerClass() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
public LocalClientHandler createHandler(LocalTeamspeakClient client) {
|
||||
public PacketHandler createHandler(TeamspeakClientSocket client) {
|
||||
try {
|
||||
return clazz.getConstructor(LocalTeamspeakClient.class).newInstance(client);
|
||||
return (PacketHandler) ConstructorUtils.invokeConstructor(clazz, client);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
@@ -1,218 +0,0 @@
|
||||
package com.github.manevolent.ts3j.protocol.client;
|
||||
|
||||
import com.github.manevolent.ts3j.command.Command;
|
||||
import com.github.manevolent.ts3j.command.response.CommandResponse;
|
||||
import com.github.manevolent.ts3j.protocol.LocalEndpoint;
|
||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||
import com.github.manevolent.ts3j.protocol.SocketRole;
|
||||
import com.github.manevolent.ts3j.protocol.header.ClientPacketHeader;
|
||||
import com.github.manevolent.ts3j.protocol.header.HeaderFlag;
|
||||
import com.github.manevolent.ts3j.protocol.packet.Packet6Ack;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketType;
|
||||
import com.github.manevolent.ts3j.protocol.packet.channel.PacketChannel;
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.client.LocalClientHandler;
|
||||
import com.github.manevolent.ts3j.protocol.packet.transformation.PacketTransformation;
|
||||
import com.github.manevolent.ts3j.protocol.socket.LocalTeamspeakSocket;
|
||||
import com.github.manevolent.ts3j.protocol.packet.Packet;
|
||||
import com.github.manevolent.ts3j.util.Ts3Crypt;
|
||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public class LocalTeamspeakClient extends LocalEndpoint implements TeamspeakClient {
|
||||
private final Object connectionStateLock = new Object();
|
||||
private final Map<String, Object> clientOptions = new LinkedHashMap<>();
|
||||
private final PacketChannel channel = new PacketChannel();
|
||||
|
||||
private ClientConnectionState connectionState = null;
|
||||
|
||||
private int packetId = 0;
|
||||
private int clientId = 0;
|
||||
private int returnCodeIndex = 0;
|
||||
|
||||
public LocalTeamspeakClient(LocalTeamspeakSocket socket) {
|
||||
super(socket);
|
||||
|
||||
if (socket.getSocketRole() != SocketRole.CLIENT)
|
||||
throw new IllegalArgumentException("invalid socket role: " + socket.getSocketRole().name());
|
||||
|
||||
setConnectionState(ClientConnectionState.DISCONNECTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketRole getRole() {
|
||||
return SocketRole.CLIENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void acknowledge(NetworkPacket packet, PacketType ackType) throws IOException {
|
||||
switch (ackType) {
|
||||
case ACK:
|
||||
Ts3Logging.debug("ACK " + packet.getHeader().getType().name());
|
||||
|
||||
Packet6Ack ack = new Packet6Ack(ProtocolRole.CLIENT);
|
||||
ack.setPacketId(packet.getHeader().getPacketId());
|
||||
send(ack);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Just return the client's single channel (to the server) here
|
||||
@Override
|
||||
protected PacketChannel getChannel(NetworkPacket packet) {
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getOptions() {
|
||||
return clientOptions;
|
||||
}
|
||||
|
||||
public void setSecureParameters(Ts3Crypt.SecureChannelParameters parameters) {
|
||||
getSocket().setPacketTransformation(new PacketTransformation(parameters.getIvStruct()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a command to the remote server.
|
||||
* @param command Command to send.
|
||||
* @return Command response object to track command response.
|
||||
* @throws IOException
|
||||
*/
|
||||
public CommandResponse sendCommand(Command command) throws IOException {
|
||||
if (command.willExpectResponse() && !isConnected())
|
||||
throw new IOException("not connected");
|
||||
|
||||
CommandResponse response;
|
||||
|
||||
if (command.willExpectResponse()) {
|
||||
int returnCodeIndex = ++ this.returnCodeIndex;
|
||||
//command.appendParameter(new CommandSingleParameter("return_code", returnCodeIndex));
|
||||
response = new CommandResponse(command, returnCodeIndex);
|
||||
} else {
|
||||
response = new CommandResponse(command, -1);
|
||||
}
|
||||
|
||||
String commandString = command.toString();
|
||||
|
||||
response.setDispatchedTime(System.currentTimeMillis());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public LocalTeamspeakClient() throws SocketException {
|
||||
this(new LocalTeamspeakSocket(SocketRole.CLIENT));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet to the server
|
||||
* @param packet Packet to send
|
||||
* @throws IOException
|
||||
*/
|
||||
int z = 0;
|
||||
public void send(Packet packet) throws IOException {
|
||||
// Construct header
|
||||
ClientPacketHeader header = new ClientPacketHeader();
|
||||
|
||||
header.setClientId(clientId);
|
||||
header.setPacketId(packetId);
|
||||
|
||||
packet.setHeaderValues(header);
|
||||
|
||||
if (connectionState == ClientConnectionState.CONNECTING) {
|
||||
header.setPacketId(z++);
|
||||
header.setPacketFlag(HeaderFlag.NEW_PROTOCOL, true);
|
||||
}
|
||||
|
||||
getSocket().send(header, packet);
|
||||
}
|
||||
|
||||
public void setConnectionState(ClientConnectionState state) {
|
||||
synchronized (connectionStateLock) {
|
||||
if (state != this.connectionState) {
|
||||
Ts3Logging.debug("State changing: " + state.name());
|
||||
|
||||
boolean wasDisconnected = this.connectionState == ClientConnectionState.DISCONNECTED;
|
||||
|
||||
this.connectionState = state;
|
||||
|
||||
if (wasDisconnected)
|
||||
setRunning(true);
|
||||
|
||||
if (channel.getHandler() != null)
|
||||
((LocalClientHandler)channel.getHandler()).handleConnectionStateChanging(state);
|
||||
|
||||
if (channel.getHandler() == null || channel.getHandler().getClass() != state.getHandlerClass()) {
|
||||
Ts3Logging.debug("Assigning " + state.getHandlerClass().getName() + " handler...");
|
||||
|
||||
try {
|
||||
channel.setHandler(state.createHandler(this));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.connectionState == ClientConnectionState.DISCONNECTED)
|
||||
clientOptions.clear();
|
||||
|
||||
connectionStateLock.notifyAll();
|
||||
|
||||
Ts3Logging.debug("State changed: " + state.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ClientConnectionState getConnectionState() {
|
||||
return connectionState;
|
||||
}
|
||||
|
||||
private void joinConnectionState(ClientConnectionState state, long wait)
|
||||
throws InterruptedException, TimeoutException {
|
||||
synchronized (connectionStateLock) {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
while (connectionState != state && System.currentTimeMillis() - start < wait) {
|
||||
connectionStateLock.wait(Math.max(0L, wait - (System.currentTimeMillis() - start)));
|
||||
}
|
||||
|
||||
if (connectionState != state)
|
||||
throw new TimeoutException("timeout waiting for " + state.name() + " state");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return connectionState == ClientConnectionState.CONNECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a connection to a server
|
||||
* @param remote remote sever to contact
|
||||
* @param password server password
|
||||
* @param timeout timeout, in milliseconds, to complete a connection.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void connect(InetSocketAddress remote, String password, long timeout) throws IOException {
|
||||
try {
|
||||
if (connectionState != ClientConnectionState.DISCONNECTED)
|
||||
throw new IllegalStateException(connectionState.name());
|
||||
|
||||
setOption("client.hostname", remote.getHostString());
|
||||
setOption("client.password", password);
|
||||
|
||||
getSocket().connect(remote);
|
||||
|
||||
setConnectionState(ClientConnectionState.CONNECTING);
|
||||
joinConnectionState(ClientConnectionState.CONNECTED, timeout);
|
||||
} catch (Throwable e) {
|
||||
setConnectionState(ClientConnectionState.DISCONNECTED);
|
||||
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.github.manevolent.ts3j.protocol.client;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.Endpoint;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public interface TeamspeakClient extends Endpoint {
|
||||
void connect(InetSocketAddress remote, String password, long timeout) throws IOException;
|
||||
|
||||
SocketAddress getRemoteAddress();
|
||||
|
||||
boolean isConnected();
|
||||
|
||||
Map<String, Object> getOptions();
|
||||
|
||||
default <T extends Object> T getOption(String key, Class<T> clazz) {
|
||||
Object value = getOptions().get(key);
|
||||
|
||||
if (value == null) return (T) null;
|
||||
|
||||
else if (!value.getClass().isAssignableFrom(clazz)) {
|
||||
throw new ClassCastException(
|
||||
clazz.getName()
|
||||
+ " is not assignable to option type " +
|
||||
value.getClass().getName()
|
||||
);
|
||||
} else
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
default Object setOption(String key, Object value) {
|
||||
return getOptions().put(key, value);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.github.manevolent.ts3j.protocol.header;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketType;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketBodyType;
|
||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -44,7 +44,7 @@ public class ClientPacketHeader extends PacketHeader {
|
||||
setClientId((buffer.getShort() & 0xFFFF));
|
||||
|
||||
byte packetTypeAndFlags = buffer.get();
|
||||
setType(PacketType.fromId((packetTypeAndFlags & 0x0F))); // PT / Packet Type + Flags
|
||||
setType(PacketBodyType.fromId((packetTypeAndFlags & 0x0F))); // PT / Packet Type + Flags
|
||||
setPacketFlags(packetTypeAndFlags & 0xF0);
|
||||
|
||||
// (payload) -- see writeBody impl in higher levels
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.github.manevolent.ts3j.protocol.header;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketType;
|
||||
import com.github.manevolent.ts3j.protocol.packet.Packet;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketBodyType;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketBody;
|
||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -13,7 +13,7 @@ public abstract class PacketHeader {
|
||||
private int packetGeneration;
|
||||
private byte[] mac = new byte[8]; // 8 bytes
|
||||
private int packetId = 0;
|
||||
private PacketType type;
|
||||
private PacketBodyType type;
|
||||
private int packetFlags;
|
||||
|
||||
protected PacketHeader(ProtocolRole role) {
|
||||
@@ -44,11 +44,11 @@ public abstract class PacketHeader {
|
||||
}
|
||||
}
|
||||
|
||||
public PacketType getType() {
|
||||
public PacketBodyType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(PacketType type) {
|
||||
public void setType(PacketBodyType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public abstract class PacketHeader {
|
||||
}
|
||||
|
||||
public void setMac(byte[] mac) {
|
||||
Packet.assertArray("mac", mac, 8);
|
||||
PacketBody.assertArray("mac", mac, 8);
|
||||
|
||||
this.mac = mac;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.github.manevolent.ts3j.protocol.header;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketType;
|
||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketBodyType;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@@ -40,7 +39,7 @@ public class ServerPacketHeader extends PacketHeader {
|
||||
|
||||
// https://github.com/ReSpeak/tsdeclarations/blob/master/ts3protocol.md
|
||||
byte packetTypeAndFlags = buffer.get();
|
||||
setType(PacketType.fromId((packetTypeAndFlags & 0x0F))); // PT / Packet Type + Flags
|
||||
setType(PacketBodyType.fromId((packetTypeAndFlags & 0x0F))); // PT / Packet Type + Flags
|
||||
setPacketFlags(packetTypeAndFlags & 0xF0);
|
||||
// (payload) -- see writeBody impl in higher levels
|
||||
|
||||
|
||||
+4
-4
@@ -5,16 +5,16 @@ import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public abstract class Packet {
|
||||
private final PacketType type;
|
||||
public abstract class PacketBody {
|
||||
private final PacketBodyType type;
|
||||
private final ProtocolRole role;
|
||||
|
||||
protected Packet(PacketType type, ProtocolRole role) {
|
||||
protected PacketBody(PacketBodyType type, ProtocolRole role) {
|
||||
this.type = type;
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public PacketType getType() {
|
||||
public PacketBodyType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
+3
-4
@@ -2,16 +2,15 @@ package com.github.manevolent.ts3j.protocol.packet;
|
||||
|
||||
import com.github.manevolent.ts3j.command.SimpleCommand;
|
||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class Packet2Command extends Packet {
|
||||
public class PacketBody2Command extends PacketBody {
|
||||
private String text;
|
||||
|
||||
public Packet2Command(ProtocolRole role) {
|
||||
super(PacketType.COMMAND, role);
|
||||
public PacketBody2Command(ProtocolRole role) {
|
||||
super(PacketBodyType.COMMAND, role);
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
+9
-4
@@ -3,13 +3,18 @@ package com.github.manevolent.ts3j.protocol.packet;
|
||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class Packet6Ack extends Packet {
|
||||
public class PacketBody6Ack extends PacketBody {
|
||||
private int packetId;
|
||||
|
||||
public Packet6Ack(ProtocolRole role) {
|
||||
super(PacketType.ACK, role);
|
||||
public PacketBody6Ack(ProtocolRole role) {
|
||||
super(PacketBodyType.ACK, role);
|
||||
}
|
||||
|
||||
public PacketBody6Ack(ProtocolRole role, int packetId) {
|
||||
super(PacketBodyType.ACK, role);
|
||||
|
||||
setPacketId(packetId);
|
||||
}
|
||||
|
||||
public int getPacketId() {
|
||||
+16
-16
@@ -7,9 +7,9 @@ import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import static com.github.manevolent.ts3j.protocol.packet.PacketType.INIT1;
|
||||
import static com.github.manevolent.ts3j.protocol.packet.PacketBodyType.INIT1;
|
||||
|
||||
public class Packet8Init1 extends Packet {
|
||||
public class PacketBody8Init1 extends PacketBody {
|
||||
private static final byte[] LOW_LEVEL_MAC = new byte[] {
|
||||
0x54, 0x53, 0x33, 0x49, 0x4E, 0x49, 0x54, 0x31
|
||||
};
|
||||
@@ -17,7 +17,7 @@ public class Packet8Init1 extends Packet {
|
||||
private byte[] version = new byte[4]; // 4 bytes
|
||||
private Step step; // step instance
|
||||
|
||||
public Packet8Init1(ProtocolRole role) {
|
||||
public PacketBody8Init1(ProtocolRole role) {
|
||||
// sec. 2 of https://github.com/ReSpeak/tsdeclarations/blob/master/ts3protocol.md
|
||||
// (The packet header values are set as following for all packets here:)
|
||||
super(INIT1, role);
|
||||
@@ -152,7 +152,7 @@ public class Packet8Init1 extends Packet {
|
||||
}
|
||||
|
||||
public void setTimestamp(byte[] timestamp) {
|
||||
Packet.assertArray("timestamp", timestamp, 4);
|
||||
PacketBody.assertArray("timestamp", timestamp, 4);
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ public class Packet8Init1 extends Packet {
|
||||
}
|
||||
|
||||
public void setRandom(byte[] random) {
|
||||
Packet.assertArray("random", random, 4);
|
||||
PacketBody.assertArray("random", random, 4);
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ public class Packet8Init1 extends Packet {
|
||||
}
|
||||
|
||||
public void setA0reversed(byte[] a0reversed) {
|
||||
Packet.assertArray("a0reversed", a0reversed, 4);
|
||||
PacketBody.assertArray("a0reversed", a0reversed, 4);
|
||||
this.a0reversed = a0reversed;
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ public class Packet8Init1 extends Packet {
|
||||
}
|
||||
|
||||
public void setServerStuff(byte[] serverStuff) {
|
||||
Packet.assertArray("serverStuff", serverStuff, 16);
|
||||
PacketBody.assertArray("serverStuff", serverStuff, 16);
|
||||
this.serverStuff = serverStuff;
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ public class Packet8Init1 extends Packet {
|
||||
}
|
||||
|
||||
public void setA0reversed(byte[] a0reversed) {
|
||||
Packet.assertArray("a0reversed", a0reversed, 4);
|
||||
PacketBody.assertArray("a0reversed", a0reversed, 4);
|
||||
this.a0reversed = a0reversed;
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ public class Packet8Init1 extends Packet {
|
||||
}
|
||||
|
||||
public void setServerStuff(byte[] serverStuff) {
|
||||
Packet.assertArray("serverStuff", serverStuff, 16);
|
||||
PacketBody.assertArray("serverStuff", serverStuff, 16);
|
||||
this.serverStuff = serverStuff;
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ public class Packet8Init1 extends Packet {
|
||||
}
|
||||
|
||||
public void setX(byte[] x) {
|
||||
Packet.assertArray("x", x, 64);
|
||||
PacketBody.assertArray("x", x, 64);
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ public class Packet8Init1 extends Packet {
|
||||
}
|
||||
|
||||
public void setN(byte[] n) {
|
||||
Packet.assertArray("n", n, 64);
|
||||
PacketBody.assertArray("n", n, 64);
|
||||
this.n = n;
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ public class Packet8Init1 extends Packet {
|
||||
}
|
||||
|
||||
public void setServerStuff(byte[] serverStuff) {
|
||||
Packet.assertArray("serverStuff", serverStuff, 100);
|
||||
PacketBody.assertArray("serverStuff", serverStuff, 100);
|
||||
this.serverStuff = serverStuff;
|
||||
}
|
||||
|
||||
@@ -359,7 +359,7 @@ public class Packet8Init1 extends Packet {
|
||||
}
|
||||
|
||||
public void setX(byte[] x) {
|
||||
Packet.assertArray("x", x, 64);
|
||||
PacketBody.assertArray("x", x, 64);
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
@@ -368,7 +368,7 @@ public class Packet8Init1 extends Packet {
|
||||
}
|
||||
|
||||
public void setN(byte[] n) {
|
||||
Packet.assertArray("n", n, 64);
|
||||
PacketBody.assertArray("n", n, 64);
|
||||
this.n = n;
|
||||
}
|
||||
|
||||
@@ -377,7 +377,7 @@ public class Packet8Init1 extends Packet {
|
||||
}
|
||||
|
||||
public void setServerStuff(byte[] serverStuff) {
|
||||
Packet.assertArray("serverStuff", serverStuff, 100);
|
||||
PacketBody.assertArray("serverStuff", serverStuff, 100);
|
||||
this.serverStuff = serverStuff;
|
||||
}
|
||||
|
||||
@@ -394,7 +394,7 @@ public class Packet8Init1 extends Packet {
|
||||
}
|
||||
|
||||
public void setY(byte[] y) {
|
||||
Packet.assertArray("y", y, 64);
|
||||
PacketBody.assertArray("y", y, 64);
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PacketBodyFragment extends PacketBody {
|
||||
private byte[] raw;
|
||||
|
||||
public PacketBodyFragment(PacketBodyType type, ProtocolRole role) {
|
||||
super(type, role);
|
||||
}
|
||||
|
||||
public byte[] getRaw() {
|
||||
return raw;
|
||||
}
|
||||
|
||||
public void setRaw(byte[] raw) {
|
||||
this.raw = raw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ByteBuffer buffer) {
|
||||
raw = new byte[buffer.remaining()];
|
||||
buffer.get(raw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ByteBuffer buffer) {
|
||||
buffer.put(raw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return raw.length;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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),
|
||||
ACK(0x6, PacketBody6Ack.class, null, true, true, false, false, true),
|
||||
ACK_LOW(0x7, null, 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(0x2, PacketBody2Command.class, ACK, true, true, true, true, true);
|
||||
|
||||
private final int index;
|
||||
private final Class<? extends PacketBody> packetClass;
|
||||
|
||||
private final PacketBodyType acknolwedgedBy;
|
||||
private final boolean encrypted, resend, splittable, compressible, mustEncrypt;
|
||||
|
||||
PacketBodyType(int index, Class<? extends PacketBody> packetClass,
|
||||
PacketBodyType acknolwedgedBy,
|
||||
boolean encrypted,
|
||||
boolean resend,
|
||||
boolean splittable,
|
||||
boolean compressible, boolean mustEncrypt) {
|
||||
this.index = index;
|
||||
this.packetClass = packetClass;
|
||||
this.acknolwedgedBy = acknolwedgedBy;
|
||||
this.resend = resend;
|
||||
this.encrypted = encrypted;
|
||||
this.splittable = splittable;
|
||||
this.compressible = compressible;
|
||||
this.mustEncrypt = mustEncrypt;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static PacketBodyType fromId(int index) {
|
||||
for (PacketBodyType value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
|
||||
public Class<? extends PacketBody> getPacketClass() {
|
||||
return packetClass;
|
||||
}
|
||||
|
||||
public boolean canEncrypt() {
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
public PacketBodyType getAcknolwedgedBy() {
|
||||
return acknolwedgedBy;
|
||||
}
|
||||
|
||||
public boolean canResend() {
|
||||
return resend;
|
||||
}
|
||||
|
||||
public boolean isSplittable() {
|
||||
return splittable;
|
||||
}
|
||||
|
||||
public boolean isCompressible() {
|
||||
return compressible;
|
||||
}
|
||||
|
||||
public boolean mustEncrypt() {
|
||||
return mustEncrypt;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class PacketFragment extends Packet {
|
||||
private byte[] fragment;
|
||||
|
||||
public PacketFragment( PacketType type, ProtocolRole role) {
|
||||
super(type, role);
|
||||
}
|
||||
|
||||
public byte[] getFragment() {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public void setFragment(byte[] fragment) {
|
||||
this.fragment = fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ByteBuffer buffer) {
|
||||
fragment = new byte[buffer.remaining()];
|
||||
buffer.get(fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ByteBuffer buffer) {
|
||||
buffer.put(fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return fragment.length;
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet;
|
||||
|
||||
public enum PacketType {
|
||||
VOICE(0x0, null, null, false, false, false, false),
|
||||
VOICE_WHISPER(0x1, null, null, false, false, false, false),
|
||||
PONG(0x5, null, null, false, false, false, false),
|
||||
PING(0x4, null, PONG, false, false, false, false),
|
||||
ACK(0x6, Packet6Ack.class, null, true, true, false, false),
|
||||
ACK_LOW(0x7, null, null, true, true, false, false),
|
||||
INIT1(0x8, Packet8Init1.class, null, false, true, false, false),
|
||||
COMMAND_LOW(0x3, null, ACK_LOW, true, true, true, true),
|
||||
COMMAND(0x2, Packet2Command.class, ACK, true, true, true, true);
|
||||
|
||||
private final int index;
|
||||
private final Class<? extends Packet> packetClass;
|
||||
|
||||
private final PacketType acknolwedgedBy;
|
||||
private final boolean encrypted, resend, splittable, compressible;
|
||||
|
||||
PacketType(int index, Class<? extends Packet> packetClass,
|
||||
PacketType acknolwedgedBy,
|
||||
boolean encrypted,
|
||||
boolean resend,
|
||||
boolean splittable,
|
||||
boolean compressible) {
|
||||
this.index = index;
|
||||
this.packetClass = packetClass;
|
||||
this.acknolwedgedBy = acknolwedgedBy;
|
||||
this.resend = resend;
|
||||
this.encrypted = encrypted;
|
||||
this.splittable = splittable;
|
||||
this.compressible = compressible;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static PacketType fromId(int index) {
|
||||
for (PacketType value : values())
|
||||
if (value.getIndex() == index) return value;
|
||||
|
||||
throw new IllegalArgumentException("invalid index: " + index);
|
||||
}
|
||||
|
||||
public Class<? extends Packet> getPacketClass() {
|
||||
return packetClass;
|
||||
}
|
||||
|
||||
public boolean isEncrypted() {
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
public PacketType getAcknolwedgedBy() {
|
||||
return acknolwedgedBy;
|
||||
}
|
||||
|
||||
public boolean canResend() {
|
||||
return resend;
|
||||
}
|
||||
|
||||
public boolean isSplittable() {
|
||||
return splittable;
|
||||
}
|
||||
|
||||
public boolean isCompressible() {
|
||||
return compressible;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet.channel;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||
import com.github.manevolent.ts3j.protocol.header.HeaderFlag;
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.PacketHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public final class PacketChannel {
|
||||
private final Queue<NetworkPacket> receive = new ConcurrentLinkedQueue<>();
|
||||
private final ReassemblyQueue reassemblyQueue = new ReassemblyQueue();
|
||||
|
||||
private PacketHandler handler;
|
||||
|
||||
/**
|
||||
* Inserts a packet into the receive queue.
|
||||
* @param networkPacket Packet to insert.
|
||||
*/
|
||||
public void insert(NetworkPacket networkPacket) {
|
||||
if ((networkPacket = reassemblyQueue.put(networkPacket)) != null)
|
||||
receive.add(networkPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the next packet in the receive queue available for dequeue.
|
||||
* @return Network packet, null otherwise
|
||||
*/
|
||||
public NetworkPacket next() {
|
||||
try {
|
||||
return receive.remove();
|
||||
} catch (NoSuchElementException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public PacketHandler getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
public void setHandler(PacketHandler handler) throws IOException {
|
||||
if (this.handler != handler) {
|
||||
if (this.handler != null) this.handler.onUnassigning();
|
||||
this.handler = handler;
|
||||
if (handler != null) handler.onAssigned();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet.channel;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||
import com.github.manevolent.ts3j.protocol.header.HeaderFlag;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketFragment;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketType;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
|
||||
public class ReassemblyQueue {
|
||||
private final Map<Integer, NetworkPacket> queue = new LinkedHashMap<>();
|
||||
private final Map<PacketType, Boolean> fragmentation = new LinkedHashMap<>();
|
||||
|
||||
public NetworkPacket reassemble(NetworkPacket lastFragment) {
|
||||
// Pull out all other packets in the queue before this one which would also be fragmented
|
||||
List<NetworkPacket> reassemblyList = new ArrayList<>();
|
||||
for (int packetId = lastFragment.getHeader().getPacketId();; packetId--) {
|
||||
NetworkPacket olderPacket = queue.get(packetId);
|
||||
|
||||
if (olderPacket == null)
|
||||
break; // Chain breaks here
|
||||
else if (olderPacket.getHeader().getType() != lastFragment.getHeader().getType())
|
||||
continue; // skip
|
||||
else {
|
||||
reassemblyList.add(olderPacket);
|
||||
fragmentation.remove(packetId); // Don't reuse this packet
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse the collection
|
||||
Collections.reverse(reassemblyList);
|
||||
|
||||
// Rebuild a master packet from the contents of all previous packet fragments
|
||||
int totalLength = reassemblyList.stream().mapToInt(x -> x.getPacket().getSize()).sum() +
|
||||
lastFragment.getPacket().getSize();
|
||||
|
||||
if (totalLength <= 0) throw new IllegalArgumentException("reassembly too small: " + totalLength);
|
||||
|
||||
ByteBuffer reassemblyBuffer = ByteBuffer.allocate(totalLength);
|
||||
|
||||
for (NetworkPacket old : reassemblyList)
|
||||
old.writeBody(reassemblyBuffer);
|
||||
|
||||
NetworkPacket firstPacket = reassemblyList.get(0);
|
||||
NetworkPacket reassembledPacket = new NetworkPacket(firstPacket.getRole());
|
||||
reassembledPacket.setHeader(firstPacket.getHeader());
|
||||
reassembledPacket.getHeader().setPacketFlag(HeaderFlag.FRAGMENTED, false);
|
||||
reassembledPacket.readBody((ByteBuffer) reassemblyBuffer.position(0));
|
||||
|
||||
return reassembledPacket;
|
||||
}
|
||||
|
||||
public NetworkPacket put(NetworkPacket networkPacket) {
|
||||
Boolean fragmentState = fragmentation.get(networkPacket.getHeader().getType());
|
||||
|
||||
if (networkPacket.getHeader().getPacketFlag(HeaderFlag.FRAGMENTED)) {
|
||||
if (!(networkPacket.getPacket() instanceof PacketFragment))
|
||||
throw new IllegalArgumentException("packet fragment object is not instance of PacketFragment class");
|
||||
|
||||
boolean fragmentationEnded = false;
|
||||
|
||||
if (fragmentState == null)
|
||||
fragmentation.put(networkPacket.getHeader().getType(), fragmentState = true);
|
||||
else if (fragmentState) {
|
||||
// Fragmentation end
|
||||
fragmentationEnded = true;
|
||||
|
||||
fragmentation.put(networkPacket.getHeader().getType(), fragmentState = false);
|
||||
}
|
||||
|
||||
queue.put(networkPacket.getHeader().getPacketId(), networkPacket);
|
||||
|
||||
if (fragmentationEnded)
|
||||
return reassemble(networkPacket);
|
||||
|
||||
return null;
|
||||
} else {
|
||||
if (fragmentState != null && fragmentState) {
|
||||
queue.put(networkPacket.getHeader().getPacketId(), networkPacket);
|
||||
|
||||
return null;
|
||||
} else {
|
||||
return networkPacket;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-9
@@ -1,30 +1,31 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet.handler;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||
import com.github.manevolent.ts3j.protocol.LocalEndpoint;
|
||||
import com.github.manevolent.ts3j.protocol.Packet;
|
||||
import com.github.manevolent.ts3j.protocol.socket.client.TeamspeakClientSocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public abstract class PacketHandler {
|
||||
private final LocalEndpoint endpoint;
|
||||
private final TeamspeakClientSocket clientSocket;
|
||||
|
||||
protected PacketHandler(LocalEndpoint endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
protected PacketHandler(TeamspeakClientSocket clientSocket) {
|
||||
this.clientSocket = clientSocket;
|
||||
}
|
||||
|
||||
public void onUnassigning() throws IOException {
|
||||
|
||||
}
|
||||
|
||||
public void onAssigned() throws IOException {
|
||||
public void onAssigned() throws IOException, TimeoutException {
|
||||
|
||||
}
|
||||
|
||||
public void handlePacket(NetworkPacket packet) throws IOException {
|
||||
public void handlePacket(Packet packet) throws IOException, TimeoutException {
|
||||
|
||||
}
|
||||
|
||||
public LocalEndpoint getEndpoint() {
|
||||
return endpoint;
|
||||
public TeamspeakClientSocket getClient() {
|
||||
return clientSocket;
|
||||
}
|
||||
}
|
||||
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet.handler.client;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.client.LocalTeamspeakClient;
|
||||
|
||||
public class LocalClientHandlerConnected extends LocalClientHandler {
|
||||
public LocalClientHandlerConnected(LocalTeamspeakClient client) {
|
||||
super(client);
|
||||
}
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet.handler.client;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.client.LocalTeamspeakClient;
|
||||
|
||||
public class LocalClientHandlerDisconnected extends LocalClientHandler {
|
||||
public LocalClientHandlerDisconnected(LocalTeamspeakClient client) {
|
||||
super(client);
|
||||
}
|
||||
}
|
||||
+5
-7
@@ -1,16 +1,14 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet.handler.client;
|
||||
package com.github.manevolent.ts3j.protocol.packet.handler.local;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.client.LocalTeamspeakClient;
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.PacketHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
|
||||
|
||||
public abstract class LocalClientHandler extends PacketHandler {
|
||||
private final LocalTeamspeakClient client;
|
||||
private final LocalTeamspeakClientSocket client;
|
||||
|
||||
protected LocalClientHandler(LocalTeamspeakClient client) {
|
||||
protected LocalClientHandler(LocalTeamspeakClientSocket client) {
|
||||
super(client);
|
||||
|
||||
this.client = client;
|
||||
@@ -20,7 +18,7 @@ public abstract class LocalClientHandler extends PacketHandler {
|
||||
|
||||
}
|
||||
|
||||
protected LocalTeamspeakClient getClient() {
|
||||
public LocalTeamspeakClientSocket getClient() {
|
||||
return client;
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet.handler.local;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
|
||||
|
||||
public class LocalClientHandlerConnected extends LocalClientHandler {
|
||||
public LocalClientHandlerConnected(LocalTeamspeakClientSocket client) {
|
||||
super(client);
|
||||
}
|
||||
}
|
||||
+36
-36
@@ -1,14 +1,12 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet.handler.client;
|
||||
package com.github.manevolent.ts3j.protocol.packet.handler.local;
|
||||
|
||||
import com.github.manevolent.ts3j.command.SimpleCommand;
|
||||
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
|
||||
import com.github.manevolent.ts3j.license.License;
|
||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||
import com.github.manevolent.ts3j.protocol.Packet;
|
||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
|
||||
import com.github.manevolent.ts3j.protocol.client.LocalTeamspeakClient;
|
||||
import com.github.manevolent.ts3j.protocol.packet.Packet2Command;
|
||||
import com.github.manevolent.ts3j.protocol.packet.Packet8Init1;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketBody2Command;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketBody8Init1;
|
||||
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
|
||||
import com.github.manevolent.ts3j.util.Ts3Crypt;
|
||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||
import javafx.util.Pair;
|
||||
@@ -16,27 +14,26 @@ import org.bouncycastle.math.ec.ECPoint;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||
private byte[] randomBytes;
|
||||
private byte[] alphaBytes;
|
||||
|
||||
public LocalClientHandlerConnecting(LocalTeamspeakClient client) {
|
||||
public LocalClientHandlerConnecting(LocalTeamspeakClientSocket client) {
|
||||
super(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssigned() throws IOException {
|
||||
Packet8Init1 packet = new Packet8Init1(ProtocolRole.CLIENT);
|
||||
public void onAssigned() throws IOException, TimeoutException {
|
||||
PacketBody8Init1 packet = new PacketBody8Init1(ProtocolRole.CLIENT);
|
||||
|
||||
// Initialize connection
|
||||
|
||||
Packet8Init1.Step0 step = new Packet8Init1.Step0();
|
||||
PacketBody8Init1.Step0 step = new PacketBody8Init1.Step0();
|
||||
Random random = new Random();
|
||||
randomBytes = new byte[4];
|
||||
random.nextBytes(randomBytes);
|
||||
@@ -47,7 +44,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||
sendInit1(packet);
|
||||
}
|
||||
|
||||
private void sendInit1(Packet8Init1 packet) throws IOException {
|
||||
private void sendInit1(PacketBody8Init1 packet) throws IOException, TimeoutException {
|
||||
Ts3Logging.debug("Connecting: sending " + packet.getClass().getSimpleName()
|
||||
+ ":"
|
||||
+ packet.getStep().getClass().getSimpleName() + "..."
|
||||
@@ -55,19 +52,19 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||
|
||||
packet.setVersion(new byte[] { 0x09, (byte)0x83, (byte)0x8C, (byte)0xCF });
|
||||
|
||||
getClient().send(packet);
|
||||
getClient().writePacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePacket(NetworkPacket packet) throws IOException {
|
||||
Ts3Logging.debug("Connecting: handling " + packet.getPacket().getClass().getSimpleName());
|
||||
public void handlePacket(Packet packet) throws IOException, TimeoutException {
|
||||
Ts3Logging.debug("Connecting: handling " + packet.getBody().getClass().getSimpleName());
|
||||
|
||||
if (packet.getPacket() instanceof Packet8Init1) {
|
||||
Packet8Init1 init1 = (Packet8Init1) packet.getPacket();
|
||||
if (packet.getBody() instanceof PacketBody8Init1) {
|
||||
PacketBody8Init1 init1 = (PacketBody8Init1) packet.getBody();
|
||||
|
||||
switch (init1.getStep().getNumber()) {
|
||||
case 1:
|
||||
Packet8Init1.Step1 serverReplyStep1 = (Packet8Init1.Step1)init1.getStep();
|
||||
PacketBody8Init1.Step1 serverReplyStep1 = (PacketBody8Init1.Step1)init1.getStep();
|
||||
// Check nonce. It's received backwards, so walk backwards over the array received
|
||||
for (int i = 0; i < 4; i ++) {
|
||||
if (randomBytes[3 - i] != serverReplyStep1.getA0reversed()[i])
|
||||
@@ -75,9 +72,9 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||
}
|
||||
|
||||
// Build response
|
||||
Packet8Init1 response2 = new Packet8Init1(ProtocolRole.CLIENT);
|
||||
PacketBody8Init1 response2 = new PacketBody8Init1(ProtocolRole.CLIENT);
|
||||
|
||||
Packet8Init1.Step2 step2 = new Packet8Init1.Step2();
|
||||
PacketBody8Init1.Step2 step2 = new PacketBody8Init1.Step2();
|
||||
step2.setA0reversed(serverReplyStep1.getA0reversed());
|
||||
step2.setServerStuff(serverReplyStep1.getServerStuff());
|
||||
response2.setStep(step2);
|
||||
@@ -85,7 +82,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||
sendInit1(response2);
|
||||
break;
|
||||
case 3:
|
||||
Packet8Init1.Step3 serverReplyStep3 = (Packet8Init1.Step3)init1.getStep();
|
||||
PacketBody8Init1.Step3 serverReplyStep3 = (PacketBody8Init1.Step3)init1.getStep();
|
||||
|
||||
// Calculate 'y'
|
||||
// which is the result of x ^ (2 ^ level) % n as an unsigned
|
||||
@@ -113,9 +110,9 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||
);
|
||||
|
||||
// Build response
|
||||
Packet8Init1 response4 = new Packet8Init1(ProtocolRole.CLIENT);
|
||||
PacketBody8Init1 response4 = new PacketBody8Init1(ProtocolRole.CLIENT);
|
||||
|
||||
Packet8Init1.Step4 step4 = new Packet8Init1.Step4();
|
||||
PacketBody8Init1.Step4 step4 = new PacketBody8Init1.Step4();
|
||||
|
||||
step4.setLevel(serverReplyStep3.getLevel());
|
||||
step4.setX(serverReplyStep3.getX());
|
||||
@@ -130,7 +127,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||
|
||||
initiv.add(new CommandSingleParameter("alpha", Base64.getEncoder().encodeToString(alphaBytes)));
|
||||
|
||||
initiv.add(new CommandSingleParameter("omega", getClient().getLocalIdentity().getPublicKeyString()));
|
||||
initiv.add(new CommandSingleParameter("omega", getClient().getIdentity().getPublicKeyString()));
|
||||
|
||||
initiv.add(new CommandSingleParameter("ot", "1")); // constant, set to 1
|
||||
|
||||
@@ -151,8 +148,8 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||
throw new IllegalArgumentException("unexpected Init1 server step: " + init1.getStep().getNumber());
|
||||
|
||||
}
|
||||
} else if (packet.getPacket() instanceof Packet2Command) {
|
||||
SimpleCommand command = ((Packet2Command) packet.getPacket()).parse();
|
||||
} else if (packet.getBody() instanceof PacketBody2Command) {
|
||||
SimpleCommand command = ((PacketBody2Command) packet.getBody()).parse();
|
||||
|
||||
if (command.getName().equalsIgnoreCase("initivexpand2")) {
|
||||
// 3.2.2 initivexpand2 (Client <- Server)
|
||||
@@ -186,12 +183,12 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||
byte[] toSign = new byte[86];
|
||||
System.arraycopy(keyPair.getKey(), 0, toSign, 0, 32);
|
||||
System.arraycopy(beta, 0, toSign, 32, 54);
|
||||
byte[] sign = getClient().getLocalIdentity().sign(toSign);
|
||||
byte[] sign = getClient().getIdentity().sign(toSign);
|
||||
|
||||
Ts3Logging.debug("Created proof (clientek): " + Ts3Logging.getHex(sign));
|
||||
|
||||
// Send clientek (telling them the shared secret)
|
||||
Packet2Command clientek = new Packet2Command(ProtocolRole.CLIENT);
|
||||
PacketBody2Command clientek = new PacketBody2Command(ProtocolRole.CLIENT);
|
||||
clientek.setText(
|
||||
new SimpleCommand(
|
||||
"clientek", ProtocolRole.CLIENT,
|
||||
@@ -199,12 +196,14 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||
new CommandSingleParameter("proof", Base64.getEncoder().encodeToString(sign))
|
||||
).build()
|
||||
);
|
||||
getClient().send(clientek);
|
||||
getClient().writePacket(clientek);
|
||||
|
||||
Ts3Crypt.SecureChannelParameters secureChannelParameters =
|
||||
Ts3Crypt.cryptoInit2(license, alphaBytes, omega, proof, beta, keyPair.getValue());
|
||||
getClient().setSecureParameters(
|
||||
Ts3Crypt.cryptoInit2(license, alphaBytes, omega, proof, beta, keyPair.getValue())
|
||||
);
|
||||
|
||||
PacketBody2Command clientinit = new PacketBody2Command(ProtocolRole.CLIENT);
|
||||
|
||||
Packet2Command clientinit = new Packet2Command(ProtocolRole.CLIENT);
|
||||
clientinit.setText(
|
||||
new SimpleCommand(
|
||||
"clientinit", ProtocolRole.CLIENT,
|
||||
@@ -220,11 +219,12 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||
new CommandSingleParameter("client_nickname_phonetic", "test"),
|
||||
new CommandSingleParameter("client_meta_data", ""),
|
||||
new CommandSingleParameter("client_default_token", ""),
|
||||
new CommandSingleParameter("client_key_offset", Long.toString(getClient().getLocalIdentity().getKeyOffset())),
|
||||
new CommandSingleParameter("client_key_offset", Long.toString(getClient().getIdentity().getKeyOffset())),
|
||||
new CommandSingleParameter("hwid", "87056c6e1268aaf5055abf8256415e0e,408978b6d98810cc03f0aa16a4c75600")
|
||||
).build()
|
||||
);
|
||||
getClient().send(clientinit);
|
||||
|
||||
getClient().writePacket(clientinit);
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet.handler.local;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
|
||||
|
||||
public class LocalClientHandlerDisconnected extends LocalClientHandler {
|
||||
public LocalClientHandlerDisconnected(LocalTeamspeakClientSocket client) {
|
||||
super(client);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet.handler.local;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
|
||||
|
||||
public class LocalClientHandlerNegotiating extends LocalClientHandler {
|
||||
public LocalClientHandlerNegotiating(LocalTeamspeakClientSocket client) {
|
||||
super(client);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet.transformation;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||
import com.github.manevolent.ts3j.protocol.Packet;
|
||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||
@@ -72,7 +72,7 @@ public class PacketTransformation {
|
||||
return new Pair<>(key, nonce);
|
||||
}
|
||||
|
||||
public ByteBuffer encrypt(NetworkPacket packet) {
|
||||
public ByteBuffer encrypt(Packet packet) {
|
||||
Pair<byte[], byte[]> parameters = computeParameters(packet.getHeader());
|
||||
|
||||
// Write header (this is temporary)
|
||||
@@ -93,7 +93,7 @@ public class PacketTransformation {
|
||||
headerWithoutMac
|
||||
);
|
||||
|
||||
int dataLen = packet.getPacket().getSize();
|
||||
int dataLen = packet.getBody().getSize();
|
||||
ByteBuffer packetBuffer = packet.writeBody(ByteBuffer.allocate(dataLen).order(ByteOrder.BIG_ENDIAN));
|
||||
|
||||
byte[] result;
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.github.manevolent.ts3j.protocol.server;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||
import com.github.manevolent.ts3j.protocol.RemoteEndpoint;
|
||||
import com.github.manevolent.ts3j.protocol.SocketRole;
|
||||
import com.github.manevolent.ts3j.protocol.client.LocalTeamspeakClient;
|
||||
import com.github.manevolent.ts3j.protocol.packet.Packet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class RemoteTeamspeakServer extends RemoteEndpoint implements TeamspeakServer {
|
||||
private final LocalTeamspeakClient client;
|
||||
private final InetSocketAddress address;
|
||||
|
||||
public RemoteTeamspeakServer(LocalTeamspeakClient client,
|
||||
InetSocketAddress address) {
|
||||
this.client = client;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketRole getRole() {
|
||||
return SocketRole.SERVER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(Packet packet) throws IOException {
|
||||
client.send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.github.manevolent.ts3j.protocol.server;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.Endpoint;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public interface TeamspeakServer extends Endpoint {
|
||||
|
||||
|
||||
|
||||
}
|
||||
+6
-9
@@ -1,18 +1,15 @@
|
||||
package com.github.manevolent.ts3j.protocol.socket;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.packet.transformation.DefaultPacketTransformation;
|
||||
import com.github.manevolent.ts3j.protocol.packet.transformation.PacketTransformation;
|
||||
import com.github.manevolent.ts3j.protocol.SocketRole;
|
||||
|
||||
public abstract class AbstractTeamspeakSocket implements TeamspeakSocket {
|
||||
private PacketTransformation transformation = new DefaultPacketTransformation();
|
||||
private final SocketRole role;
|
||||
|
||||
@Override
|
||||
public PacketTransformation getPacketTransformation() {
|
||||
return transformation;
|
||||
protected AbstractTeamspeakSocket(SocketRole role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPacketTransformation(PacketTransformation transformation) {
|
||||
this.transformation = transformation;
|
||||
public SocketRole getRole() {
|
||||
return role;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
package com.github.manevolent.ts3j.protocol.socket;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||
import com.github.manevolent.ts3j.protocol.SocketRole;
|
||||
import com.github.manevolent.ts3j.protocol.header.HeaderFlag;
|
||||
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||
import com.github.manevolent.ts3j.protocol.packet.Packet;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketFragment;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketType;
|
||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Low-level protocol socket (not a client, used for server implementation also)
|
||||
*
|
||||
* Used simply to transcribe packets on the network
|
||||
*/
|
||||
public class LocalTeamspeakSocket extends AbstractTeamspeakSocket implements TeamspeakSocket {
|
||||
private final SocketRole socketRole;
|
||||
|
||||
private final DatagramSocket datagramSocket;
|
||||
private final DatagramPacket datagramPacket = new DatagramPacket(new byte[1500], 1500);
|
||||
|
||||
public LocalTeamspeakSocket(SocketRole socketRole, DatagramSocket datagramSocket) {
|
||||
this.socketRole = socketRole;
|
||||
this.datagramSocket = datagramSocket;
|
||||
}
|
||||
|
||||
public LocalTeamspeakSocket(SocketRole socketRole) throws SocketException {
|
||||
this(socketRole, new DatagramSocket());
|
||||
}
|
||||
|
||||
public SocketRole getSocketRole() {
|
||||
return socketRole;
|
||||
}
|
||||
|
||||
public InetSocketAddress getLocalAddress() {
|
||||
return new InetSocketAddress(datagramSocket.getLocalAddress(), datagramSocket.getLocalPort());
|
||||
}
|
||||
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
return new InetSocketAddress(datagramSocket.getInetAddress(), datagramSocket.getPort());
|
||||
}
|
||||
|
||||
public void connect(SocketAddress remote) throws SocketException {
|
||||
datagramSocket.connect(remote);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an actual datagram to the network
|
||||
* @param packet Packet to write
|
||||
* @throws IOException
|
||||
*/
|
||||
private void sendNetworkPacket(DatagramPacket packet) throws IOException {
|
||||
if (packet.getAddress() == null || packet.getPort() < 0)
|
||||
packet.setSocketAddress(datagramSocket.getRemoteSocketAddress());
|
||||
|
||||
Ts3Logging.debug(
|
||||
"[NETWORK] WRITE Len=" + packet.getLength() + " to " + packet.getSocketAddress()
|
||||
);
|
||||
|
||||
//Ts3Logging.debug(Ts3Logging.getHex(packet.getData(), packet.getLength()));
|
||||
|
||||
datagramSocket.send(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a NetworkPacket to the network, first converting it to a datagram.
|
||||
* @param header Header to send
|
||||
* @throws IOException
|
||||
*/
|
||||
public void send(PacketHeader header, Packet packet) throws IOException {
|
||||
if (header == null)
|
||||
throw new NullPointerException("header");
|
||||
else if (header.getRole() != getSocketRole().getOut())
|
||||
throw new IllegalArgumentException("packet role mismatch: " +
|
||||
header.getRole().name() + " != " +
|
||||
getSocketRole().getOut().name());
|
||||
|
||||
// Ensure type matches
|
||||
header.setType(packet.getType());
|
||||
|
||||
// Construct a network packet
|
||||
NetworkPacket networkPacket = new NetworkPacket(header.getRole());
|
||||
networkPacket.setHeader(header);
|
||||
networkPacket.setPacket(packet);
|
||||
|
||||
// Flush to a buffer
|
||||
ByteBuffer outputBuffer;
|
||||
|
||||
if (networkPacket.getHeader().getType().isEncrypted() &&
|
||||
!networkPacket.getHeader().getPacketFlag(HeaderFlag.UNENCRYPTED)) {
|
||||
outputBuffer = getPacketTransformation().encrypt(networkPacket);
|
||||
} else {
|
||||
outputBuffer = ByteBuffer.allocate(networkPacket.getSize());
|
||||
|
||||
outputBuffer.order(ByteOrder.BIG_ENDIAN);
|
||||
networkPacket.writeHeader(outputBuffer);
|
||||
networkPacket.writeBody(outputBuffer);
|
||||
}
|
||||
|
||||
Ts3Logging.debug("[PROTOCOL] WRITE " + packet.getType().name());
|
||||
|
||||
sendNetworkPacket(new DatagramPacket(outputBuffer.array(), outputBuffer.position()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a datagram from the network.
|
||||
* @return Datagram.
|
||||
* @throws IOException
|
||||
*/
|
||||
private DatagramPacket receiveNetworkPacket() throws IOException {
|
||||
datagramSocket.receive(datagramPacket);
|
||||
|
||||
Ts3Logging.debug(
|
||||
"[NETWORK] READ Len=" + datagramPacket.getLength() + " from " + datagramPacket.getSocketAddress()
|
||||
);
|
||||
|
||||
//Ts3Logging.debug(Ts3Logging.getHex(datagramPacket.getData(), datagramPacket.getLength()));
|
||||
|
||||
return datagramPacket;
|
||||
}
|
||||
|
||||
public NetworkPacket receive() throws IOException {
|
||||
while (true) {
|
||||
DatagramPacket datagramPacket = receiveNetworkPacket();
|
||||
|
||||
NetworkPacket networkPacket = new NetworkPacket(getSocketRole().getIn());
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(datagramPacket.getData());
|
||||
networkPacket.readHeader(buffer);
|
||||
|
||||
boolean fragment = networkPacket.getHeader().getPacketFlag(HeaderFlag.FRAGMENTED);
|
||||
if (fragment && !networkPacket.getHeader().getType().isSplittable()) continue; // drop packet
|
||||
|
||||
ByteBuffer packetBuffer;
|
||||
|
||||
if (networkPacket.getHeader().getType().isEncrypted()) {
|
||||
packetBuffer = ByteBuffer.wrap(
|
||||
getPacketTransformation().decrypt(
|
||||
networkPacket.getHeader(),
|
||||
buffer,
|
||||
datagramPacket.getLength() - networkPacket.getHeader().getSize()
|
||||
)
|
||||
);
|
||||
} else {
|
||||
packetBuffer = buffer;
|
||||
}
|
||||
|
||||
if (fragment) {
|
||||
Ts3Logging.debug("[PROTOCOL] READ " +
|
||||
networkPacket.getHeader().getType().name() + " fragment");
|
||||
|
||||
networkPacket.setPacket(
|
||||
new PacketFragment(
|
||||
networkPacket.getHeader().getType(),
|
||||
getSocketRole().getIn()
|
||||
)
|
||||
);
|
||||
} else {
|
||||
Ts3Logging.debug("[PROTOCOL] READ " +
|
||||
networkPacket.getHeader().getType().name());
|
||||
}
|
||||
|
||||
|
||||
networkPacket.readBody(packetBuffer);
|
||||
|
||||
return networkPacket;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +1,14 @@
|
||||
package com.github.manevolent.ts3j.protocol.socket;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.SocketRole;
|
||||
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||
import com.github.manevolent.ts3j.protocol.packet.Packet;
|
||||
import com.github.manevolent.ts3j.protocol.packet.transformation.PacketTransformation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Closeable;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public interface TeamspeakSocket {
|
||||
SocketRole getSocketRole();
|
||||
public interface TeamspeakSocket extends Closeable {
|
||||
|
||||
void send(PacketHeader header, Packet packet) throws IOException;
|
||||
SocketRole getRole();
|
||||
|
||||
PacketTransformation getPacketTransformation();
|
||||
|
||||
void setPacketTransformation(PacketTransformation transformation);
|
||||
InetSocketAddress getLocalSocketAddress();
|
||||
|
||||
}
|
||||
|
||||
+452
@@ -0,0 +1,452 @@
|
||||
package com.github.manevolent.ts3j.protocol.socket.client;
|
||||
|
||||
import com.github.manevolent.ts3j.command.Command;
|
||||
import com.github.manevolent.ts3j.command.response.CommandResponse;
|
||||
import com.github.manevolent.ts3j.identity.Identity;
|
||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||
import com.github.manevolent.ts3j.protocol.Packet;
|
||||
import com.github.manevolent.ts3j.protocol.SocketRole;
|
||||
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
|
||||
import com.github.manevolent.ts3j.protocol.header.HeaderFlag;
|
||||
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketBody;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketBody6Ack;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketBodyFragment;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketBodyType;
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.PacketHandler;
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.local.LocalClientHandler;
|
||||
import com.github.manevolent.ts3j.protocol.packet.transformation.DefaultPacketTransformation;
|
||||
import com.github.manevolent.ts3j.protocol.packet.transformation.PacketTransformation;
|
||||
import com.github.manevolent.ts3j.protocol.socket.AbstractTeamspeakSocket;
|
||||
import com.github.manevolent.ts3j.util.Ts3Crypt;
|
||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public abstract class AbstractTeamspeakClientSocket
|
||||
extends AbstractTeamspeakSocket
|
||||
implements TeamspeakClientSocket {
|
||||
private final Object connectionStateLock = new Object();
|
||||
private final Map<PacketBodyType, ReassemblyQueue> reassemblyQueue = new LinkedHashMap<>();
|
||||
private final NetworkReader networkReader = new NetworkReader();
|
||||
private final Map<String, Object> clientOptions = new LinkedHashMap<>();
|
||||
|
||||
private Identity identity = null;
|
||||
private ClientConnectionState connectionState = null;
|
||||
private PacketTransformation transformation = new DefaultPacketTransformation();
|
||||
private PacketHandler handler;
|
||||
|
||||
private Thread networkThread = null;
|
||||
private boolean reading = false;
|
||||
|
||||
protected AbstractTeamspeakClientSocket() {
|
||||
super(SocketRole.CLIENT);
|
||||
|
||||
try {
|
||||
setState(ClientConnectionState.DISCONNECTED);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// Initialize reassembly queue
|
||||
for (PacketBodyType bodyType : PacketBodyType.values())
|
||||
if (bodyType.isSplittable())
|
||||
reassemblyQueue.put(bodyType, new ReassemblyQueue());
|
||||
}
|
||||
|
||||
protected void start() {
|
||||
Thread networkThread = new Thread(networkReader);
|
||||
networkThread.setDaemon(true);
|
||||
networkThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getOptions() {
|
||||
return clientOptions;
|
||||
}
|
||||
|
||||
public final void setSecureParameters(Ts3Crypt.SecureChannelParameters parameters) {
|
||||
setTransformation(new PacketTransformation(parameters.getIvStruct()));
|
||||
}
|
||||
|
||||
protected abstract NetworkPacket readNetworkPacket() throws IOException;
|
||||
|
||||
protected abstract void writeNetworkPacket(NetworkPacket packet) throws IOException;
|
||||
|
||||
public void setState(ClientConnectionState state) throws IOException, TimeoutException {
|
||||
synchronized (connectionStateLock) {
|
||||
if (state != this.connectionState) {
|
||||
Ts3Logging.debug("State changing: " + state.name());
|
||||
|
||||
boolean wasDisconnected = this.connectionState == ClientConnectionState.DISCONNECTED;
|
||||
|
||||
if (wasDisconnected) setReading(true);
|
||||
|
||||
this.connectionState = state;
|
||||
|
||||
if (this.connectionState == ClientConnectionState.DISCONNECTED) setReading(false);
|
||||
|
||||
PacketHandler handler = getHandler();
|
||||
|
||||
if (handler != null)
|
||||
((LocalClientHandler) handler).handleConnectionStateChanging(state);
|
||||
|
||||
if (handler == null || handler.getClass() != state.getHandlerClass()) {
|
||||
Ts3Logging.debug("Assigning " + state.getHandlerClass().getName() + " handler...");
|
||||
|
||||
setHandler(state.createHandler(this));
|
||||
}
|
||||
|
||||
if (this.connectionState == ClientConnectionState.DISCONNECTED)
|
||||
clientOptions.clear();
|
||||
|
||||
connectionStateLock.notifyAll();
|
||||
|
||||
Ts3Logging.debug("State changed: " + state.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ClientConnectionState getState() {
|
||||
return connectionState;
|
||||
}
|
||||
|
||||
public void waitForState(ClientConnectionState state, long wait)
|
||||
throws InterruptedException, TimeoutException {
|
||||
synchronized (connectionStateLock) {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
while (connectionState != state && System.currentTimeMillis() - start < wait) {
|
||||
connectionStateLock.wait(Math.max(0L, wait - (System.currentTimeMillis() - start)));
|
||||
}
|
||||
|
||||
if (connectionState != state)
|
||||
throw new TimeoutException("timeout waiting for " + state.name() + " state");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return connectionState == ClientConnectionState.CONNECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a command to the remote server.
|
||||
* @param command Command to send.
|
||||
* @return Command response object to track command response.
|
||||
* @throws IOException
|
||||
*/
|
||||
public CommandResponse sendCommand(Command command) throws IOException {
|
||||
if (command.willExpectResponse() && !isConnected())
|
||||
throw new IOException("not connected");
|
||||
|
||||
CommandResponse response;
|
||||
|
||||
if (command.willExpectResponse()) {
|
||||
|
||||
//command.appendParameter(new CommandSingleParameter("return_code", returnCodeIndex));
|
||||
response = new CommandResponse(command, 0); // TODO
|
||||
} else {
|
||||
response = new CommandResponse(command, -1);
|
||||
}
|
||||
|
||||
String commandString = command.toString();
|
||||
|
||||
response.setDispatchedTime(System.currentTimeMillis());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writePacket(PacketBody body) throws IOException, TimeoutException {
|
||||
PacketHeader header;
|
||||
|
||||
try {
|
||||
header = body.getRole().getHeaderClass().newInstance();
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// Construct a network packet
|
||||
Packet packet = new Packet(body.getRole());
|
||||
packet.setHeader(header);
|
||||
packet.setBody(body);
|
||||
|
||||
writePacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writePacket(Packet packet) throws IOException, TimeoutException {
|
||||
if (packet.getHeader() == null)
|
||||
throw new NullPointerException("header");
|
||||
else if (packet.getHeader().getRole() != getRole().getOut())
|
||||
throw new IllegalArgumentException("packet role mismatch: " +
|
||||
packet.getHeader().getRole().name() + " != " +
|
||||
getRole().getOut().name());
|
||||
|
||||
// Ensure type matches
|
||||
packet.getHeader().setType(packet.getHeader().getType());
|
||||
|
||||
// Make sure new protocol is set if it must be set
|
||||
if (getState() == ClientConnectionState.CONNECTING)
|
||||
packet.getHeader().setPacketFlag(HeaderFlag.NEW_PROTOCOL, true);
|
||||
|
||||
// Set header values
|
||||
packet.getBody().setHeaderValues(packet.getHeader());
|
||||
|
||||
// Flush to a buffer
|
||||
ByteBuffer outputBuffer;
|
||||
|
||||
if (packet.getBody().getType().canEncrypt() &&
|
||||
!packet.getHeader().getPacketFlag(HeaderFlag.UNENCRYPTED)) {
|
||||
outputBuffer = getTransformation().encrypt(packet);
|
||||
} else {
|
||||
outputBuffer = ByteBuffer.allocate(packet.getSize());
|
||||
|
||||
outputBuffer.order(ByteOrder.BIG_ENDIAN);
|
||||
packet.writeHeader(outputBuffer);
|
||||
packet.writeBody(outputBuffer);
|
||||
}
|
||||
|
||||
Ts3Logging.debug("[PROTOCOL] WRITE " + packet.getHeader().getType().name());
|
||||
|
||||
// Find if the packet must be acknowledged and create a promise for that action
|
||||
// TODO
|
||||
|
||||
writeNetworkPacket(new NetworkPacket(
|
||||
new DatagramPacket(outputBuffer.array(), outputBuffer.position()),
|
||||
packet.getHeader(),
|
||||
(ByteBuffer) outputBuffer.position(0)
|
||||
));
|
||||
|
||||
// If necessary, fulfill promise for the packet acknowledgement or throw TimeoutException
|
||||
// TODO
|
||||
}
|
||||
|
||||
private Packet readPacketIntl() throws IOException {
|
||||
NetworkPacket networkPacket = readNetworkPacket();
|
||||
|
||||
if (networkPacket.getHeader() == null) throw new NullPointerException("header");
|
||||
|
||||
Packet packet = new Packet(networkPacket.getHeader().getRole());
|
||||
packet.setHeader(networkPacket.getHeader());
|
||||
|
||||
boolean fragment = networkPacket.getHeader().getPacketFlag(HeaderFlag.FRAGMENTED);
|
||||
|
||||
if (networkPacket.getHeader().getType().isSplittable()) {
|
||||
if (fragment)
|
||||
throw new IllegalArgumentException("packet is fragment, but not splittable");
|
||||
else {
|
||||
fragment = reassemblyQueue.get(networkPacket.getHeader().getType()).isReassembling();
|
||||
}
|
||||
}
|
||||
|
||||
boolean encrypted = !networkPacket.getHeader().getPacketFlag(HeaderFlag.UNENCRYPTED) &&
|
||||
networkPacket.getHeader().getType().canEncrypt();
|
||||
|
||||
if (networkPacket.getHeader().getType().mustEncrypt() && !encrypted)
|
||||
throw new IllegalArgumentException("packet is unencrypted, but must encrypt");
|
||||
|
||||
ByteBuffer packetBuffer =
|
||||
(ByteBuffer) networkPacket.getBuffer().position(networkPacket.getHeader().getSize());
|
||||
|
||||
// Decrypt
|
||||
if (encrypted) {
|
||||
packetBuffer = ByteBuffer.wrap(
|
||||
getTransformation().decrypt(
|
||||
networkPacket.getHeader(),
|
||||
packetBuffer,
|
||||
networkPacket.getDatagram().getLength() - networkPacket.getHeader().getSize()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Fragment handling
|
||||
if (fragment) {
|
||||
Ts3Logging.debug("[PROTOCOL] READ " + networkPacket.getHeader().getType().name() + " fragment");
|
||||
|
||||
packet.setBody(
|
||||
new PacketBodyFragment(
|
||||
networkPacket.getHeader().getType(),
|
||||
getRole().getIn()
|
||||
)
|
||||
);
|
||||
} else {
|
||||
Ts3Logging.debug("[PROTOCOL] READ " + networkPacket.getHeader().getType().name());
|
||||
}
|
||||
|
||||
// Compression?
|
||||
// TODO
|
||||
|
||||
// Read packet body
|
||||
packet.readBody(packetBuffer);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Packet readPacket() throws IOException, TimeoutException {
|
||||
while (true) {
|
||||
Packet packet = readPacketIntl();
|
||||
|
||||
// Find if the packet must be acknowledged
|
||||
PacketBodyType ackType = packet.getHeader().getType().getAcknolwedgedBy();
|
||||
if (ackType != null) {
|
||||
switch (ackType) {
|
||||
case ACK:
|
||||
writePacket(new PacketBody6Ack(getRole().getOut(), packet.getHeader().getPacketId()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Find if the packet is itself an acknowledgement
|
||||
switch (packet.getHeader().getType()) {
|
||||
case ACK:
|
||||
break;
|
||||
case ACK_LOW:
|
||||
break;
|
||||
default:
|
||||
getHandler().handlePacket(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketTransformation getTransformation() {
|
||||
return transformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTransformation(PacketTransformation transformation) {
|
||||
this.transformation = transformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketHandler getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
protected void setHandler(PacketHandler handler) throws IOException, TimeoutException {
|
||||
if (this.handler != handler) {
|
||||
if (this.handler != null) this.handler.onUnassigning();
|
||||
this.handler = handler;
|
||||
if (handler != null) handler.onAssigned();
|
||||
}
|
||||
}
|
||||
|
||||
public Identity getIdentity() {
|
||||
return identity;
|
||||
}
|
||||
|
||||
public void setIdentity(Identity identity) {
|
||||
this.identity = identity;
|
||||
}
|
||||
|
||||
protected boolean isReading() {
|
||||
return reading;
|
||||
}
|
||||
|
||||
protected void setReading(boolean b) {
|
||||
if (this.reading != b) {
|
||||
this.reading = b;
|
||||
|
||||
if (!reading) {
|
||||
if (networkThread != null) networkThread.interrupt();
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class NetworkReader implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
while (isReading()) {
|
||||
try {
|
||||
readPacket();
|
||||
} catch (Exception e) {
|
||||
if (e instanceof InterruptedException) continue;
|
||||
|
||||
Ts3Logging.debug("Problem handling packet", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ReassemblyQueue {
|
||||
private final Map<Integer, Packet> queue = new LinkedHashMap<>();
|
||||
private boolean state = false;
|
||||
|
||||
public Packet reassemble(Packet lastFragment) {
|
||||
// Pull out all other packets in the queue before this one which would also be fragmented
|
||||
List<Packet> reassemblyList = new ArrayList<>();
|
||||
for (int packetId = lastFragment.getHeader().getPacketId();; packetId--) {
|
||||
Packet olderPacket = queue.get(packetId);
|
||||
|
||||
if (olderPacket == null)
|
||||
break; // Chain breaks here
|
||||
else if (olderPacket.getHeader().getType() != lastFragment.getHeader().getType())
|
||||
continue; // skip
|
||||
else {
|
||||
reassemblyList.add(olderPacket);
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse the collection
|
||||
Collections.reverse(reassemblyList);
|
||||
|
||||
// Rebuild a master packet from the contents of all previous packet fragments
|
||||
int totalLength = reassemblyList.stream().mapToInt(x -> x.getBody().getSize()).sum() + lastFragment.getBody().getSize();
|
||||
if (totalLength < 0) throw new IllegalArgumentException("reassembly too small: " + totalLength);
|
||||
|
||||
ByteBuffer reassemblyBuffer = ByteBuffer.allocate(totalLength);
|
||||
|
||||
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));
|
||||
|
||||
return reassembledPacket;
|
||||
}
|
||||
|
||||
public Packet put(Packet packet) {
|
||||
if (packet.getHeader().getPacketFlag(HeaderFlag.FRAGMENTED)) {
|
||||
boolean oldState = state;
|
||||
|
||||
state = !state;
|
||||
|
||||
if (!(packet.getBody() instanceof PacketBodyFragment))
|
||||
throw new IllegalArgumentException("packet fragment object is not representative of a fragment");
|
||||
|
||||
boolean fragmentationEnded = oldState;
|
||||
|
||||
queue.put(packet.getHeader().getPacketId(), packet);
|
||||
|
||||
if (fragmentationEnded)
|
||||
return reassemble(packet);
|
||||
|
||||
return null;
|
||||
} else {
|
||||
if (state) {
|
||||
queue.put(packet.getHeader().getPacketId(), packet);
|
||||
|
||||
return null;
|
||||
} else {
|
||||
return packet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isReassembling() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
package com.github.manevolent.ts3j.protocol.socket.client;
|
||||
|
||||
import com.github.manevolent.ts3j.identity.LocalIdentity;
|
||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
|
||||
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public class LocalTeamspeakClientSocket extends AbstractTeamspeakClientSocket {
|
||||
private final DatagramSocket socket;
|
||||
|
||||
private final DatagramPacket packet = new DatagramPacket(new byte[500], 500);
|
||||
|
||||
{
|
||||
try {
|
||||
socket = new DatagramSocket();
|
||||
} catch (SocketException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public LocalTeamspeakClientSocket() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NetworkPacket readNetworkPacket() throws IOException {
|
||||
socket.receive(packet);
|
||||
|
||||
PacketHeader header;
|
||||
|
||||
try {
|
||||
header = getRole().getIn().getHeaderClass().newInstance();
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
ByteBuffer buffer = ByteBuffer
|
||||
.wrap(packet.getData(), packet.getOffset(), packet.getLength())
|
||||
.order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
header.read(buffer);
|
||||
|
||||
return new NetworkPacket(
|
||||
packet,
|
||||
header,
|
||||
buffer
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeNetworkPacket(NetworkPacket packet) throws IOException {
|
||||
socket.send(packet.getDatagram());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LocalIdentity getIdentity() {
|
||||
return (LocalIdentity) super.getIdentity();
|
||||
}
|
||||
|
||||
public void setIdentity(LocalIdentity identity) {
|
||||
super.setIdentity(identity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a connection to a server
|
||||
* @param remote remote sever to contact
|
||||
* @param password server password
|
||||
* @param timeout timeout, in milliseconds, to complete a connection.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void connect(InetSocketAddress remote, String password, long timeout)
|
||||
throws IOException, TimeoutException {
|
||||
try {
|
||||
Ts3Logging.debug("Connecting to " + remote + "...");
|
||||
|
||||
ClientConnectionState connectionState = getState();
|
||||
|
||||
if (connectionState != ClientConnectionState.DISCONNECTED)
|
||||
throw new IllegalStateException(connectionState.name());
|
||||
|
||||
setOption("client.hostname", remote.getHostString());
|
||||
setOption("client.password", password);
|
||||
|
||||
socket.connect(remote);
|
||||
|
||||
Ts3Logging.debug("Changing state...");
|
||||
setState(ClientConnectionState.CONNECTING);
|
||||
|
||||
waitForState(ClientConnectionState.CONNECTED, timeout);
|
||||
} catch (TimeoutException e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
setState(ClientConnectionState.DISCONNECTED);
|
||||
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReading() {
|
||||
return socket.isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getLocalSocketAddress() {
|
||||
return (InetSocketAddress) socket.getLocalSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.github.manevolent.ts3j.protocol.socket.client;
|
||||
|
||||
import com.github.manevolent.ts3j.identity.Identity;
|
||||
import com.github.manevolent.ts3j.protocol.Packet;
|
||||
import com.github.manevolent.ts3j.protocol.packet.PacketBody;
|
||||
import com.github.manevolent.ts3j.protocol.packet.handler.PacketHandler;
|
||||
import com.github.manevolent.ts3j.protocol.packet.transformation.PacketTransformation;
|
||||
import com.github.manevolent.ts3j.protocol.socket.TeamspeakSocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public interface TeamspeakClientSocket extends TeamspeakSocket {
|
||||
|
||||
Map<String, Object> getOptions();
|
||||
|
||||
default <T extends Object> T getOption(String key, Class<T> clazz) {
|
||||
Object value = getOptions().get(key);
|
||||
|
||||
if (value == null) return (T) null;
|
||||
|
||||
else if (!value.getClass().isAssignableFrom(clazz)) {
|
||||
throw new ClassCastException(
|
||||
clazz.getName()
|
||||
+ " is not assignable to option type " +
|
||||
value.getClass().getName()
|
||||
);
|
||||
} else
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
default Object setOption(String key, Object value) {
|
||||
return getOptions().put(key, value);
|
||||
}
|
||||
|
||||
boolean isConnected();
|
||||
|
||||
Identity getIdentity();
|
||||
|
||||
void setIdentity(Identity identity);
|
||||
|
||||
PacketTransformation getTransformation();
|
||||
|
||||
void setTransformation(PacketTransformation transformation);
|
||||
|
||||
PacketHandler getHandler();
|
||||
|
||||
Packet readPacket() throws IOException, TimeoutException;
|
||||
|
||||
void writePacket(PacketBody body) throws IOException, TimeoutException;
|
||||
|
||||
void writePacket(Packet packet) throws IOException, TimeoutException;
|
||||
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.github.manevolent.ts3j.protocol.socket.server;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.socket.client.TeamspeakClientSocket;
|
||||
|
||||
public interface TeamspeakServerSocket {
|
||||
|
||||
TeamspeakClientSocket accept();
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package com.github.manevolent.ts3j;
|
||||
|
||||
import com.github.manevolent.ts3j.identity.LocalIdentity;
|
||||
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
|
||||
import com.github.manevolent.ts3j.protocol.client.LocalTeamspeakClient;
|
||||
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -16,9 +16,9 @@ public class ServerConnectionTest extends TestCase {
|
||||
try {
|
||||
LocalIdentity localIdentity = LocalIdentity.generateNew(8);
|
||||
|
||||
LocalTeamspeakClient client = new LocalTeamspeakClient();
|
||||
LocalTeamspeakClientSocket client = new LocalTeamspeakClientSocket();
|
||||
|
||||
client.setLocalIdentity(localIdentity);
|
||||
client.setIdentity(localIdentity);
|
||||
|
||||
client.connect(new InetSocketAddress(
|
||||
"ts.teamlixo.net",
|
||||
@@ -27,7 +27,7 @@ public class ServerConnectionTest extends TestCase {
|
||||
10000L
|
||||
);
|
||||
|
||||
assertEquals(client.getConnectionState(), ClientConnectionState.CONNECTED);
|
||||
assertEquals(client.getState(), ClientConnectionState.CONNECTED);
|
||||
} catch (Throwable ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user