Working quickly to finish packet generation following. I've implemented my own counter now, using the same window system as the previous Java rewrite, using just an integer array and one array copy call. Hopefully this is good for performance.

Also, there was a breaking issue with the computeParameters() call that the test did not account for.  Once you reached packet ID 128, the encryption keys were all off.  This was due to the packet ID byte shifting: it worked for the first 127 bytes.  We can thank Java's lack of unsigned primitives, and a little bit of my own ignorance for that issue.

At this point, I believe all we need now are the clientQuery commands.  Several events must be added, and the responding commands must be typed out as well.  More to come.  Home stretch.
This commit is contained in:
Manevolent
2018-08-01 12:27:09 -04:00
parent 1bdffc8634
commit f50ada0888
11 changed files with 317 additions and 230 deletions
+1 -1
View File
@@ -127,7 +127,7 @@ Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component "Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system (kernel, generationWindowSize system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to (if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it. produce the work, or an object code interpreter used to run it.
@@ -29,9 +29,9 @@ public class Sha512
if (offset < 0) if (offset < 0)
throw new Exception("offset"); throw new Exception("offset");
if (count < 0) if (count < 0)
throw new Exception("count"); throw new Exception("put");
if (data.length - offset < count) if (data.length - offset < count)
throw new Exception("Requires offset + count <= data.Length"); throw new Exception("Requires offset + put <= data.Length");
Array16<Long> block = new Array16<Long>(); Array16<Long> block = new Array16<Long>();
int bytesInBuffer = (int)_totalBytes & (BlockSize - 1); int bytesInBuffer = (int)_totalBytes & (BlockSize - 1);
@@ -155,7 +155,7 @@ public class FileTransfer extends Wrapper {
/** /**
* Returns how many milliseconds have elapsed since the file transfer was first announced to * Returns how many milliseconds have elapsed since the file transfer was first announced to
* the server. This value will count up even if the file transfer has not yet been started, * the server. This value will put up even if the file transfer has not yet been started,
* but will stop if the transfer has been paused or is complete. * but will stop if the transfer has been paused or is complete.
* *
* @return the transfer runtime in milliseconds * @return the transfer runtime in milliseconds
@@ -1,164 +0,0 @@
package com.github.manevolent.ts3j.protocol;
public class RingQueue {
private static final int
NOT_SET = 0x00,
SET = 0x01,
OUT_OF_WINDOW = 0x00,
IN_WINDOW = 0x10;
private boolean[] ringBufferSet;
private final int initialBufferSize = 16;
private int currentStart, currentLength;
private int mappedBaseOffset;
private final int mappedMod;
private int generation;
private final int maxBufferSize;
public RingQueue(int maxBufferSize, int mod) {
if (maxBufferSize == -1)
{
this.maxBufferSize = (mod / 2) - 1;
}
else
{
if (maxBufferSize >= mod)
throw new IllegalArgumentException("Modulo must be bigger than buffer size");
this.maxBufferSize = maxBufferSize;
}
int setBufferSize = Math.min(initialBufferSize, maxBufferSize);
ringBufferSet = new boolean[setBufferSize];
mappedMod = mod;
clear();
}
private void bufferSet(int index) {
bufferExtend(index);
int local = indexToLocal(index);
int newLength = local - currentStart + 1 + (local >= currentStart ? 0 : ringBufferSet.length);
currentLength = Math.max(currentLength, newLength);
ringBufferSet[local] = true;
}
private boolean stateGet(int index) {
bufferExtend(index);
int local = indexToLocal(index);
return ringBufferSet[local];
}
private void bufferPop() {
ringBufferSet[currentStart] = false;
currentStart = (currentStart + 1) % ringBufferSet.length;
currentLength --;
}
private void bufferExtend(int index) {
if (index < ringBufferSet.length)
return;
if (index >= maxBufferSize)
throw new IllegalArgumentException("the index does not fit into the maximal buffer size");
int extendto = index < ringBufferSet.length * 2
? Math.min(ringBufferSet.length * 2, maxBufferSize)
: Math.min(index + ringBufferSet.length, maxBufferSize);
Object[] extRingBuffer = new Object[extendto];
boolean[] extRingBufferSet = new boolean[extendto];
System.arraycopy(ringBufferSet, currentStart, extRingBufferSet, 0, ringBufferSet.length - currentStart);
System.arraycopy(ringBufferSet, 0, extRingBufferSet, ringBufferSet.length - currentStart, currentStart);
currentStart = 0;
ringBufferSet = extRingBufferSet;
}
private int indexToLocal(int index) {
return (currentStart + index) % ringBufferSet.length;
}
public void clear()
{
currentStart = 0;
currentLength = 0;
for (int i = 0; i < ringBufferSet.length; i ++) ringBufferSet[i] = false;
mappedBaseOffset = 0;
generation = 0;
}
public void set(int mappedValue) {
int index = mappedToIndex(mappedValue);
if (isSetIndex(index) != ItemSetStatus.IN_WINDOW_NOT_SET)
throw new IllegalStateException("object cannot be set");
bufferSet(index);
}
private int mappedToIndex(int mappedValue) {
if (mappedValue >= mappedMod)
throw new ArrayIndexOutOfBoundsException("mappedValue");
if (isNextGen(mappedValue)) {
return (mappedValue + mappedMod) - mappedBaseOffset;
} else {
return mappedValue - mappedBaseOffset;
}
}
public boolean isNextGen(int mappedValue) {
return mappedBaseOffset > mappedMod - maxBufferSize && mappedValue < maxBufferSize;
}
public int getGeneration(int mappedValue) {
return generation + (isNextGen(mappedValue) ? 1 : 0);
}
private ItemSetStatus isSetIndex(int index) {
if (index < 0)
return ItemSetStatus.OUT_OF_WINDOW_SET;
else if (index > currentLength && index < maxBufferSize)
return ItemSetStatus.IN_WINDOW_NOT_SET;
else if (index >= maxBufferSize)
return ItemSetStatus.OUT_OF_WINDOW_NOT_SET;
return stateGet(index) ? ItemSetStatus.IN_WINDOW_SET : ItemSetStatus.IN_WINDOW_NOT_SET;
}
public int getCount() {
return currentLength;
}
public boolean isSet(int packetId) {
return isSetIndex(packetId) == ItemSetStatus.IN_WINDOW_SET;
}
public enum ItemSetStatus {
OUT_OF_WINDOW_NOT_SET(0x00),
OUT_OF_WINDOW_SET(0x01),
IN_WINDOW_NOT_SET(0x10),
IN_WINDOW_SET(0x11);
private final int index;
ItemSetStatus(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
public static ItemSetStatus fromId(int index) {
for (ItemSetStatus value : values())
if (value.getIndex() == index) return value;
throw new IllegalArgumentException("invalid index: " + index);
}
}
}
@@ -107,6 +107,7 @@ public abstract class PacketHeader {
} }
public void setGeneration(int packetGeneration) { public void setGeneration(int packetGeneration) {
if (packetGeneration < 0) throw new IllegalArgumentException("packetGeneration");
this.packetGeneration = packetGeneration; this.packetGeneration = packetGeneration;
} }
} }
@@ -2,6 +2,7 @@ package com.github.manevolent.ts3j.protocol.packet;
import com.github.manevolent.ts3j.enums.CodecType; import com.github.manevolent.ts3j.enums.CodecType;
import com.github.manevolent.ts3j.protocol.ProtocolRole; import com.github.manevolent.ts3j.protocol.ProtocolRole;
import com.github.manevolent.ts3j.util.Ts3Debugging;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@@ -1,7 +1,7 @@
package com.github.manevolent.ts3j.protocol.packet; package com.github.manevolent.ts3j.protocol.packet;
public enum PacketBodyType { public enum PacketBodyType {
VOICE(0x0, PacketBody0Voice.class, null, false, false, false, false, false), VOICE(0x0, PacketBody0Voice.class, null, true, false, false, false, false),
VOICE_WHISPER(0x1, PacketBody1VoiceWhisper.class, null, false, false, false, false, false), VOICE_WHISPER(0x1, PacketBody1VoiceWhisper.class, null, false, false, false, false, false),
PONG(0x5, PacketBody5Pong.class, null, false, false, false, false, false), PONG(0x5, PacketBody5Pong.class, null, false, false, false, false, false),
PING(0x4, PacketBody4Ping.class, PONG, false, false, false, false, false), PING(0x4, PacketBody4Ping.class, PONG, false, false, false, false, false),
@@ -56,8 +56,8 @@ public class PacketTransformation {
// ivStruct[0] = ivStruct[0] xor ((PId & 0xFF00) >> 8) // ivStruct[0] = ivStruct[0] xor ((PId & 0xFF00) >> 8)
// ivStruct[1] = ivStruct[1] xor ((PId & 0x00FF) >> 0) // ivStruct[1] = ivStruct[1] xor ((PId & 0x00FF) >> 0)
key[0] = (byte) (key[0] ^ ((byte) (header.getPacketId() & 0xFF) >> 8) & 0xFF); key[0] = (byte) (key[0] ^ (header.getPacketId() >> 8) & 0xFF);
key[1] = (byte) (key[1] ^ ((byte) (header.getPacketId() & 0xFF) >> 0) & 0xFF); key[1] = (byte) (key[1] ^ (header.getPacketId() >> 0) & 0xFF);
return new Pair<>(key, nonce); return new Pair<>(key, nonce);
} }
@@ -4,7 +4,6 @@ import com.github.manevolent.ts3j.command.*;
import com.github.manevolent.ts3j.identity.Identity; import com.github.manevolent.ts3j.identity.Identity;
import com.github.manevolent.ts3j.protocol.NetworkPacket; import com.github.manevolent.ts3j.protocol.NetworkPacket;
import com.github.manevolent.ts3j.protocol.Packet; import com.github.manevolent.ts3j.protocol.Packet;
import com.github.manevolent.ts3j.protocol.RingQueue;
import com.github.manevolent.ts3j.protocol.SocketRole; import com.github.manevolent.ts3j.protocol.SocketRole;
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState; import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
import com.github.manevolent.ts3j.protocol.header.ClientPacketHeader; import com.github.manevolent.ts3j.protocol.header.ClientPacketHeader;
@@ -49,8 +48,8 @@ public abstract class AbstractTeamspeakClientSocket
private final Map<Integer, PacketResponse> sendQueue = new ConcurrentHashMap<>(); private final Map<Integer, PacketResponse> sendQueue = new ConcurrentHashMap<>();
private final Map<Integer, PacketResponse> sendQueueLow = new ConcurrentHashMap<>(); private final Map<Integer, PacketResponse> sendQueueLow = new ConcurrentHashMap<>();
private final Map<PacketBodyType, PacketCounter> localSendCounter = new ConcurrentHashMap<>(); private final Map<PacketBodyType, LocalCounter> localSendCounter = new ConcurrentHashMap<>();
private final Map<PacketBodyType, RingQueue> remoteQueue = new ConcurrentHashMap<>(); private final Map<PacketBodyType, RemoteCounter> remoteSendCounter = new ConcurrentHashMap<>();
private CommandProcessor commandProcessor; private CommandProcessor commandProcessor;
@@ -89,12 +88,12 @@ public abstract class AbstractTeamspeakClientSocket
// Initialize counters // Initialize counters
for (PacketBodyType bodyType : PacketBodyType.values()) { for (PacketBodyType bodyType : PacketBodyType.values()) {
if (bodyType == PacketBodyType.INIT1) { if (bodyType == PacketBodyType.INIT1) {
localSendCounter.put(bodyType, new PacketCounterZero()); localSendCounter.put(bodyType, new LocalCounterZero());
remoteSendCounter.put(bodyType, new RemoteCounterZero());
} else { } else {
localSendCounter.put(bodyType, new PacketCounterFull(true)); localSendCounter.put(bodyType, new LocalCounterFull(65536, true));
remoteSendCounter.put(bodyType, new RemoteCounterFull(65536, 100));
} }
remoteQueue.put(bodyType, new RingQueue(100, 65536));
} }
} }
@@ -216,7 +215,7 @@ public abstract class AbstractTeamspeakClientSocket
case ACK: case ACK:
counter = new Pair<>( counter = new Pair<>(
((PacketBody6Ack)packet.getBody()).getPacketId(), ((PacketBody6Ack)packet.getBody()).getPacketId(),
remoteQueue.get(PacketBodyType.ACK).getGeneration( remoteSendCounter.get(PacketBodyType.ACK).getGeneration(
((PacketBody6Ack) packet.getBody()).getPacketId() ((PacketBody6Ack) packet.getBody()).getPacketId()
) )
); );
@@ -224,7 +223,7 @@ public abstract class AbstractTeamspeakClientSocket
case ACK_LOW: case ACK_LOW:
counter = new Pair<>( counter = new Pair<>(
((PacketBody7AckLow)packet.getBody()).getPacketId(), ((PacketBody7AckLow)packet.getBody()).getPacketId(),
remoteQueue.get(PacketBodyType.ACK_LOW).getGeneration( remoteSendCounter.get(PacketBodyType.ACK_LOW).getGeneration(
((PacketBody7AckLow) packet.getBody()).getPacketId() ((PacketBody7AckLow) packet.getBody()).getPacketId()
) )
); );
@@ -232,7 +231,7 @@ public abstract class AbstractTeamspeakClientSocket
case PONG: case PONG:
counter = new Pair<>( counter = new Pair<>(
((PacketBody5Pong)packet.getBody()).getPacketId(), ((PacketBody5Pong)packet.getBody()).getPacketId(),
remoteQueue.get(PacketBodyType.PONG).getGeneration( remoteSendCounter.get(PacketBodyType.PONG).getGeneration(
((PacketBody5Pong) packet.getBody()).getPacketId() ((PacketBody5Pong) packet.getBody()).getPacketId()
) )
); );
@@ -414,11 +413,6 @@ public abstract class AbstractTeamspeakClientSocket
private Packet readPacketIntl(NetworkPacket networkPacket) throws IOException { private Packet readPacketIntl(NetworkPacket networkPacket) throws IOException {
if (networkPacket.getHeader() == null) throw new NullPointerException("header"); if (networkPacket.getHeader() == null) throw new NullPointerException("header");
RingQueue ring = remoteQueue.get(networkPacket.getHeader().getType());
int currentGeneration = ring == null ? 0 : ring.getGeneration(networkPacket.getHeader().getPacketId());
networkPacket.getHeader().setGeneration(currentGeneration);
Packet packet = new Packet(networkPacket.getHeader().getRole()); Packet packet = new Packet(networkPacket.getHeader().getRole());
packet.setHeader(networkPacket.getHeader()); packet.setHeader(networkPacket.getHeader());
@@ -432,8 +426,7 @@ public abstract class AbstractTeamspeakClientSocket
"packet is fragment, but not splittable" "packet is fragment, but not splittable"
); );
boolean encrypted = !networkPacket.getHeader().getPacketFlag(HeaderFlag.UNENCRYPTED) && boolean encrypted = !networkPacket.getHeader().getPacketFlag(HeaderFlag.UNENCRYPTED);
networkPacket.getHeader().getType().canEncrypt();
if (networkPacket.getHeader().getType().mustEncrypt() && !encrypted) if (networkPacket.getHeader().getType().mustEncrypt() && !encrypted)
throw new IllegalArgumentException( throw new IllegalArgumentException(
@@ -446,7 +439,7 @@ public abstract class AbstractTeamspeakClientSocket
// Decrypt // Decrypt
if (encrypted) { if (encrypted) {
Ts3Debugging.debug("[PROTOCOL] DECRYPT " + networkPacket.getHeader().getType().name() Ts3Debugging.debug("[PROTOCOL] DECRYPT " + networkPacket.getHeader().getType().name()
+ " generation=" + currentGeneration); + " generation=" + networkPacket.getHeader().getGeneration());
try { try {
packetBuffer = ByteBuffer.wrap( packetBuffer = ByteBuffer.wrap(
@@ -521,6 +514,15 @@ public abstract class AbstractTeamspeakClientSocket
continue; continue;
} }
// Get packet generation
RemoteCounter counter = remoteSendCounter.get(networkPacket.getHeader().getType());
int generation = 0;
if (counter != null) {
generation = counter.getGeneration(networkPacket.getHeader().getPacketId());
}
networkPacket.getHeader().setGeneration(generation);
// Read packet (decrypt, decompress, etc)
Packet packet = readPacketIntl(networkPacket); Packet packet = readPacketIntl(networkPacket);
// Find if the packet must be acknowledged // Find if the packet must be acknowledged
@@ -561,16 +563,14 @@ public abstract class AbstractTeamspeakClientSocket
if (packet.getHeader().getType().canResend() && if (packet.getHeader().getType().canResend() &&
packet.getHeader().getType() != PacketBodyType.INIT1) { packet.getHeader().getType() != PacketBodyType.INIT1) {
// Find if we already acknowledged this packet // Find if we already acknowledged this packet
RingQueue ring = remoteQueue.get(packet.getHeader().getType()); if (!counter.put(packet.getHeader().getPacketId())) {
if (ring.isSet(packet.getHeader().getPacketId())) {
Ts3Debugging.debug("[PROTOCOL] Ignoring handling resent packet: " + Ts3Debugging.debug("[PROTOCOL] Ignoring handling resent packet: " +
packet + " " + packet + " " +
packet.getHeader().getType().name() + " id=" + packet.getHeader().getType().name() + " id=" +
packet.getHeader().getPacketId()); packet.getHeader().getPacketId() + " generation=" +
packet.getHeader().getGeneration());
continue; // Drop the packet continue; // Drop the packet
} else {
ring.set(packet.getHeader().getPacketId());
} }
} }
@@ -809,19 +809,249 @@ public abstract class AbstractTeamspeakClientSocket
} }
} }
private interface PacketCounter { public interface RemoteCounter {
Pair<Integer, Integer> next();
int getPacketId();
boolean setPacketId(int packetId);
int getGenerationId();
void setGenerationId(int i);
default Pair<Integer,Integer> current() { /**
return new Pair<>(getPacketId(), getGenerationId()); * Gets the generation for the specified packet ID.
*
* @param packetId packet ID
* @return expected generation
*/
int getGeneration(int packetId);
/**
* Gets the current generation of the counter.
*
* @return current generation
*/
int getCurrentGeneration();
/**
* Counts the given packet by its packet ID and stores it in history.
*
* @param packetId packet to put
* @return true if the packet was placed, false otherwise
*/
boolean put(int packetId);
}
public static class RemoteCounterZero implements RemoteCounter {
@Override
public int getGeneration(int packetId) {
return 0;
}
@Override
public int getCurrentGeneration() {
return 0;
}
@Override
public boolean put(int packetId) {
return true;
}
}
public static class RemoteCounterFull implements RemoteCounter {
private final Integer[] buffer; // history buffer
private final int bufferSize; // size of the packet history buffer, used for remembering received packets
private final int generationSize; // size of the physical window, typically 65536 for a 16-bit/ushort window field
private Pair<Integer, Integer>
bufferStart = new Pair<>(0,0), // position of the start of the buffer in the window
bufferEnd; // position of the end of the buffer in the window
/**
* Constructs a new full counter
*
* @param windowSize size of the packet history buffer, used for remembering received packets
* @param generationSize length of any given generation, typically 65536 for a 16-bit/ushort window field
*/
public RemoteCounterFull(int generationSize, int windowSize) { // most often will be 65536, 100
if (windowSize >= generationSize) throw new IllegalArgumentException("windowSize > generationSize");
this.buffer = new Integer[windowSize];
this.bufferSize = windowSize;
this.generationSize = generationSize;
this.bufferEnd = new Pair<>(0, windowSize - 1);
}
@Override
public int getGeneration(int packetId) {
packetId %= generationSize;
// find if right of the start and within the start's bounds
if (packetId >= bufferStart.getValue()
&& packetId < bufferStart.getValue() + bufferSize
&& packetId < generationSize) {
return bufferStart.getKey();
}
// find if left of end and within the end's bounds
if (packetId <= bufferEnd.getValue()
&& packetId > 0
&& packetId > bufferEnd.getValue() - bufferSize
&& packetId < generationSize) {
return bufferEnd.getKey();
}
// find if outside of buffer to the right
if (packetId > bufferEnd.getValue())
return bufferEnd.getKey(); // ahead in current generation
// find if outside of buffer to the left
if (packetId < bufferStart.getValue())
return bufferEnd.getKey() + 1; // modulated wrap, ahead of current generation
// should never arrive here.
throw new IllegalStateException();
}
@Override
public int getCurrentGeneration() {
return bufferEnd.getKey();
}
@Override
public boolean put(int packetId) {
// find if right of the start and within the start's bounds
if (packetId >= bufferStart.getValue()
&& packetId < bufferStart.getValue() + bufferSize
&& packetId < generationSize) {
return putRelative(packetId - bufferStart.getValue(), bufferStart.getKey());
}
// find if left of end and within the end's bounds
if (packetId <= bufferEnd.getValue()
&& packetId >= 0
&& packetId > bufferEnd.getValue() - bufferSize
&& packetId < generationSize) {
return putRelative(bufferSize - (bufferEnd.getValue() - packetId) - 1, bufferEnd.getKey());
}
// distance the start position must move right by
int amountMoved = 0;
// find if outside of buffer to the right
if (packetId > bufferEnd.getValue()) { // slide buffer forward uniformly
amountMoved = packetId - bufferEnd.getValue();
}
// find if outside of buffer to the left
if (packetId < bufferStart.getValue()) { // start wrapping buffer or continue to do so
amountMoved =
(generationSize - bufferStart.getValue()) +
packetId + 1;
amountMoved -= bufferSize;
}
if (amountMoved > 0) { // should always be true
int toMove = Math.max(0, bufferSize - amountMoved);
Ts3Debugging.debug(Arrays.toString(buffer));
if (toMove > 0 && toMove < bufferSize)
System.arraycopy(buffer, amountMoved, buffer, 0, toMove);
int toNullify = Math.max(0, Math.min(bufferSize, amountMoved));
for (int i = bufferSize - toNullify; i < bufferSize; i ++)
buffer[i] = null;
Ts3Debugging.debug(Arrays.toString(buffer));
int bufferStartGeneration = bufferStart.getKey();
int bufferStartPosition = bufferStart.getValue();
bufferStartPosition += amountMoved;
if (bufferStartPosition >= generationSize) {
bufferStartPosition %= generationSize;
bufferStartGeneration ++;
}
int bufferEndGeneration;
int bufferEndPosition = (bufferStartPosition + bufferSize - 1) % generationSize;
if (bufferEndPosition < bufferStartPosition)
bufferEndGeneration = bufferStartGeneration + 1;
else
bufferEndGeneration = bufferStartGeneration;
this.bufferStart = new Pair<>(bufferStartGeneration, bufferStartPosition);
this.bufferEnd = new Pair<>(bufferEndGeneration, bufferEndPosition);
} else
throw new IllegalStateException();
// if we had to adjust the buffer size, recursively retry put
return put(packetId);
}
private boolean putRelative(int index, int generation) {
Integer existing = buffer[index];
Ts3Debugging.debug("putRelative: " + index);
if (existing == null || existing != generation) {
buffer[index] = generation;
return true;
} else {
return false;
}
} }
} }
private class PacketCounterZero implements PacketCounter {
public static class RemoteCounterFullModulated extends RemoteCounterFull {
private final int modulationSize;
private final int modulationsPerGeneration;
/**
* Constructs a new full, modulated counter
* This counter is used to sub-modulate a full packet counter, in smaller increments, while still using
* the same code in the backend to modulate the physical window.
*
* @param generationSize length of any given generation, typically 65536 for a 16-bit/ushort window field
* @param windowSize size of the packet history buffer, used for remembering received packets
* @param modulationSize slots within the generation to extrapolated generations by
*/
public RemoteCounterFullModulated(int generationSize, int windowSize, int modulationSize) {
super(generationSize, windowSize);
if (generationSize % modulationSize != 0)
throw new IllegalArgumentException("modulationSize invalid");
this.modulationSize = modulationSize;
this.modulationsPerGeneration = generationSize / modulationSize;
}
private int calculateSubGeneration(int packetId) {
return packetId == 0 ? 0 : (int) Math.floor(packetId / modulationSize);
}
@Override
public int getGeneration(int packetId) {
return (super.getGeneration(packetId) * modulationsPerGeneration) + calculateSubGeneration(packetId);
}
@Override
public int getCurrentGeneration() {
// We can't exactly do this right now. This will require extra work.
throw new UnsupportedOperationException();
}
}
public interface LocalCounter {
Pair<Integer, Integer> next();
int getPacketId();
boolean setPacketId(int packetId);
int getGeneration();
void setGeneration(int i);
default Pair<Integer,Integer> current() {
return new Pair<>(getPacketId(), getGeneration());
}
}
public static class LocalCounterZero implements LocalCounter {
@Override @Override
public Pair<Integer, Integer> next() { public Pair<Integer, Integer> next() {
return new Pair<>(0,0); return new Pair<>(0,0);
@@ -838,27 +1068,31 @@ public abstract class AbstractTeamspeakClientSocket
} }
@Override @Override
public int getGenerationId() { public int getGeneration() {
return 0; return 0;
} }
@Override @Override
public void setGenerationId(int i) { public void setGeneration(int i) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
} }
private class PacketCounterFull implements PacketCounter { public static class LocalCounterFull implements LocalCounter {
private final Object sendLock = new Object(); private final Object sendLock = new Object();
private final boolean counting; private final boolean counting;
private final int generationSize;
private int packetId = 0; private int packetId = 0;
private int generationId = 0; private int generationId = 0;
public PacketCounterFull(boolean counting) { public LocalCounterFull(int generationSize, boolean counting) {
this.generationSize = generationSize;
this.counting = counting; this.counting = counting;
} }
public PacketCounterFull(int start, boolean counting) { public LocalCounterFull(int generationSize, int start, boolean counting) {
this.generationSize = generationSize;
this.counting = counting; this.counting = counting;
setPacketId(start); setPacketId(start);
@@ -873,9 +1107,9 @@ public abstract class AbstractTeamspeakClientSocket
synchronized (sendLock) { synchronized (sendLock) {
if (this.packetId == packetId) return false; if (this.packetId == packetId) return false;
if (packetId >= 65536) { if (packetId >= generationSize) {
this.packetId = 0; // wrap around this.packetId = 0; // wrap around
if (isCounting()) setGenerationId(getGenerationId() + 1); if (isCounting()) setGeneration(getGeneration() + 1);
} else { } else {
this.packetId = packetId; this.packetId = packetId;
} }
@@ -884,14 +1118,14 @@ public abstract class AbstractTeamspeakClientSocket
} }
} }
public int getGenerationId() { public int getGeneration() {
return generationId; return generationId;
} }
@Override @Override
public void setGenerationId(int i) { public void setGeneration(int i) {
synchronized (sendLock) { synchronized (sendLock) {
Ts3Debugging.debug("setGenerationId: " + i); Ts3Debugging.debug("setGeneration: " + i);
this.generationId = i; this.generationId = i;
} }
} }
@@ -0,0 +1,32 @@
package com.github.manevolent.ts3j;
import com.github.manevolent.ts3j.protocol.socket.client.AbstractTeamspeakClientSocket;
import com.github.manevolent.ts3j.util.Ts3Debugging;
import javafx.util.Pair;
import junit.framework.TestCase;
public class PacketCounterTest extends TestCase {
public static void main(String[] args) throws Exception {
new PacketCounterTest().testParser();
}
public void testParser() throws Exception {
Ts3Debugging.setEnabled(true);
AbstractTeamspeakClientSocket.LocalCounter localCounter =
new AbstractTeamspeakClientSocket.LocalCounterFull(65536, 0, true);
AbstractTeamspeakClientSocket.RemoteCounter remoteCounter =
new AbstractTeamspeakClientSocket.RemoteCounterFull(65536, 100);
for (int i = 0; i < 65580 / 5; i ++) {
Pair<Integer, Integer> packet = null;
for (int n = 0; n < 5; n ++)
packet = localCounter.next();
assertEquals(true, remoteCounter.put(packet.getKey()));
assertEquals(packet.getValue().intValue(), remoteCounter.getCurrentGeneration());
}
}
}
@@ -41,23 +41,6 @@ public class ServerConnectionTest {
10000L 10000L
); );
client.setMicrophone(new Microphone() {
@Override
public boolean isReady() {
return true;
}
@Override
public CodecType getCodec() {
return CodecType.OPUS_MUSIC;
}
@Override
public byte[] provide() {
return new byte[20];
}
});
assertEquals(client.getState(), ClientConnectionState.CONNECTED); assertEquals(client.getState(), ClientConnectionState.CONNECTED);
while (true) { while (true) {