Moving further along with the connection-level commands that are received, and now have model objects to store variables. No callbacks to the server yet, but that will happen soon. Right now I have channel listings and basic client information done.

This commit is contained in:
Manevolent
2018-07-28 22:34:18 -04:00
parent f124583fa6
commit f533e2e918
26 changed files with 1282 additions and 186 deletions
@@ -1,6 +1,6 @@
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.parameter.CommandParameter;
import com.github.manevolent.ts3j.protocol.ProtocolRole; import com.github.manevolent.ts3j.protocol.ProtocolRole;
public interface Command { public interface Command {
@@ -1,6 +1,9 @@
package com.github.manevolent.ts3j.command; package com.github.manevolent.ts3j.command;
import com.github.manevolent.ts3j.command.client.ClientChannelListFinishedProcessor;
import com.github.manevolent.ts3j.command.client.ClientChannelListProcessor;
import com.github.manevolent.ts3j.command.client.ClientInitServerProcessor; import com.github.manevolent.ts3j.command.client.ClientInitServerProcessor;
import com.github.manevolent.ts3j.command.client.ClientNotifyClientEnterViewProcessor;
import com.github.manevolent.ts3j.protocol.socket.client.AbstractTeamspeakClientSocket; import com.github.manevolent.ts3j.protocol.socket.client.AbstractTeamspeakClientSocket;
import com.github.manevolent.ts3j.util.Ts3Debugging; import com.github.manevolent.ts3j.util.Ts3Debugging;
@@ -15,19 +18,28 @@ public final class CommandHandler implements CommandProcessor {
} }
@Override @Override
public void process(AbstractTeamspeakClientSocket socket, ComplexCommand complexCommand) public void process(AbstractTeamspeakClientSocket socket, MultiCommand multiCommand)
throws CommandProcessException { throws CommandProcessException {
Ts3Debugging.debug("[COMMAND] " + complexCommand.build()); CommandProcessor processor = commandProcessors.get(multiCommand.getName().toLowerCase());
CommandProcessor processor = commandProcessors.get(complexCommand.getName().toLowerCase());
if (processor != null) if (processor != null)
processor.process(socket, complexCommand); processor.process(socket, multiCommand);
} }
public static CommandHandler createLocalClient() { @Override
public void process(AbstractTeamspeakClientSocket socket, SingleCommand command)
throws CommandProcessException {
CommandProcessor processor = commandProcessors.get(command.getName().toLowerCase());
if (processor != null)
processor.process(socket, command);
}
public static CommandHandler createLocalClientHandler() {
CommandHandler handler = new CommandHandler(); CommandHandler handler = new CommandHandler();
handler.commandProcessors.put("initserver", new ClientInitServerProcessor()); handler.commandProcessors.put("initserver", new ClientInitServerProcessor());
handler.commandProcessors.put("channellist", new ClientChannelListProcessor());
handler.commandProcessors.put("notifycliententerview", new ClientNotifyClientEnterViewProcessor());
handler.commandProcessors.put("channellistfinished", new ClientChannelListFinishedProcessor());
return handler; return handler;
} }
@@ -3,6 +3,14 @@ package com.github.manevolent.ts3j.command;
import com.github.manevolent.ts3j.protocol.socket.client.AbstractTeamspeakClientSocket; import com.github.manevolent.ts3j.protocol.socket.client.AbstractTeamspeakClientSocket;
public interface CommandProcessor { public interface CommandProcessor {
void process(AbstractTeamspeakClientSocket socket, ComplexCommand complexCommand) default void process(AbstractTeamspeakClientSocket client,
throws CommandProcessException; MultiCommand multiCommand)
throws CommandProcessException {
for (SingleCommand singleCommand : multiCommand.simplify()) process(client, singleCommand);
}
default void process(AbstractTeamspeakClientSocket client,
SingleCommand singleCommand)
throws CommandProcessException { }
} }
@@ -1,90 +0,0 @@
package com.github.manevolent.ts3j.command;
import com.github.manevolent.ts3j.command.part.CommandOption;
import com.github.manevolent.ts3j.command.part.CommandParameter;
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
import com.github.manevolent.ts3j.protocol.ProtocolRole;
import com.github.manevolent.ts3j.util.Ts3String;
import java.util.*;
public class ComplexCommand implements Command {
private final String name;
private final ProtocolRole role;
private final List<CommandParameter> params;
public ComplexCommand(String name, ProtocolRole role, CommandParameter... params) {
this(name, role, Arrays.asList(params));
}
public ComplexCommand(String name, ProtocolRole role, List<CommandParameter> params) {
this.name = name;
this.role = role;
this.params = new LinkedList<>();
for (CommandParameter parameter : params) add(parameter);
}
@Override
public String getName() {
return name;
}
@Override
public ProtocolRole getRole() {
return role;
}
@Override
public Iterable<CommandParameter> getParameters() {
return Collections.unmodifiableCollection(params);
}
public boolean has(String key) {
return params.stream().anyMatch(x -> x.getName().equalsIgnoreCase(key));
}
public void add(CommandParameter parameter) {
if (parameter.getName().trim().length() <= 0)
throw new IllegalArgumentException("invalid key (empty)");
this.params.add(parameter);
}
public SimpleCommand simplify() {
return new SimpleCommand(
name,
role,
params
);
}
public static ComplexCommand parse(ProtocolRole role, String commandText) {
String[] commandParts = commandText.split(" ");
String commandLabel = commandParts[0].toLowerCase();
List<CommandParameter> parameterList = new LinkedList<>();
for (int i = 1; i < commandParts.length; i ++) {
String commandParamText = commandParts[i].trim();
if (commandParamText.length() <= 0) continue;
if (commandParamText.startsWith("-")) { // Option
parameterList.add(new CommandOption(commandParamText.substring(1)));
} else {
String[] commandParamParts = commandParamText.split("=", 2);
parameterList.add(new CommandSingleParameter(
commandParamParts[0].toLowerCase(),
commandParamParts.length > 1 ?
Ts3String.unescape(commandParamParts[1]) :
null
)
);
}
}
return new ComplexCommand(commandLabel, role, parameterList);
}
}
@@ -0,0 +1,117 @@
package com.github.manevolent.ts3j.command;
import com.github.manevolent.ts3j.command.parameter.CommandOption;
import com.github.manevolent.ts3j.command.parameter.CommandParameter;
import com.github.manevolent.ts3j.command.parameter.CommandSingleParameter;
import com.github.manevolent.ts3j.protocol.ProtocolRole;
import com.github.manevolent.ts3j.util.Ts3String;
import java.util.*;
public class MultiCommand implements Command {
private final String name;
private final ProtocolRole role;
private final List<SingleCommand> commands;
public MultiCommand(String name, ProtocolRole role, SingleCommand... params) {
this(name, role, Arrays.asList(params));
}
public MultiCommand(String name, ProtocolRole role, List<SingleCommand> params) {
this.name = name;
this.role = role;
this.commands = Collections.unmodifiableList(params);
}
@Override
public String getName() {
return name;
}
@Override
public ProtocolRole getRole() {
return role;
}
@Override
public Iterable<CommandParameter> getParameters() {
throw new UnsupportedOperationException();
}
@Override
public void add(CommandParameter parameter) {
throw new UnsupportedOperationException();
}
public List<SingleCommand> simplify() {
return Collections.unmodifiableList(commands);
}
public SingleCommand simplifyFirst() {
return commands.stream().findFirst().orElse(null);
}
public SingleCommand simplifyOne() {
if (commands.size() > 1) throw new IllegalArgumentException("ambiguous; more than one command");
return commands.stream().findFirst().orElse(null);
}
@Override
public String build() {
StringBuilder builder = new StringBuilder();
builder.append(getName().toLowerCase());
List<SingleCommand> singleCommands = simplify();
for (int i = 0; i < singleCommands.size(); i ++) {
boolean first = i == 0;
boolean last = i == singleCommands.size() - 1;
if (!first) builder.append("\\p");
for (CommandParameter parameter : singleCommands.get(i).getParameters()) {
builder.append(" " + (parameter.toString()).trim());
}
}
return builder.toString();
}
public static MultiCommand parse(ProtocolRole role, String text) {
String[] labelAndText = text.split(" ", 2);
String label = labelAndText[0].toLowerCase();
String[] commands = labelAndText.length > 1 ? labelAndText[1].split("\\|") : new String[0];
List<SingleCommand> singleCommands = new LinkedList<>();
for (String commandText : commands) {
String[] commandParts = commandText.split(" ");
List<CommandParameter> parameterList = new LinkedList<>();
for (int i = 0; i < commandParts.length; i++) {
String commandParamText = commandParts[i].trim();
if (commandParamText.length() <= 0) continue;
if (commandParamText.startsWith("-")) { // Option
parameterList.add(new CommandOption(commandParamText.substring(1)));
} else {
String[] commandParamParts = commandParamText.split("=", 2);
parameterList.add(new CommandSingleParameter(
commandParamParts[0].toLowerCase(),
commandParamParts.length > 1 ?
Ts3String.unescape(commandParamParts[1]) :
null
)
);
}
}
singleCommands.add(new SingleCommand(label, role, parameterList));
}
return new MultiCommand(label, role, singleCommands);
}
}
@@ -1,23 +1,20 @@
package com.github.manevolent.ts3j.command; package com.github.manevolent.ts3j.command;
import com.github.manevolent.ts3j.command.part.CommandOption; import com.github.manevolent.ts3j.command.parameter.CommandParameter;
import com.github.manevolent.ts3j.command.part.CommandParameter;
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
import com.github.manevolent.ts3j.protocol.ProtocolRole; import com.github.manevolent.ts3j.protocol.ProtocolRole;
import com.github.manevolent.ts3j.util.Ts3String;
import java.util.*; import java.util.*;
public class SimpleCommand implements Command { public class SingleCommand implements Command {
private final String name; private final String name;
private final ProtocolRole role; private final ProtocolRole role;
private final Map<String, CommandParameter> params; private final Map<String, CommandParameter> params;
public SimpleCommand(String name, ProtocolRole role, CommandParameter... params) { public SingleCommand(String name, ProtocolRole role, CommandParameter... params) {
this(name, role, Arrays.asList(params)); this(name, role, Arrays.asList(params));
} }
public SimpleCommand(String name, ProtocolRole role, List<CommandParameter> params) { public SingleCommand(String name, ProtocolRole role, List<CommandParameter> params) {
this.name = name; this.name = name;
this.role = role; this.role = role;
@@ -47,7 +44,7 @@ public class SimpleCommand implements Command {
public void add(CommandParameter parameter) { public void add(CommandParameter parameter) {
CommandParameter existing = get(parameter.getName()); CommandParameter existing = get(parameter.getName());
if (existing != null) throw new IllegalArgumentException(parameter.getName() + " already set"); if (existing != null) throw new IllegalArgumentException(getName() + ": " + parameter.getName() + " already set");
if (parameter.getName().trim().length() <= 0) if (parameter.getName().trim().length() <= 0)
throw new IllegalArgumentException("invalid key (empty)"); throw new IllegalArgumentException("invalid key (empty)");
@@ -63,7 +60,7 @@ public class SimpleCommand implements Command {
return this.params.remove(key); return this.params.remove(key);
} }
public static SimpleCommand parse(ProtocolRole role, String commandText) { public static SingleCommand parse(ProtocolRole role, String commandText) {
return ComplexCommand.parse(role, commandText).simplify(); return MultiCommand.parse(role, commandText).simplifyFirst();
} }
} }
@@ -0,0 +1,20 @@
package com.github.manevolent.ts3j.command.client;
import com.github.manevolent.ts3j.command.CommandProcessException;
import com.github.manevolent.ts3j.command.CommandProcessor;
import com.github.manevolent.ts3j.command.MultiCommand;
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
import com.github.manevolent.ts3j.protocol.socket.client.AbstractTeamspeakClientSocket;
public class ClientChannelListFinishedProcessor implements CommandProcessor {
@Override
public void process(AbstractTeamspeakClientSocket client,
MultiCommand command) throws CommandProcessException {
if (client.getState() == ClientConnectionState.RETRIEVING_DATA)
try {
client.setState(ClientConnectionState.CONNECTED);
} catch (Exception e) {
throw new CommandProcessException(e);
}
}
}
@@ -0,0 +1,45 @@
package com.github.manevolent.ts3j.command.client;
import com.github.manevolent.ts3j.command.CommandProcessException;
import com.github.manevolent.ts3j.command.CommandProcessor;
import com.github.manevolent.ts3j.command.SingleCommand;
import com.github.manevolent.ts3j.enums.CodecType;
import com.github.manevolent.ts3j.model.Channel;
import com.github.manevolent.ts3j.protocol.socket.client.AbstractTeamspeakClientSocket;
public class ClientChannelListProcessor implements CommandProcessor {
@Override
public void process(AbstractTeamspeakClientSocket client,
SingleCommand command) throws CommandProcessException {
int cid = Integer.parseInt(command.get("cid").getValue());
Channel channel = client.getVirtualServer().getChannelById(cid);
if (channel == null)
channel = new Channel(
client.getVirtualServer(), Integer.parseInt(command.get("cid").getValue())
);
channel.setName(command.get("channel_name").getValue());
channel.setCodec(CodecType.fromId(Integer.parseInt(command.get("channel_codec").getValue())));
channel.setCodecQuality(Integer.parseInt(command.get("channel_codec_quality").getValue()));
channel.setMaxClients(Integer.parseInt(command.get("channel_maxclients").getValue()));
channel.setMaxFamilyClients(Integer.parseInt(command.get("channel_maxfamilyclients").getValue()));
channel.setOrder(Integer.parseInt(command.get("channel_order").getValue()));
channel.setPermanent(command.get("channel_flag_permanent").getValue().equals("1"));
channel.setSemiPermanent(command.get("channel_flag_semi_permanent").getValue().equals("1"));
channel.setServerDefault(command.get("channel_flag_default").getValue().equals("1"));
channel.setHasPassword(command.get("channel_flag_password").getValue().equals("1"));
channel.setCodecLatencyFactor(command.get("channel_codec_latency_factor").getValue().equals("1"));
channel.setUnencrypted(command.get("channel_codec_is_unencrypted").getValue().equals("1"));
channel.setDeleteDelay(Integer.parseInt(command.get("channel_delete_delay").getValue()));
channel.setUnlimited(command.get("channel_flag_maxclients_unlimited").getValue().equals("1"));
channel.setFamilyUnlimited(command.get("channel_flag_maxfamilyclients_unlimited").getValue().equals("1"));
channel.setInheritingMaxfamilyUnlimited(command.get("channel_flag_maxfamilyclients_inherited").getValue().equals("1"));
channel.setNeededTalkPower(Integer.parseInt(command.get("channel_needed_talk_power").getValue()));
channel.setForcingSilence(command.get("channel_forced_silence").getValue().equals("1"));
channel.setIconId(Integer.parseInt(command.get("channel_icon_id").getValue()));
channel.setPrivated(command.get("channel_flag_private").getValue().equals("1"));
client.getVirtualServer().updateChannel(channel);
}
}
@@ -2,24 +2,46 @@ package com.github.manevolent.ts3j.command.client;
import com.github.manevolent.ts3j.command.CommandProcessException; import com.github.manevolent.ts3j.command.CommandProcessException;
import com.github.manevolent.ts3j.command.CommandProcessor; import com.github.manevolent.ts3j.command.CommandProcessor;
import com.github.manevolent.ts3j.command.ComplexCommand; import com.github.manevolent.ts3j.command.MultiCommand;
import com.github.manevolent.ts3j.command.SimpleCommand; import com.github.manevolent.ts3j.command.SingleCommand;
import com.github.manevolent.ts3j.enums.HostBannerMode;
import com.github.manevolent.ts3j.enums.HostMessageMode;
import com.github.manevolent.ts3j.enums.VoiceEncryptionMode;
import com.github.manevolent.ts3j.model.VirtualServer;
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState; import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
import com.github.manevolent.ts3j.protocol.socket.client.AbstractTeamspeakClientSocket; import com.github.manevolent.ts3j.protocol.socket.client.AbstractTeamspeakClientSocket;
import com.github.manevolent.ts3j.util.Ts3Debugging;
public class ClientInitServerProcessor implements CommandProcessor { public class ClientInitServerProcessor implements CommandProcessor {
@Override @Override
public void process(AbstractTeamspeakClientSocket socket, ComplexCommand complexCommand) throws CommandProcessException { public void process(AbstractTeamspeakClientSocket client,
SimpleCommand command = complexCommand.simplify(); MultiCommand multiCommand) throws CommandProcessException {
SingleCommand command = multiCommand.simplifyOne();
Ts3Debugging.debug(command.get("virtualserver_welcomemessage").getValue()); VirtualServer server = new VirtualServer();
if (socket.getState() == ClientConnectionState.RETRIEVING_DATA) server.setName(command.get("virtualserver_name").getValue());
try { server.setWelcomeMessage(command.get("virtualserver_welcomemessage").getValue());
socket.setState(ClientConnectionState.CONNECTED); server.setPlatform(command.get("virtualserver_platform").getValue());
} catch (Exception e) { server.setVersion(command.get("virtualserver_version").getValue());
throw new CommandProcessException(e); server.setMaxClients(Integer.parseInt(command.get("virtualserver_maxclients").getValue()));
} server.setCreated(Long.parseLong(command.get("virtualserver_created").getValue()));
server.setEncryptionMode(VoiceEncryptionMode.fromId(Integer.parseInt(command.get("virtualserver_codec_encryption_mode").getValue())));
server.setHostMessageMode(HostMessageMode.fromId(Integer.parseInt(command.get("virtualserver_hostmessage_mode").getValue())));
server.setDefaultGroupId(Integer.parseInt(command.get("virtualserver_default_server_group").getValue()));
server.setDefaultChannelGroupId(Integer.parseInt(command.get("virtualserver_default_channel_group").getValue()));
server.setHostbannerUrl(command.get("virtualserver_hostbanner_gfx_url").getValue());
server.setPrioritySpeakerDimmModifier(Double.parseDouble(command.get("virtualserver_priority_speaker_dimm_modificator").getValue()));
server.setId(Integer.parseInt(command.get("virtualserver_id").getValue()));
server.setNamePhonetic(command.get("virtualserver_name_phonetic").getValue());
server.setIconId(Long.parseLong(command.get("virtualserver_icon_id").getValue()));
server.setServerIp(command.get("virtualserver_ip").getValue());
server.setAskForPriviledgeKey(Integer.parseInt(command.get("virtualserver_ask_for_privilegekey").getValue()) == 1);
server.setHostBannerMode(HostBannerMode.fromId(Integer.parseInt(command.get("virtualserver_hostbanner_mode").getValue())));
server.setDefaultTempChannelDeleteDelay(Integer.parseInt(command.get("virtualserver_channel_temp_delete_delay_default").getValue()));
server.setNickname(command.get("virtualserver_nickname").getValue());
client.setVirtualServer(server);
client.setClientId(Integer.parseInt(command.get("aclid").getValue()));
} }
} }
@@ -0,0 +1,71 @@
package com.github.manevolent.ts3j.command.client;
import com.github.manevolent.ts3j.command.CommandProcessException;
import com.github.manevolent.ts3j.command.CommandProcessor;
import com.github.manevolent.ts3j.command.SingleCommand;
import com.github.manevolent.ts3j.enums.ClientType;
import com.github.manevolent.ts3j.identity.Uid;
import com.github.manevolent.ts3j.model.Client;
import com.github.manevolent.ts3j.protocol.socket.client.AbstractTeamspeakClientSocket;
import java.util.Arrays;
import java.util.stream.Collectors;
public class ClientNotifyClientEnterViewProcessor implements CommandProcessor {
@Override
public void process(AbstractTeamspeakClientSocket client,
SingleCommand command) throws CommandProcessException {
int clientId = Integer.parseInt(command.get("clid").getValue());
Client newClient = client.getVirtualServer().getClientById(clientId);
if (newClient == null)
newClient = new Client(
Integer.parseInt(command.get("clid").getValue()),
Integer.parseInt(command.get("client_database_id").getValue()),
client.getVirtualServer(),
new Uid(command.get("client_unique_identifier").getValue()),
ClientType.fromId(Integer.parseInt(command.get("client_type").getValue())));
newClient.setNickname(command.get("client_nickname").getValue());
newClient.setInputMuted(command.get("client_input_muted").getValue().equals("1"));
newClient.setOutputMuted(command.get("client_output_muted").getValue().equals("1"));
newClient.setInputHardware(command.get("client_input_hardware").getValue().equals("1"));
newClient.setOutputHardware(command.get("client_output_hardware").getValue().equals("1"));
newClient.setRecording(command.get("client_is_recording").getValue().equals("1"));
newClient.setChannelGroupId(Integer.parseInt(command.get("client_channel_group_id").getValue()));
newClient.setServerGroupIds(
Arrays.stream(command.get("client_servergroups").getValue().split(",")).map(
Integer::parseInt
).collect(Collectors.toList())
);
newClient.setAway(command.get("client_away").getValue().equals("1"));
newClient.setTalkPower(Integer.parseInt(command.get("client_talk_power").getValue()));
newClient.setTalkRequested(command.get("client_talk_request").getValue().equals("1"));
newClient.setTalker(command.get("client_is_talker").getValue().equals("1"));
newClient.setPrioritySpeaker(command.get("client_is_priority_speaker").getValue().equals("1"));
newClient.setUnreadMessages(Integer.parseInt(command.get("client_unread_messages").getValue()));
newClient.setNeededServerQueryViewPower(Integer.parseInt(command.get("client_needed_serverquery_view_power").getValue()));
newClient.setIconId(Integer.parseInt(command.get("client_icon_id").getValue()));
newClient.setChannelCommander(command.get("client_is_channel_commander").getValue().equals("1"));
newClient.setCountry(command.get("client_country").getValue());
newClient.setInheritedChannelGroupChannelId(Integer.parseInt(command.get("client_channel_group_inherited_channel_id").getValue()));
client.getVirtualServer().updateConnectedClient(newClient);
}
}
@@ -1,4 +1,4 @@
package com.github.manevolent.ts3j.command.part; package com.github.manevolent.ts3j.command.parameter;
public class CommandOption implements CommandParameter { public class CommandOption implements CommandParameter {
private final String key; private final String key;
@@ -1,4 +1,4 @@
package com.github.manevolent.ts3j.command.part; package com.github.manevolent.ts3j.command.parameter;
import java.util.Base64; import java.util.Base64;
@@ -1,4 +1,4 @@
package com.github.manevolent.ts3j.command.part; package com.github.manevolent.ts3j.command.parameter;
import com.github.manevolent.ts3j.util.Ts3String; import com.github.manevolent.ts3j.util.Ts3String;
@@ -1,7 +1,7 @@
package com.github.manevolent.ts3j.command.response; package com.github.manevolent.ts3j.command.response;
import com.github.manevolent.ts3j.command.Command; import com.github.manevolent.ts3j.command.Command;
import com.github.manevolent.ts3j.command.ComplexCommand; import com.github.manevolent.ts3j.command.MultiCommand;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
@@ -11,7 +11,7 @@ public class CommandResponse {
private final int returnCode; private final int returnCode;
private long dispatchedTime; private long dispatchedTime;
private ComplexCommand returnedCommand; private MultiCommand returnedCommand;
public CommandResponse(Command command, int returnCode) { public CommandResponse(Command command, int returnCode) {
this.command = command; this.command = command;
@@ -26,12 +26,12 @@ public class CommandResponse {
return returnCode; return returnCode;
} }
public ComplexCommand getResponse() public MultiCommand getResponse()
throws InterruptedException, TimeoutException { throws InterruptedException, TimeoutException {
return getResponse(Integer.MAX_VALUE); return getResponse(Integer.MAX_VALUE);
} }
public ComplexCommand getResponse(int millis) public MultiCommand getResponse(int millis)
throws InterruptedException, TimeoutException { throws InterruptedException, TimeoutException {
if (returnCode < 0) return null; if (returnCode < 0) return null;
@@ -59,11 +59,11 @@ public class CommandResponse {
this.dispatchedTime = dispatchedTime; this.dispatchedTime = dispatchedTime;
} }
public ComplexCommand getReturnedCommand() { public MultiCommand getReturnedCommand() {
return returnedCommand; return returnedCommand;
} }
public void setReturnedCommand(ComplexCommand returnedCommand) { public void setReturnedCommand(MultiCommand returnedCommand) {
synchronized (returnLock) { synchronized (returnLock) {
if (this.returnCode < 0 ) throw new IllegalArgumentException("not expecting return command"); if (this.returnCode < 0 ) throw new IllegalArgumentException("not expecting return command");
if (this.returnedCommand != null) throw new IllegalArgumentException("already set return command"); if (this.returnedCommand != null) throw new IllegalArgumentException("already set return command");
@@ -21,7 +21,7 @@ public class Identity {
} }
private final ECPoint publicKey; private final ECPoint publicKey;
private final String fingerprint; private final Uid uid;
private final String publicKeyString; private final String publicKeyString;
private long keyOffset = 0L, lastCheckedKeyOffset = 0L; private long keyOffset = 0L, lastCheckedKeyOffset = 0L;
@@ -29,7 +29,7 @@ public class Identity {
public Identity(ECPoint publicKey) { public Identity(ECPoint publicKey) {
this.publicKey = publicKey; this.publicKey = publicKey;
this.publicKeyString = Base64.getEncoder().encodeToString(Ts3Crypt.encodePublicKey(publicKey)); this.publicKeyString = Base64.getEncoder().encodeToString(Ts3Crypt.encodePublicKey(publicKey));
this.fingerprint = generateFingerprint(); this.uid = generateUid();
} }
public ECPoint getPublicKey() { public ECPoint getPublicKey() {
@@ -40,8 +40,8 @@ public class Identity {
return publicKeyString; return publicKeyString;
} }
public String getFingerprint() { public Uid getUid() {
return fingerprint; return uid;
} }
public long getLastCheckedKeyOffset() { public long getLastCheckedKeyOffset() {
@@ -129,11 +129,9 @@ public class Identity {
return curr; return curr;
} }
private String generateFingerprint() { private Uid generateUid() {
synchronized (sha1) { synchronized (sha1) {
return Base64.getEncoder().encodeToString( return new Uid(sha1.digest(getPublicKeyString().getBytes(Charset.forName("ASCII"))));
sha1.digest(getPublicKeyString().getBytes(Charset.forName("ASCII")))
);
} }
} }
} }
@@ -0,0 +1,42 @@
package com.github.manevolent.ts3j.identity;
import java.util.Arrays;
import java.util.Base64;
public final class Uid {
private final byte[] uidBytes;
private final String base64;
public Uid(String base64) {
this.base64 = base64;
this.uidBytes = Base64.getDecoder().decode(base64);
}
public Uid(byte[] bytes) {
this.uidBytes = bytes;
this.base64 = Base64.getEncoder().encodeToString(bytes);
}
public String toBase64() {
return base64;
}
public byte[] toByteArray() {
return uidBytes;
}
@Override
public int hashCode() {
return Arrays.hashCode(toByteArray());
}
@Override
public boolean equals(Object b) {
if (b == null || !(b instanceof Uid)) return false;
return equals((Uid) b);
}
public boolean equals(Uid b) {
return b != null && Arrays.equals(b.toByteArray(), toByteArray());
}
}
@@ -0,0 +1,201 @@
package com.github.manevolent.ts3j.model;
import com.github.manevolent.ts3j.enums.CodecType;
public class Channel {
private final VirtualServer server;
private final int channelId;
private String name;
private CodecType codec;
private int codecQuality;
private int maxClients;
private int maxFamilyClients;
private int order;
private boolean permanent;
private boolean semiPermanent;
private boolean serverDefault;
private boolean hasPassword;
private boolean codecLatencyFactor;
private boolean unencrypted;
private int deleteDelay;
private boolean unlimited;
private boolean familyUnlimited;
private boolean inheritingMaxfamilyUnlimited;
private int neededTalkPower;
private boolean forcingSilence;
private int iconId;
private boolean privated;
public Channel(VirtualServer server, int channelId) {
this.server = server;
this.channelId = channelId;
}
public int getChannelId() {
return channelId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public CodecType getCodec() {
return codec;
}
public void setCodec(CodecType codec) {
this.codec = codec;
}
public int getCodecQuality() {
return codecQuality;
}
public void setCodecQuality(int codecQuality) {
this.codecQuality = codecQuality;
}
public int getMaxClients() {
return maxClients;
}
public void setMaxClients(int maxClients) {
this.maxClients = maxClients;
}
public int getMaxFamilyClients() {
return maxFamilyClients;
}
public void setMaxFamilyClients(int maxFamilyClients) {
this.maxFamilyClients = maxFamilyClients;
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
public boolean isPermanent() {
return permanent;
}
public void setPermanent(boolean permanent) {
this.permanent = permanent;
}
public boolean isSemiPermanent() {
return semiPermanent;
}
public void setSemiPermanent(boolean semiPermanent) {
this.semiPermanent = semiPermanent;
}
public boolean isServerDefault() {
return serverDefault;
}
public void setServerDefault(boolean serverDefault) {
this.serverDefault = serverDefault;
}
public boolean hasPassword() {
return hasPassword;
}
public void setHasPassword(boolean hasPassword) {
this.hasPassword = hasPassword;
}
public boolean isCodecLatencyFactor() {
return codecLatencyFactor;
}
public void setCodecLatencyFactor(boolean codecLatencyFactor) {
this.codecLatencyFactor = codecLatencyFactor;
}
public boolean isUnencrypted() {
return unencrypted;
}
public void setUnencrypted(boolean unencrypted) {
this.unencrypted = unencrypted;
}
public int getDeleteDelay() {
return deleteDelay;
}
public void setDeleteDelay(int deleteDelay) {
this.deleteDelay = deleteDelay;
}
public boolean isUnlimited() {
return unlimited;
}
public void setUnlimited(boolean unlimited) {
this.unlimited = unlimited;
}
public boolean isFamilyUnlimited() {
return familyUnlimited;
}
public void setFamilyUnlimited(boolean familyUnlimited) {
this.familyUnlimited = familyUnlimited;
}
public boolean isInheritingMaxfamilyUnlimited() {
return inheritingMaxfamilyUnlimited;
}
public void setInheritingMaxfamilyUnlimited(boolean inheritingMaxfamilyUnlimited) {
this.inheritingMaxfamilyUnlimited = inheritingMaxfamilyUnlimited;
}
public int getNeededTalkPower() {
return neededTalkPower;
}
public void setNeededTalkPower(int neededTalkPower) {
this.neededTalkPower = neededTalkPower;
}
public boolean isForcingSilence() {
return forcingSilence;
}
public void setForcingSilence(boolean forcingSilence) {
this.forcingSilence = forcingSilence;
}
public int getIconId() {
return iconId;
}
public void setIconId(int iconId) {
this.iconId = iconId;
}
public boolean isPrivated() {
return privated;
}
public void setPrivated(boolean privated) {
this.privated = privated;
}
public VirtualServer getServer() {
return server;
}
}
@@ -0,0 +1,261 @@
package com.github.manevolent.ts3j.model;
import com.github.manevolent.ts3j.enums.ClientType;
import com.github.manevolent.ts3j.identity.Uid;
import java.util.*;
public class Client {
private final int id;
private final int databaseId;
private final VirtualServer server;
private final Uid uid;
private final ClientType clientType;
private String nickname;
private int channelId;
private int channelGroupId;
private boolean speaking;
private boolean outputMuted, inputMuted;
private boolean recording;
private boolean inputHardware, outputHardware;
private boolean away;
private int talkPower;
private boolean talkRequested;
private boolean talker;
private boolean prioritySpeaker;
private int unreadMessages;
private int neededServerQueryViewPower;
private int iconId;
private boolean channelCommander;
private String country;
private int inheritedChannelGroupChannelId;
private List<Integer> serverGroupIds = new ArrayList<>();
public Client(int id, int databaseId, VirtualServer server, Uid uid, ClientType clientType) {
this.id = id;
this.databaseId = databaseId;
this.server = server;
this.uid = uid;
this.clientType = clientType;
}
public VirtualServer getServer() {
return server;
}
public Uid getUid() {
return uid;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
/**
* Gets the client's current client connection ID.
* @return client ID.
*/
public int getId() {
return id;
}
/**
* Gets the current connected-to channel.
* @return channel currently connected to by the client.
*/
public Channel getChannel() {
return server.getChannelById(getChannelId());
}
/**
* Gets the channel ID currently connected to.
* @return channel currently connected to by the client.
*/
public int getChannelId() {
return channelId;
}
public void setChannelId(int channelId) {
this.channelId = channelId;
}
public boolean isSpeaking() {
return speaking;
}
public void setSpeaking(boolean speaking) {
this.speaking = speaking;
}
public boolean isInputMuted() {
return inputMuted;
}
public void setInputMuted(boolean inputMuted) {
this.inputMuted = inputMuted;
}
public boolean isOutputMuted() {
return outputMuted;
}
public void setOutputMuted(boolean outputMuted) {
this.outputMuted = outputMuted;
}
public boolean hasOutputHardware() {
return outputHardware;
}
public void setOutputHardware(boolean outputHardware) {
this.outputHardware = outputHardware;
}
public boolean hasInputHardware() {
return inputHardware;
}
public void setInputHardware(boolean inputHardware) {
this.inputHardware = inputHardware;
}
public int getDatabaseId() {
return databaseId;
}
public boolean isRecording() {
return recording;
}
public void setRecording(boolean recording) {
this.recording = recording;
}
public boolean isInServerGroup(int serverGroupId) {
return serverGroupIds.contains(serverGroupId);
}
public void setServerGroupIds(Collection<Integer> serverGroupIds) {
synchronized (serverGroupIds) {
this.serverGroupIds.clear();
this.serverGroupIds.addAll(serverGroupIds);
}
}
public void setServerGroupIds(Integer... serverGroupIds) {
setServerGroupIds(Arrays.asList(serverGroupIds));
}
public Iterable<Integer> getServerGroupIds() {
return Collections.unmodifiableList(serverGroupIds);
}
public int getChannelGroupId() {
return channelGroupId;
}
public void setChannelGroupId(int channelGroupId) {
this.channelGroupId = channelGroupId;
}
public boolean isAway() {
return away;
}
public void setAway(boolean away) {
this.away = away;
}
public int getTalkPower() {
return talkPower;
}
public void setTalkPower(int talkPower) {
this.talkPower = talkPower;
}
public boolean isTalkRequested() {
return talkRequested;
}
public void setTalkRequested(boolean talkRequested) {
this.talkRequested = talkRequested;
}
public boolean isTalker() {
return talker;
}
public void setTalker(boolean talker) {
this.talker = talker;
}
public boolean isPrioritySpeaker() {
return prioritySpeaker;
}
public void setPrioritySpeaker(boolean prioritySpeaker) {
this.prioritySpeaker = prioritySpeaker;
}
public int getUnreadMessages() {
return unreadMessages;
}
public void setUnreadMessages(int unreadMessages) {
this.unreadMessages = unreadMessages;
}
public int getNeededServerQueryViewPower() {
return neededServerQueryViewPower;
}
public void setNeededServerQueryViewPower(int neededServerQueryViewPower) {
this.neededServerQueryViewPower = neededServerQueryViewPower;
}
public int getIconId() {
return iconId;
}
public void setIconId(int iconId) {
this.iconId = iconId;
}
public boolean isChannelCommander() {
return channelCommander;
}
public void setChannelCommander(boolean channelCommander) {
this.channelCommander = channelCommander;
}
public int getInheritedChannelGroupChannelId() {
return inheritedChannelGroupChannelId;
}
public void setInheritedChannelGroupChannelId(int inheritedChannelGroupChannelId) {
this.inheritedChannelGroupChannelId = inheritedChannelGroupChannelId;
}
public ClientType getClientType() {
return clientType;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
@@ -0,0 +1,322 @@
package com.github.manevolent.ts3j.model;
import com.github.manevolent.ts3j.enums.HostBannerMode;
import com.github.manevolent.ts3j.enums.HostMessageMode;
import com.github.manevolent.ts3j.enums.VoiceEncryptionMode;
import com.github.manevolent.ts3j.identity.Uid;
import java.util.*;
import java.util.stream.Collectors;
public class VirtualServer {
private int id;
private String name;
private String nickname;
private String welcomeMessage;
private String platform;
private String version;
private int maxClients;
private long created;
private VoiceEncryptionMode encryptionMode;
private HostMessageMode hostMessageMode;
private int defaultGroupId;
private int defaultChannelGroupId;
private String hostbannerUrl;
private int hostBannerInterval;
private double prioritySpeakerDimmModifier;
private String namePhonetic;
private long iconId;
private String serverIp;
private boolean askForPriviledgeKey;
private HostBannerMode hostBannerMode;
private int defaultTempChannelDeleteDelay;
/**
* Connected clients
*/
private final Map<Integer, Client> clients = new HashMap<>();
/**
* Server channels
*/
private final Map<Integer, Channel> channels = new HashMap<>();
public VirtualServer() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getWelcomeMessage() {
return welcomeMessage;
}
public void setWelcomeMessage(String welcomeMessage) {
this.welcomeMessage = welcomeMessage;
}
public String getPlatform() {
return platform;
}
public void setPlatform(String platform) {
this.platform = platform;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public int getMaxClients() {
return maxClients;
}
public void setMaxClients(int maxClients) {
this.maxClients = maxClients;
}
public long getCreated() {
return created;
}
public void setCreated(long created) {
this.created = created;
}
public VoiceEncryptionMode getEncryptionMode() {
return encryptionMode;
}
public void setEncryptionMode(VoiceEncryptionMode encryptionMode) {
this.encryptionMode = encryptionMode;
}
public HostMessageMode getHostMessageMode() {
return hostMessageMode;
}
public void setHostMessageMode(HostMessageMode hostMessageMode) {
this.hostMessageMode = hostMessageMode;
}
/**
* Gets the default server group ID users are assigned to when connecting to the server for the first time.
* @return default server group ID.
*/
public int getDefaultGroupId() {
return defaultGroupId;
}
public void setDefaultGroupId(int defaultGroupId) {
this.defaultGroupId = defaultGroupId;
}
public int getDefaultChannelGroupId() {
return defaultChannelGroupId;
}
public void setDefaultChannelGroupId(int defaultChannelGroupId) {
this.defaultChannelGroupId = defaultChannelGroupId;
}
public String getHostbannerUrl() {
return hostbannerUrl;
}
public void setHostbannerUrl(String hostbannerUrl) {
this.hostbannerUrl = hostbannerUrl;
}
public int getHostBannerInterval() {
return hostBannerInterval;
}
public void setHostBannerInterval(int hostBannerInterval) {
this.hostBannerInterval = hostBannerInterval;
}
public double getPrioritySpeakerDimmModifier() {
return prioritySpeakerDimmModifier;
}
public void setPrioritySpeakerDimmModifier(double prioritySpeakerDimmModifier) {
this.prioritySpeakerDimmModifier = prioritySpeakerDimmModifier;
}
public String getNamePhonetic() {
return namePhonetic;
}
public void setNamePhonetic(String namePhonetic) {
this.namePhonetic = namePhonetic;
}
public long getIconId() {
return iconId;
}
public void setIconId(long iconId) {
this.iconId = iconId;
}
public String getServerIp() {
return serverIp;
}
public void setServerIp(String serverIp) {
this.serverIp = serverIp;
}
public boolean isAskForPriviledgeKey() {
return askForPriviledgeKey;
}
public void setAskForPriviledgeKey(boolean askForPriviledgeKey) {
this.askForPriviledgeKey = askForPriviledgeKey;
}
public HostBannerMode getHostBannerMode() {
return hostBannerMode;
}
public void setHostBannerMode(HostBannerMode hostBannerMode) {
this.hostBannerMode = hostBannerMode;
}
public int getDefaultTempChannelDeleteDelay() {
return defaultTempChannelDeleteDelay;
}
public void setDefaultTempChannelDeleteDelay(int defaultTempChannelDeleteDelay) {
this.defaultTempChannelDeleteDelay = defaultTempChannelDeleteDelay;
}
/**
* Gets a channel by its numeric ID.
* @param id channel ID.
* @return the corresponding channel, if such a channel exists, null otherwise.
*/
public Channel getChannelById(int id) {
return channels.get(id);
}
/**
* Tries to add a new channel to the server. If the channel ID is already in use, IllegalStateException is thrown.
* @param channel channel to add.
*/
public void updateChannel(Channel channel) {
synchronized (channels) {
this.channels.put(channel.getChannelId(), channel);
}
}
/**
* Gets all channels by a specific name.
* @param name Name to search for.
* @return List of channels with the specified name.
*/
public List<Channel> getChannelsByName(String name) {
return this.channels.values()
.stream()
.filter(x -> x.getName().equalsIgnoreCase(name))
.collect(Collectors.toList());
}
/**
* Gets the first channel by a specific name.
* @param name Name to search for.
* @return List of channels with the specified name.
*/
public Channel getChannelByName(String name) throws IllegalArgumentException {
return getChannelsOrderd()
.stream()
.filter(x -> x.getName().equalsIgnoreCase(name))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("channel not found"));
}
/**
* Gets a real channel list, potentially unordered.
* @return Unordered channel list.
*/
public Collection<Channel> getChannels() {
return Collections.unmodifiableCollection(channels.values());
}
/**
* Gets the channel list as ordered by the server.
* @return Iterable list of ordered channels.
*/
public Collection<Channel> getChannelsOrderd() {
return Collections.unmodifiableCollection(channels.values()
.stream()
.sorted(Comparator.comparingInt(Channel::getOrder))
.collect(Collectors.toList())
);
}
/**
* Gets a list of known clients to the server.
* @return clients
*/
public Collection<Client> getClients() {
throw new UnsupportedOperationException(); // TODO
}
/**
* Gets a list of connected clients.
* @return connected clients.
*/
public Collection<Client> getConnectedClients() {
return Collections.unmodifiableCollection(clients.values());
}
public List<Client> getClientsByUid(Uid uid) {
return getConnectedClients().stream().filter(x -> x.getUid().equals(uid)).collect(Collectors.toList());
}
public void updateConnectedClient(Client client) {
synchronized (clients) {
this.clients.put(client.getId(), client);
}
}
public void removeConnectedClient(Client client) {
synchronized (clients) {
if (this.clients.containsKey(client.getId()))
this.clients.remove(client.getId());
else
throw new IllegalStateException("client ID doesn't exist");
}
}
public Client getClientById(int clientId) {
return this.clients.get(clientId);
}
}
@@ -1,8 +1,7 @@
package com.github.manevolent.ts3j.protocol.packet; package com.github.manevolent.ts3j.protocol.packet;
import com.github.manevolent.ts3j.command.Command; import com.github.manevolent.ts3j.command.Command;
import com.github.manevolent.ts3j.command.ComplexCommand; import com.github.manevolent.ts3j.command.MultiCommand;
import com.github.manevolent.ts3j.command.SimpleCommand;
import com.github.manevolent.ts3j.protocol.ProtocolRole; import com.github.manevolent.ts3j.protocol.ProtocolRole;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@@ -29,8 +28,8 @@ public class PacketBody2Command extends PacketBody {
return text; return text;
} }
public ComplexCommand parse() { public MultiCommand parse() {
return ComplexCommand.parse(getRole(), getText()); return MultiCommand.parse(getRole(), getText());
} }
public void setText(String text) { public void setText(String text) {
@@ -1,7 +1,7 @@
package com.github.manevolent.ts3j.protocol.packet; package com.github.manevolent.ts3j.protocol.packet;
import com.github.manevolent.ts3j.command.Command; import com.github.manevolent.ts3j.command.Command;
import com.github.manevolent.ts3j.command.ComplexCommand; import com.github.manevolent.ts3j.command.MultiCommand;
import com.github.manevolent.ts3j.protocol.ProtocolRole; import com.github.manevolent.ts3j.protocol.ProtocolRole;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@@ -28,8 +28,8 @@ public class PacketBody3CommandLow extends PacketBody {
return text; return text;
} }
public ComplexCommand parse() { public MultiCommand parse() {
return ComplexCommand.parse(getRole(), getText()); return MultiCommand.parse(getRole(), getText());
} }
public void setText(String text) { public void setText(String text) {
@@ -1,8 +1,8 @@
package com.github.manevolent.ts3j.protocol.packet.handler.local; package com.github.manevolent.ts3j.protocol.packet.handler.local;
import com.github.manevolent.ts3j.command.CommandHandler; import com.github.manevolent.ts3j.command.CommandHandler;
import com.github.manevolent.ts3j.command.SimpleCommand; import com.github.manevolent.ts3j.command.SingleCommand;
import com.github.manevolent.ts3j.command.part.CommandSingleParameter; import com.github.manevolent.ts3j.command.parameter.CommandSingleParameter;
import com.github.manevolent.ts3j.protocol.Packet; import com.github.manevolent.ts3j.protocol.Packet;
import com.github.manevolent.ts3j.protocol.ProtocolRole; import com.github.manevolent.ts3j.protocol.ProtocolRole;
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState; import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
@@ -116,7 +116,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
alphaBytes = new byte[10]; alphaBytes = new byte[10];
new Random().nextBytes(alphaBytes); new Random().nextBytes(alphaBytes);
SimpleCommand initiv = new SimpleCommand("clientinitiv", ProtocolRole.CLIENT); SingleCommand initiv = new SingleCommand("clientinitiv", ProtocolRole.CLIENT);
initiv.add(new CommandSingleParameter("alpha", Base64.getEncoder().encodeToString(alphaBytes))); initiv.add(new CommandSingleParameter("alpha", Base64.getEncoder().encodeToString(alphaBytes)));
@@ -142,7 +142,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
} }
} else if (packet.getBody() instanceof PacketBody2Command) { } else if (packet.getBody() instanceof PacketBody2Command) {
SimpleCommand command = ((PacketBody2Command) packet.getBody()).parse().simplify(); SingleCommand command = ((PacketBody2Command) packet.getBody()).parse().simplifyOne();
if (command.getName().equalsIgnoreCase("initivexpand2")) { if (command.getName().equalsIgnoreCase("initivexpand2")) {
// 3.2.2 initivexpand2 (Client <- Server) // 3.2.2 initivexpand2 (Client <- Server)
@@ -177,7 +177,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
); );
getClient().sendCommand( getClient().sendCommand(
new SimpleCommand( new SingleCommand(
"clientek", ProtocolRole.CLIENT, "clientek", ProtocolRole.CLIENT,
new CommandSingleParameter("ek", Base64.getEncoder().encodeToString(keyPair.getKey())), new CommandSingleParameter("ek", Base64.getEncoder().encodeToString(keyPair.getKey())),
new CommandSingleParameter("proof", Base64.getEncoder().encodeToString(signature)) new CommandSingleParameter("proof", Base64.getEncoder().encodeToString(signature))
@@ -186,7 +186,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
getClient().setSecureParameters(Ts3Crypt.cryptoInit2(license, alphaBytes, beta, keyPair.getValue())); getClient().setSecureParameters(Ts3Crypt.cryptoInit2(license, alphaBytes, beta, keyPair.getValue()));
getClient().sendCommand(new SimpleCommand( getClient().sendCommand(new SingleCommand(
"clientinit", "clientinit",
ProtocolRole.CLIENT, ProtocolRole.CLIENT,
new CommandSingleParameter("client_nickname", getClient().getNickname()), new CommandSingleParameter("client_nickname", getClient().getNickname()),
@@ -229,7 +229,7 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
) )
); );
getClient().setCommandProcessor(CommandHandler.createLocalClient()); getClient().setCommandProcessor(CommandHandler.createLocalClientHandler());
getClient().setState(ClientConnectionState.RETRIEVING_DATA); getClient().setState(ClientConnectionState.RETRIEVING_DATA);
} else if (command.getName().equals("error")) { } else if (command.getName().equals("error")) {
getClient().setState(ClientConnectionState.DISCONNECTED); getClient().setState(ClientConnectionState.DISCONNECTED);
@@ -2,11 +2,12 @@ package com.github.manevolent.ts3j.protocol.packet.handler.local;
import com.github.manevolent.ts3j.command.CommandProcessException; import com.github.manevolent.ts3j.command.CommandProcessException;
import com.github.manevolent.ts3j.command.CommandProcessor; import com.github.manevolent.ts3j.command.CommandProcessor;
import com.github.manevolent.ts3j.command.ComplexCommand; import com.github.manevolent.ts3j.command.MultiCommand;
import com.github.manevolent.ts3j.protocol.Packet; import com.github.manevolent.ts3j.protocol.Packet;
import com.github.manevolent.ts3j.protocol.packet.PacketBody2Command; import com.github.manevolent.ts3j.protocol.packet.PacketBody2Command;
import com.github.manevolent.ts3j.protocol.packet.PacketBody3CommandLow; import com.github.manevolent.ts3j.protocol.packet.PacketBody3CommandLow;
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket; import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
import com.github.manevolent.ts3j.util.Ts3Debugging;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
@@ -16,7 +17,7 @@ public abstract class LocalClientHandlerFull extends LocalClientHandler {
super(client); super(client);
} }
protected void handleCommand(ComplexCommand command) throws CommandProcessException { protected void handleCommand(MultiCommand command) throws CommandProcessException {
CommandProcessor processor = getClient().getCommandProcessor(); CommandProcessor processor = getClient().getCommandProcessor();
if (processor != null) processor.process(getClient(), command); if (processor != null) processor.process(getClient(), command);
} }
@@ -25,8 +26,9 @@ public abstract class LocalClientHandlerFull extends LocalClientHandler {
public void handlePacket(Packet packet) throws IOException, TimeoutException { public void handlePacket(Packet packet) throws IOException, TimeoutException {
switch (packet.getBody().getType()) { switch (packet.getBody().getType()) {
case COMMAND: case COMMAND:
Ts3Debugging.debug(((PacketBody2Command) packet.getBody()).getText());
try { try {
handleCommand(ComplexCommand.parse( handleCommand(MultiCommand.parse(
packet.getRole(), packet.getRole(),
((PacketBody2Command) packet.getBody()).getText() ((PacketBody2Command) packet.getBody()).getText()
)); ));
@@ -36,7 +38,7 @@ public abstract class LocalClientHandlerFull extends LocalClientHandler {
break; break;
case COMMAND_LOW: case COMMAND_LOW:
try { try {
handleCommand(ComplexCommand.parse( handleCommand(MultiCommand.parse(
packet.getRole(), packet.getRole(),
((PacketBody3CommandLow) packet.getBody()).getText() ((PacketBody3CommandLow) packet.getBody()).getText()
)); ));
@@ -2,9 +2,11 @@ package com.github.manevolent.ts3j.protocol.socket.client;
import com.github.manevolent.ts3j.command.Command; import com.github.manevolent.ts3j.command.Command;
import com.github.manevolent.ts3j.command.CommandProcessor; import com.github.manevolent.ts3j.command.CommandProcessor;
import com.github.manevolent.ts3j.command.part.CommandSingleParameter; import com.github.manevolent.ts3j.command.parameter.CommandSingleParameter;
import com.github.manevolent.ts3j.command.response.CommandResponse; import com.github.manevolent.ts3j.command.response.CommandResponse;
import com.github.manevolent.ts3j.identity.Identity; import com.github.manevolent.ts3j.identity.Identity;
import com.github.manevolent.ts3j.model.Client;
import com.github.manevolent.ts3j.model.VirtualServer;
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.ProtocolRole; import com.github.manevolent.ts3j.protocol.ProtocolRole;
@@ -43,10 +45,13 @@ 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<PacketBodyType, PacketCounter> sendCounter = new ConcurrentHashMap<>(); private final Map<PacketBodyType, PacketCounter> localSendCounter = new ConcurrentHashMap<>();
private final Map<PacketBodyType, PacketCounter> remoteSendCounter = new ConcurrentHashMap<>();
private CommandProcessor commandProcessor; private CommandProcessor commandProcessor;
private VirtualServer virtualServer;
private Identity identity = null; private Identity identity = null;
private ClientConnectionState connectionState = null; private ClientConnectionState connectionState = null;
private PacketTransformation transformation = new DefaultPacketTransformation(); private PacketTransformation transformation = new DefaultPacketTransformation();
@@ -80,10 +85,13 @@ public abstract class AbstractTeamspeakClientSocket
// Initialize counters // Initialize counters
for (PacketBodyType bodyType : PacketBodyType.values()) for (PacketBodyType bodyType : PacketBodyType.values())
if (bodyType == PacketBodyType.INIT1) if (bodyType == PacketBodyType.INIT1) {
sendCounter.put(PacketBodyType.INIT1, new PacketCounterZero()); localSendCounter.put(PacketBodyType.INIT1, new PacketCounterZero());
else remoteSendCounter.put(PacketBodyType.INIT1, new PacketCounterZero());
sendCounter.put(bodyType, new PacketCounterFull()); } else {
localSendCounter.put(bodyType, new PacketCounterFull());
remoteSendCounter.put(bodyType, new PacketCounterFull(-1));
}
} }
protected void start() { protected void start() {
@@ -228,7 +236,7 @@ public abstract class AbstractTeamspeakClientSocket
// Set header values // Set header values
packet.getBody().setHeaderValues(packet.getHeader()); packet.getBody().setHeaderValues(packet.getHeader());
Pair<Integer, Integer> counterResult = sendCounter.get(packet.getHeader().getType()).next(); Pair<Integer, Integer> counterResult = localSendCounter.get(packet.getHeader().getType()).next();
if (packet.getHeader() instanceof ClientPacketHeader) if (packet.getHeader() instanceof ClientPacketHeader)
((ClientPacketHeader) packet.getHeader()).setClientId(clientId); ((ClientPacketHeader) packet.getHeader()).setClientId(clientId);
@@ -460,11 +468,22 @@ public abstract class AbstractTeamspeakClientSocket
} }
// Find if the packet must be reassembled // Find if the packet must be reassembled
if (packet.getBody() instanceof PacketBodyFragment) { 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;
@@ -580,6 +599,18 @@ public abstract class AbstractTeamspeakClientSocket
this.commandProcessor = commandProcessor; this.commandProcessor = commandProcessor;
} }
public VirtualServer getVirtualServer() {
return virtualServer;
}
public void setVirtualServer(VirtualServer virtualServer) {
this.virtualServer = virtualServer;
}
public Client getSelf() {
return virtualServer.getClientById(clientId);
}
private class NetworkHandler implements Runnable { private class NetworkHandler implements Runnable {
@Override @Override
public void run() { public void run() {
@@ -692,6 +723,8 @@ public abstract class AbstractTeamspeakClientSocket
private interface PacketCounter { private interface PacketCounter {
Pair<Integer, Integer> next(); Pair<Integer, Integer> next();
int getPacketId();
boolean setPacketId(int packetId);
int getGenerationId(); int getGenerationId();
} }
@@ -701,6 +734,16 @@ public abstract class AbstractTeamspeakClientSocket
return new Pair<>(0,0); return new Pair<>(0,0);
} }
@Override
public int getPacketId() {
return 0;
}
@Override
public boolean setPacketId(int packetId) {
throw new UnsupportedOperationException();
}
@Override @Override
public int getGenerationId() { public int getGenerationId() {
return 0; return 0;
@@ -712,10 +755,34 @@ public abstract class AbstractTeamspeakClientSocket
private int packetId = 0; private int packetId = 0;
private int generationId = 0; private int generationId = 0;
public PacketCounterFull() {
}
public PacketCounterFull(int start) {
setPacketId(start);
}
public int getPacketId() { public int getPacketId() {
return packetId; return packetId;
} }
@Override
public boolean setPacketId(int packetId) {
synchronized (sendLock) {
if (this.packetId == packetId) return false;
if (packetId >= 65536) {
packetId = 0;
generationId++;
} else {
this.packetId = packetId;
}
return true;
}
}
public int getGenerationId() { public int getGenerationId() {
return generationId; return generationId;
} }
@@ -723,13 +790,7 @@ public abstract class AbstractTeamspeakClientSocket
@Override @Override
public Pair<Integer, Integer> next() { public Pair<Integer, Integer> next() {
synchronized (sendLock) { synchronized (sendLock) {
packetId++; setPacketId(packetId + 1);
if (packetId >= 65536) {
packetId = 0;
generationId++;
}
return new Pair<>(packetId, generationId); return new Pair<>(packetId, generationId);
} }
} }
@@ -781,6 +842,8 @@ public abstract class AbstractTeamspeakClientSocket
} }
public Packet put(Packet packet) { public Packet put(Packet packet) {
Packet reassembled;
if (packet.getHeader().getPacketFlag(HeaderFlag.FRAGMENTED)) { if (packet.getHeader().getPacketFlag(HeaderFlag.FRAGMENTED)) {
boolean oldState = state; boolean oldState = state;
@@ -791,19 +854,22 @@ public abstract class AbstractTeamspeakClientSocket
queue.put(packet.getHeader().getPacketId(), packet); queue.put(packet.getHeader().getPacketId(), packet);
if (oldState) if (oldState) {
return reassemble(packet); reassembled = reassemble(packet);
} else {
return null; reassembled = null;
}
} else { } else {
if (state) { if (state) {
queue.put(packet.getHeader().getPacketId(), packet); queue.put(packet.getHeader().getPacketId(), packet);
return null; reassembled = null;
} else { } else {
return packet; reassembled = packet;
} }
} }
return reassembled;
} }
public boolean isReassembling() { public boolean isReassembling() {
@@ -1,7 +1,6 @@
package com.github.manevolent.ts3j; package com.github.manevolent.ts3j;
import com.github.manevolent.ts3j.identity.LocalIdentity; import com.github.manevolent.ts3j.identity.LocalIdentity;
import com.github.manevolent.ts3j.util.Ts3Crypt;
import junit.framework.TestCase; import junit.framework.TestCase;
import java.math.BigInteger; import java.math.BigInteger;
@@ -26,7 +25,7 @@ public class ClientUidGenerationTest extends TestCase {
assertEquals("MEsDAgcAAgEgAiBpPRbTliVt9KxtIz8saYdwcnNgcwaKLb" + assertEquals("MEsDAgcAAgEgAiBpPRbTliVt9KxtIz8saYdwcnNgcwaKLb" +
"KYSpDNO87u9gIgSWWPKcSJ9P6VZKJfRdpWwcfMdJv+NA9/hXUtz1uwRVI=", keyPair.getPublicKeyString()); "KYSpDNO87u9gIgSWWPKcSJ9P6VZKJfRdpWwcfMdJv+NA9/hXUtz1uwRVI=", keyPair.getPublicKeyString());
assertEquals("27QHSPRzQe+enxh56eUokkLmsFg=", keyPair.getFingerprint()); assertEquals("27QHSPRzQe+enxh56eUokkLmsFg=", keyPair.getUid().toBase64());
assertEquals( assertEquals(
"MEsDAgcAAgEgAiBpPRbTliVt9KxtIz8saYdwcnNgcwaKLbKY" + "MEsDAgcAAgEgAiBpPRbTliVt9KxtIz8saYdwcnNgcwaKLbKY" +
@@ -1,27 +1,24 @@
package com.github.manevolent.ts3j; package com.github.manevolent.ts3j;
import com.github.manevolent.ts3j.identity.LocalIdentity; import com.github.manevolent.ts3j.identity.LocalIdentity;
import com.github.manevolent.ts3j.model.Channel;
import com.github.manevolent.ts3j.protocol.client.ClientConnectionState; import com.github.manevolent.ts3j.protocol.client.ClientConnectionState;
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket; import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
import com.github.manevolent.ts3j.util.Ts3Crypt; import com.github.manevolent.ts3j.util.Ts3Crypt;
import com.github.manevolent.ts3j.util.Ts3Debugging;
import junit.framework.TestCase; import junit.framework.TestCase;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Base64; import java.util.Base64;
import java.util.Collections;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
public class ServerConnectionTest { public class ServerConnectionTest {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
try { try {
/**LocalIdentity localIdentity = LocalIdentity.load(
Ts3Crypt.decodePublicKey(Base64.getDecoder()
.decode("MEsDAgcAAgEgAiBpPRbTliVt9KxtIz8saYdwcnNgcwaKLbKYSpDNO87u9gIgSWWPKcSJ9P6VZKJfRdpWwcfMdJv+NA9/hXUtz1uwRVI=")),
new BigInteger(Base64.getDecoder().decode("Tj6YXM3qyRv8n25L2pH+OEJnRUl4auQf8+znjYrOmWU=")),
2294,
2295
);**/
LocalIdentity localIdentity = LocalIdentity.generateNew(10); LocalIdentity localIdentity = LocalIdentity.generateNew(10);
LocalTeamspeakClientSocket client = new LocalTeamspeakClientSocket(); LocalTeamspeakClientSocket client = new LocalTeamspeakClientSocket();
@@ -40,6 +37,13 @@ public class ServerConnectionTest {
); );
assertEquals(client.getState(), ClientConnectionState.CONNECTED); assertEquals(client.getState(), ClientConnectionState.CONNECTED);
Ts3Debugging.info("Connected to " + client.getVirtualServer().getName() + ".");
Ts3Debugging.info(" Client name: " + client.getSelf().getNickname());
Ts3Debugging.info(" Channels: " +
client.getVirtualServer().getChannelsOrderd()
.stream().map(Channel::getName).collect(Collectors.toList())
);
} catch (Throwable ex) { } catch (Throwable ex) {
ex.printStackTrace(); ex.printStackTrace();
} }