package com.limegroup.gnutella.lws.server; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.limewire.core.settings.LWSSettings; import org.limewire.lws.server.LWSDispatcherSupport; import org.limewire.lws.server.LWSServerUtil; import org.limewire.net.SocketsManager; import com.google.inject.Injector; import com.google.inject.Stage; import com.limegroup.gnutella.LifecycleManager; import com.limegroup.gnutella.LimeTestUtils; import com.limegroup.gnutella.util.LimeTestCase; /** * This is a simpler class than {@link AbstractCommunicationSupport} in the * <code>lwsserver</code> component, because it doesn't have a mock local * server representing that on the client. Instead it's an integration test so * only has a mock remote server and mock web page javascript code to send the * client commands. * * <p> * * There should be only one method for each class, or else multiple instances of * the remote server are created, and we need to share that among everything. */ abstract class AbstractCommunicationSupportWithNoLocalServer extends LimeTestCase { protected final Log LOG = LogFactory.getLog(getClass()); public final int LOCAL_PORT = LocalServerImpl.PORT; public final int REMOTE_PORT = RemoteServerImpl.PORT; protected final Map<String,String> EMPTY_ARGS = new HashMap<String,String>(); /** * The number of times we'll try for a private key. Because we can't respond * in the client on the same thread we received a request, we hand back the * web page code the public key before giving it to the server. So it may * have not arrived yet. */ private final int TIMES_TO_TRY_FOR_PRIVATE_KEY = 3; /** * Public key analogy for {@link TIMES_TO_TRY_FOR_PRIVATE_KEY} */ private final int TIMES_TO_TRY_FOR_PUBLIC_KEY = 3; /** * Time to sleep between tries for the private key if we haven't gotten it * yet. */ private final int SLEEP_TIME_BETWEEN_PRIVATE_KEY_TRIES = 300; /** * Public key analogy for {@link SLEEP_TIME_BETWEEN_PRIVATE_KEY_TRIES} */ private final int SLEEP_TIME_BETWEEN_PUBLIC_KEY_TRIES = 300; private RemoteServerImpl remoteServer; private Thread remoteThread; private CommandSender sender; private LWSManager lwsManager; private LifecycleManager lifecycleManager; private String privateKey; private String sharedKey; public AbstractCommunicationSupportWithNoLocalServer(String s) { super(s); } // ------------------------------------------------------- // Access // ------------------------------------------------------- protected final RemoteServerImpl getRemoteServer() { return this.remoteServer; } protected final CommandSender getCommandSender() { return this.sender; } protected final LWSManager getLWSManager() { return lwsManager; } /** * This is not a {@link Collections#emptyMap()} because we may want to add * to it. */ protected final Map<String, String> NULL_ARGS = new HashMap<String,String>(); protected final Map<String, String> DUMMY_CALLBACK_ARGS; { DUMMY_CALLBACK_ARGS = new HashMap<String,String>(); DUMMY_CALLBACK_ARGS.put(LWSDispatcherSupport.Parameters.CALLBACK, "dummy"); } /** Override with functionality <b>after</b> {@link #setUp()}. */ protected void beforeSetup() { } /** Override with functionality <b>after</b> {@link #setUp()}. */ protected void afterSetup() { } /** Override with functionality <b>after</b> {@link #tearDown()}. */ protected void beforeTearDown() { } /** Override with functionality <b>after</b> {@link #tearDown()}. */ protected void afterTearDown() { } protected final Thread getRemoteThread() { return remoteThread; } private Injector inj; @Override protected final void setUp() throws Exception { note("begin setUp"); beforeSetup(); LWSSettings.LWS_AUTHENTICATION_HOSTNAME.setValue("localhost"); LWSSettings.LWS_AUTHENTICATION_PORT.setValue(8080); inj = LimeTestUtils.createInjector(Stage.PRODUCTION); remoteServer = new RemoteServerImpl(inj.getInstance(SocketsManager.class), LOCAL_PORT); lifecycleManager = inj.getInstance(LifecycleManager.class); lifecycleManager.start(); remoteThread = remoteServer.start(); lwsManager = getInstance(LWSManager.class); sender = new CommandSender(); afterSetup(); note("end setUp"); } @Override protected final void tearDown() throws Exception { note("begin tearDown"); beforeTearDown(); doDetatch(); remoteServer.shutDown(); lifecycleManager.shutdown(); privateKey = null; sharedKey = null; remoteThread = null; afterTearDown(); note("end tearDown"); } protected final void doDetatch() { getCommandSender().detach(getPrivateKey(), getSharedKey()); lwsManager.clearHandlersAndListeners(); } protected final String doAuthenticate() { note("Authenticating"); return doAuthenticate(getPrivateKey(), getSharedKey()); } protected final void note(Object str) { LOG.debug(str); } protected final String doAuthenticate(final String privateKey, final String sharedKey) { Map<String, String> args = new HashMap<String, String>(); args.put("private", privateKey); args.put("shared", sharedKey); note("Authenticating with private key '" + privateKey + "' and shared key '" + sharedKey + "'"); return sendMessageFromWebpageToClient("Authenticate", args); } protected final String sendPing() { Map<String, String> args = new HashMap<String, String>(); return sendMessageFromWebpageToClient("Ping", args); } protected final String getPrivateKey() { if (privateKey == null) { requestPrivateAndSharedKeys(); } return privateKey; } protected final String getSharedKey() { if (sharedKey == null) { requestPrivateAndSharedKeys(); } return sharedKey; } protected final static class KeyPair { private final String publicKey; private final String sharedKey; private final boolean isValid; private KeyPair(String publicKey, String sharedKey, boolean isValid) { this.publicKey = publicKey; this.sharedKey = sharedKey; this.isValid = isValid; } protected final String getPublicKey() { return publicKey; } protected final String getSharedKey() { return sharedKey; } /** * Returns whether this request was valid or not. * * @return whether this request was valid or not. */ protected final boolean isValid() { return isValid; } @Override public final String toString() { return "<publicKey=" + getPublicKey() + ",sharedKey=" + getSharedKey() + ",isValid=" + isValid() + ">"; } } protected final KeyPair getPublicAndSharedKeys() { String res = sendMessageFromWebpageToClient(LWSDispatcherSupport.Commands.START_COM, NULL_ARGS); LOG.debug("have public and shared keys " + res); String parts[] = res.split(" "); if (parts.length < 2) { return new KeyPair(null, null, false); } return new KeyPair(parts[0], sharedKey = parts[1], true); } protected final String getPublicKey() { return sendMessageFromWebpageToClient(LWSDispatcherSupport.Commands.START_COM, NULL_ARGS); } /** * Returns the response after calling * {@link #sendMessageFromWebpageToClient(String, Map)} after adding the * private and shared keys. * <p> * Also, the only generic command the client understands is <code>Msg</code>, * with a argument of <code>command=</code><em>Command</em> where * <em>Command</em> would be something like <code>Download</code> or * <code>GetInfo</code>. * * @param cmd Command to send * @param args arguments * @param includeDummyCallback <code>true</code> to include the callback * <code>dummy</code>. This is used as a convenience in testing * handlers when we don't need to have an explicit callback function. * @return the response after calling * {@link #sendMessageFromWebpageToClient(String, Map)} after adding * the private and shared keys. */ protected final String sendCommandToClient(String cmd, Map<String, String> args, boolean includeDummyCallback) { Map<String, String> newArgs = new HashMap<String, String>(args); newArgs.put("command", cmd); newArgs.put("private", getPrivateKey()); newArgs.put("shared", getSharedKey()); if (includeDummyCallback) { newArgs.put("callback", "dummy"); } return sendMessageFromWebpageToClient("Msg", newArgs); } /** * Returns the value of calling * {@link #sendCommandToClient(String, Map, boolean)} with the last argument * <code>false</code>, so this method requires an explicit callback * function in <code>args</code>. * * @return the value of calling * {@link #sendCommandToClient(String, Map, boolean)} with the last * argument <code>false</code>, so this method requires an * explicit callback function in <code>args</code>. * @see #sendCommandToClient(String, Map, boolean) * */ protected final String sendCommandToClient(String cmd, Map<String, String> args) { return sendCommandToClient(cmd, args, false); } protected final String sendMessageFromWebpageToClient(String cmd, Map<String, String> args) { args.put("callback", "dummy"); String responseWithCallback = sender.sendMessage(cmd, args); return LWSServerUtil.removeCallback(responseWithCallback); } /** * Returns a {@link FakeJavascriptCodeInTheWebpage#Handler} for ensuring * there was some error, but the type doesn't matter. * * @return a {@link FakeJavascriptCodeInTheWebpage#Handler} for ensuring * there was some error, but the type doesn't matter. */ protected final FakeJavascriptCodeInTheWebpage.Handler errorHandlerAny() { return new FakeJavascriptCodeInTheWebpage.Handler() { public void handle(final String res) { final String have = LWSServerUtil.unwrapError(LWSServerUtil.removeCallback(res)); // // All errors are of the form // // <String> [ '.' <String> ]+ // // See LWSDispatcherSupport#ErrorCodes // assertTrue(have.indexOf('.') != -1); } }; } /** * @see Injector#getInstance(Class) */ protected final <T> T getInstance(Class<T> type) { return inj.getInstance(type); } // ----------------------------------------------------------- // Private // ----------------------------------------------------------- private void requestPrivateAndSharedKeys() { // // We'll try this a few times to make up for the race condition that exists and in unavoidable // String publicKey = null; for (int i=0; i<TIMES_TO_TRY_FOR_PUBLIC_KEY; i++) { KeyPair kp = getPublicAndSharedKeys(); publicKey = kp.getPublicKey(); if (publicKey != null) break; try { Thread.sleep(SLEEP_TIME_BETWEEN_PUBLIC_KEY_TRIES); } catch (Exception e) { // // Not crucial, but would like to see the error // e.printStackTrace(); } } Map<String, String> args = new HashMap<String, String>(); args.put(LWSDispatcherSupport.Parameters.PUBLIC, publicKey); String ip = "127.0.0.1"; // // We'll try this TIMES_TO_TRY_FOR_PRIVATE_KEY times // See the comment at TIMES_TO_TRY_FOR_PRIVATE_KEY for why. // for (int i=0; i<TIMES_TO_TRY_FOR_PRIVATE_KEY; i++) { privateKey = remoteServer.lookupPrivateKey(publicKey, ip); if (LWSServerUtil.isValidPrivateKey(privateKey)) break; try { Thread.sleep(SLEEP_TIME_BETWEEN_PRIVATE_KEY_TRIES); } catch (InterruptedException e) { // ignore } } } }