package games.strategy.net.nio; import java.io.Serializable; import java.net.InetSocketAddress; import java.nio.channels.SocketChannel; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.logging.Level; import java.util.logging.Logger; import games.strategy.net.IConnectionLogin; import games.strategy.net.MessageHeader; import games.strategy.net.Node; public class ClientQuarantineConversation extends QuarantineConversation { private static final Logger s_logger = Logger.getLogger(ClientQuarantineConversation.class.getName()); private enum STEP { READ_CHALLENGE, READ_ERROR, READ_NAMES, READ_ADDRESS } private final IConnectionLogin login; private final SocketChannel channel; private final NIOSocket socket; private final CountDownLatch showLatch = new CountDownLatch(1); private final CountDownLatch doneShowLatch = new CountDownLatch(1); private final String macAddress; private STEP step = STEP.READ_CHALLENGE; private String localName; private String serverName; private InetSocketAddress networkVisibleAddress; private InetSocketAddress serverLocalAddress; private Map<String, String> challengeProperties; private Map<String, String> challengeResponse; private volatile boolean isClosed = false; private volatile String errorMessage; public ClientQuarantineConversation(final IConnectionLogin login, final SocketChannel channel, final NIOSocket socket, final String localName, final String mac) { this.login = login; this.localName = localName; macAddress = mac; this.socket = socket; this.channel = channel; // Send the local name send(this.localName); // Send the mac address send(macAddress); } public String getLocalName() { return localName; } public String getMacAddress() { return macAddress; } public String getErrorMessage() { return errorMessage; } public String getServerName() { return serverName; } public void showCredentials() { /** * We need to do this in the thread that created the socket, since * the thread that creates the socket will often be, or will block the * swing event thread, but the getting of a username/password * must be done in the swing event thread. * So we have complex code to switch back and forth. */ try { showLatch.await(); } catch (final InterruptedException e) { Thread.currentThread().interrupt(); } if (login != null && challengeProperties != null) { try { if (isClosed) { return; } challengeResponse = login.getProperties(challengeProperties); } finally { doneShowLatch.countDown(); } } } @Override @SuppressWarnings("unchecked") public ACTION message(final Object o) { try { switch (step) { case READ_CHALLENGE: // read name, send challenge final Map<String, String> challenge = (Map<String, String>) o; if (s_logger.isLoggable(Level.FINER)) { s_logger.log(Level.FINER, "read challenge:" + challenge); } if (challenge != null) { challengeProperties = challenge; showLatch.countDown(); try { doneShowLatch.await(); } catch (final InterruptedException e) { // ignore } if (isClosed) { return ACTION.NONE; } if (s_logger.isLoggable(Level.FINER)) { s_logger.log(Level.FINER, "writing response" + challengeResponse); } send((Serializable) challengeResponse); } else { showLatch.countDown(); if (s_logger.isLoggable(Level.FINER)) { s_logger.log(Level.FINER, "sending null response"); } send(null); } step = STEP.READ_ERROR; return ACTION.NONE; case READ_ERROR: if (o != null) { if (s_logger.isLoggable(Level.FINER)) { s_logger.log(Level.FINER, "error:" + o); } errorMessage = (String) o; // acknowledge the error send(null); return ACTION.TERMINATE; } step = STEP.READ_NAMES; return ACTION.NONE; case READ_NAMES: final String[] strings = ((String[]) o); if (s_logger.isLoggable(Level.FINER)) { s_logger.log(Level.FINER, "new local name:" + strings[0]); } localName = strings[0]; serverName = strings[1]; step = STEP.READ_ADDRESS; return ACTION.NONE; case READ_ADDRESS: // this is the adress that others see us as final InetSocketAddress[] address = (InetSocketAddress[]) o; // this is the address the server thinks he is networkVisibleAddress = address[0]; serverLocalAddress = address[1]; if (s_logger.isLoggable(Level.FINE)) { s_logger.log(Level.FINE, "Server local address:" + serverLocalAddress); s_logger.log(Level.FINE, "channel remote address:" + channel.socket().getRemoteSocketAddress()); s_logger.log(Level.FINE, "network visible address:" + networkVisibleAddress); s_logger.log(Level.FINE, "channel local adresss:" + channel.socket().getLocalSocketAddress()); } return ACTION.UNQUARANTINE; default: throw new IllegalStateException("Invalid state"); } } catch (final Throwable t) { isClosed = true; showLatch.countDown(); doneShowLatch.countDown(); s_logger.log(Level.SEVERE, "error with connection", t); return ACTION.TERMINATE; } } private void send(final Serializable object) { // this messenger is quarantined, so to and from dont matter final MessageHeader header = new MessageHeader(Node.NULL_NODE, Node.NULL_NODE, object); socket.send(channel, header); } public InetSocketAddress getNetworkVisibleSocketAdress() { return networkVisibleAddress; } public InetSocketAddress getServerLocalAddress() { return serverLocalAddress; } @Override public void close() { isClosed = true; showLatch.countDown(); doneShowLatch.countDown(); } }