package com.limegroup.gnutella;
import java.io.File;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import junit.framework.Test;
import org.limewire.core.settings.ConnectionSettings;
import org.limewire.core.settings.FilterSettings;
import org.limewire.core.settings.NetworkSettings;
import org.limewire.core.settings.UltrapeerSettings;
import org.limewire.core.settings.UploadSettings;
import org.limewire.io.GUID;
import org.limewire.net.TLSManager;
import org.limewire.util.PrivilegedAccessor;
import org.limewire.util.TestUtils;
import com.google.inject.Injector;
import com.google.inject.Stage;
import com.limegroup.gnutella.downloader.PushDownloadManager;
import com.limegroup.gnutella.downloader.RemoteFileDescFactory;
import com.limegroup.gnutella.library.FileManager;
import com.limegroup.gnutella.library.FileManagerTestUtils;
import com.limegroup.gnutella.messagehandlers.MessageHandler;
import com.limegroup.gnutella.messages.Message;
import com.limegroup.gnutella.messages.PushRequest;
import com.limegroup.gnutella.messages.QueryReply;
import com.limegroup.gnutella.messages.QueryRequest;
import com.limegroup.gnutella.util.LimeTestCase;
public class MulticastTest extends LimeTestCase {
private final int DELAY = 1000;
private FileManager fileManager;
private MulticastHandler M_HANDLER;
private UnicastedHandler U_HANDLER;
private static final String FILE_NAME =
"com/limegroup/gnutella/metadata/metadata.mp3";
private MessageRouterImpl messageRouter;
private ConnectionServices connectionServices;
private SearchServices searchServices;
private PushDownloadManager pushDownloadManager;
private DownloadServices downloadServices;
private ForMeReplyHandler forMeReplyHandler;
private LifecycleManager lifeCycleManager;
private RemoteFileDescFactory remoteFileDescFactory;
private TLSManager TLSManager;
protected Injector injector;
private PushEndpointFactory pushEndpointFactory;
public MulticastTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(MulticastTest.class);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(suite());
}
private void setSettings() throws Exception {
FilterSettings.BLACK_LISTED_IP_ADDRESSES.setValue(new String[] { "*.*.*.*" });
// Set the local host to not be banned so pushes can go through
String ip = InetAddress.getLocalHost().getHostAddress();
FilterSettings.WHITE_LISTED_IP_ADDRESSES.setValue(new String[] { ip });
NetworkSettings.PORT.setValue(TEST_PORT);
ConnectionSettings.CONNECT_ON_STARTUP.setValue(false);
ConnectionSettings.DO_NOT_BOOTSTRAP.setValue(true);
UltrapeerSettings.EVER_ULTRAPEER_CAPABLE.setValue(true);
UltrapeerSettings.DISABLE_ULTRAPEER_MODE.setValue(false);
UltrapeerSettings.FORCE_ULTRAPEER_MODE.setValue(true);
UltrapeerSettings.MAX_LEAVES.setValue(1);
ConnectionSettings.NUM_CONNECTIONS.setValue(3);
ConnectionSettings.LOCAL_IS_PRIVATE.setValue(false);
ConnectionSettings.WATCHDOG_ACTIVE.setValue(false);
ConnectionSettings.MULTICAST_PORT.setValue(9021);
ConnectionSettings.ALLOW_MULTICAST_LOOPBACK.setValue(true);
}
@Override
public void setUp() throws Exception {
setSettings();
M_HANDLER = new MulticastHandler();
U_HANDLER = new UnicastedHandler();
injector = LimeTestUtils.createInjector(Stage.PRODUCTION);
fileManager = injector.getInstance(FileManager.class);
connectionServices = injector.getInstance(ConnectionServices.class);
messageRouter = (MessageRouterImpl) injector.getInstance(MessageRouter.class);
searchServices = injector.getInstance(SearchServices.class);
pushDownloadManager = injector.getInstance(PushDownloadManager.class);
downloadServices = injector.getInstance(DownloadServices.class);
forMeReplyHandler = injector.getInstance(ForMeReplyHandler.class);
lifeCycleManager = injector.getInstance(LifecycleManager.class);
remoteFileDescFactory = injector.getInstance(RemoteFileDescFactory.class);
TLSManager = injector.getInstance(NetworkManager.class);
pushEndpointFactory = injector.getInstance(PushEndpointFactory.class);
lifeCycleManager.start();
connectionServices.connect();
// MUST SLEEP TO LET THE FILE MANAGER INITIALIZE
sleep(2000);
// Set these after RouterService is started & MessageRouter
// is initialized, or else they'll be erased.
messageRouter.addMulticastMessageHandler(PushRequest.class, M_HANDLER);
messageRouter.addMulticastMessageHandler(QueryRequest.class, M_HANDLER);
messageRouter.addUDPMessageHandler(QueryReply.class, U_HANDLER);
messageRouter.addUDPMessageHandler(PushRequest.class, U_HANDLER);
FileManagerTestUtils.waitForLoad(fileManager,3000);
File file = TestUtils.getResourceFile(FILE_NAME);
assertNotNull(fileManager.getGnutellaFileList().add(file).get());
assertEquals("unexpected number of shared files", 1, fileManager.getGnutellaFileList().size() );
}
@Override
protected void tearDown() throws Exception {
connectionServices.disconnect();
lifeCycleManager.shutdown();
}
private static void sleep(int time) {
try {
Thread.sleep(time);
} catch(InterruptedException ignore) {}
}
/**
* Tests that a multicast message is sent by utilizing the 'loopback'
* feature of multicast. Notably, we receive the message even if we
* sent it.
*/
public void testQueryIsSentAndReceived() {
byte[] guid = searchServices.newQueryGUID();
searchServices.query(guid, "sam");
// sleep to let the search go.
sleep(DELAY);
assertEquals( "unexpected number of multicast messages", 1,
M_HANDLER.multicasted.size() );
Message m = M_HANDLER.multicasted.get(0);
assertInstanceof( QueryRequest.class, m );
QueryRequest qr = (QueryRequest)m;
assertEquals("unexpected query", "sam", qr.getQuery() );
assertTrue( "should be multicast", qr.isMulticast() );
assertTrue( "should not be firewalled", !qr.isFirewalledSource() );
// note it was hopped once.
assertEquals("wrong ttl", 0, qr.getTTL());
assertEquals("wrong hops", 1, qr.getHops());
}
public void testQueryRepliesIsFirewalled() throws Exception {
testQueryReplies(false);
}
public void testQueryRepliesNotFirewalled() throws Exception {
testQueryReplies(true);
}
/**
* Tests that replies to multicast queries contain the MCAST GGEP header
* and other appropriate info.
*/
public void testQueryReplies(boolean acceptedIncoming) throws Exception {
byte[] guid = searchServices.newQueryGUID();
//searchServices = getSearchServices(acceptedIncoming);
searchServices.query(guid, "metadata"); // search for the name
// sleep to let the search process.
sleep(DELAY);
assertEquals( "unexpected number of multicast messages", 1,
M_HANDLER.multicasted.size() );
assertEquals( "unexpected number of replies", 1,
U_HANDLER.unicasted.size() );
Message m = U_HANDLER.unicasted.get(0);
assertInstanceof( QueryReply.class, m);
QueryReply qr = (QueryReply)m;
assertTrue("should have MCAST header", qr.isReplyToMulticastQuery());
assertTrue("should not need push", !qr.getNeedsPush());
assertTrue("should not be busy", !qr.getIsBusy());
assertEquals("should only have 1 result", 1, qr.getResultCount());
assertTrue("should be measured speed", qr.getIsMeasuredSpeed());
byte[] xml = qr.getXMLBytes();
assertNotNull("didn't have xml", xml);
assertGreaterThan("xml too small", 10, xml.length);
// remember it was hopped once
assertEquals("wrong ttl", 0, qr.getTTL() );
assertEquals("wrong hops", 1, qr.getHops() );
// wipe out the address so the first addr == my addr check isn't used
wipeAddress(qr);
PrivilegedAccessor.setValue(injector.getInstance(Acceptor.class), "_acceptedIncoming", acceptedIncoming);
assertEquals("wrong qos", 4, qr.calculateQualityOfService());
}
/**
* Tests that a push sent from a multicast RFD is sent via multicast
* and includes the TLS flag.
* This does NOT test that ManagedDownloader will actively push
* multicast requests, nor does it check that we can parse
* the incoming GIV. It also does not test to ensure that multicast
* pushes are given priority over all other uploads.
*/
public void testPushSentThroughMulticastWithTLS() throws Exception {
// first go through some boring stuff to get a correct QueryReply
// that we can convert to an RFD.
byte[] guid = searchServices.newQueryGUID();
searchServices.query(guid, "metadata");
sleep(DELAY);
assertEquals("should have sent query", 1,
M_HANDLER.multicasted.size());
assertEquals("should have gotten reply", 1,
U_HANDLER.unicasted.size());
Message m = U_HANDLER.unicasted.get(0);
assertInstanceof( QueryReply.class, m);
QueryReply qr = (QueryReply)m;
// Because we're acting as both the sender & receiver, our
// routing tables are a little confused, so we must reset
// the push route table to map the guid to ForMeReplyHandler
// from a UDPReplyHandler
reroutePush(qr.getClientGUID());
// okay, now we have a QueryReply to convert to an RFD.
List responses = qr.getResultsAsList();
assertEquals("should only have 1 response", 1, responses.size());
Response res = (Response)responses.get(0);
RemoteFileDesc rfd = res.toRemoteFileDesc(qr, null, remoteFileDescFactory, pushEndpointFactory);
assertTrue("rfd should be multicast", rfd.isReplyToMulticast());
// clear the data to make it easier to look at again...
M_HANDLER.multicasted.clear();
U_HANDLER.unicasted.clear();
// Finally, we have the RFD we want to push.
TLSManager.setIncomingTLSEnabled(true);
pushDownloadManager.sendPush(rfd);
// sleep to make sure the push goes through.
sleep(DELAY);
assertEquals("should have sent & received push", 1,
M_HANDLER.multicasted.size());
// should be a push.
m = M_HANDLER.multicasted.get(0);
assertInstanceof(PushRequest.class, m);
PushRequest pr = (PushRequest)m;
// note it was hopped.
assertTrue(pr.isTLSCapable());
assertEquals("wrong ttl", 0, pr.getTTL());
assertEquals("wrong hops", 1, pr.getHops());
assertTrue("wrong client guid",
Arrays.equals(rfd.getClientGUID(), pr.getClientGUID()));
assertEquals("wrong index", rfd.getIndex(), pr.getIndex());
assertEquals("should not have unicasted anything", 0,
U_HANDLER.unicasted.size());
}
/**
* Tests that a push sent from a multicast RFD is sent via multicast
* and does not include the TLS flag.
* This does NOT test that ManagedDownloader will actively push
* multicast requests, nor does it check that we can parse
* the incoming GIV. It also does not test to ensure that multicast
* pushes are given priority over all other uploads.
*/
public void testPushSentThroughMulticastWithoutTLS() throws Exception {
// first go through some boring stuff to get a correct QueryReply
// that we can convert to an RFD.
byte[] guid = searchServices.newQueryGUID();
searchServices.query(guid, "metadata");
sleep(DELAY);
assertEquals("should have sent query", 1,
M_HANDLER.multicasted.size());
assertEquals("should have gotten reply", 1,
U_HANDLER.unicasted.size());
Message m = U_HANDLER.unicasted.get(0);
assertInstanceof( QueryReply.class, m);
QueryReply qr = (QueryReply)m;
// Because we're acting as both the sender & receiver, our
// routing tables are a little confused, so we must reset
// the push route table to map the guid to ForMeReplyHandler
// from a UDPReplyHandler
reroutePush(qr.getClientGUID());
// okay, now we have a QueryReply to convert to an RFD.
List responses = qr.getResultsAsList();
assertEquals("should only have 1 response", 1, responses.size());
Response res = (Response)responses.get(0);
RemoteFileDesc rfd = res.toRemoteFileDesc(qr, null, remoteFileDescFactory, pushEndpointFactory);
assertTrue("rfd should be multicast", rfd.isReplyToMulticast());
// clear the data to make it easier to look at again...
M_HANDLER.multicasted.clear();
U_HANDLER.unicasted.clear();
// Finally, we have the RFD we want to push.
TLSManager.setIncomingTLSEnabled(false);
pushDownloadManager.sendPush(rfd);
// sleep to make sure the push goes through.
sleep(DELAY);
assertEquals("should have sent & received push", 1,
M_HANDLER.multicasted.size());
// should be a push.
m = M_HANDLER.multicasted.get(0);
assertInstanceof(PushRequest.class, m);
PushRequest pr = (PushRequest)m;
// note it was hopped.
assertFalse(pr.isTLSCapable());
assertEquals("wrong ttl", 0, pr.getTTL());
assertEquals("wrong hops", 1, pr.getHops());
assertTrue("wrong client guid",
Arrays.equals(rfd.getClientGUID(), pr.getClientGUID()));
assertEquals("wrong index", rfd.getIndex(), pr.getIndex());
assertEquals("should not have unicasted anything", 0,
U_HANDLER.unicasted.size());
}
/**
* Tests to ensure multicast requests are sent via push
* and will upload regardless of the slots left.
*/
public void testPushesHaveUploadPriority() throws Exception {
// Make it so a normal upload request would fail.
UploadSettings.UPLOADS_PER_PERSON.setValue(0);
// first go through some boring stuff to get a correct QueryReply
// that we can convert to an RFD.
byte[] guid = searchServices.newQueryGUID();
searchServices.query(guid, "metadata");
sleep(DELAY);
assertEquals("should have sent query", 1,
M_HANDLER.multicasted.size());
assertEquals("should have gotten reply", 1,
U_HANDLER.unicasted.size());
Message m = U_HANDLER.unicasted.get(0);
assertInstanceof( QueryReply.class, m);
QueryReply qr = (QueryReply)m;
// Because we're acting as both the sender & receiver, our
// routing tables are a little confused, so we must reset
// the push route table to map the guid to ForMeReplyHandler
// from a UDPReplyHandler
reroutePush(qr.getClientGUID());
// okay, now we have a QueryReply to convert to an RFD.
List responses = qr.getResultsAsList();
assertEquals("should only have 1 response", 1, responses.size());
Response res = (Response)responses.get(0);
RemoteFileDesc rfd = res.toRemoteFileDesc(qr, null, remoteFileDescFactory, pushEndpointFactory);
// clear the data to make it easier to look at again...
M_HANDLER.multicasted.clear();
U_HANDLER.unicasted.clear();
assertFalse("file should not be saved yet",
new File( _savedDir, "metadata.mp3").exists());
downloadServices.download(new RemoteFileDesc[] { rfd }, false,
new GUID(guid));
// sleep to make sure the download starts & push goes through.
sleep(3000);
assertEquals("should have sent & received push", 1,
M_HANDLER.multicasted.size());
// should be a push.
m = M_HANDLER.multicasted.get(0);
assertInstanceof(PushRequest.class, m);
assertEquals("should not have unicasted anything", 0,
U_HANDLER.unicasted.size());
assertTrue("file should have been downloaded & saved",
new File(_savedDir, "metadata.mp3").exists());
// Get rid of this file, so the -Dtimes=X option works properly... =)
assertEquals("unexpected number of shared files", 2, fileManager.getGnutellaFileList().size());
File temp = new File(_savedDir, "metadata.mp3");
if (temp.exists()) {
fileManager.getManagedFileList().remove(temp);
temp.delete();
}
sleep(2 * DELAY);
assertFalse("file should have been deleted", temp.exists());
assertEquals("unexpected number of shared files", 1, fileManager.getGnutellaFileList().size());
}
private static void wipeAddress(QueryReply qr) throws Exception {
// TODO mock QueryReply
PrivilegedAccessor.setValue(qr, "_address", new byte[4]);
}
private void reroutePush(byte[] guid) throws Exception {
RouteTable rt = messageRouter.getPushRouteTable();
rt.routeReply(guid, forMeReplyHandler);
}
private static class MulticastHandler implements MessageHandler {
List<Message> multicasted = new LinkedList<Message>();
public void handleMessage(Message msg, InetSocketAddress addr, ReplyHandler handler) {
multicasted.add(msg);
}
}
private static class UnicastedHandler implements MessageHandler {
List<Message> unicasted = new LinkedList<Message>();
public void handleMessage(Message msg, InetSocketAddress addr, ReplyHandler handler) {
unicasted.add(msg);
}
}
}