diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/client/LocalClientHandlerConnecting.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/client/LocalClientHandlerConnecting.java index b7a30a3..ec84c5b 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/client/LocalClientHandlerConnecting.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/client/LocalClientHandlerConnecting.java @@ -1,6 +1,7 @@ package com.github.manevolent.ts3j.protocol.packet.handler.client; import com.github.manevolent.ts3j.command.Command; +import com.github.manevolent.ts3j.command.CommandException; import com.github.manevolent.ts3j.command.SingleCommand; import com.github.manevolent.ts3j.command.parameter.CommandSingleParameter; import com.github.manevolent.ts3j.protocol.Packet; @@ -265,15 +266,28 @@ public class LocalClientHandlerConnecting extends LocalClientHandler { getClient().setCommandProcessor(getClient()); getClient().setState(ClientConnectionState.RETRIEVING_DATA); } else if (command.getName().equals("error")) { - getClient().setState(ClientConnectionState.DISCONNECTED); + CommandException refusal = refusal(command); + getClient().refuse(refusal); - throw new IOException(command.get("msg").getValue()); + throw new IOException(refusal.getMessage(), refusal); } else { throw new IOException("Unknown Init command: " + command.getName()); } } } + /** The server's answer to a handshake command it refused, as an exception carrying its error id. */ + static CommandException refusal(SingleCommand error) { + String message = error.has("msg") ? error.get("msg").getValue() : "connection refused"; + int id; + try { + id = error.has("id") ? Integer.parseInt(error.get("id").getValue()) : -1; + } catch (NumberFormatException e) { + id = -1; + } + return new CommandException(message, id); + } + private SingleCommand createInitIv() { alphaBytes = new byte[10]; new Random().nextBytes(alphaBytes); diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/client/LocalClientHandlerRetrievingData.java b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/client/LocalClientHandlerRetrievingData.java index 77bbf49..87a7021 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/client/LocalClientHandlerRetrievingData.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/packet/handler/client/LocalClientHandlerRetrievingData.java @@ -1,9 +1,9 @@ package com.github.manevolent.ts3j.protocol.packet.handler.client; +import com.github.manevolent.ts3j.command.CommandException; 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.LocalTeamspeakClientSocket; @@ -15,13 +15,14 @@ public class LocalClientHandlerRetrievingData extends LocalClientHandlerFull { @Override protected void handleCommand(MultiCommand command) throws CommandProcessException { if (command.getName().equalsIgnoreCase("error")) { + // clientinit was refused, e.g. for a nickname in use or a wrong server password. + CommandException refusal = LocalClientHandlerConnecting.refusal(command.simplifyOne()); try { - getClient().setState(ClientConnectionState.DISCONNECTED); + getClient().refuse(refusal); } catch (Exception e) { getClient().getExceptionHandler().accept(e); } - - throw new CommandProcessException(command.simplifyOne().get("msg").getValue()); + throw new CommandProcessException(refusal.getMessage()); } CommandProcessor processor = getClient().getCommandProcessor(); diff --git a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java index 7573ce7..35d9fb7 100644 --- a/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java +++ b/src/main/java/com/github/manevolent/ts3j/protocol/socket/client/AbstractTeamspeakClientSocket.java @@ -1,5 +1,7 @@ package com.github.manevolent.ts3j.protocol.socket.client; +import com.github.manevolent.ts3j.command.CommandException; + import com.github.manevolent.ts3j.command.*; import com.github.manevolent.ts3j.identity.Identity; import com.github.manevolent.ts3j.protocol.NetworkPacket; @@ -52,6 +54,8 @@ public abstract class AbstractTeamspeakClientSocket private final Map clientOptions = new ConcurrentHashMap<>(); private final Object connectionStateLock = new Object(); + /** Why the server turned the connection down while it was being set up; null otherwise. */ + private volatile CommandException refusal; private final LinkedBlockingDeque readQueue = new LinkedBlockingDeque<>(65536); @@ -172,8 +176,10 @@ public abstract class AbstractTeamspeakClientSocket boolean initialState = this.connectionState == null; - if (state == ClientConnectionState.CONNECTING) + if (state == ClientConnectionState.CONNECTING) { + refusal = null; setReading(true); + } this.connectionState = state; @@ -260,13 +266,25 @@ public abstract class AbstractTeamspeakClientSocket connectionStateLock.wait(Math.max(0L, wait - (System.currentTimeMillis() - start))); } - if (connectionState != state && connectionState == ClientConnectionState.DISCONNECTED) + if (connectionState != state && connectionState == ClientConnectionState.DISCONNECTED) { + CommandException refused = refusal; + if (refused != null) throw new IOException(refused.getMessage(), refused); throw new IOException("disconnected while waiting for " + state.name() + " state"); + } if (connectionState != state) throw new TimeoutException("timeout waiting for " + state.name() + " state"); } } + /** + * Records the error the server answered a handshake command with, and leaves the connection: + * {@link #waitForState} then fails with it rather than with a bare disconnect. + */ + public void refuse(CommandException error) throws IOException, TimeoutException { + refusal = error; + setState(ClientConnectionState.DISCONNECTED); + } + public boolean isConnected() { return connectionState == ClientConnectionState.CONNECTED; }