package org.limewire.lws.server; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.limewire.net.SocketsManager; import org.limewire.net.SocketsManagerImpl; import org.limewire.util.BaseTestCase; /** * Provides the basis methods for doing communication. Subclasses should test * each aspect of this communication separately. */ abstract class AbstractCommunicationSupport extends BaseTestCase { public final static int LOCAL_PORT = LocalServerImpl.PORT; public final static int REMOTE_PORT = RemoteServerImpl.PORT; /** * 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 static int TIMES_TO_TRY_FOR_PRIVATE_KEY = 3; /** * Time to sleep between tries for the private key if we haven't gotten it * yet. */ private final static int SLEEP_TIME_BETWEEN_PRIVATE_KEY_TRIES = 1000; private LocalServerImpl localServer; private RemoteServerImpl remoteServer; private FakeJavascriptCodeInTheWebpage code; private String privateKey; private String sharedKey; private Thread localThread; private Thread remoteThread; public AbstractCommunicationSupport(String s) { super(s); } // ------------------------------------------------------- // Access // ------------------------------------------------------- protected final LocalServerImpl getLocalServer() { return this.localServer; } protected final RemoteServerImpl getRemoteServer() { return this.remoteServer; } protected final FakeJavascriptCodeInTheWebpage getCode() { return this.code; } /** * This is not a {@link Collections#emptyMap()} because we may want to add * to it. */ protected static final Map<String, String> NULL_ARGS = new HashMap<String,String>(); protected static final Map<String, String> DUMMY_CALLBACK_ARGS; static { 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() { } /** * Returns a handler that ensures that the response returned is one of * the <tt>code</tt>s. * * @param wants expected codes * @return a handler that ensures that the response returned is * on of the <tt>code</tt> */ protected final FakeJavascriptCodeInTheWebpage.Handler errorHandler(final String want) { return errorHandler(new String[]{want}); } /** * 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)); assertTrue(have.indexOf('.') != -1); } }; } /** * Returns a {@link FakeJavascriptCodeInTheWebpage#Handler} for ensuring * there was an error from <code>wants</code>. * * @return Returns a {@link FakeJavascriptCodeInTheWebpage#Handler} for * ensuring there was an error from <code>wants</code>. */ protected final FakeJavascriptCodeInTheWebpage.Handler errorHandler(final String[] wants) { return new FakeJavascriptCodeInTheWebpage.Handler() { public void handle(final String res) { // // We first have to remove the parens and single quotes // from around the message, because we always pass a // callback back to javascript // final String have = LWSServerUtil.unwrapError(LWSServerUtil.removeCallback(res)); // // Make sure it's *one* of the expected results // boolean haveIt = false; for (String want: wants) { if (have.equals(want)) { haveIt = true; break; } } if (!haveIt) { fail("didn't received one message of: " + unwrap(wants) + " but did receive '" + have + "'"); } } }; } protected final Thread getLocalThread() { return localThread; } protected final Thread getRemoteThread() { return remoteThread; } @Override protected final void setUp() throws Exception { beforeSetup(); SocketsManager socketsManager = new SocketsManagerImpl(); localServer = new LocalServerImpl(socketsManager, "localhost", REMOTE_PORT); remoteServer = new RemoteServerImpl(socketsManager, LOCAL_PORT); localThread = localServer.start(); remoteThread = remoteServer.start(); code = new FakeJavascriptCodeInTheWebpage(socketsManager, localServer, remoteServer); afterSetup(); } @Override protected final void tearDown() throws Exception { beforeTearDown(); stop(localServer); stop(remoteServer); localThread = null; remoteThread = null; afterTearDown(); } // ------------------------------------------------------- // Convenience // ------------------------------------------------------- protected final String doAuthenticate() { return doAuthenticate(getPrivateKey()); } protected final String doAuthenticate(final String privateKey) { Map<String, String> args = new HashMap<String, String>(); args.put(LWSDispatcherSupport.Parameters.PRIVATE, privateKey); return sendMessageFromWebpageToClient(LWSDispatcherSupport.Commands.AUTHENTICATE, 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); String parts[] = res.split(" "); if (parts.length < 2) { return new KeyPair(null, null, false); } return new KeyPair(parts[0], sharedKey = parts[1], true); } // ----------------------------------------------------------- // Private // ----------------------------------------------------------- private void requestPrivateAndSharedKeys() { KeyPair kp = getPublicAndSharedKeys(); String publicKey = kp.getPublicKey(); try { Thread.sleep(100); } 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); // // 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 = sendMessageFromClientToRemoteServer(LWSDispatcherSupport.Commands.GIVE_KEY, args); if (LWSServerUtil.isValidPrivateKey(privateKey)) break; try { Thread.sleep(SLEEP_TIME_BETWEEN_PRIVATE_KEY_TRIES); } catch (InterruptedException e) { // ignore } } } private String unwrap(String[] ss) { StringBuffer sb = new StringBuffer("{ "); if (ss != null) { for (String s : ss) { sb.append(String.valueOf(s)); sb.append(" "); } } sb.append("}"); return sb.toString(); } private String sendMessageFromWebpageToClient(final String cmd, final Map<String, String> args) { args.put(LWSDispatcherSupport.Parameters.CALLBACK, "dummy"); final String[] result = new String[1]; getCode().sendLocalMsg(cmd, args, new FakeJavascriptCodeInTheWebpage.Handler() { public void handle(final String res) { result[0] = LWSServerUtil.removeCallback(res); } }); return result[0]; } private String sendMessageFromClientToRemoteServer(final String cmd, final Map<String, String> args) { args.put(LWSDispatcherSupport.Parameters.CALLBACK, "dummy"); final String[] result = new String[1]; getCode().sendRemoteMsg(cmd, args, new FakeJavascriptCodeInTheWebpage.Handler() { public void handle(final String res) { result[0] = LWSServerUtil.removeCallback(res); } }); return result[0]; } private void stop(final AbstractServer t) { if (t != null) { t.shutDown(); } } }