Got past encryption. Further refactoring as the scope of the project comes into view. Packet fragment reassembly in progress, can ACK commands right now and reassemble clientinitiv2
This commit is contained in:
@@ -2,8 +2,12 @@ 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;
|
||||
@@ -13,10 +17,10 @@ import java.net.SocketException;
|
||||
public abstract class LocalEndpoint implements Endpoint {
|
||||
private final LocalTeamspeakSocket socket;
|
||||
|
||||
|
||||
private PacketHandler handler = null;
|
||||
private LocalIdentity localIdentity = null;
|
||||
|
||||
private final ReassemblyQueue reassemblyQueue = new ReassemblyQueue();
|
||||
|
||||
private final Object runLock = new Object();
|
||||
private Thread receiverThread;
|
||||
private boolean running = false;
|
||||
@@ -41,6 +45,9 @@ public abstract class LocalEndpoint implements Endpoint {
|
||||
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;
|
||||
@@ -69,22 +76,6 @@ public abstract class LocalEndpoint implements Endpoint {
|
||||
return socket.receive();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
protected void handlePacket(NetworkPacket packet) throws IOException {
|
||||
if (packet.getHeader().getPacketFlag(HeaderFlag.FRAGMENTED)) {
|
||||
|
||||
} else {
|
||||
if (this.handler != null) this.handler.handlePacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public LocalIdentity getLocalIdentity() {
|
||||
return localIdentity;
|
||||
}
|
||||
@@ -100,7 +91,21 @@ public abstract class LocalEndpoint implements Endpoint {
|
||||
|
||||
while (running) {
|
||||
try {
|
||||
handlePacket(receive());
|
||||
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;
|
||||
|
||||
|
||||
@@ -49,35 +49,43 @@ public final class NetworkPacket {
|
||||
* Writes the existing data out to the specified byte buffer
|
||||
* @param buffer Buffer to write to
|
||||
*/
|
||||
public void write(ByteBuffer buffer) {
|
||||
public ByteBuffer write(ByteBuffer buffer) {
|
||||
writeHeader(buffer);
|
||||
writeBody(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void writeHeader(ByteBuffer buffer) {
|
||||
public ByteBuffer writeHeader(ByteBuffer buffer) {
|
||||
if (getHeader() == null) throw new NullPointerException("header");
|
||||
|
||||
getHeader().setType(getPacket().getType());
|
||||
|
||||
getHeader().write(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void writeBody(ByteBuffer 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 void read(ByteBuffer buffer) {
|
||||
public ByteBuffer read(ByteBuffer buffer) {
|
||||
readHeader(buffer);
|
||||
readBody(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void readHeader(ByteBuffer buffer) {
|
||||
public ByteBuffer readHeader(ByteBuffer buffer) {
|
||||
if (getHeader() == null) {
|
||||
try {
|
||||
setHeader(getRole().getHeaderClass().newInstance());
|
||||
@@ -87,9 +95,11 @@ public final class NetworkPacket {
|
||||
}
|
||||
|
||||
getHeader().read(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void readBody(ByteBuffer buffer) {
|
||||
public ByteBuffer readBody(ByteBuffer buffer) {
|
||||
if (getPacket() == null) {
|
||||
try {
|
||||
setPacket(
|
||||
@@ -103,6 +113,8 @@ public final class NetworkPacket {
|
||||
}
|
||||
|
||||
getPacket().read(buffer);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+82
-40
@@ -1,12 +1,15 @@
|
||||
package com.github.manevolent.ts3j.protocol.client;
|
||||
|
||||
import com.github.manevolent.ts3j.command.Command;
|
||||
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
|
||||
import com.github.manevolent.ts3j.command.response.CommandResponse;
|
||||
import com.github.manevolent.ts3j.identity.LocalIdentity;
|
||||
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.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.socket.LocalTeamspeakSocket;
|
||||
import com.github.manevolent.ts3j.protocol.packet.Packet;
|
||||
@@ -18,16 +21,16 @@ import java.net.SocketException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Function;
|
||||
|
||||
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 LocalClientHandler handler;
|
||||
private ClientConnectionState clientConnectionState = null;
|
||||
private ClientConnectionState connectionState = null;
|
||||
|
||||
private int packetId = 0;
|
||||
private int clientId = 0;
|
||||
private int returnCodeIndex = 0;
|
||||
|
||||
public LocalTeamspeakClient(LocalTeamspeakSocket socket) {
|
||||
@@ -36,9 +39,45 @@ public class LocalTeamspeakClient extends LocalEndpoint implements TeamspeakClie
|
||||
if (socket.getSocketRole() != SocketRole.CLIENT)
|
||||
throw new IllegalArgumentException("invalid socket role: " + socket.getSocketRole().name());
|
||||
|
||||
setClientConnectionState(ClientConnectionState.DISCONNECTED);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
@@ -64,48 +103,49 @@ public class LocalTeamspeakClient extends LocalEndpoint implements TeamspeakClie
|
||||
this(new LocalTeamspeakSocket(SocketRole.CLIENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketRole getRole() {
|
||||
return SocketRole.CLIENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a packet to the server
|
||||
* @param packet Packet to send
|
||||
* @throws IOException
|
||||
*/
|
||||
public void send(Packet packet) throws IOException {
|
||||
// Construct header
|
||||
ClientPacketHeader header = new ClientPacketHeader();
|
||||
|
||||
header.setClientId(clientId);
|
||||
header.setPacketId(packetId);
|
||||
|
||||
packet.setHeaderValues(header);
|
||||
|
||||
getSocket().send(header, packet);
|
||||
}
|
||||
|
||||
protected void setClientConnectionState(ClientConnectionState state) {
|
||||
private void setConnectionState(ClientConnectionState state) {
|
||||
synchronized (connectionStateLock) {
|
||||
if (state != this.clientConnectionState) {
|
||||
if (state != this.connectionState) {
|
||||
Ts3Logging.debug("State changing: " + state.name());
|
||||
|
||||
boolean wasDisconnected = this.clientConnectionState == ClientConnectionState.DISCONNECTED;
|
||||
boolean wasDisconnected = this.connectionState == ClientConnectionState.DISCONNECTED;
|
||||
|
||||
this.clientConnectionState = state;
|
||||
this.connectionState = state;
|
||||
|
||||
if (wasDisconnected)
|
||||
setRunning(true);
|
||||
|
||||
if (handler != null)
|
||||
handler.handleConnectionStateChanging(state);
|
||||
if (channel.getHandler() != null)
|
||||
((LocalClientHandler)channel.getHandler()).handleConnectionStateChanging(state);
|
||||
|
||||
if (handler == null || handler.getClass() != state.getHandlerClass()) {
|
||||
if (channel.getHandler() == null || channel.getHandler().getClass() != state.getHandlerClass()) {
|
||||
Ts3Logging.debug("Assigning " + state.getHandlerClass().getName() + " handler...");
|
||||
|
||||
handler = state.createHandler(this);
|
||||
|
||||
try {
|
||||
setHandler(handler);
|
||||
channel.setHandler(state.createHandler(this));
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.clientConnectionState == ClientConnectionState.DISCONNECTED)
|
||||
if (this.connectionState == ClientConnectionState.DISCONNECTED)
|
||||
clientOptions.clear();
|
||||
|
||||
connectionStateLock.notifyAll();
|
||||
@@ -115,17 +155,8 @@ public class LocalTeamspeakClient extends LocalEndpoint implements TeamspeakClie
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return clientConnectionState == ClientConnectionState.CONNECTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getOptions() {
|
||||
return clientOptions;
|
||||
}
|
||||
|
||||
public ClientConnectionState getClientConnectionState() {
|
||||
return clientConnectionState;
|
||||
public ClientConnectionState getConnectionState() {
|
||||
return connectionState;
|
||||
}
|
||||
|
||||
private void joinConnectionState(ClientConnectionState state, long wait)
|
||||
@@ -133,29 +164,40 @@ public class LocalTeamspeakClient extends LocalEndpoint implements TeamspeakClie
|
||||
synchronized (connectionStateLock) {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
while (clientConnectionState != state && System.currentTimeMillis() - start < wait) {
|
||||
while (connectionState != state && System.currentTimeMillis() - start < wait) {
|
||||
connectionStateLock.wait(Math.max(0L, wait - (System.currentTimeMillis() - start)));
|
||||
}
|
||||
|
||||
if (clientConnectionState != state)
|
||||
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 (clientConnectionState != ClientConnectionState.DISCONNECTED)
|
||||
throw new IllegalStateException(clientConnectionState.name());
|
||||
if (connectionState != ClientConnectionState.DISCONNECTED)
|
||||
throw new IllegalStateException(connectionState.name());
|
||||
|
||||
setOption("client.hostname", remote.getHostString());
|
||||
setOption("client.password", password);
|
||||
|
||||
getSocket().connect(remote);
|
||||
|
||||
setClientConnectionState(ClientConnectionState.CONNECTING);
|
||||
setConnectionState(ClientConnectionState.CONNECTING);
|
||||
joinConnectionState(ClientConnectionState.CONNECTED, timeout);
|
||||
} catch (Throwable e) {
|
||||
setClientConnectionState(ClientConnectionState.DISCONNECTED);
|
||||
setConnectionState(ClientConnectionState.DISCONNECTED);
|
||||
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class Packet2Command extends Packet {
|
||||
private String text;
|
||||
|
||||
public Packet2Command(ProtocolRole role) {
|
||||
super(PacketType.COMMAND, role);
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ByteBuffer buffer) {
|
||||
|
||||
text = Charset.forName("ASCII").decode(buffer).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ByteBuffer buffer) {
|
||||
|
||||
buffer.put(text.getBytes(Charset.forName("ASCII")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return 0;
|
||||
return text.length();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class Packet6Ack extends Packet {
|
||||
private int packetId;
|
||||
|
||||
public Packet6Ack(ProtocolRole role) {
|
||||
super(PacketType.ACK, role);
|
||||
}
|
||||
|
||||
public int getPacketId() {
|
||||
return packetId;
|
||||
}
|
||||
|
||||
public void setPacketId(int packetId) {
|
||||
this.packetId = packetId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(ByteBuffer buffer) {
|
||||
packetId = buffer.getShort() & 0x0000FFFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ByteBuffer buffer) {
|
||||
buffer.putShort((short) (getPacketId() & 0xFFFFFF));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,11 @@ public enum PacketType {
|
||||
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, null, null, true, true, 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)
|
||||
;
|
||||
COMMAND(0x2, Packet2Command.class, ACK, true, true, true, true);
|
||||
|
||||
private final int index;
|
||||
private final Class<? extends Packet> packetClass;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -52,7 +52,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||
|
||||
@Override
|
||||
public void handlePacket(NetworkPacket packet) throws IOException {
|
||||
Ts3Logging.debug("Connecting: handling " + packet.getClass().getSimpleName());
|
||||
Ts3Logging.debug("Connecting: handling " + packet.getPacket().getClass().getSimpleName());
|
||||
|
||||
if (packet.getPacket() instanceof Packet8Init1) {
|
||||
Packet8Init1 init1 = (Packet8Init1) packet.getPacket();
|
||||
@@ -136,7 +136,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||
|
||||
}
|
||||
} else if (packet.getPacket() instanceof Packet2Command) {
|
||||
|
||||
Ts3Logging.debug(((Packet2Command) packet.getPacket()).getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+70
-23
@@ -1,5 +1,6 @@
|
||||
package com.github.manevolent.ts3j.protocol.packet.transformation;
|
||||
|
||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||
@@ -17,6 +18,7 @@ import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class PacketTransformation {
|
||||
private static final int MAC_LEN = 8;
|
||||
private static MessageDigest SHA256;
|
||||
|
||||
{
|
||||
@@ -70,33 +72,34 @@ public class PacketTransformation {
|
||||
return new Pair<>(key, nonce);
|
||||
}
|
||||
|
||||
public byte[] encrypt(PacketHeader header, byte[] body) {
|
||||
return transform(header, body, true);
|
||||
}
|
||||
public ByteBuffer encrypt(NetworkPacket packet) {
|
||||
Pair<byte[], byte[]> parameters = computeParameters(packet.getHeader());
|
||||
|
||||
public byte[] decrypt(PacketHeader header, byte[] body) {
|
||||
return transform(header, body, false);
|
||||
}
|
||||
// Write header (this is temporary)
|
||||
ByteBuffer headerBuffer = packet.writeHeader(ByteBuffer.allocate(packet.getHeader().getSize()));
|
||||
|
||||
private byte[] transform(PacketHeader header, byte[] body, boolean encrypt) {
|
||||
Pair<byte[], byte[]> parameters = computeParameters(header);
|
||||
// Get the header without a MAC for the associated text field
|
||||
byte[] headerWithoutMac = new byte[packet.getHeader().getSize() - MAC_LEN];
|
||||
System.arraycopy(headerBuffer.array(), MAC_LEN, headerWithoutMac, 0, headerWithoutMac.length);
|
||||
|
||||
byte[] associatedText = new byte[8];
|
||||
|
||||
CipherParameters cipherParameters = new AEADParameters(new KeyParameter(
|
||||
parameters.getKey()),
|
||||
8 * associatedText.length,
|
||||
CipherParameters cipherParameters = new AEADParameters(
|
||||
new KeyParameter(parameters.getKey()),
|
||||
8 * MAC_LEN,
|
||||
parameters.getValue(),
|
||||
encrypt ? associatedText : header.getMac()
|
||||
headerWithoutMac
|
||||
);
|
||||
|
||||
int dataLen = packet.getPacket().getSize();
|
||||
ByteBuffer packetBuffer = packet.writeBody(ByteBuffer.allocate(dataLen));
|
||||
|
||||
byte[] result;
|
||||
int len;
|
||||
synchronized (cipher){
|
||||
cipher.init(encrypt, cipherParameters);
|
||||
|
||||
result = new byte[cipher.getOutputSize(body.length)];
|
||||
len = cipher.processBytes(body, 0, body.length, result, 0);
|
||||
synchronized (cipher) {
|
||||
cipher.init(true, cipherParameters);
|
||||
|
||||
result = new byte[cipher.getOutputSize(dataLen)];
|
||||
len = cipher.processBytes(packetBuffer.array(), 0, dataLen, result, 0);
|
||||
|
||||
try {
|
||||
len += cipher.doFinal(result, len);
|
||||
@@ -105,12 +108,56 @@ public class PacketTransformation {
|
||||
}
|
||||
}
|
||||
|
||||
byte[] output = new byte[len];
|
||||
System.arraycopy(result, 0, output, 0, len);
|
||||
ByteBuffer outputBuffer = ByteBuffer.allocate(MAC_LEN + headerWithoutMac.length + (len - MAC_LEN));
|
||||
|
||||
if (encrypt)
|
||||
header.setMac(associatedText);
|
||||
// MAC
|
||||
outputBuffer.put(result, len - MAC_LEN, MAC_LEN);
|
||||
|
||||
return output;
|
||||
// Rest of header
|
||||
outputBuffer.put(headerWithoutMac);
|
||||
|
||||
// Encrypted body
|
||||
outputBuffer.put(result, 0, len - MAC_LEN);
|
||||
|
||||
return outputBuffer;
|
||||
}
|
||||
|
||||
public byte[] decrypt(PacketHeader header, ByteBuffer buffer, int dataLen) {
|
||||
Pair<byte[], byte[]> parameters = computeParameters(header);
|
||||
|
||||
byte[] headerWithoutMac = new byte[header.getSize() - MAC_LEN];
|
||||
System.arraycopy(buffer.array(), MAC_LEN, headerWithoutMac, 0, headerWithoutMac.length);
|
||||
|
||||
// Get the header without a MAC for the associated text field
|
||||
CipherParameters cipherParameters = new AEADParameters(
|
||||
new KeyParameter(parameters.getKey()),
|
||||
8 * MAC_LEN,
|
||||
parameters.getValue(),
|
||||
headerWithoutMac
|
||||
);
|
||||
|
||||
byte[] result;
|
||||
int len;
|
||||
|
||||
synchronized (cipher) {
|
||||
|
||||
cipher.init(false, cipherParameters);
|
||||
result = new byte[cipher.getOutputSize(dataLen + MAC_LEN)];
|
||||
|
||||
len = cipher.processBytes(buffer.array(), header.getSize(), dataLen, result, 0);
|
||||
len += cipher.processBytes(buffer.array(), 0, MAC_LEN, result, len);
|
||||
try {
|
||||
len += cipher.doFinal(result, len);
|
||||
} catch (InvalidCipherTextException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (len != dataLen)
|
||||
throw new IllegalArgumentException(len + " != " + dataLen);
|
||||
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
+53
-30
@@ -5,6 +5,8 @@ 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;
|
||||
@@ -78,28 +80,25 @@ public class LocalTeamspeakSocket extends AbstractTeamspeakSocket implements Tea
|
||||
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);
|
||||
|
||||
if (networkPacket.getHeader().getMac() == null) {
|
||||
// Generate mac?
|
||||
}
|
||||
|
||||
ByteBuffer outputBuffer = ByteBuffer.allocate(networkPacket.getSize());
|
||||
outputBuffer.order(ByteOrder.BIG_ENDIAN);
|
||||
networkPacket.writeHeader(outputBuffer);
|
||||
// Flush to a buffer
|
||||
ByteBuffer outputBuffer;
|
||||
|
||||
if (networkPacket.getHeader().getType().isEncrypted() &&
|
||||
!networkPacket.getHeader().getPacketFlag(HeaderFlag.UNENCRYPTED)) {
|
||||
ByteBuffer encryptionBuffer = ByteBuffer.allocate(500);
|
||||
networkPacket.writeBody(encryptionBuffer);
|
||||
|
||||
byte[] unencryptedBody = new byte[encryptionBuffer.position()];
|
||||
System.arraycopy(encryptionBuffer.array(), 0, unencryptedBody, 0, unencryptedBody.length);
|
||||
|
||||
outputBuffer.put(getPacketTransformation().encrypt(header, unencryptedBody));
|
||||
outputBuffer = getPacketTransformation().encrypt(networkPacket);
|
||||
} else {
|
||||
outputBuffer = ByteBuffer.allocate(networkPacket.getSize());
|
||||
|
||||
outputBuffer.order(ByteOrder.BIG_ENDIAN);
|
||||
networkPacket.writeHeader(outputBuffer);
|
||||
networkPacket.writeBody(outputBuffer);
|
||||
}
|
||||
|
||||
@@ -117,7 +116,7 @@ public class LocalTeamspeakSocket extends AbstractTeamspeakSocket implements Tea
|
||||
datagramSocket.receive(datagramPacket);
|
||||
|
||||
Ts3Logging.debug(
|
||||
"[NETWORK] READ Len=" + datagramPacket.getLength() + " to " + datagramPacket.getSocketAddress() +
|
||||
"[NETWORK] READ Len=" + datagramPacket.getLength() + " from " + datagramPacket.getSocketAddress() +
|
||||
"\n" + Ts3Logging.getHex(datagramPacket.getData(), datagramPacket.getLength())
|
||||
);
|
||||
|
||||
@@ -125,27 +124,51 @@ public class LocalTeamspeakSocket extends AbstractTeamspeakSocket implements Tea
|
||||
}
|
||||
|
||||
public NetworkPacket receive() throws IOException {
|
||||
DatagramPacket datagramPacket = receiveNetworkPacket();
|
||||
while (true) {
|
||||
DatagramPacket datagramPacket = receiveNetworkPacket();
|
||||
|
||||
NetworkPacket networkPacket = new NetworkPacket(getSocketRole().getIn());
|
||||
NetworkPacket networkPacket = new NetworkPacket(getSocketRole().getIn());
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(datagramPacket.getData());
|
||||
networkPacket.readHeader(buffer);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(datagramPacket.getData());
|
||||
networkPacket.readHeader(buffer);
|
||||
|
||||
Ts3Logging.debug("[PROTOCOL] READ " + networkPacket.getHeader().getType().name());
|
||||
boolean fragment = networkPacket.getHeader().getPacketFlag(HeaderFlag.FRAGMENTED);
|
||||
if (fragment && !networkPacket.getHeader().getType().isSplittable()) continue; // drop packet
|
||||
|
||||
if (networkPacket.getHeader().getType().isEncrypted()) {
|
||||
byte[] transformBuffer = new byte[buffer.remaining()];
|
||||
buffer.get(transformBuffer);
|
||||
ByteBuffer packetBuffer;
|
||||
|
||||
networkPacket.readBody(ByteBuffer.wrap(
|
||||
getPacketTransformation().decrypt(networkPacket.getHeader(), transformBuffer)
|
||||
));
|
||||
} else {
|
||||
networkPacket.readBody(buffer);
|
||||
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;
|
||||
}
|
||||
|
||||
return networkPacket;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ public class ServerConnectionTest extends TestCase {
|
||||
10000L
|
||||
);
|
||||
|
||||
assertEquals(client.getClientConnectionState(), ClientConnectionState.CONNECTED);
|
||||
assertEquals(client.getConnectionState(), ClientConnectionState.CONNECTED);
|
||||
} catch (Throwable ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user