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.
|
form of a work.
|
||||||
|
|
||||||
A "Standard Interface" means an interface that either is an official
|
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
|
interfaces specified for a particular programming language, one that
|
||||||
is widely used among developers working in that language.
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
|||||||
@@ -35,5 +35,11 @@
|
|||||||
<artifactId>bcprov-jdk15on</artifactId>
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
<version>1.60</version>
|
<version>1.60</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-lang</groupId>
|
||||||
|
<artifactId>commons-lang</artifactId>
|
||||||
|
<version>2.6</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</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;
|
package com.github.manevolent.ts3j.protocol;
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
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;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a fully contained network packet, traveling in or out.
|
|
||||||
*/
|
|
||||||
public final class NetworkPacket {
|
public final class NetworkPacket {
|
||||||
private final ProtocolRole role;
|
private final DatagramPacket datagram;
|
||||||
|
private final PacketHeader header;
|
||||||
|
private final ByteBuffer buffer;
|
||||||
|
|
||||||
private PacketHeader header;
|
public NetworkPacket(DatagramPacket datagram, PacketHeader header, ByteBuffer buffer) {
|
||||||
private Packet packet;
|
this.datagram = datagram;
|
||||||
|
this.header = header;
|
||||||
public NetworkPacket(ProtocolRole role) {
|
this.buffer = buffer;
|
||||||
if (role == null) throw new NullPointerException("role");
|
|
||||||
|
|
||||||
this.role = role;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
public PacketHeader getHeader() {
|
||||||
return header;
|
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
|
* Gets the original packet buffer.
|
||||||
* @param buffer Buffer to write to
|
* @return Packet buffer.
|
||||||
*/
|
*/
|
||||||
public ByteBuffer write(ByteBuffer buffer) {
|
public ByteBuffer getBuffer() {
|
||||||
writeHeader(buffer);
|
|
||||||
writeBody(buffer);
|
|
||||||
|
|
||||||
return buffer;
|
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;
|
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.PacketHandler;
|
||||||
import com.github.manevolent.ts3j.protocol.packet.handler.client.LocalClientHandlerConnected;
|
import com.github.manevolent.ts3j.protocol.packet.handler.local.LocalClientHandler;
|
||||||
import com.github.manevolent.ts3j.protocol.packet.handler.client.LocalClientHandlerConnecting;
|
import com.github.manevolent.ts3j.protocol.packet.handler.local.LocalClientHandlerConnected;
|
||||||
import com.github.manevolent.ts3j.protocol.packet.handler.client.LocalClientHandlerDisconnected;
|
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 {
|
public enum ClientConnectionState {
|
||||||
CONNECTING(LocalClientHandlerConnecting.class),
|
CONNECTING(LocalClientHandlerConnecting.class),
|
||||||
|
NEGOTIATING(LocalClientHandlerConnecting.class),
|
||||||
CONNECTED(LocalClientHandlerConnected.class),
|
CONNECTED(LocalClientHandlerConnected.class),
|
||||||
DISCONNECTED(LocalClientHandlerDisconnected.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;
|
this.clazz = clazz;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Class<? extends LocalClientHandler> getHandlerClass() {
|
public Class<? extends PacketHandler> getHandlerClass() {
|
||||||
return clazz;
|
return clazz;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalClientHandler createHandler(LocalTeamspeakClient client) {
|
public PacketHandler createHandler(TeamspeakClientSocket client) {
|
||||||
try {
|
try {
|
||||||
return clazz.getConstructor(LocalTeamspeakClient.class).newInstance(client);
|
return (PacketHandler) ConstructorUtils.invokeConstructor(clazz, client);
|
||||||
} catch (ReflectiveOperationException ex) {
|
} catch (ReflectiveOperationException ex) {
|
||||||
throw new RuntimeException(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;
|
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 com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@@ -44,7 +44,7 @@ public class ClientPacketHeader extends PacketHeader {
|
|||||||
setClientId((buffer.getShort() & 0xFFFF));
|
setClientId((buffer.getShort() & 0xFFFF));
|
||||||
|
|
||||||
byte packetTypeAndFlags = buffer.get();
|
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);
|
setPacketFlags(packetTypeAndFlags & 0xF0);
|
||||||
|
|
||||||
// (payload) -- see writeBody impl in higher levels
|
// (payload) -- see writeBody impl in higher levels
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.github.manevolent.ts3j.protocol.header;
|
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.packet.Packet;
|
import com.github.manevolent.ts3j.protocol.packet.PacketBody;
|
||||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@@ -13,7 +13,7 @@ public abstract class PacketHeader {
|
|||||||
private int packetGeneration;
|
private int packetGeneration;
|
||||||
private byte[] mac = new byte[8]; // 8 bytes
|
private byte[] mac = new byte[8]; // 8 bytes
|
||||||
private int packetId = 0;
|
private int packetId = 0;
|
||||||
private PacketType type;
|
private PacketBodyType type;
|
||||||
private int packetFlags;
|
private int packetFlags;
|
||||||
|
|
||||||
protected PacketHeader(ProtocolRole role) {
|
protected PacketHeader(ProtocolRole role) {
|
||||||
@@ -44,11 +44,11 @@ public abstract class PacketHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PacketType getType() {
|
public PacketBodyType getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setType(PacketType type) {
|
public void setType(PacketBodyType type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ public abstract class PacketHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setMac(byte[] mac) {
|
public void setMac(byte[] mac) {
|
||||||
Packet.assertArray("mac", mac, 8);
|
PacketBody.assertArray("mac", mac, 8);
|
||||||
|
|
||||||
this.mac = mac;
|
this.mac = mac;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package com.github.manevolent.ts3j.protocol.header;
|
package com.github.manevolent.ts3j.protocol.header;
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||||
import com.github.manevolent.ts3j.protocol.packet.PacketType;
|
import com.github.manevolent.ts3j.protocol.packet.PacketBodyType;
|
||||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
@@ -40,7 +39,7 @@ public class ServerPacketHeader extends PacketHeader {
|
|||||||
|
|
||||||
// https://github.com/ReSpeak/tsdeclarations/blob/master/ts3protocol.md
|
// https://github.com/ReSpeak/tsdeclarations/blob/master/ts3protocol.md
|
||||||
byte packetTypeAndFlags = buffer.get();
|
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);
|
setPacketFlags(packetTypeAndFlags & 0xF0);
|
||||||
// (payload) -- see writeBody impl in higher levels
|
// (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;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
public abstract class Packet {
|
public abstract class PacketBody {
|
||||||
private final PacketType type;
|
private final PacketBodyType type;
|
||||||
private final ProtocolRole role;
|
private final ProtocolRole role;
|
||||||
|
|
||||||
protected Packet(PacketType type, ProtocolRole role) {
|
protected PacketBody(PacketBodyType type, ProtocolRole role) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.role = role;
|
this.role = role;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PacketType getType() {
|
public PacketBodyType getType() {
|
||||||
return type;
|
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.command.SimpleCommand;
|
||||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
public class Packet2Command extends Packet {
|
public class PacketBody2Command extends PacketBody {
|
||||||
private String text;
|
private String text;
|
||||||
|
|
||||||
public Packet2Command(ProtocolRole role) {
|
public PacketBody2Command(ProtocolRole role) {
|
||||||
super(PacketType.COMMAND, role);
|
super(PacketBodyType.COMMAND, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getText() {
|
public String getText() {
|
||||||
+9
-4
@@ -3,13 +3,18 @@ package com.github.manevolent.ts3j.protocol.packet;
|
|||||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
|
|
||||||
public class Packet6Ack extends Packet {
|
public class PacketBody6Ack extends PacketBody {
|
||||||
private int packetId;
|
private int packetId;
|
||||||
|
|
||||||
public Packet6Ack(ProtocolRole role) {
|
public PacketBody6Ack(ProtocolRole role) {
|
||||||
super(PacketType.ACK, role);
|
super(PacketBodyType.ACK, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PacketBody6Ack(ProtocolRole role, int packetId) {
|
||||||
|
super(PacketBodyType.ACK, role);
|
||||||
|
|
||||||
|
setPacketId(packetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPacketId() {
|
public int getPacketId() {
|
||||||
+16
-16
@@ -7,9 +7,9 @@ import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
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[] {
|
private static final byte[] LOW_LEVEL_MAC = new byte[] {
|
||||||
0x54, 0x53, 0x33, 0x49, 0x4E, 0x49, 0x54, 0x31
|
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 byte[] version = new byte[4]; // 4 bytes
|
||||||
private Step step; // step instance
|
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
|
// sec. 2 of https://github.com/ReSpeak/tsdeclarations/blob/master/ts3protocol.md
|
||||||
// (The packet header values are set as following for all packets here:)
|
// (The packet header values are set as following for all packets here:)
|
||||||
super(INIT1, role);
|
super(INIT1, role);
|
||||||
@@ -152,7 +152,7 @@ public class Packet8Init1 extends Packet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setTimestamp(byte[] timestamp) {
|
public void setTimestamp(byte[] timestamp) {
|
||||||
Packet.assertArray("timestamp", timestamp, 4);
|
PacketBody.assertArray("timestamp", timestamp, 4);
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ public class Packet8Init1 extends Packet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setRandom(byte[] random) {
|
public void setRandom(byte[] random) {
|
||||||
Packet.assertArray("random", random, 4);
|
PacketBody.assertArray("random", random, 4);
|
||||||
this.random = random;
|
this.random = random;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ public class Packet8Init1 extends Packet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setA0reversed(byte[] a0reversed) {
|
public void setA0reversed(byte[] a0reversed) {
|
||||||
Packet.assertArray("a0reversed", a0reversed, 4);
|
PacketBody.assertArray("a0reversed", a0reversed, 4);
|
||||||
this.a0reversed = a0reversed;
|
this.a0reversed = a0reversed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +208,7 @@ public class Packet8Init1 extends Packet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setServerStuff(byte[] serverStuff) {
|
public void setServerStuff(byte[] serverStuff) {
|
||||||
Packet.assertArray("serverStuff", serverStuff, 16);
|
PacketBody.assertArray("serverStuff", serverStuff, 16);
|
||||||
this.serverStuff = serverStuff;
|
this.serverStuff = serverStuff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ public class Packet8Init1 extends Packet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setA0reversed(byte[] a0reversed) {
|
public void setA0reversed(byte[] a0reversed) {
|
||||||
Packet.assertArray("a0reversed", a0reversed, 4);
|
PacketBody.assertArray("a0reversed", a0reversed, 4);
|
||||||
this.a0reversed = a0reversed;
|
this.a0reversed = a0reversed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +253,7 @@ public class Packet8Init1 extends Packet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setServerStuff(byte[] serverStuff) {
|
public void setServerStuff(byte[] serverStuff) {
|
||||||
Packet.assertArray("serverStuff", serverStuff, 16);
|
PacketBody.assertArray("serverStuff", serverStuff, 16);
|
||||||
this.serverStuff = serverStuff;
|
this.serverStuff = serverStuff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,7 +290,7 @@ public class Packet8Init1 extends Packet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setX(byte[] x) {
|
public void setX(byte[] x) {
|
||||||
Packet.assertArray("x", x, 64);
|
PacketBody.assertArray("x", x, 64);
|
||||||
this.x = x;
|
this.x = x;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,7 +299,7 @@ public class Packet8Init1 extends Packet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setN(byte[] n) {
|
public void setN(byte[] n) {
|
||||||
Packet.assertArray("n", n, 64);
|
PacketBody.assertArray("n", n, 64);
|
||||||
this.n = n;
|
this.n = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +308,7 @@ public class Packet8Init1 extends Packet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setServerStuff(byte[] serverStuff) {
|
public void setServerStuff(byte[] serverStuff) {
|
||||||
Packet.assertArray("serverStuff", serverStuff, 100);
|
PacketBody.assertArray("serverStuff", serverStuff, 100);
|
||||||
this.serverStuff = serverStuff;
|
this.serverStuff = serverStuff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +359,7 @@ public class Packet8Init1 extends Packet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setX(byte[] x) {
|
public void setX(byte[] x) {
|
||||||
Packet.assertArray("x", x, 64);
|
PacketBody.assertArray("x", x, 64);
|
||||||
this.x = x;
|
this.x = x;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +368,7 @@ public class Packet8Init1 extends Packet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setN(byte[] n) {
|
public void setN(byte[] n) {
|
||||||
Packet.assertArray("n", n, 64);
|
PacketBody.assertArray("n", n, 64);
|
||||||
this.n = n;
|
this.n = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,7 +377,7 @@ public class Packet8Init1 extends Packet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setServerStuff(byte[] serverStuff) {
|
public void setServerStuff(byte[] serverStuff) {
|
||||||
Packet.assertArray("serverStuff", serverStuff, 100);
|
PacketBody.assertArray("serverStuff", serverStuff, 100);
|
||||||
this.serverStuff = serverStuff;
|
this.serverStuff = serverStuff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +394,7 @@ public class Packet8Init1 extends Packet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setY(byte[] y) {
|
public void setY(byte[] y) {
|
||||||
Packet.assertArray("y", y, 64);
|
PacketBody.assertArray("y", y, 64);
|
||||||
this.y = y;
|
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;
|
package com.github.manevolent.ts3j.protocol.packet.handler;
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
import com.github.manevolent.ts3j.protocol.Packet;
|
||||||
import com.github.manevolent.ts3j.protocol.LocalEndpoint;
|
import com.github.manevolent.ts3j.protocol.socket.client.TeamspeakClientSocket;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
public abstract class PacketHandler {
|
public abstract class PacketHandler {
|
||||||
private final LocalEndpoint endpoint;
|
private final TeamspeakClientSocket clientSocket;
|
||||||
|
|
||||||
protected PacketHandler(LocalEndpoint endpoint) {
|
protected PacketHandler(TeamspeakClientSocket clientSocket) {
|
||||||
this.endpoint = endpoint;
|
this.clientSocket = clientSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onUnassigning() throws IOException {
|
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() {
|
public TeamspeakClientSocket getClient() {
|
||||||
return endpoint;
|
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.ClientConnectionState;
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.protocol.client.LocalTeamspeakClient;
|
|
||||||
import com.github.manevolent.ts3j.protocol.packet.handler.PacketHandler;
|
import com.github.manevolent.ts3j.protocol.packet.handler.PacketHandler;
|
||||||
|
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public abstract class LocalClientHandler extends PacketHandler {
|
public abstract class LocalClientHandler extends PacketHandler {
|
||||||
private final LocalTeamspeakClient client;
|
private final LocalTeamspeakClientSocket client;
|
||||||
|
|
||||||
protected LocalClientHandler(LocalTeamspeakClient client) {
|
protected LocalClientHandler(LocalTeamspeakClientSocket client) {
|
||||||
super(client);
|
super(client);
|
||||||
|
|
||||||
this.client = client;
|
this.client = client;
|
||||||
@@ -20,7 +18,7 @@ public abstract class LocalClientHandler extends PacketHandler {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected LocalTeamspeakClient getClient() {
|
public LocalTeamspeakClientSocket getClient() {
|
||||||
return client;
|
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.SimpleCommand;
|
||||||
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
|
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
|
||||||
import com.github.manevolent.ts3j.license.License;
|
import com.github.manevolent.ts3j.protocol.Packet;
|
||||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
|
||||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||||
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
|
import com.github.manevolent.ts3j.protocol.packet.PacketBody2Command;
|
||||||
import com.github.manevolent.ts3j.protocol.client.LocalTeamspeakClient;
|
import com.github.manevolent.ts3j.protocol.packet.PacketBody8Init1;
|
||||||
import com.github.manevolent.ts3j.protocol.packet.Packet2Command;
|
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
|
||||||
import com.github.manevolent.ts3j.protocol.packet.Packet8Init1;
|
|
||||||
import com.github.manevolent.ts3j.util.Ts3Crypt;
|
import com.github.manevolent.ts3j.util.Ts3Crypt;
|
||||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||||
import javafx.util.Pair;
|
import javafx.util.Pair;
|
||||||
@@ -16,27 +14,26 @@ import org.bouncycastle.math.ec.ECPoint;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
public class LocalClientHandlerConnecting extends LocalClientHandler {
|
public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||||
private byte[] randomBytes;
|
private byte[] randomBytes;
|
||||||
private byte[] alphaBytes;
|
private byte[] alphaBytes;
|
||||||
|
|
||||||
public LocalClientHandlerConnecting(LocalTeamspeakClient client) {
|
public LocalClientHandlerConnecting(LocalTeamspeakClientSocket client) {
|
||||||
super(client);
|
super(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAssigned() throws IOException {
|
public void onAssigned() throws IOException, TimeoutException {
|
||||||
Packet8Init1 packet = new Packet8Init1(ProtocolRole.CLIENT);
|
PacketBody8Init1 packet = new PacketBody8Init1(ProtocolRole.CLIENT);
|
||||||
|
|
||||||
// Initialize connection
|
// Initialize connection
|
||||||
|
|
||||||
Packet8Init1.Step0 step = new Packet8Init1.Step0();
|
PacketBody8Init1.Step0 step = new PacketBody8Init1.Step0();
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
randomBytes = new byte[4];
|
randomBytes = new byte[4];
|
||||||
random.nextBytes(randomBytes);
|
random.nextBytes(randomBytes);
|
||||||
@@ -47,7 +44,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
|||||||
sendInit1(packet);
|
sendInit1(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendInit1(Packet8Init1 packet) throws IOException {
|
private void sendInit1(PacketBody8Init1 packet) throws IOException, TimeoutException {
|
||||||
Ts3Logging.debug("Connecting: sending " + packet.getClass().getSimpleName()
|
Ts3Logging.debug("Connecting: sending " + packet.getClass().getSimpleName()
|
||||||
+ ":"
|
+ ":"
|
||||||
+ packet.getStep().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 });
|
packet.setVersion(new byte[] { 0x09, (byte)0x83, (byte)0x8C, (byte)0xCF });
|
||||||
|
|
||||||
getClient().send(packet);
|
getClient().writePacket(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handlePacket(NetworkPacket packet) throws IOException {
|
public void handlePacket(Packet packet) throws IOException, TimeoutException {
|
||||||
Ts3Logging.debug("Connecting: handling " + packet.getPacket().getClass().getSimpleName());
|
Ts3Logging.debug("Connecting: handling " + packet.getBody().getClass().getSimpleName());
|
||||||
|
|
||||||
if (packet.getPacket() instanceof Packet8Init1) {
|
if (packet.getBody() instanceof PacketBody8Init1) {
|
||||||
Packet8Init1 init1 = (Packet8Init1) packet.getPacket();
|
PacketBody8Init1 init1 = (PacketBody8Init1) packet.getBody();
|
||||||
|
|
||||||
switch (init1.getStep().getNumber()) {
|
switch (init1.getStep().getNumber()) {
|
||||||
case 1:
|
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
|
// Check nonce. It's received backwards, so walk backwards over the array received
|
||||||
for (int i = 0; i < 4; i ++) {
|
for (int i = 0; i < 4; i ++) {
|
||||||
if (randomBytes[3 - i] != serverReplyStep1.getA0reversed()[i])
|
if (randomBytes[3 - i] != serverReplyStep1.getA0reversed()[i])
|
||||||
@@ -75,9 +72,9 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build response
|
// 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.setA0reversed(serverReplyStep1.getA0reversed());
|
||||||
step2.setServerStuff(serverReplyStep1.getServerStuff());
|
step2.setServerStuff(serverReplyStep1.getServerStuff());
|
||||||
response2.setStep(step2);
|
response2.setStep(step2);
|
||||||
@@ -85,7 +82,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
|||||||
sendInit1(response2);
|
sendInit1(response2);
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
Packet8Init1.Step3 serverReplyStep3 = (Packet8Init1.Step3)init1.getStep();
|
PacketBody8Init1.Step3 serverReplyStep3 = (PacketBody8Init1.Step3)init1.getStep();
|
||||||
|
|
||||||
// Calculate 'y'
|
// Calculate 'y'
|
||||||
// which is the result of x ^ (2 ^ level) % n as an unsigned
|
// which is the result of x ^ (2 ^ level) % n as an unsigned
|
||||||
@@ -113,9 +110,9 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Build response
|
// 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.setLevel(serverReplyStep3.getLevel());
|
||||||
step4.setX(serverReplyStep3.getX());
|
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("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
|
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());
|
throw new IllegalArgumentException("unexpected Init1 server step: " + init1.getStep().getNumber());
|
||||||
|
|
||||||
}
|
}
|
||||||
} else if (packet.getPacket() instanceof Packet2Command) {
|
} else if (packet.getBody() instanceof PacketBody2Command) {
|
||||||
SimpleCommand command = ((Packet2Command) packet.getPacket()).parse();
|
SimpleCommand command = ((PacketBody2Command) packet.getBody()).parse();
|
||||||
|
|
||||||
if (command.getName().equalsIgnoreCase("initivexpand2")) {
|
if (command.getName().equalsIgnoreCase("initivexpand2")) {
|
||||||
// 3.2.2 initivexpand2 (Client <- Server)
|
// 3.2.2 initivexpand2 (Client <- Server)
|
||||||
@@ -186,12 +183,12 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
|||||||
byte[] toSign = new byte[86];
|
byte[] toSign = new byte[86];
|
||||||
System.arraycopy(keyPair.getKey(), 0, toSign, 0, 32);
|
System.arraycopy(keyPair.getKey(), 0, toSign, 0, 32);
|
||||||
System.arraycopy(beta, 0, toSign, 32, 54);
|
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));
|
Ts3Logging.debug("Created proof (clientek): " + Ts3Logging.getHex(sign));
|
||||||
|
|
||||||
// Send clientek (telling them the shared secret)
|
// Send clientek (telling them the shared secret)
|
||||||
Packet2Command clientek = new Packet2Command(ProtocolRole.CLIENT);
|
PacketBody2Command clientek = new PacketBody2Command(ProtocolRole.CLIENT);
|
||||||
clientek.setText(
|
clientek.setText(
|
||||||
new SimpleCommand(
|
new SimpleCommand(
|
||||||
"clientek", ProtocolRole.CLIENT,
|
"clientek", ProtocolRole.CLIENT,
|
||||||
@@ -199,12 +196,14 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
|||||||
new CommandSingleParameter("proof", Base64.getEncoder().encodeToString(sign))
|
new CommandSingleParameter("proof", Base64.getEncoder().encodeToString(sign))
|
||||||
).build()
|
).build()
|
||||||
);
|
);
|
||||||
getClient().send(clientek);
|
getClient().writePacket(clientek);
|
||||||
|
|
||||||
Ts3Crypt.SecureChannelParameters secureChannelParameters =
|
getClient().setSecureParameters(
|
||||||
Ts3Crypt.cryptoInit2(license, alphaBytes, omega, proof, beta, keyPair.getValue());
|
Ts3Crypt.cryptoInit2(license, alphaBytes, omega, proof, beta, keyPair.getValue())
|
||||||
|
);
|
||||||
|
|
||||||
|
PacketBody2Command clientinit = new PacketBody2Command(ProtocolRole.CLIENT);
|
||||||
|
|
||||||
Packet2Command clientinit = new Packet2Command(ProtocolRole.CLIENT);
|
|
||||||
clientinit.setText(
|
clientinit.setText(
|
||||||
new SimpleCommand(
|
new SimpleCommand(
|
||||||
"clientinit", ProtocolRole.CLIENT,
|
"clientinit", ProtocolRole.CLIENT,
|
||||||
@@ -220,11 +219,12 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
|||||||
new CommandSingleParameter("client_nickname_phonetic", "test"),
|
new CommandSingleParameter("client_nickname_phonetic", "test"),
|
||||||
new CommandSingleParameter("client_meta_data", ""),
|
new CommandSingleParameter("client_meta_data", ""),
|
||||||
new CommandSingleParameter("client_default_token", ""),
|
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")
|
new CommandSingleParameter("hwid", "87056c6e1268aaf5055abf8256415e0e,408978b6d98810cc03f0aa16a4c75600")
|
||||||
).build()
|
).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;
|
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.ProtocolRole;
|
||||||
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||||
@@ -72,7 +72,7 @@ public class PacketTransformation {
|
|||||||
return new Pair<>(key, nonce);
|
return new Pair<>(key, nonce);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteBuffer encrypt(NetworkPacket packet) {
|
public ByteBuffer encrypt(Packet packet) {
|
||||||
Pair<byte[], byte[]> parameters = computeParameters(packet.getHeader());
|
Pair<byte[], byte[]> parameters = computeParameters(packet.getHeader());
|
||||||
|
|
||||||
// Write header (this is temporary)
|
// Write header (this is temporary)
|
||||||
@@ -93,7 +93,7 @@ public class PacketTransformation {
|
|||||||
headerWithoutMac
|
headerWithoutMac
|
||||||
);
|
);
|
||||||
|
|
||||||
int dataLen = packet.getPacket().getSize();
|
int dataLen = packet.getBody().getSize();
|
||||||
ByteBuffer packetBuffer = packet.writeBody(ByteBuffer.allocate(dataLen).order(ByteOrder.BIG_ENDIAN));
|
ByteBuffer packetBuffer = packet.writeBody(ByteBuffer.allocate(dataLen).order(ByteOrder.BIG_ENDIAN));
|
||||||
|
|
||||||
byte[] result;
|
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;
|
package com.github.manevolent.ts3j.protocol.socket;
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.protocol.packet.transformation.DefaultPacketTransformation;
|
import com.github.manevolent.ts3j.protocol.SocketRole;
|
||||||
import com.github.manevolent.ts3j.protocol.packet.transformation.PacketTransformation;
|
|
||||||
|
|
||||||
public abstract class AbstractTeamspeakSocket implements TeamspeakSocket {
|
public abstract class AbstractTeamspeakSocket implements TeamspeakSocket {
|
||||||
private PacketTransformation transformation = new DefaultPacketTransformation();
|
private final SocketRole role;
|
||||||
|
|
||||||
@Override
|
protected AbstractTeamspeakSocket(SocketRole role) {
|
||||||
public PacketTransformation getPacketTransformation() {
|
this.role = role;
|
||||||
return transformation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public SocketRole getRole() {
|
||||||
public void setPacketTransformation(PacketTransformation transformation) {
|
return role;
|
||||||
this.transformation = transformation;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
package com.github.manevolent.ts3j.protocol.socket;
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.protocol.SocketRole;
|
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 {
|
public interface TeamspeakSocket extends Closeable {
|
||||||
SocketRole getSocketRole();
|
|
||||||
|
|
||||||
void send(PacketHeader header, Packet packet) throws IOException;
|
SocketRole getRole();
|
||||||
|
|
||||||
PacketTransformation getPacketTransformation();
|
InetSocketAddress getLocalSocketAddress();
|
||||||
|
|
||||||
void setPacketTransformation(PacketTransformation transformation);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+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.identity.LocalIdentity;
|
||||||
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
|
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 junit.framework.TestCase;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
@@ -16,9 +16,9 @@ public class ServerConnectionTest extends TestCase {
|
|||||||
try {
|
try {
|
||||||
LocalIdentity localIdentity = LocalIdentity.generateNew(8);
|
LocalIdentity localIdentity = LocalIdentity.generateNew(8);
|
||||||
|
|
||||||
LocalTeamspeakClient client = new LocalTeamspeakClient();
|
LocalTeamspeakClientSocket client = new LocalTeamspeakClientSocket();
|
||||||
|
|
||||||
client.setLocalIdentity(localIdentity);
|
client.setIdentity(localIdentity);
|
||||||
|
|
||||||
client.connect(new InetSocketAddress(
|
client.connect(new InetSocketAddress(
|
||||||
"ts.teamlixo.net",
|
"ts.teamlixo.net",
|
||||||
@@ -27,7 +27,7 @@ public class ServerConnectionTest extends TestCase {
|
|||||||
10000L
|
10000L
|
||||||
);
|
);
|
||||||
|
|
||||||
assertEquals(client.getConnectionState(), ClientConnectionState.CONNECTED);
|
assertEquals(client.getState(), ClientConnectionState.CONNECTED);
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user