Got past Init1, required a large refactor and the addition of bouncycastle.
Serious thanks to Splamy for doing a lot of the hard work already.
This commit is contained in:
@@ -29,5 +29,11 @@
|
|||||||
<version>RELEASE</version>
|
<version>RELEASE</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
<version>1.60</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
|
|
||||||
package com.github.manevolent.ts3j;
|
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.handler.*;
|
|
||||||
|
|
||||||
public enum ClientConnectionState {
|
|
||||||
CONNECTING(TeamspeakClientHandlerConnecting.class),
|
|
||||||
CONNECTED(TeamspeakClientHandlerConnected.class),
|
|
||||||
DISCONNECTED(TeamspeakClientHandlerDisconnected.class);
|
|
||||||
|
|
||||||
private final Class<? extends TeamspeakClientHandler> clazz;
|
|
||||||
|
|
||||||
ClientConnectionState(Class<? extends TeamspeakClientHandler> clazz) {
|
|
||||||
this.clazz = clazz;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<? extends TeamspeakClientHandler> getHandlerClass() {
|
|
||||||
return clazz;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TeamspeakClientHandler createHandler(Teamspeak3Client client) {
|
|
||||||
try {
|
|
||||||
return clazz.getConstructor(Teamspeak3Client.class).newInstance(client);
|
|
||||||
} catch (ReflectiveOperationException ex) {
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,48 @@
|
|||||||
package com.github.manevolent.ts3j.command;
|
package com.github.manevolent.ts3j.command;
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.command.part.CommandParameter;
|
import com.github.manevolent.ts3j.command.part.CommandParameter;
|
||||||
|
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||||
|
|
||||||
public interface Command {
|
public interface Command {
|
||||||
|
/**
|
||||||
|
* Gets the name, or label, of this command.
|
||||||
|
* @return Command label.
|
||||||
|
*/
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the intended use role of this command (source).
|
||||||
|
* @return Command role.
|
||||||
|
*/
|
||||||
|
ProtocolRole getRole();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the parameter list of this command.
|
||||||
|
* @return Parameter list.
|
||||||
|
*/
|
||||||
Iterable<CommandParameter> getParameters();
|
Iterable<CommandParameter> getParameters();
|
||||||
void appendParameter(CommandParameter parameter);
|
|
||||||
|
/**
|
||||||
|
* Finds if the command is expected to be responded to with a specific return_code.
|
||||||
|
* This is handled by supporting protocol handlers, not by the command code itself.
|
||||||
|
* @return true if the command is expected to support a return_code.
|
||||||
|
*/
|
||||||
|
default boolean willExpectResponse() { return false; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the command to a netowrk-issueable string.
|
||||||
|
* The implementation currently defaults to the basic command format for TS3:
|
||||||
|
* label -flag parameter=value
|
||||||
|
* @return Baked command string.
|
||||||
|
*/
|
||||||
|
default String build() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append(getName());
|
||||||
|
|
||||||
|
for (CommandParameter parameter : getParameters()) {
|
||||||
|
builder.append(" " + (parameter.toString()).trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,34 @@
|
|||||||
package com.github.manevolent.ts3j.command;
|
package com.github.manevolent.ts3j.command;
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.command.part.CommandParameter;
|
import com.github.manevolent.ts3j.command.part.CommandParameter;
|
||||||
|
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class SimpleCommand implements Command {
|
public class SimpleCommand implements Command {
|
||||||
private final String name;
|
private final String name;
|
||||||
private final List<CommandParameter> parameterList = new LinkedList<>();
|
private final ProtocolRole role;
|
||||||
|
private final Collection<CommandParameter> params;
|
||||||
|
|
||||||
public SimpleCommand(String name) {
|
public SimpleCommand(String name, ProtocolRole role, Collection<CommandParameter> params) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.role = role;
|
||||||
|
this.params = params;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProtocolRole getRole() {
|
||||||
|
return role;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterable<CommandParameter> getParameters() {
|
public Iterable<CommandParameter> getParameters() {
|
||||||
return Collections.unmodifiableList(parameterList);
|
return Collections.unmodifiableCollection(params);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appendParameter(CommandParameter parameter) {
|
|
||||||
parameterList.add(parameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append(name);
|
|
||||||
|
|
||||||
for (CommandParameter parameter : getParameters()) {
|
|
||||||
builder.append(" ");
|
|
||||||
builder.append(parameter.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.github.manevolent.ts3j.command;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.command.part.CommandParameter;
|
||||||
|
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public abstract class TypedCommand implements Command {
|
||||||
|
private final ProtocolRole role;
|
||||||
|
private final String name;
|
||||||
|
private final Map<String, CommandParameter> parameters = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
protected TypedCommand(ProtocolRole role, String name) {
|
||||||
|
this.role = role;
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CommandParameter set(CommandParameter commandParameter) {
|
||||||
|
this.parameters.put(commandParameter.getName(), commandParameter);
|
||||||
|
|
||||||
|
return commandParameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandParameter get(String key) {
|
||||||
|
return this.parameters.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<CommandParameter> getParameters() {
|
||||||
|
return Collections.unmodifiableCollection(parameters.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProtocolRole getRole() {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,68 @@
|
|||||||
package com.github.manevolent.ts3j.command.part;
|
package com.github.manevolent.ts3j.command.part;
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.enums.CommandPartType;
|
|
||||||
|
|
||||||
public class CommandOption implements CommandParameter {
|
public class CommandOption implements CommandParameter {
|
||||||
private final String value;
|
private final String key;
|
||||||
|
private boolean enabled = false;
|
||||||
|
|
||||||
public CommandOption(String value) {
|
public CommandOption(String key) {
|
||||||
this.value = " -" + value;
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(boolean value) {
|
||||||
|
this.enabled = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandPartType getType() {
|
public String getName() {
|
||||||
return CommandPartType.OPTION;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(char value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(String value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void set(byte value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(short value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(int value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(long value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(float value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(double value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (enabled)
|
||||||
|
return "-" + key;
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,23 @@
|
|||||||
package com.github.manevolent.ts3j.command.part;
|
package com.github.manevolent.ts3j.command.part;
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.enums.CommandPartType;
|
|
||||||
|
|
||||||
public interface CommandParameter {
|
public interface CommandParameter {
|
||||||
|
|
||||||
CommandPartType getType();
|
String getName();
|
||||||
|
|
||||||
|
void set(char value);
|
||||||
|
void set(String value);
|
||||||
|
void set(boolean value);
|
||||||
|
void set(byte value);
|
||||||
|
void set(short value);
|
||||||
|
void set(int value);
|
||||||
|
void set(long value);
|
||||||
|
void set(float value);
|
||||||
|
void set(double value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the command part to a usable command string element.
|
||||||
|
* @return Command string element.
|
||||||
|
*/
|
||||||
|
String toString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,81 +1,70 @@
|
|||||||
package com.github.manevolent.ts3j.command.part;
|
package com.github.manevolent.ts3j.command.part;
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.enums.CommandPartType;
|
import com.github.manevolent.ts3j.util.Ts3String;
|
||||||
|
|
||||||
public class CommandSingleParameter implements CommandParameter {
|
public class CommandSingleParameter implements CommandParameter {
|
||||||
private final String key, value;
|
private final String key;
|
||||||
|
private String value;
|
||||||
|
|
||||||
public CommandSingleParameter(String key, String value) {
|
public CommandSingleParameter(String key) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandSingleParameter(String key, boolean value) {
|
|
||||||
this.key = key;
|
|
||||||
this.value = serialize(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandSingleParameter(String key, byte value) {
|
|
||||||
this.key = key;
|
|
||||||
this.value = serialize(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandSingleParameter(String key, short value) {
|
|
||||||
this.key = key;
|
|
||||||
this.value = serialize(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandSingleParameter(String key, int value) {
|
|
||||||
this.key = key;
|
|
||||||
this.value = serialize(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandSingleParameter(String key, long value) {
|
|
||||||
this.key = key;
|
|
||||||
this.value = serialize(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandSingleParameter(String key, float value) {
|
|
||||||
this.key = key;
|
|
||||||
this.value = serialize(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandSingleParameter(String key, double value) {
|
|
||||||
this.key = key;
|
|
||||||
this.value = serialize(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandPartType getType() {
|
public String toString() {
|
||||||
return CommandPartType.SINGLE_PARAMETER;
|
if (value == null)
|
||||||
|
return "";
|
||||||
|
else
|
||||||
|
return (key + "=" + Ts3String.escape(value.trim()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String serialize(boolean value) {
|
@Override
|
||||||
return value ? "1": "0";
|
public String getName() {
|
||||||
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String serialize(byte value) {
|
@Override
|
||||||
return Byte.toString(value);
|
public void set(char value) {
|
||||||
|
this.value = Character.toString(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String serialize(short value) {
|
@Override
|
||||||
return Short.toString(value);
|
public void set(String value) {
|
||||||
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String serialize(int value) {
|
@Override
|
||||||
return Integer.toString(value);
|
public void set(boolean value) {
|
||||||
|
this.value = (value ? "1" : "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String serialize(long value) {
|
@Override
|
||||||
return Long.toString(value);
|
public void set(byte value) {
|
||||||
|
this.value = Byte.toString(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String serialize(float value) {
|
@Override
|
||||||
return Float.toString(value);
|
public void set(short value) {
|
||||||
|
this.value = Short.toString(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String serialize(double value) {
|
@Override
|
||||||
return Double.toString(value);
|
public void set(int value) {
|
||||||
|
this.value = Integer.toString(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(long value) {
|
||||||
|
this.value = Long.toString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(float value) {
|
||||||
|
this.value = Float.toString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(double value) {
|
||||||
|
this.value = Double.toString(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.github.manevolent.ts3j.command.type.client;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.command.TypedCommand;
|
||||||
|
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
|
||||||
|
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||||
|
|
||||||
|
public class ClientInitIV extends TypedCommand {
|
||||||
|
public ClientInitIV() {
|
||||||
|
super(ProtocolRole.CLIENT, "clientinitiv");
|
||||||
|
|
||||||
|
set(new CommandSingleParameter("ot"));
|
||||||
|
set(new CommandSingleParameter("alpha"));
|
||||||
|
set(new CommandSingleParameter("omega"));
|
||||||
|
set(new CommandSingleParameter("ip"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package com.github.manevolent.ts3j.handler;
|
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.Teamspeak3Client;
|
|
||||||
import com.github.manevolent.ts3j.ClientConnectionState;
|
|
||||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public abstract class TeamspeakClientHandler {
|
|
||||||
private final Teamspeak3Client client;
|
|
||||||
|
|
||||||
protected TeamspeakClientHandler(Teamspeak3Client client) {
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final Teamspeak3Client getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleConnectionStateChanging(ClientConnectionState clientConnectionState) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onAssigned() throws IOException {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handlePacket(NetworkPacket packet) throws IOException {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.github.manevolent.ts3j.handler;
|
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.Teamspeak3Client;
|
|
||||||
|
|
||||||
public class TeamspeakClientHandlerConnected extends TeamspeakClientHandler {
|
|
||||||
public TeamspeakClientHandlerConnected(Teamspeak3Client client) {
|
|
||||||
super(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-9
@@ -1,9 +0,0 @@
|
|||||||
package com.github.manevolent.ts3j.handler;
|
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.Teamspeak3Client;
|
|
||||||
|
|
||||||
public class TeamspeakClientHandlerDisconnected extends TeamspeakClientHandler {
|
|
||||||
public TeamspeakClientHandlerDisconnected(Teamspeak3Client client) {
|
|
||||||
super(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
package com.github.manevolent.ts3j.identity;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||||
|
import org.bouncycastle.asn1.ASN1Encodable;
|
||||||
|
import org.bouncycastle.asn1.DERBitString;
|
||||||
|
import org.bouncycastle.asn1.DERInteger;
|
||||||
|
import org.bouncycastle.asn1.DERSequence;
|
||||||
|
import org.bouncycastle.math.ec.ECPoint;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.security.DigestException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
public class Identity {
|
||||||
|
private static MessageDigest sha1;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
sha1 = MessageDigest.getInstance("SHA-1");
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ECPoint publicKey;
|
||||||
|
private final String fingerprint;
|
||||||
|
private final String publicKeyString;
|
||||||
|
|
||||||
|
private long keyOffset = 0L, lastCheckedKeyOffset = 0L;
|
||||||
|
|
||||||
|
public Identity(ECPoint publicKey) {
|
||||||
|
this.publicKey = publicKey;
|
||||||
|
|
||||||
|
this.publicKeyString = Base64.getEncoder().encodeToString(encodePublicKey());
|
||||||
|
this.fingerprint = generateFingerprint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECPoint getPublicKey() {
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPublicKeyString() {
|
||||||
|
return publicKeyString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFingerprint() {
|
||||||
|
return fingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLastCheckedKeyOffset() {
|
||||||
|
return lastCheckedKeyOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastCheckedKeyOffset(long lastCheckedKeyOffset) {
|
||||||
|
this.lastCheckedKeyOffset = lastCheckedKeyOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getKeyOffset() {
|
||||||
|
return keyOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeyOffset(long keyOffset) {
|
||||||
|
this.keyOffset = keyOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void improveSecurity(int target) {
|
||||||
|
byte[] hashBuffer = new byte[getPublicKeyString().length() + 20];
|
||||||
|
byte[] pubKeyBytes = getPublicKeyString().getBytes(Charset.forName("ASCII"));
|
||||||
|
System.arraycopy(pubKeyBytes, 0, hashBuffer, 0, pubKeyBytes.length);
|
||||||
|
|
||||||
|
|
||||||
|
setLastCheckedKeyOffset(Math.max(getKeyOffset(), getLastCheckedKeyOffset()));
|
||||||
|
int best = getSecurityLevel(hashBuffer, pubKeyBytes.length, getKeyOffset());
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (best >= target) return;
|
||||||
|
|
||||||
|
int curr = getSecurityLevel(hashBuffer, pubKeyBytes.length, lastCheckedKeyOffset);
|
||||||
|
|
||||||
|
if (curr > best)
|
||||||
|
{
|
||||||
|
Ts3Logging.debug("Improved identity security level: from " + best + " to " + curr);
|
||||||
|
keyOffset = lastCheckedKeyOffset;
|
||||||
|
best = curr;
|
||||||
|
}
|
||||||
|
lastCheckedKeyOffset ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSecurityLevel() {
|
||||||
|
byte[] hashBuffer = new byte[getPublicKeyString().length() + 20];
|
||||||
|
byte[] pubKeyBytes = getPublicKeyString().getBytes(Charset.forName("ASCII"));
|
||||||
|
System.arraycopy(pubKeyBytes, 0, hashBuffer, 0, pubKeyBytes.length);
|
||||||
|
|
||||||
|
return getSecurityLevel(hashBuffer, pubKeyBytes.length, getKeyOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getSecurityLevel(byte[] hashBuffer, int pubKeyLen, long offset) {
|
||||||
|
byte[] numBuffer = new byte[20];
|
||||||
|
int numLen = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
numBuffer[numLen] = (byte)((offset % 10) & 0xFF);
|
||||||
|
offset /= 10;
|
||||||
|
numLen ++;
|
||||||
|
} while (offset > 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < numLen; i ++)
|
||||||
|
hashBuffer[pubKeyLen + i] = numBuffer[numLen - (i + 1)];
|
||||||
|
|
||||||
|
byte[] outHash;
|
||||||
|
synchronized (sha1) {
|
||||||
|
sha1.update(hashBuffer, 0, pubKeyLen + numLen);
|
||||||
|
outHash = sha1.digest();
|
||||||
|
}
|
||||||
|
|
||||||
|
return getLeadingZeroBits(outHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GOOD
|
||||||
|
private static int getLeadingZeroBits(byte[] data) {
|
||||||
|
int curr = 0;
|
||||||
|
int i ;
|
||||||
|
for (i = 0; i < data.length; i ++) {
|
||||||
|
if (data[i] == 0) curr += 8;
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
if (i < data.length)
|
||||||
|
for (int bit = 0; bit < 8; bit ++) {
|
||||||
|
if ((data[i] & (1 << bit)) == 0) curr++;
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
return curr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] encodePublicKey() {
|
||||||
|
try {
|
||||||
|
byte[] dataArray = new DERSequence(new ASN1Encodable[]{
|
||||||
|
new DERBitString(new byte[]{0b0000_0000}, 7),
|
||||||
|
new DERInteger(32),
|
||||||
|
new DERInteger(publicKey.getAffineXCoord().toBigInteger()),
|
||||||
|
new DERInteger(publicKey.getAffineYCoord().toBigInteger())
|
||||||
|
}).getEncoded();
|
||||||
|
|
||||||
|
return dataArray;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateFingerprint() {
|
||||||
|
synchronized (sha1) {
|
||||||
|
return Base64.getEncoder().encodeToString(sha1.digest(encodePublicKey()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.github.manevolent.ts3j.identity;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||||
|
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||||
|
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||||
|
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||||
|
import org.bouncycastle.jce.ECNamedCurveTable;
|
||||||
|
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
|
||||||
|
import org.bouncycastle.math.ec.ECPoint;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.*;
|
||||||
|
|
||||||
|
public class LocalIdentity extends Identity {
|
||||||
|
static {
|
||||||
|
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
private final BigInteger privateKey;
|
||||||
|
|
||||||
|
public LocalIdentity(ECPoint publicKey, BigInteger privateKey) {
|
||||||
|
super(publicKey);
|
||||||
|
|
||||||
|
this.privateKey = privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LocalIdentity load(ECPoint publicKey,
|
||||||
|
BigInteger privateKey,
|
||||||
|
long keyOffset,
|
||||||
|
long lastCheckedKeyOffset) {
|
||||||
|
LocalIdentity localIdentity = new LocalIdentity(publicKey, privateKey);
|
||||||
|
|
||||||
|
localIdentity.setKeyOffset(keyOffset);
|
||||||
|
localIdentity.setLastCheckedKeyOffset(lastCheckedKeyOffset);
|
||||||
|
|
||||||
|
return localIdentity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LocalIdentity generateNew(int securityLevel) throws GeneralSecurityException {
|
||||||
|
ECNamedCurveParameterSpec ecp = ECNamedCurveTable.getParameterSpec("prime256v1");
|
||||||
|
ECDomainParameters domainParams =
|
||||||
|
new ECDomainParameters(ecp.getCurve(), ecp.getG(), ecp.getN(), ecp.getH(), ecp.getSeed());
|
||||||
|
ECKeyGenerationParameters keyGenParams = new ECKeyGenerationParameters(domainParams, new SecureRandom());
|
||||||
|
|
||||||
|
ECKeyPairGenerator generator = new ECKeyPairGenerator();
|
||||||
|
generator.init(keyGenParams);
|
||||||
|
|
||||||
|
AsymmetricCipherKeyPair keyPair = generator.generateKeyPair();
|
||||||
|
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) keyPair.getPrivate();
|
||||||
|
ECPublicKeyParameters publicKey = (ECPublicKeyParameters) keyPair.getPublic();
|
||||||
|
|
||||||
|
LocalIdentity localIdentity = load(publicKey.getQ().normalize(), privateKey.getD(), 0, 0);
|
||||||
|
localIdentity.improveSecurity(securityLevel);
|
||||||
|
|
||||||
|
return localIdentity;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.github.manevolent.ts3j.protocol;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.protocol.packet.Packet;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public interface Endpoint {
|
||||||
|
|
||||||
|
SocketRole getRole();
|
||||||
|
|
||||||
|
void send(Packet packet) throws IOException;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package com.github.manevolent.ts3j.protocol;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.identity.LocalIdentity;
|
||||||
|
import com.github.manevolent.ts3j.protocol.packet.handler.PacketHandler;
|
||||||
|
import com.github.manevolent.ts3j.protocol.socket.LocalTeamspeakSocket;
|
||||||
|
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
|
||||||
|
public abstract class LocalEndpoint implements Endpoint {
|
||||||
|
private final LocalTeamspeakSocket socket;
|
||||||
|
|
||||||
|
|
||||||
|
private PacketHandler handler = null;
|
||||||
|
private LocalIdentity localIdentity = null;
|
||||||
|
|
||||||
|
private final Object runLock = new Object();
|
||||||
|
private Thread receiverThread;
|
||||||
|
private boolean running = false;
|
||||||
|
|
||||||
|
protected LocalEndpoint(LocalTeamspeakSocket socket) {
|
||||||
|
this.socket = socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LocalEndpoint(SocketRole socketRole) throws SocketException {
|
||||||
|
this.socket = new LocalTeamspeakSocket(socketRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InetSocketAddress getLocalAddress() {
|
||||||
|
return getSocket().getLocalAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InetSocketAddress getRemoteAddress() {
|
||||||
|
return getSocket().getLocalAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final LocalTeamspeakSocket getSocket() {
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRunning(boolean running) {
|
||||||
|
if (this.running != running) {
|
||||||
|
this.running = running;
|
||||||
|
|
||||||
|
if (running) {
|
||||||
|
Ts3Logging.debug("Starting network receiver thread...");
|
||||||
|
|
||||||
|
receiverThread = new Thread(new PacketReceiver());
|
||||||
|
receiverThread.setDaemon(true);
|
||||||
|
receiverThread.start();
|
||||||
|
} else {
|
||||||
|
if (receiverThread != null) {
|
||||||
|
receiverThread.interrupt();
|
||||||
|
receiverThread = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketRole getRole() {
|
||||||
|
return socket.getSocketRole();
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetworkPacket receive() throws IOException {
|
||||||
|
return socket.receive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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 (this.handler != null) this.handler.handlePacket(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalIdentity getLocalIdentity() {
|
||||||
|
return localIdentity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalIdentity(LocalIdentity localIdentity) {
|
||||||
|
this.localIdentity = localIdentity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PacketReceiver implements Runnable {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Ts3Logging.debug("Entering packet receiver");
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
handlePacket(receive());
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (e instanceof InterruptedException) continue;
|
||||||
|
|
||||||
|
Ts3Logging.debug("Problem handling packet", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ts3Logging.debug("Leaving packet receiver");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.github.manevolent.ts3j.protocol;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
public abstract class RemoteEndpoint implements Endpoint {
|
||||||
|
public abstract InetSocketAddress getRemoteAddress();
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.github.manevolent.ts3j.protocol;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||||
|
import com.github.manevolent.ts3j.protocol.packet.Packet;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public interface TeamspeakSocket {
|
||||||
|
SocketRole getSocketRole();
|
||||||
|
|
||||||
|
void send(PacketHeader header, Packet packet) throws IOException;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
package com.github.manevolent.ts3j.protocol.client;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.protocol.packet.handler.client.LocalClientHandler;
|
||||||
|
import com.github.manevolent.ts3j.protocol.packet.handler.client.LocalClientHandlerConnected;
|
||||||
|
import com.github.manevolent.ts3j.protocol.packet.handler.client.LocalClientHandlerConnecting;
|
||||||
|
import com.github.manevolent.ts3j.protocol.packet.handler.client.LocalClientHandlerDisconnected;
|
||||||
|
|
||||||
|
public enum ClientConnectionState {
|
||||||
|
CONNECTING(LocalClientHandlerConnecting.class),
|
||||||
|
CONNECTED(LocalClientHandlerConnected.class),
|
||||||
|
DISCONNECTED(LocalClientHandlerDisconnected.class);
|
||||||
|
|
||||||
|
private final Class<? extends LocalClientHandler> clazz;
|
||||||
|
|
||||||
|
ClientConnectionState(Class<? extends LocalClientHandler> clazz) {
|
||||||
|
this.clazz = clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<? extends LocalClientHandler> getHandlerClass() {
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalClientHandler createHandler(LocalTeamspeakClient client) {
|
||||||
|
try {
|
||||||
|
return clazz.getConstructor(LocalTeamspeakClient.class).newInstance(client);
|
||||||
|
} catch (ReflectiveOperationException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
-89
@@ -1,50 +1,71 @@
|
|||||||
package com.github.manevolent.ts3j;
|
package com.github.manevolent.ts3j.protocol.client;
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.command.Command;
|
import com.github.manevolent.ts3j.command.Command;
|
||||||
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
|
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
|
||||||
import com.github.manevolent.ts3j.command.response.CommandResponse;
|
import com.github.manevolent.ts3j.command.response.CommandResponse;
|
||||||
import com.github.manevolent.ts3j.handler.TeamspeakClientHandler;
|
import com.github.manevolent.ts3j.identity.LocalIdentity;
|
||||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
import com.github.manevolent.ts3j.protocol.LocalEndpoint;
|
||||||
import com.github.manevolent.ts3j.protocol.SocketRole;
|
import com.github.manevolent.ts3j.protocol.SocketRole;
|
||||||
import com.github.manevolent.ts3j.protocol.Teamspeak3Socket;
|
|
||||||
import com.github.manevolent.ts3j.protocol.header.ClientPacketHeader;
|
import com.github.manevolent.ts3j.protocol.header.ClientPacketHeader;
|
||||||
|
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;
|
import com.github.manevolent.ts3j.protocol.packet.Packet;
|
||||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramSocket;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
public class Teamspeak3Client {
|
public class LocalTeamspeakClient extends LocalEndpoint implements TeamspeakClient {
|
||||||
private final PacketReceiver packetReceiver = new PacketReceiver();
|
|
||||||
|
|
||||||
private final Teamspeak3Socket socket;
|
|
||||||
private final Object connectionStateLock = new Object();
|
private final Object connectionStateLock = new Object();
|
||||||
|
|
||||||
private final Map<String, Object> clientOptions = new LinkedHashMap<>();
|
private final Map<String, Object> clientOptions = new LinkedHashMap<>();
|
||||||
|
|
||||||
private TeamspeakClientHandler handler;
|
private LocalClientHandler handler;
|
||||||
|
|
||||||
private InetSocketAddress remote = null;
|
|
||||||
private ClientConnectionState clientConnectionState = null;
|
private ClientConnectionState clientConnectionState = null;
|
||||||
|
|
||||||
private int returnCodeIndex = 0;
|
private int returnCodeIndex = 0;
|
||||||
|
|
||||||
public Teamspeak3Client(DatagramSocket datagramSocket) throws SocketException {
|
public LocalTeamspeakClient(LocalTeamspeakSocket socket) {
|
||||||
this.socket = new Teamspeak3Socket(SocketRole.CLIENT, datagramSocket);
|
super(socket);
|
||||||
|
|
||||||
|
if (socket.getSocketRole() != SocketRole.CLIENT)
|
||||||
|
throw new IllegalArgumentException("invalid socket role: " + socket.getSocketRole().name());
|
||||||
|
|
||||||
setClientConnectionState(ClientConnectionState.DISCONNECTED);
|
setClientConnectionState(ClientConnectionState.DISCONNECTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Teamspeak3Client() throws SocketException {
|
public CommandResponse sendCommand(Command command) throws IOException {
|
||||||
this(new DatagramSocket());
|
if (command.willExpectResponse() && !isConnected())
|
||||||
|
throw new IOException("not connected");
|
||||||
|
|
||||||
|
CommandResponse response;
|
||||||
|
|
||||||
|
if (command.willExpectResponse()) {
|
||||||
|
int returnCodeIndex = ++ this.returnCodeIndex;
|
||||||
|
//command.appendParameter(new CommandSingleParameter("return_code", returnCodeIndex));
|
||||||
|
response = new CommandResponse(command, returnCodeIndex);
|
||||||
|
} else {
|
||||||
|
response = new CommandResponse(command, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String commandString = command.toString();
|
||||||
|
|
||||||
|
response.setDispatchedTime(System.currentTimeMillis());
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InetSocketAddress getRemote() {
|
public LocalTeamspeakClient() throws SocketException {
|
||||||
return remote;
|
this(new LocalTeamspeakSocket(SocketRole.CLIENT));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketRole getRole() {
|
||||||
|
return SocketRole.CLIENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void send(Packet packet) throws IOException {
|
public void send(Packet packet) throws IOException {
|
||||||
@@ -53,7 +74,7 @@ public class Teamspeak3Client {
|
|||||||
|
|
||||||
packet.setHeaderValues(header);
|
packet.setHeaderValues(header);
|
||||||
|
|
||||||
socket.send(header, packet);
|
getSocket().send(header, packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setClientConnectionState(ClientConnectionState state) {
|
protected void setClientConnectionState(ClientConnectionState state) {
|
||||||
@@ -65,13 +86,8 @@ public class Teamspeak3Client {
|
|||||||
|
|
||||||
this.clientConnectionState = state;
|
this.clientConnectionState = state;
|
||||||
|
|
||||||
if (wasDisconnected) {
|
if (wasDisconnected)
|
||||||
Ts3Logging.debug("Starting network receiver thread...");
|
setRunning(true);
|
||||||
|
|
||||||
Thread receiverThread = new Thread(new PacketReceiver());
|
|
||||||
receiverThread.setDaemon(true);
|
|
||||||
receiverThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handler != null)
|
if (handler != null)
|
||||||
handler.handleConnectionStateChanging(state);
|
handler.handleConnectionStateChanging(state);
|
||||||
@@ -82,7 +98,7 @@ public class Teamspeak3Client {
|
|||||||
handler = state.createHandler(this);
|
handler = state.createHandler(this);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
handler.onAssigned();
|
setHandler(handler);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
@@ -102,6 +118,11 @@ public class Teamspeak3Client {
|
|||||||
return clientConnectionState == ClientConnectionState.CONNECTED;
|
return clientConnectionState == ClientConnectionState.CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getOptions() {
|
||||||
|
return clientOptions;
|
||||||
|
}
|
||||||
|
|
||||||
public ClientConnectionState getClientConnectionState() {
|
public ClientConnectionState getClientConnectionState() {
|
||||||
return clientConnectionState;
|
return clientConnectionState;
|
||||||
}
|
}
|
||||||
@@ -120,46 +141,6 @@ public class Teamspeak3Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends Object> T getOption(String key, Class<T> clazz) {
|
|
||||||
Object value = clientOptions.get(key);
|
|
||||||
|
|
||||||
if (value == null) return (T) null;
|
|
||||||
|
|
||||||
else if (!value.getClass().isAssignableFrom(clazz)) {
|
|
||||||
throw new ClassCastException(
|
|
||||||
clazz.getName()
|
|
||||||
+ " is not assignable to option type " +
|
|
||||||
value.getClass().getName()
|
|
||||||
);
|
|
||||||
} else
|
|
||||||
return (T) value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object setOption(String key, Object value) {
|
|
||||||
return clientOptions.put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandResponse sendCommand(Command command) throws IOException {
|
|
||||||
if (command.willExpectResponse() && !isConnected())
|
|
||||||
throw new IOException("not connected");
|
|
||||||
|
|
||||||
CommandResponse response;
|
|
||||||
|
|
||||||
if (command.willExpectResponse()) {
|
|
||||||
int returnCodeIndex = ++ this.returnCodeIndex;
|
|
||||||
command.appendParameter(new CommandSingleParameter("return_code", returnCodeIndex));
|
|
||||||
response = new CommandResponse(command, returnCodeIndex);
|
|
||||||
} else {
|
|
||||||
response = new CommandResponse(command, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
String commandString = command.toString();
|
|
||||||
|
|
||||||
response.setDispatchedTime(System.currentTimeMillis());
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void connect(InetSocketAddress remote, String password, long timeout) throws IOException {
|
public void connect(InetSocketAddress remote, String password, long timeout) throws IOException {
|
||||||
try {
|
try {
|
||||||
if (clientConnectionState != ClientConnectionState.DISCONNECTED)
|
if (clientConnectionState != ClientConnectionState.DISCONNECTED)
|
||||||
@@ -168,7 +149,7 @@ public class Teamspeak3Client {
|
|||||||
setOption("client.hostname", remote.getHostString());
|
setOption("client.hostname", remote.getHostString());
|
||||||
setOption("client.password", password);
|
setOption("client.password", password);
|
||||||
|
|
||||||
socket.connect(remote);
|
getSocket().connect(remote);
|
||||||
|
|
||||||
setClientConnectionState(ClientConnectionState.CONNECTING);
|
setClientConnectionState(ClientConnectionState.CONNECTING);
|
||||||
joinConnectionState(ClientConnectionState.CONNECTED, timeout);
|
joinConnectionState(ClientConnectionState.CONNECTED, timeout);
|
||||||
@@ -178,25 +159,4 @@ public class Teamspeak3Client {
|
|||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePacket(NetworkPacket packet) throws Exception {
|
|
||||||
if (handler != null) handler.handlePacket(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PacketReceiver implements Runnable {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Ts3Logging.debug("Entering packet receiver");
|
|
||||||
|
|
||||||
while (clientConnectionState != ClientConnectionState.DISCONNECTED) {
|
|
||||||
try {
|
|
||||||
handlePacket(socket.receive());
|
|
||||||
} catch (Exception e) {
|
|
||||||
Ts3Logging.debug("Problem handling packet", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ts3Logging.debug("Leaving packet receiver");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.github.manevolent.ts3j.protocol.client;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.protocol.Endpoint;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface TeamspeakClient extends Endpoint {
|
||||||
|
void connect(InetSocketAddress remote, String password, long timeout) throws IOException;
|
||||||
|
|
||||||
|
SocketAddress getRemoteAddress();
|
||||||
|
|
||||||
|
boolean isConnected();
|
||||||
|
|
||||||
|
Map<String, Object> getOptions();
|
||||||
|
|
||||||
|
default <T extends Object> T getOption(String key, Class<T> clazz) {
|
||||||
|
Object value = getOptions().get(key);
|
||||||
|
|
||||||
|
if (value == null) return (T) null;
|
||||||
|
|
||||||
|
else if (!value.getClass().isAssignableFrom(clazz)) {
|
||||||
|
throw new ClassCastException(
|
||||||
|
clazz.getName()
|
||||||
|
+ " is not assignable to option type " +
|
||||||
|
value.getClass().getName()
|
||||||
|
);
|
||||||
|
} else
|
||||||
|
return (T) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
default Object setOption(String key, Object value) {
|
||||||
|
return getOptions().put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -90,6 +90,9 @@ public class Packet8Init1 extends Packet {
|
|||||||
case 3:
|
case 3:
|
||||||
step = new Step3();
|
step = new Step3();
|
||||||
break;
|
break;
|
||||||
|
case 4:
|
||||||
|
step = new Step4();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("invalid Init1 step: " + stepNumber);
|
throw new IllegalArgumentException("invalid Init1 step: " + stepNumber);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.github.manevolent.ts3j.protocol.packet.handler;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||||
|
import com.github.manevolent.ts3j.protocol.LocalEndpoint;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public abstract class PacketHandler {
|
||||||
|
private final LocalEndpoint endpoint;
|
||||||
|
|
||||||
|
protected PacketHandler(LocalEndpoint endpoint) {
|
||||||
|
this.endpoint = endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onUnassigning() throws IOException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAssigned() throws IOException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handlePacket(NetworkPacket packet) throws IOException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalEndpoint getEndpoint() {
|
||||||
|
return endpoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
package com.github.manevolent.ts3j.protocol.packet.handler.client;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.protocol.client.LocalTeamspeakClient;
|
||||||
|
import com.github.manevolent.ts3j.protocol.packet.handler.PacketHandler;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public abstract class LocalClientHandler extends PacketHandler {
|
||||||
|
private final LocalTeamspeakClient client;
|
||||||
|
|
||||||
|
protected LocalClientHandler(LocalTeamspeakClient client) {
|
||||||
|
super(client);
|
||||||
|
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleConnectionStateChanging(ClientConnectionState clientConnectionState) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LocalTeamspeakClient getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package com.github.manevolent.ts3j.protocol.packet.handler.client;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.protocol.client.LocalTeamspeakClient;
|
||||||
|
|
||||||
|
public class LocalClientHandlerConnected extends LocalClientHandler {
|
||||||
|
public LocalClientHandlerConnected(LocalTeamspeakClient client) {
|
||||||
|
super(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
-9
@@ -1,20 +1,23 @@
|
|||||||
package com.github.manevolent.ts3j.handler;
|
package com.github.manevolent.ts3j.protocol.packet.handler.client;
|
||||||
|
|
||||||
import com.github.manevolent.ts3j.Teamspeak3Client;
|
import com.github.manevolent.ts3j.command.type.client.ClientInitIV;
|
||||||
import com.github.manevolent.ts3j.command.Command;
|
|
||||||
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||||
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
import com.github.manevolent.ts3j.protocol.ProtocolRole;
|
||||||
|
import com.github.manevolent.ts3j.protocol.client.LocalTeamspeakClient;
|
||||||
import com.github.manevolent.ts3j.protocol.packet.Packet8Init1;
|
import com.github.manevolent.ts3j.protocol.packet.Packet8Init1;
|
||||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
public class TeamspeakClientHandlerConnecting extends TeamspeakClientHandler {
|
public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||||
private byte[] randomBytes;
|
private byte[] randomBytes;
|
||||||
|
private byte[] alphaBytes;
|
||||||
|
|
||||||
public TeamspeakClientHandlerConnecting(Teamspeak3Client client) {
|
public LocalClientHandlerConnecting(LocalTeamspeakClient client) {
|
||||||
super(client);
|
super(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,10 +93,12 @@ public class TeamspeakClientHandlerConnecting extends TeamspeakClientHandler {
|
|||||||
byte[] solution =
|
byte[] solution =
|
||||||
x.modPow(BigInteger.valueOf(2L).pow(serverReplyStep3.getLevel()), n).toByteArray();
|
x.modPow(BigInteger.valueOf(2L).pow(serverReplyStep3.getLevel()), n).toByteArray();
|
||||||
|
|
||||||
|
Ts3Logging.debug(Ts3Logging.getHex(solution));
|
||||||
|
|
||||||
System.arraycopy(
|
System.arraycopy(
|
||||||
solution, Math.max(0, solution.length - 64),
|
solution, Math.abs(solution.length - 64),
|
||||||
y, 64 - solution.length,
|
y, solution.length < 64 ? 64 - solution.length : 0,
|
||||||
solution.length
|
solution.length < 64 ? solution.length : 64
|
||||||
);
|
);
|
||||||
|
|
||||||
// Build response
|
// Build response
|
||||||
@@ -107,8 +112,17 @@ public class TeamspeakClientHandlerConnecting extends TeamspeakClientHandler {
|
|||||||
step4.setY(y);
|
step4.setY(y);
|
||||||
step4.setServerStuff(serverReplyStep3.getServerStuff());
|
step4.setServerStuff(serverReplyStep3.getServerStuff());
|
||||||
|
|
||||||
|
alphaBytes = new byte[10];
|
||||||
|
new Random().nextBytes(alphaBytes);
|
||||||
|
|
||||||
step4.setClientIVcommand(new byte[4]); // TODO
|
ClientInitIV command = new ClientInitIV();
|
||||||
|
|
||||||
|
command.get("alpha").set(Base64.getEncoder().encodeToString(alphaBytes));
|
||||||
|
command.get("omega").set(getClient().getLocalIdentity().getPublicKeyString());
|
||||||
|
command.get("ot").set(1); // ?
|
||||||
|
command.get("ip").set(null);
|
||||||
|
|
||||||
|
step4.setClientIVcommand(command.build().getBytes(Charset.forName("UTF8")));
|
||||||
|
|
||||||
response4.setStep(step4);
|
response4.setStep(step4);
|
||||||
|
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package com.github.manevolent.ts3j.protocol.packet.handler.client;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.protocol.client.LocalTeamspeakClient;
|
||||||
|
|
||||||
|
public class LocalClientHandlerDisconnected extends LocalClientHandler {
|
||||||
|
public LocalClientHandlerDisconnected(LocalTeamspeakClient client) {
|
||||||
|
super(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.github.manevolent.ts3j.protocol.server;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||||
|
import com.github.manevolent.ts3j.protocol.RemoteEndpoint;
|
||||||
|
import com.github.manevolent.ts3j.protocol.SocketRole;
|
||||||
|
import com.github.manevolent.ts3j.protocol.client.LocalTeamspeakClient;
|
||||||
|
import com.github.manevolent.ts3j.protocol.packet.Packet;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
public class RemoteTeamspeakServer extends RemoteEndpoint implements TeamspeakServer {
|
||||||
|
private final LocalTeamspeakClient client;
|
||||||
|
private final InetSocketAddress address;
|
||||||
|
|
||||||
|
public RemoteTeamspeakServer(LocalTeamspeakClient client,
|
||||||
|
InetSocketAddress address) {
|
||||||
|
this.client = client;
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SocketRole getRole() {
|
||||||
|
return SocketRole.SERVER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(Packet packet) throws IOException {
|
||||||
|
client.send(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InetSocketAddress getRemoteAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.github.manevolent.ts3j.protocol.server;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.protocol.Endpoint;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
public interface TeamspeakServer extends Endpoint {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+17
-8
@@ -1,14 +1,14 @@
|
|||||||
package com.github.manevolent.ts3j.protocol;
|
package com.github.manevolent.ts3j.protocol.socket;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.protocol.NetworkPacket;
|
||||||
|
import com.github.manevolent.ts3j.protocol.SocketRole;
|
||||||
|
import com.github.manevolent.ts3j.protocol.TeamspeakSocket;
|
||||||
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
import com.github.manevolent.ts3j.protocol.header.PacketHeader;
|
||||||
import com.github.manevolent.ts3j.protocol.packet.Packet;
|
import com.github.manevolent.ts3j.protocol.packet.Packet;
|
||||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramPacket;
|
import java.net.*;
|
||||||
import java.net.DatagramSocket;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
@@ -17,18 +17,18 @@ import java.nio.ByteOrder;
|
|||||||
*
|
*
|
||||||
* Used simply to transcribe packets on the network
|
* Used simply to transcribe packets on the network
|
||||||
*/
|
*/
|
||||||
public final class Teamspeak3Socket {
|
public class LocalTeamspeakSocket implements TeamspeakSocket {
|
||||||
private final SocketRole socketRole;
|
private final SocketRole socketRole;
|
||||||
|
|
||||||
private final DatagramSocket datagramSocket;
|
private final DatagramSocket datagramSocket;
|
||||||
private final DatagramPacket datagramPacket = new DatagramPacket(new byte[1500], 1500);
|
private final DatagramPacket datagramPacket = new DatagramPacket(new byte[1500], 1500);
|
||||||
|
|
||||||
public Teamspeak3Socket(SocketRole socketRole, DatagramSocket datagramSocket) {
|
public LocalTeamspeakSocket(SocketRole socketRole, DatagramSocket datagramSocket) {
|
||||||
this.socketRole = socketRole;
|
this.socketRole = socketRole;
|
||||||
this.datagramSocket = datagramSocket;
|
this.datagramSocket = datagramSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Teamspeak3Socket(SocketRole socketRole) throws SocketException {
|
public LocalTeamspeakSocket(SocketRole socketRole) throws SocketException {
|
||||||
this(socketRole, new DatagramSocket());
|
this(socketRole, new DatagramSocket());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +36,14 @@ public final class Teamspeak3Socket {
|
|||||||
return socketRole;
|
return socketRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InetSocketAddress getLocalAddress() {
|
||||||
|
return new InetSocketAddress(datagramSocket.getLocalAddress(), datagramSocket.getLocalPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
public InetSocketAddress getRemoteAddress() {
|
||||||
|
return new InetSocketAddress(datagramSocket.getInetAddress(), datagramSocket.getPort());
|
||||||
|
}
|
||||||
|
|
||||||
public void connect(SocketAddress remote) throws SocketException {
|
public void connect(SocketAddress remote) throws SocketException {
|
||||||
datagramSocket.connect(remote);
|
datagramSocket.connect(remote);
|
||||||
}
|
}
|
||||||
@@ -116,4 +124,5 @@ public final class Teamspeak3Socket {
|
|||||||
|
|
||||||
return networkPacket;
|
return networkPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -18,8 +18,8 @@ public final class Ts3Logging {
|
|||||||
System.err.println("[INFO] " + message);
|
System.err.println("[INFO] " + message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void debug(String message) {
|
public static void debug(Object message) {
|
||||||
System.err.println("[DEBUG] " + message);
|
System.err.println("[DEBUG] " + message.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void debug(String message, Throwable ex) {
|
public static void debug(String message, Throwable ex) {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.github.manevolent.ts3j;
|
package com.github.manevolent.ts3j;
|
||||||
|
|
||||||
|
import com.github.manevolent.ts3j.identity.LocalIdentity;
|
||||||
|
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
|
||||||
|
import com.github.manevolent.ts3j.protocol.client.LocalTeamspeakClient;
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
@@ -10,15 +13,23 @@ public class ServerConnectionTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testParser() throws Exception {
|
public void testParser() throws Exception {
|
||||||
Teamspeak3Client client = new Teamspeak3Client();
|
try {
|
||||||
|
LocalIdentity localIdentity = LocalIdentity.generateNew(8);
|
||||||
|
|
||||||
client.connect(new InetSocketAddress(
|
LocalTeamspeakClient client = new LocalTeamspeakClient();
|
||||||
"voice.teamspeak.com",
|
|
||||||
9987),
|
|
||||||
null,
|
|
||||||
10000L
|
|
||||||
);
|
|
||||||
|
|
||||||
assertEquals(client.getClientConnectionState(), ClientConnectionState.CONNECTED);
|
client.setLocalIdentity(localIdentity);
|
||||||
|
|
||||||
|
client.connect(new InetSocketAddress(
|
||||||
|
"voice.teamspeak.com",
|
||||||
|
9987),
|
||||||
|
null,
|
||||||
|
10000L
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(client.getClientConnectionState(), ClientConnectionState.CONNECTED);
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user