Beginning to work on audio stuff now. Lots of changes.
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
package com.github.manevolent.ts3j.audio;
|
||||||
|
|
||||||
|
public enum AudioEncoding {
|
||||||
|
OPUS,
|
||||||
|
SPEEX,
|
||||||
|
PCM
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.github.manevolent.ts3j.audio;
|
||||||
|
|
||||||
|
public interface Microphone {
|
||||||
|
default boolean isMuted() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isReady();
|
||||||
|
|
||||||
|
default AudioEncoding getEncoding() {
|
||||||
|
return AudioEncoding.PCM;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] provide();
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.github.manevolent.ts3j.event;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* TeamSpeak 3 Java API
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 Bert De Geyter
|
||||||
|
* %%
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ClientChannelGroupChangedEvent extends BaseEvent {
|
||||||
|
|
||||||
|
public ClientChannelGroupChangedEvent(Map<String, String> map) {
|
||||||
|
super(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getClientId() {
|
||||||
|
return getInt("clid");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getChannelGroupId() {
|
||||||
|
return getInt("cgid");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getChannelId() {
|
||||||
|
return getInt("cid");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInheritedChannelId() {
|
||||||
|
return getInt("cgi");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fire(TS3Listener listener) {
|
||||||
|
listener.onClientChannelGroupChanged(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,4 +57,7 @@ public interface TS3Listener {
|
|||||||
void onServerGroupList(ServerGroupListEvent e);
|
void onServerGroupList(ServerGroupListEvent e);
|
||||||
|
|
||||||
void onClientNeededPermissions(ClientNeededPermissionsEvent e);
|
void onClientNeededPermissions(ClientNeededPermissionsEvent e);
|
||||||
|
|
||||||
|
void onClientChannelGroupChanged(ClientChannelGroupChangedEvent e);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import com.github.manevolent.ts3j.protocol.header.HeaderFlag;
|
|||||||
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
public class PacketBody5Pong extends PacketBody {
|
public class PacketBody5Pong extends PacketBody {
|
||||||
private int packetId;
|
private int packetId;
|
||||||
|
|||||||
+6
-3
@@ -141,6 +141,8 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
|||||||
|
|
||||||
}
|
}
|
||||||
} else if (packet.getBody() instanceof PacketBody2Command) {
|
} else if (packet.getBody() instanceof PacketBody2Command) {
|
||||||
|
Ts3Debugging.debug(((PacketBody2Command) packet.getBody()).getText());
|
||||||
|
|
||||||
SingleCommand command = ((PacketBody2Command) packet.getBody()).parse().simplifyOne();
|
SingleCommand command = ((PacketBody2Command) packet.getBody()).parse().simplifyOne();
|
||||||
|
|
||||||
if (command.getName().equalsIgnoreCase("initivexpand2")) {
|
if (command.getName().equalsIgnoreCase("initivexpand2")) {
|
||||||
@@ -186,18 +188,19 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
|||||||
|
|
||||||
getClient().setSecureParameters(Ts3Crypt.cryptoInit2(license, alphaBytes, beta, keyPair.getValue()));
|
getClient().setSecureParameters(Ts3Crypt.cryptoInit2(license, alphaBytes, beta, keyPair.getValue()));
|
||||||
|
|
||||||
|
alphaBytes = null;
|
||||||
|
|
||||||
getClient().writePacket(new PacketBody2Command(
|
getClient().writePacket(new PacketBody2Command(
|
||||||
ProtocolRole.CLIENT,
|
ProtocolRole.CLIENT,
|
||||||
new SingleCommand(
|
new SingleCommand(
|
||||||
"clientinit",
|
"clientinit",
|
||||||
ProtocolRole.CLIENT,
|
ProtocolRole.CLIENT,
|
||||||
new CommandSingleParameter("client_nickname", getClient().getNickname()),
|
new CommandSingleParameter("client_nickname", getClient().getNickname()),
|
||||||
new CommandSingleParameter("client_version", "3.1.8 [Build: 1516614607]"),
|
new CommandSingleParameter("client_version", "3.?.? [Build: 5680278000]"),
|
||||||
new CommandSingleParameter("client_platform", "Windows"),
|
new CommandSingleParameter("client_platform", "Windows"),
|
||||||
new CommandSingleParameter(
|
new CommandSingleParameter(
|
||||||
"client_version_sign",
|
"client_version_sign",
|
||||||
"LJ5q+KWT4KwBX7oR/9j9A12hBrq5ds5ony99" +
|
"DX5NIYLvfJEUjuIbCidnoeozxIDRRkpq3I9vVMBmE9L2qnekOoBzSenkzsg2lC9CMv8K5hkEzhr2TYUYSwUXCg=="
|
||||||
"f9kepNmqFskhT7gfB51bAJNgAMOzXVCeaItNmc10F2wUNktqCw=="
|
|
||||||
),
|
),
|
||||||
new CommandSingleParameter("client_input_hardware", "1"),
|
new CommandSingleParameter("client_input_hardware", "1"),
|
||||||
new CommandSingleParameter("client_output_hardware", "1"),
|
new CommandSingleParameter("client_output_hardware", "1"),
|
||||||
|
|||||||
+3
-6
@@ -119,7 +119,8 @@ public class PacketTransformation {
|
|||||||
return outputBuffer;
|
return outputBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] decrypt(PacketHeader header, ByteBuffer buffer, int dataLen) {
|
public byte[] decrypt(PacketHeader header, ByteBuffer buffer, int dataLen)
|
||||||
|
throws InvalidCipherTextException {
|
||||||
Pair<byte[], byte[]> parameters = computeParameters(header);
|
Pair<byte[], byte[]> parameters = computeParameters(header);
|
||||||
|
|
||||||
byte[] headerWithoutMac = new byte[header.getSize() - MAC_LEN];
|
byte[] headerWithoutMac = new byte[header.getSize() - MAC_LEN];
|
||||||
@@ -142,11 +143,7 @@ public class PacketTransformation {
|
|||||||
|
|
||||||
len = cipher.processBytes(buffer.array(), header.getSize(), dataLen, result, 0);
|
len = cipher.processBytes(buffer.array(), header.getSize(), dataLen, result, 0);
|
||||||
len += cipher.processBytes(buffer.array(), 0, MAC_LEN, result, len);
|
len += cipher.processBytes(buffer.array(), 0, MAC_LEN, result, len);
|
||||||
try {
|
len += cipher.doFinal(result, len);
|
||||||
len += cipher.doFinal(result, len);
|
|
||||||
} catch (InvalidCipherTextException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len != dataLen)
|
if (len != dataLen)
|
||||||
throw new IllegalArgumentException(len + " != " + dataLen);
|
throw new IllegalArgumentException(len + " != " + dataLen);
|
||||||
|
|||||||
+218
-69
@@ -4,6 +4,7 @@ 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;
|
||||||
@@ -19,9 +20,11 @@ import com.github.manevolent.ts3j.util.QuickLZ;
|
|||||||
import com.github.manevolent.ts3j.util.Ts3Crypt;
|
import com.github.manevolent.ts3j.util.Ts3Crypt;
|
||||||
import com.github.manevolent.ts3j.util.Ts3Debugging;
|
import com.github.manevolent.ts3j.util.Ts3Debugging;
|
||||||
import javafx.util.Pair;
|
import javafx.util.Pair;
|
||||||
|
import org.bouncycastle.crypto.InvalidCipherTextException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramPacket;
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -39,8 +42,10 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
private final Map<PacketBodyType, Reassembly> reassemblyQueue = new ConcurrentHashMap<>();
|
private final Map<PacketBodyType, Reassembly> reassemblyQueue = new ConcurrentHashMap<>();
|
||||||
private final Map<String, Object> clientOptions = new ConcurrentHashMap<>();
|
private final Map<String, Object> clientOptions = new ConcurrentHashMap<>();
|
||||||
private final Map<Integer, PacketResponse> sendQueue = new ConcurrentHashMap<>();
|
private final Map<Integer, PacketResponse> sendQueue = new ConcurrentHashMap<>();
|
||||||
|
private final Map<Integer, PacketResponse> sendQueueLow = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private final Map<PacketBodyType, PacketCounter> localSendCounter = new ConcurrentHashMap<>();
|
private final Map<PacketBodyType, PacketCounter> localSendCounter = new ConcurrentHashMap<>();
|
||||||
private final Map<PacketBodyType, PacketCounter> remoteSendCounter = new ConcurrentHashMap<>();
|
private final Map<PacketBodyType, RingQueue> remoteQueue = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private CommandProcessor commandProcessor;
|
private CommandProcessor commandProcessor;
|
||||||
|
|
||||||
@@ -53,6 +58,8 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
private int clientId = 0;
|
private int clientId = 0;
|
||||||
private int packetId = 0;
|
private int packetId = 0;
|
||||||
|
|
||||||
|
private long lastPing;
|
||||||
|
|
||||||
private final NetworkReader networkReader = new NetworkReader();
|
private final NetworkReader networkReader = new NetworkReader();
|
||||||
private final NetworkHandler networkHandler = new NetworkHandler();
|
private final NetworkHandler networkHandler = new NetworkHandler();
|
||||||
private Thread networkThread = null;
|
private Thread networkThread = null;
|
||||||
@@ -75,14 +82,15 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
reassemblyQueue.put(bodyType, new Reassembly());
|
reassemblyQueue.put(bodyType, new Reassembly());
|
||||||
|
|
||||||
// Initialize counters
|
// Initialize counters
|
||||||
for (PacketBodyType bodyType : PacketBodyType.values())
|
for (PacketBodyType bodyType : PacketBodyType.values()) {
|
||||||
if (bodyType == PacketBodyType.INIT1) {
|
if (bodyType == PacketBodyType.INIT1) {
|
||||||
localSendCounter.put(PacketBodyType.INIT1, new PacketCounterZero());
|
localSendCounter.put(bodyType, new PacketCounterZero());
|
||||||
remoteSendCounter.put(PacketBodyType.INIT1, new PacketCounterZero());
|
|
||||||
} else {
|
} else {
|
||||||
localSendCounter.put(bodyType, new PacketCounterFull());
|
localSendCounter.put(bodyType, new PacketCounterFull(true));
|
||||||
remoteSendCounter.put(bodyType, new PacketCounterFull(-1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remoteQueue.put(bodyType, new RingQueue(100, 65536));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void start() {
|
protected void start() {
|
||||||
@@ -104,7 +112,7 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
setTransformation(new PacketTransformation(parameters.getIvStruct(), parameters.getFakeSignature()));
|
setTransformation(new PacketTransformation(parameters.getIvStruct(), parameters.getFakeSignature()));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract NetworkPacket readNetworkPacket() throws IOException;
|
protected abstract NetworkPacket readNetworkPacket(int timeout) throws IOException;
|
||||||
|
|
||||||
protected abstract void writeNetworkPacket(NetworkPacket packet) throws IOException;
|
protected abstract void writeNetworkPacket(NetworkPacket packet) throws IOException;
|
||||||
|
|
||||||
@@ -166,21 +174,93 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
return connectionState == ClientConnectionState.CONNECTED;
|
return connectionState == ClientConnectionState.CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private PacketHeader newOutgoingHeader() {
|
||||||
public void writePacket(PacketBody body) throws IOException, TimeoutException {
|
|
||||||
PacketHeader header;
|
PacketHeader header;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
header = body.getRole().getHeaderClass().newInstance();
|
header = getRole().getOut().getHeaderClass().newInstance();
|
||||||
} catch (ReflectiveOperationException e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writePacket(PacketBody body) throws IOException, TimeoutException {
|
||||||
// Construct a network packet
|
// Construct a network packet
|
||||||
Packet packet = new Packet(body.getRole());
|
Packet packet = new Packet(body.getRole());
|
||||||
packet.setHeader(header);
|
|
||||||
packet.setBody(body);
|
packet.setBody(body);
|
||||||
|
|
||||||
|
// Generate header
|
||||||
|
PacketHeader header = newOutgoingHeader();
|
||||||
|
|
||||||
|
// Ensure type matches
|
||||||
|
header.setType(packet.getBody().getType());
|
||||||
|
|
||||||
|
// Set header values
|
||||||
|
body.setHeaderValues(header);
|
||||||
|
|
||||||
|
if (header instanceof ClientPacketHeader)
|
||||||
|
((ClientPacketHeader) header).setClientId(clientId);
|
||||||
|
|
||||||
|
// Count packet
|
||||||
|
Pair<Integer, Integer> counter;
|
||||||
|
|
||||||
|
switch (header.getType()) {
|
||||||
|
case ACK:
|
||||||
|
counter = new Pair<>(
|
||||||
|
((PacketBody6Ack)packet.getBody()).getPacketId(),
|
||||||
|
remoteQueue.get(PacketBodyType.ACK).getGeneration(
|
||||||
|
((PacketBody6Ack) packet.getBody()).getPacketId()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ACK_LOW:
|
||||||
|
counter = new Pair<>(
|
||||||
|
((PacketBody7AckLow)packet.getBody()).getPacketId(),
|
||||||
|
remoteQueue.get(PacketBodyType.ACK_LOW).getGeneration(
|
||||||
|
((PacketBody7AckLow) packet.getBody()).getPacketId()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case PONG:
|
||||||
|
counter = new Pair<>(
|
||||||
|
((PacketBody5Pong)packet.getBody()).getPacketId(),
|
||||||
|
remoteQueue.get(PacketBodyType.PONG).getGeneration(
|
||||||
|
((PacketBody5Pong) packet.getBody()).getPacketId()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
/*case PING:
|
||||||
|
counter = localSendCounter.get(PacketBodyType.PING).current();
|
||||||
|
break;*/
|
||||||
|
case INIT1:
|
||||||
|
counter = new Pair<>(101, 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
counter = localSendCounter.get(header.getType()).next();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
header.setPacketId(counter.getKey());
|
||||||
|
header.setGeneration(counter.getValue());
|
||||||
|
|
||||||
|
switch (header.getType()) {
|
||||||
|
case INIT1:
|
||||||
|
case PONG:
|
||||||
|
case PING:
|
||||||
|
// setHeaderValues will do this, too
|
||||||
|
header.setPacketFlag(HeaderFlag.UNENCRYPTED, true);
|
||||||
|
break;
|
||||||
|
case COMMAND:
|
||||||
|
case COMMAND_LOW:
|
||||||
|
header.setPacketFlag(HeaderFlag.NEW_PROTOCOL, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
packet.setHeader(header);
|
||||||
|
|
||||||
writePacket(packet);
|
writePacket(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,30 +273,6 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
packet.getHeader().getRole().name() + " != " +
|
packet.getHeader().getRole().name() + " != " +
|
||||||
getRole().getOut().name());
|
getRole().getOut().name());
|
||||||
|
|
||||||
// Ensure type matches
|
|
||||||
packet.getHeader().setType(packet.getBody().getType());
|
|
||||||
|
|
||||||
// Set header values
|
|
||||||
packet.getBody().setHeaderValues(packet.getHeader());
|
|
||||||
|
|
||||||
Pair<Integer, Integer> counterResult = localSendCounter.get(packet.getHeader().getType()).next();
|
|
||||||
if (packet.getHeader() instanceof ClientPacketHeader)
|
|
||||||
((ClientPacketHeader) packet.getHeader()).setClientId(clientId);
|
|
||||||
|
|
||||||
packet.getHeader().setPacketId(counterResult.getKey());
|
|
||||||
packet.getHeader().setGeneration(counterResult.getValue());
|
|
||||||
|
|
||||||
switch (packet.getHeader().getType()) {
|
|
||||||
case INIT1:
|
|
||||||
packet.getHeader().setPacketFlag(HeaderFlag.UNENCRYPTED, true);
|
|
||||||
packet.getHeader().setPacketId((short)101);
|
|
||||||
break;
|
|
||||||
case COMMAND:
|
|
||||||
case COMMAND_LOW:
|
|
||||||
packet.getHeader().setPacketFlag(HeaderFlag.NEW_PROTOCOL, true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet.getSize() > 500) {
|
if (packet.getSize() > 500) {
|
||||||
int totalSize = packet.getBody().getSize();
|
int totalSize = packet.getBody().getSize();
|
||||||
|
|
||||||
@@ -274,6 +330,10 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
ByteBuffer outputBuffer;
|
ByteBuffer outputBuffer;
|
||||||
|
|
||||||
if (!packet.getHeader().getPacketFlag(HeaderFlag.UNENCRYPTED)) {
|
if (!packet.getHeader().getPacketFlag(HeaderFlag.UNENCRYPTED)) {
|
||||||
|
Ts3Debugging.debug("[PROTOCOL] ENCRYPT " +
|
||||||
|
packet.getHeader().getType().name() + " generation=" +
|
||||||
|
packet.getHeader().getGeneration());
|
||||||
|
|
||||||
outputBuffer = getTransformation().encrypt(packet);
|
outputBuffer = getTransformation().encrypt(packet);
|
||||||
} else {
|
} else {
|
||||||
if (packet.getHeader().getType() == PacketBodyType.INIT1) {
|
if (packet.getHeader().getType() == PacketBodyType.INIT1) {
|
||||||
@@ -315,7 +375,7 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
writeNetworkPacket(networkPacket);
|
writeNetworkPacket(networkPacket);
|
||||||
|
|
||||||
// If necessary, fulfill promise for the packet acknowledgement or throw TimeoutException
|
// If necessary, fulfill promise for the packet acknowledgement or throw TimeoutException
|
||||||
if (response != null) {
|
if (response != null && networkPacket.getHeader().getType() != PacketBodyType.PING) {
|
||||||
while (response.getRetries() < response.getMaxTries()) {
|
while (response.getRetries() < response.getMaxTries()) {
|
||||||
try {
|
try {
|
||||||
if (getState() == ClientConnectionState.DISCONNECTED)
|
if (getState() == ClientConnectionState.DISCONNECTED)
|
||||||
@@ -344,6 +404,11 @@ 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());
|
||||||
|
|
||||||
@@ -366,18 +431,25 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
" is unencrypted, but must be encrypted"
|
" is unencrypted, but must be encrypted"
|
||||||
);
|
);
|
||||||
|
|
||||||
ByteBuffer packetBuffer =
|
ByteBuffer packetBuffer = networkPacket.getBuffer();
|
||||||
(ByteBuffer) networkPacket.getBuffer().position(networkPacket.getHeader().getSize());
|
|
||||||
|
|
||||||
// Decrypt
|
// Decrypt
|
||||||
if (encrypted) {
|
if (encrypted) {
|
||||||
packetBuffer = ByteBuffer.wrap(
|
Ts3Debugging.debug("[PROTOCOL] DECRYPT " + networkPacket.getHeader().getType().name()
|
||||||
getTransformation().decrypt(
|
+ " generation=" + currentGeneration);
|
||||||
networkPacket.getHeader(),
|
|
||||||
packetBuffer,
|
try {
|
||||||
networkPacket.getDatagram().getLength() - networkPacket.getHeader().getSize()
|
packetBuffer = ByteBuffer.wrap(
|
||||||
)
|
getTransformation().decrypt(
|
||||||
);
|
networkPacket.getHeader(),
|
||||||
|
packetBuffer,
|
||||||
|
networkPacket.getDatagram().getLength() -
|
||||||
|
networkPacket.getHeader().getSize()
|
||||||
|
)
|
||||||
|
).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
} catch (InvalidCipherTextException e) {
|
||||||
|
throw new IOException("failed to decrypt " + networkPacket.getHeader().getType().name(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fragment handling
|
// Fragment handling
|
||||||
@@ -399,7 +471,9 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
if (packet.getBody() instanceof PacketBodyCompressed) {
|
if (packet.getBody() instanceof PacketBodyCompressed) {
|
||||||
byte[] decompressed = QuickLZ.decompress(((PacketBodyCompressed) packet.getBody()).getCompressed());
|
byte[] decompressed = QuickLZ.decompress(((PacketBodyCompressed) packet.getBody()).getCompressed());
|
||||||
|
|
||||||
packetBuffer = ByteBuffer.wrap(decompressed);
|
packetBuffer = ByteBuffer
|
||||||
|
.wrap(decompressed)
|
||||||
|
.order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
packet = new Packet(packet.getRole(), packet.getHeader());
|
packet = new Packet(packet.getRole(), packet.getHeader());
|
||||||
|
|
||||||
@@ -413,43 +487,89 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Packet readPacket() throws IOException, TimeoutException {
|
public Packet readPacket() throws
|
||||||
|
IOException,
|
||||||
|
TimeoutException {
|
||||||
|
NetworkPacket networkPacket;
|
||||||
|
long timeout;
|
||||||
|
|
||||||
while (isReading()) {
|
while (isReading()) {
|
||||||
Packet packet = readPacketIntl(readNetworkPacket());
|
try {
|
||||||
|
timeout = getState() == ClientConnectionState.CONNECTED ?
|
||||||
|
Math.min(1000L, 1000L - (System.currentTimeMillis() - lastPing)) :
|
||||||
|
Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
if (timeout <= 0) throw new SocketTimeoutException();
|
||||||
|
|
||||||
|
networkPacket = readNetworkPacket((int) timeout);
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
if (getState() == ClientConnectionState.CONNECTED) {
|
||||||
|
writePacket(new PacketBody4Ping(getRole().getOut()));
|
||||||
|
lastPing = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Packet packet = readPacketIntl(networkPacket);
|
||||||
|
|
||||||
// Find if the packet must be acknowledged
|
// Find if the packet must be acknowledged
|
||||||
PacketBodyType ackType = packet.getHeader().getType().getAcknolwedgedBy();
|
PacketBodyType ackType = packet
|
||||||
|
.getHeader()
|
||||||
|
.getType()
|
||||||
|
.getAcknolwedgedBy();
|
||||||
|
|
||||||
if (ackType != null) {
|
if (ackType != null) {
|
||||||
switch (ackType) {
|
switch (ackType) {
|
||||||
case ACK:
|
case ACK:
|
||||||
|
Ts3Debugging.debug("[PROTOCOL] Acknowledge " + packet + " " + packet.getHeader().getType().name()
|
||||||
|
+ " id=" + packet.getHeader().getPacketId());
|
||||||
writePacket(new PacketBody6Ack(getRole().getOut(), packet.getHeader().getPacketId()));
|
writePacket(new PacketBody6Ack(getRole().getOut(), packet.getHeader().getPacketId()));
|
||||||
break;
|
break;
|
||||||
case ACK_LOW:
|
case ACK_LOW:
|
||||||
writePacket(new PacketBody7AckLow(getRole().getOut(), packet.getHeader().getPacketId()));
|
writePacket(new PacketBody7AckLow(getRole().getOut(), packet.getHeader().getPacketId()));
|
||||||
break;
|
break;
|
||||||
case PONG:
|
case PONG:
|
||||||
writePacket(new PacketBody5Pong(getRole().getOut(), packet.getHeader().getPacketId()));
|
Packet pong = new Packet(
|
||||||
|
getRole().getOut(),
|
||||||
|
newOutgoingHeader()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pong.getHeader() instanceof ClientPacketHeader)
|
||||||
|
((ClientPacketHeader) pong.getHeader()).setClientId(getClientId());
|
||||||
|
|
||||||
|
pong.getHeader().setPacketFlag(HeaderFlag.UNENCRYPTED, true);
|
||||||
|
pong.getHeader().setPacketId(packet.getHeader().getPacketId());
|
||||||
|
pong.setBody(new PacketBody5Pong(getRole().getOut(), packet.getHeader().getPacketId()));
|
||||||
|
|
||||||
|
writePacket(pong);
|
||||||
|
|
||||||
continue; // Don't pass pings to the parent
|
continue; // Don't pass pings to the parent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (packet.getHeader().getType().canResend() &&
|
||||||
|
packet.getHeader().getType() != PacketBodyType.INIT1) {
|
||||||
|
// Find if we already acknowledged this packet
|
||||||
|
RingQueue ring = remoteQueue.get(packet.getHeader().getType());
|
||||||
|
if (ring.isSet(packet.getHeader().getPacketId())) {
|
||||||
|
Ts3Debugging.debug("[PROTOCOL] Ignoring handling resent packet: " +
|
||||||
|
packet + " " +
|
||||||
|
packet.getHeader().getType().name() + " id=" +
|
||||||
|
packet.getHeader().getPacketId());
|
||||||
|
|
||||||
|
continue; // Drop the packet
|
||||||
|
} else {
|
||||||
|
ring.set(packet.getHeader().getPacketId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Find if the packet must be reassembled
|
// Find if the packet must be reassembled
|
||||||
if (packet.getHeader().getType().isSplittable()) {
|
if (packet.getHeader().getType().isSplittable()) {
|
||||||
packet = reassemblyQueue.get(packet.getHeader().getType()).put(packet);
|
packet = reassemblyQueue.get(packet.getHeader().getType()).put(packet);
|
||||||
if (packet == null) continue;
|
if (packet == null) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.getHeader().getType().canResend() && packet.getHeader().getType() != PacketBodyType.INIT1)
|
|
||||||
// Find if we already acknowledged this packet
|
|
||||||
if (!remoteSendCounter
|
|
||||||
.get(packet.getHeader().getType())
|
|
||||||
.setPacketId(packet.getHeader().getPacketId())) {
|
|
||||||
Ts3Debugging.debug("[PROTOCOL] Ignoring handling resent packet: " +
|
|
||||||
packet.getHeader().getType().name() + " id=" +
|
|
||||||
packet.getHeader().getPacketId());
|
|
||||||
continue; // Drop the packet
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find if the packet is itself an acknowledgement
|
// Find if the packet is itself an acknowledgement
|
||||||
boolean handle;
|
boolean handle;
|
||||||
PacketResponse response;
|
PacketResponse response;
|
||||||
@@ -459,7 +579,11 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
handle = false;
|
handle = false;
|
||||||
break;
|
break;
|
||||||
case ACK_LOW:
|
case ACK_LOW:
|
||||||
response = sendQueue.get(((PacketBody7AckLow)packet.getBody()).getPacketId());
|
response = sendQueueLow.get(((PacketBody7AckLow)packet.getBody()).getPacketId());
|
||||||
|
handle = false;
|
||||||
|
break;
|
||||||
|
case PONG:
|
||||||
|
response = null;
|
||||||
handle = false;
|
handle = false;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -680,6 +804,11 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
int getPacketId();
|
int getPacketId();
|
||||||
boolean setPacketId(int packetId);
|
boolean setPacketId(int packetId);
|
||||||
int getGenerationId();
|
int getGenerationId();
|
||||||
|
void setGenerationId(int i);
|
||||||
|
|
||||||
|
default Pair<Integer,Integer> current() {
|
||||||
|
return new Pair<>(getPacketId(), getGenerationId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PacketCounterZero implements PacketCounter {
|
private class PacketCounterZero implements PacketCounter {
|
||||||
@@ -702,18 +831,26 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
public int getGenerationId() {
|
public int getGenerationId() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGenerationId(int i) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PacketCounterFull implements PacketCounter {
|
private class PacketCounterFull implements PacketCounter {
|
||||||
private final Object sendLock = new Object();
|
private final Object sendLock = new Object();
|
||||||
|
private final boolean counting;
|
||||||
private int packetId = 0;
|
private int packetId = 0;
|
||||||
private int generationId = 0;
|
private int generationId = 0;
|
||||||
|
|
||||||
public PacketCounterFull() {
|
public PacketCounterFull(boolean counting) {
|
||||||
|
this.counting = counting;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PacketCounterFull(int start) {
|
public PacketCounterFull(int start, boolean counting) {
|
||||||
|
this.counting = counting;
|
||||||
|
|
||||||
setPacketId(start);
|
setPacketId(start);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -727,8 +864,8 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
if (this.packetId == packetId) return false;
|
if (this.packetId == packetId) return false;
|
||||||
|
|
||||||
if (packetId >= 65536) {
|
if (packetId >= 65536) {
|
||||||
packetId = 0;
|
this.packetId = 0; // wrap around
|
||||||
generationId++;
|
if (isCounting()) setGenerationId(getGenerationId() + 1);
|
||||||
} else {
|
} else {
|
||||||
this.packetId = packetId;
|
this.packetId = packetId;
|
||||||
}
|
}
|
||||||
@@ -741,13 +878,25 @@ public abstract class AbstractTeamspeakClientSocket
|
|||||||
return generationId;
|
return generationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setGenerationId(int i) {
|
||||||
|
synchronized (sendLock) {
|
||||||
|
Ts3Debugging.debug("setGenerationId: " + i);
|
||||||
|
this.generationId = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Pair<Integer, Integer> next() {
|
public Pair<Integer, Integer> next() {
|
||||||
synchronized (sendLock) {
|
synchronized (sendLock) {
|
||||||
setPacketId(packetId + 1);
|
setPacketId(getPacketId() + 1);
|
||||||
return new Pair<>(packetId, generationId);
|
return current();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCounting() {
|
||||||
|
return counting;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Reassembly {
|
private class Reassembly {
|
||||||
|
|||||||
+13
-7
@@ -16,10 +16,7 @@ import com.github.manevolent.ts3j.protocol.packet.PacketBody2Command;
|
|||||||
import com.github.manevolent.ts3j.util.Ts3Debugging;
|
import com.github.manevolent.ts3j.util.Ts3Debugging;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramPacket;
|
import java.net.*;
|
||||||
import java.net.DatagramSocket;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -67,10 +64,12 @@ public class LocalTeamspeakClientSocket
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected NetworkPacket readNetworkPacket() throws IOException {
|
protected NetworkPacket readNetworkPacket(int timeout) throws
|
||||||
socket.receive(packet);
|
IOException,
|
||||||
|
SocketTimeoutException {
|
||||||
|
|
||||||
Ts3Debugging.debug("[NETWORK] READ len=" + packet.getLength() + " from " + packet.getSocketAddress());
|
socket.setSoTimeout(timeout);
|
||||||
|
socket.receive(packet);
|
||||||
|
|
||||||
PacketHeader header;
|
PacketHeader header;
|
||||||
|
|
||||||
@@ -86,6 +85,11 @@ public class LocalTeamspeakClientSocket
|
|||||||
|
|
||||||
header.read(buffer);
|
header.read(buffer);
|
||||||
|
|
||||||
|
Ts3Debugging.debug("[NETWORK] READ" +
|
||||||
|
" id=" + header.getPacketId() +
|
||||||
|
" len=" + packet.getLength() +
|
||||||
|
" from " + packet.getSocketAddress());
|
||||||
|
|
||||||
return new NetworkPacket(
|
return new NetworkPacket(
|
||||||
packet,
|
packet,
|
||||||
header,
|
header,
|
||||||
@@ -325,6 +329,8 @@ public class LocalTeamspeakClientSocket
|
|||||||
public void process(AbstractTeamspeakClientSocket client, SingleCommand singleCommand)
|
public void process(AbstractTeamspeakClientSocket client, SingleCommand singleCommand)
|
||||||
throws CommandProcessException {
|
throws CommandProcessException {
|
||||||
try {
|
try {
|
||||||
|
setClientId(Integer.parseInt(singleCommand.get("aclid").getValue()));
|
||||||
|
|
||||||
setState(ClientConnectionState.CONNECTED);
|
setState(ClientConnectionState.CONNECTED);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new CommandProcessException(e);
|
throw new CommandProcessException(e);
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Discord4J.
|
||||||
|
*
|
||||||
|
* Discord4J is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Discord4J is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Discord4J. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.github.manevolent.ts3j.util;
|
||||||
|
|
||||||
|
import java.util.concurrent.locks.LockSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Thread that executes the passed task every specified period.
|
||||||
|
*
|
||||||
|
* Time of execution is held in account when calculating the time to sleep until the next execution.
|
||||||
|
*/
|
||||||
|
public class HighPrecisionRecurrentTask extends Thread {
|
||||||
|
|
||||||
|
private final int periodInNanos;
|
||||||
|
private final int spinningTimeNanos;
|
||||||
|
private final Runnable target;
|
||||||
|
private volatile boolean stop;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param periodInMilis The period in milliseconds of the task execution.
|
||||||
|
* @param percentageOfSleepSpinning The percentage of total sleeping that should be done spinning to increase accuracy.
|
||||||
|
* @param target The task to run.
|
||||||
|
*/
|
||||||
|
public HighPrecisionRecurrentTask(int periodInMilis, float percentageOfSleepSpinning, Runnable target) {
|
||||||
|
super("TS3J AudioThread");
|
||||||
|
if (percentageOfSleepSpinning < 0 || percentageOfSleepSpinning > 1)
|
||||||
|
throw new IllegalArgumentException("percentageOfSleepSpinning < 0 | percentageOfSleepSpinning > 1");
|
||||||
|
this.periodInNanos = periodInMilis * 1_000_000;
|
||||||
|
this.spinningTimeNanos = (int) (periodInNanos * percentageOfSleepSpinning);
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStop() {
|
||||||
|
return stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStop(boolean stop) {
|
||||||
|
this.stop = stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings(value = "empty-statement")
|
||||||
|
public void run() {
|
||||||
|
long nextTarget = 0;
|
||||||
|
while (!stop) {
|
||||||
|
long now = System.nanoTime();
|
||||||
|
target.run();
|
||||||
|
long total = System.nanoTime() - now;
|
||||||
|
nextTarget = now + periodInNanos - total;
|
||||||
|
sleepFor(periodInNanos - total - spinningTimeNanos);
|
||||||
|
while (nextTarget > System.nanoTime()) {
|
||||||
|
; //consume cycles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sleepFor(long nanos) {
|
||||||
|
if (nanos > 0) {
|
||||||
|
long elapsed = 0;
|
||||||
|
while (elapsed < nanos) {
|
||||||
|
long t0 = System.nanoTime();
|
||||||
|
LockSupport.parkNanos(nanos - elapsed);
|
||||||
|
elapsed += System.nanoTime() - t0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -324,7 +324,7 @@ public final class QuickLZ
|
|||||||
int level = (source[0] >>> 2) & 0x3;
|
int level = (source[0] >>> 2) & 0x3;
|
||||||
|
|
||||||
if (level != 1 && level != 3)
|
if (level != 1 && level != 3)
|
||||||
throw new RuntimeException("Java version only supports level 1 and 3");
|
return source;
|
||||||
|
|
||||||
if ((source[0] & 1) != 1)
|
if ((source[0] & 1) != 1)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
package com.github.manevolent.ts3j.util;
|
package com.github.manevolent.ts3j.util;
|
||||||
|
|
||||||
public final class Ts3Debugging {
|
public final class Ts3Debugging {
|
||||||
|
private static boolean enabled;
|
||||||
|
|
||||||
|
public static boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setEnabled(boolean enabled) {
|
||||||
|
Ts3Debugging.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
public static byte[] hexStringToByteArray(String s) {
|
public static byte[] hexStringToByteArray(String s) {
|
||||||
int len = s.length();
|
int len = s.length();
|
||||||
byte[] data = new byte[len / 2];
|
byte[] data = new byte[len / 2];
|
||||||
@@ -25,19 +35,23 @@ public final class Ts3Debugging {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void info(String message) {
|
public static void info(String message) {
|
||||||
System.err.println("[INFO] " + message);
|
if (isEnabled()) {
|
||||||
|
System.err.println("[INFO] " + message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void debug(Object message) {
|
public static void debug(Object message) {
|
||||||
System.err.println("[DEBUG] " + message.toString());
|
if (isEnabled()) System.err.println("[DEBUG] " + message.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void debug(byte[] message) {
|
public static void debug(byte[] message) {
|
||||||
debug(getHex(message));
|
if (isEnabled()) debug(getHex(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void debug(String message, Throwable ex) {
|
public static void debug(String message, Throwable ex) {
|
||||||
System.err.println("[DEBUG] " + message);
|
if (isEnabled()) {
|
||||||
ex.printStackTrace();
|
System.err.println("[DEBUG] " + message);
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,9 @@ public class ServerConnectionTest {
|
|||||||
|
|
||||||
assertEquals(client.getState(), ClientConnectionState.CONNECTED);
|
assertEquals(client.getState(), ClientConnectionState.CONNECTED);
|
||||||
|
|
||||||
client.getChannels().complete();
|
while (true) {
|
||||||
|
Thread.sleep(1000L);
|
||||||
|
}
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user