package com.limegroup.gnutella.downloader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import junit.framework.Test;
import org.limewire.collection.Range;
import org.limewire.core.settings.NetworkSettings;
import org.limewire.gnutella.tests.LimeTestCase;
import org.limewire.gnutella.tests.LimeTestUtils;
import org.limewire.gnutella.tests.NetworkManagerStub;
import org.limewire.io.ConnectableImpl;
import org.limewire.io.GUID;
import org.limewire.io.LocalSocketAddressProvider;
import org.limewire.io.LocalSocketAddressProviderStub;
import org.limewire.net.SocketsManager;
import org.limewire.security.MACCalculatorRepositoryManager;
import org.limewire.util.PrivilegedAccessor;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
import com.limegroup.gnutella.ActivityCallback;
import com.limegroup.gnutella.ApplicationServices;
import com.limegroup.gnutella.ConnectionManager;
import com.limegroup.gnutella.ConnectionServices;
import com.limegroup.gnutella.DownloadManager;
import com.limegroup.gnutella.DownloadManagerImpl;
import com.limegroup.gnutella.Downloader;
import com.limegroup.gnutella.ForMeReplyHandler;
import com.limegroup.gnutella.GuidMapManager;
import com.limegroup.gnutella.HostCatcher;
import com.limegroup.gnutella.MessageDispatcher;
import com.limegroup.gnutella.MessageHandlerBinder;
import com.limegroup.gnutella.MessageRouter;
import com.limegroup.gnutella.MulticastService;
import com.limegroup.gnutella.NetworkManager;
import com.limegroup.gnutella.PongCacher;
import com.limegroup.gnutella.QueryUnicaster;
import com.limegroup.gnutella.RemoteFileDesc;
import com.limegroup.gnutella.ReplyHandler;
import com.limegroup.gnutella.Response;
import com.limegroup.gnutella.ResponseFactory;
import com.limegroup.gnutella.ResponseFactoryImpl;
import com.limegroup.gnutella.RouteTable;
import com.limegroup.gnutella.SpamServices;
import com.limegroup.gnutella.Statistics;
import com.limegroup.gnutella.UDPReplyHandlerCache;
import com.limegroup.gnutella.UDPService;
import com.limegroup.gnutella.URN;
import com.limegroup.gnutella.UploadManager;
import com.limegroup.gnutella.Downloader.DownloadState;
import com.limegroup.gnutella.auth.ContentManager;
import com.limegroup.gnutella.connection.RoutedConnectionFactory;
import com.limegroup.gnutella.dht.DHTManager;
import com.limegroup.gnutella.filters.URNFilter;
import com.limegroup.gnutella.guess.OnDemandUnicaster;
import com.limegroup.gnutella.library.FileViewManager;
import com.limegroup.gnutella.library.SharedFilesKeywordIndex;
import com.limegroup.gnutella.messagehandlers.InspectionRequestHandler;
import com.limegroup.gnutella.messagehandlers.LimeACKHandler;
import com.limegroup.gnutella.messagehandlers.OOBHandler;
import com.limegroup.gnutella.messagehandlers.UDPCrawlerPingHandler;
import com.limegroup.gnutella.messages.OutgoingQueryReplyFactory;
import com.limegroup.gnutella.messages.PingReplyFactory;
import com.limegroup.gnutella.messages.PingRequestFactory;
import com.limegroup.gnutella.messages.QueryReply;
import com.limegroup.gnutella.messages.QueryReplyFactory;
import com.limegroup.gnutella.messages.QueryRequest;
import com.limegroup.gnutella.messages.QueryRequestFactory;
import com.limegroup.gnutella.messages.StaticMessages;
import com.limegroup.gnutella.messages.vendor.HeadPongFactory;
import com.limegroup.gnutella.messages.vendor.ReplyNumberVendorMessageFactory;
import com.limegroup.gnutella.routing.QRPUpdater;
import com.limegroup.gnutella.search.QueryDispatcher;
import com.limegroup.gnutella.search.QueryHandlerFactory;
import com.limegroup.gnutella.search.SearchResultHandler;
import com.limegroup.gnutella.simpp.SimppManager;
import com.limegroup.gnutella.stubs.ConnectionManagerStub;
import com.limegroup.gnutella.stubs.MessageRouterStub;
import com.limegroup.gnutella.util.LimeWireUtils;
import com.limegroup.gnutella.util.QueryUtils;
import com.limegroup.gnutella.version.UpdateHandler;
/**
* This test makes sure that LimeWire does the following things:
* 1) Does NOT requery ever.
* 2) Wakes up from the WAITING_FROM_RESULTS state when a new, valid query comes
* in.
* 3) Wakes up from the GAVE_UP state when a new, valid query comes in.
*/
public class RequeryDownloadTest extends LimeTestCase {
/** The main test fixture. Contains the incomplete file and hash below. */
private DownloadManagerImpl downloadManager;
/** Where to send and receive messages */
private static TestMessageRouter messageRouter;
/** The simulated downloads.dat file. Used only to build _mgr. */
private File snapshot;
/** The name of the completed file. */
private String filename="some file.txt";
/** The incomplete file to resume from. */
private File incompleteFile;
/** The hash of file when complete. */
private URN hash;
/** The uploader */
private TestUploader testUploader;
/** The TestMessageRouter's queryRouteTable. */
private RouteTable routeTable;
private Injector injector;
private ForMeReplyHandler forMeReplyHandler;
private static final int PORT = 6939;
//////////////////////////// Fixtures /////////////////////////
public RequeryDownloadTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(RequeryDownloadTest.class);
}
@Override
public void setUp() throws Exception {
final LocalSocketAddressProviderStub localSocketAddressProviderStub = new LocalSocketAddressProviderStub();
localSocketAddressProviderStub.setLocalAddressPrivate(false);
injector = LimeTestUtils.createInjectorNonEagerly(new AbstractModule() {
@Override
protected void configure() {
bind(MessageRouter.class).to(TestMessageRouter.class);
bind(ConnectionManager.class).to(ConnectionManagerStub.class);
bind(NetworkManager.class).to(NetworkManagerStub.class);
bind(LocalSocketAddressProvider.class).toInstance(localSocketAddressProviderStub);
}
});
hash = TestFile.hash();
NetworkManagerStub networkManager = (NetworkManagerStub) injector
.getInstance(NetworkManager.class);
networkManager.setListeningPort(NetworkSettings.PORT.getValue());
messageRouter = (TestMessageRouter)injector.getInstance(MessageRouter.class);
routeTable = (RouteTable) PrivilegedAccessor.getValue(messageRouter, "_queryRouteTable");
forMeReplyHandler = injector.getInstance(ForMeReplyHandler.class);
initializeIncompleteFileManager();
downloadManager = (DownloadManagerImpl)injector.getInstance(DownloadManager.class);
downloadManager.start();
downloadManager.scheduleWaitingPump();
testUploader = injector.getInstance(TestUploader.class);
testUploader.start("uploader 6666", 6666, false);
testUploader.setRate(Integer.MAX_VALUE);
RequeryManager.NO_DELAY = true;
new File(getSaveDirectory(), filename).delete();
}
/** Creates the incomplete file and returns an IncompleteFileManager with
* info for that file. */
private void initializeIncompleteFileManager() throws Exception {
IncompleteFileManager ifm= injector.getInstance(IncompleteFileManager.class);
Set<URN> urns=new HashSet<URN>(1);
urns.add(hash);
RemoteFileDesc rfd = injector.getInstance(RemoteFileDescFactory.class).createRemoteFileDesc(new ConnectableImpl("1.2.3.4", PORT, false), 13l, filename, TestFile.length(),
new byte[16], 56, 4, true, null, urns, false, "", -1);
//Create incompleteFile, write a few bytes
incompleteFile=ifm.getFile(rfd);
try {
incompleteFile.delete();
incompleteFile.createNewFile();
OutputStream out=new FileOutputStream(incompleteFile);
out.write(TestFile.getByte(0));
out.write(TestFile.getByte(1));
out.close();
} catch (IOException e) {
fail("Couldn't create incomplete file", e);
}
//Record information in IncompleteFileManager.
VerifyingFileFactory verifyingFileFactory = injector.getInstance(VerifyingFileFactory.class);
VerifyingFile vf= verifyingFileFactory.createVerifyingFile(TestFile.length());
vf.addInterval(Range.createRange(0, 1)); //inclusive
ifm.addEntry(incompleteFile, vf, true);
}
@Override
public void tearDown() {
if(testUploader != null)
testUploader.stopThread();
if (incompleteFile != null )
incompleteFile.delete();
if (snapshot != null)
snapshot.delete();
new File(getSaveDirectory(), filename).delete();
}
/////////////////////////// Actual Tests /////////////////////////////
/** Gets response with exact match, starts downloading. */
public void testExactMatch() throws Exception {
doTest(filename, hash, true);
}
/** Gets response with same hash, different name, starts downloading. */
public void testHashMatch() throws Exception {
doTest("different name.txt", hash, true);
}
/** Gets a response that doesn't match--can't download. */
public void testNoMatch() throws Exception {
doTest("some other file.txt", null, false);
}
/** Runs the tests again with pro set */
public void testProExact() throws Exception {
PrivilegedAccessor.setValue(LimeWireUtils.class, "_isPro", Boolean.TRUE);
testExactMatch();
}
public void testProHash() throws Exception {
PrivilegedAccessor.setValue(LimeWireUtils.class, "_isPro", Boolean.TRUE);
testHashMatch();
}
public void testProNo() throws Exception {
PrivilegedAccessor.setValue(LimeWireUtils.class, "_isPro", Boolean.TRUE);
testNoMatch();
}
/**
* Skeleton method for all tests.
* @param responseName the file name to send in responses
* @param responseURN the SHA1 urn to send in responses, or null for none
* @param shouldDownload true if the downloader should actually start
* the download. False if the response shouldn't satisfy it.
*/
private void doTest(String responseName,
URN responseURN,
boolean shouldDownload) throws Exception {
// we need to seed the MessageRouter with a GUID that it will recognize
byte[] guidToUse = GUID.makeGuid();
routeTable.routeReply(guidToUse, forMeReplyHandler);
//Start a download for the given incomplete file. Give the thread time
//to start up and send its requery.
Downloader downloader = null;
downloader = downloadManager.download(incompleteFile);
assertTrue(downloader instanceof ResumeDownloader);
assertEquals(DownloadState.QUEUED,downloader.getState());
DownloadTestUtils.strictWaitForState(downloader, DownloadState.WAITING_FOR_USER, DownloadState.QUEUED);
messageRouter.broadcastLatch = new CountDownLatch(1);
downloader.resume();
DownloadTestUtils.strictWaitForState(downloader, DownloadState.WAITING_FOR_GNET_RESULTS, DownloadState.QUEUED, DownloadState.GAVE_UP);
//Check that we can get query of right type.
//TODO: try resume without URN
assertTrue(messageRouter.broadcastLatch.await(1, TimeUnit.SECONDS));
assertEquals("unexpected router.broadcasts size", 1, messageRouter.broadcasts.size());
Object m=messageRouter.broadcasts.get(0);
assertInstanceof("m should be a query request", QueryRequest.class, m);
QueryRequest qr=(QueryRequest)m;
// no more requeries
assertTrue((GUID.isLimeGUID(qr.getGUID())) &&
!(GUID.isLimeRequeryGUID(qr.getGUID())));
// since filename is the first thing ever submitted it should always
// query for allFiles[0].getFileName()
String qString =QueryUtils.createQueryString(filename);
assertEquals("should have queried for filename", qString,
qr.getQuery());
Set urns=qr.getQueryUrns();
assertNotNull("urns shouldn't be null", urns);
assertEquals("should only have NO urn", 0, urns.size());
// not relevant anymore, we don't send URN queries
// assertTrue("urns should contain the hash", urns.contains(_hash));
//Send a response to the query.
Set<URN> responseURNs = null;
if (responseURN != null) {
responseURNs = new HashSet<URN>(1);
responseURNs.add(responseURN);
}
ResponseFactoryImpl responseFactory = (ResponseFactoryImpl)injector.getInstance(ResponseFactory.class);
Response response = responseFactory.createResponse(0L, TestFile.length(), responseName, -1, responseURNs, null, null, null);
byte[] ip = {(byte)127, (byte)0, (byte)0, (byte)1};
QueryReplyFactory queryReplyFactory = injector.getInstance(QueryReplyFactory.class);
QueryReply reply = queryReplyFactory.createQueryReply(guidToUse, (byte)6, 6666,
ip, 0l, new Response[] { response }, new byte[16], false, false, true, false,
false, false);//supports chat, is multicast response....
RoutedConnectionFactory managedConnectionFactory = injector.getInstance(RoutedConnectionFactory.class);
messageRouter.handleQueryReply(reply, managedConnectionFactory.createRoutedConnection("1.2.3.4", PORT));
//Make sure the downloader does the right thing with the response.
if (shouldDownload) {
DownloadTestUtils.strictWaitForState(downloader, DownloadState.COMPLETE, 30, TimeUnit.SECONDS, DownloadState.WAITING_FOR_GNET_RESULTS, DownloadState.CONNECTING, DownloadState.HASHING, DownloadState.SAVING, DownloadState.DOWNLOADING);
} else {
//b) No match: keep waiting for results
Thread.sleep(2000); // sleep a bit and make sure nothing happened in the meantime.
assertEquals("downloader should wait for user", DownloadState.WAITING_FOR_GNET_RESULTS, downloader.getState());
downloader.stop();
}
}
@Singleton
private static class TestMessageRouter extends MessageRouterStub {
private volatile CountDownLatch broadcastLatch;
private final List<QueryRequest> broadcasts=Collections.synchronizedList(new LinkedList<QueryRequest>());
@Inject
public TestMessageRouter(NetworkManager networkManager,
QueryRequestFactory queryRequestFactory,
QueryHandlerFactory queryHandlerFactory,
OnDemandUnicaster onDemandUnicaster,
HeadPongFactory headPongFactory, PingReplyFactory pingReplyFactory,
ConnectionManager connectionManager, @Named("forMeReplyHandler")
ReplyHandler forMeReplyHandler, QueryUnicaster queryUnicaster,
FileViewManager fileManager, ContentManager contentManager,
DHTManager dhtManager, UploadManager uploadManager,
DownloadManager downloadManager, UDPService udpService,
SearchResultHandler searchResultHandler,
SocketsManager socketsManager, HostCatcher hostCatcher,
QueryReplyFactory queryReplyFactory, StaticMessages staticMessages,
Provider<MessageDispatcher> messageDispatcher,
MulticastService multicastService, QueryDispatcher queryDispatcher,
Provider<ActivityCallback> activityCallback,
ConnectionServices connectionServices,
ApplicationServices applicationServices,
@Named("backgroundExecutor")
ScheduledExecutorService backgroundExecutor,
Provider<PongCacher> pongCacher,
Provider<SimppManager> simppManager,
Provider<UpdateHandler> updateHandler,
GuidMapManager guidMapManager,
UDPReplyHandlerCache udpReplyHandlerCache,
Provider<InspectionRequestHandler> inspectionRequestHandlerFactory,
Provider<UDPCrawlerPingHandler> udpCrawlerPingHandlerFactory,
Statistics statistics,
ReplyNumberVendorMessageFactory replyNumberVendorMessageFactory,
PingRequestFactory pingRequestFactory,
MessageHandlerBinder messageHandlerBinder,
Provider<OOBHandler> oobHandlerFactory,
Provider<MACCalculatorRepositoryManager> macManager,
Provider<LimeACKHandler> limeACKHandler,
OutgoingQueryReplyFactory outgoingQueryReplyFactory,
SharedFilesKeywordIndex sharedFilesKeywordIndex,
QRPUpdater qrpUpdater, URNFilter urnFilter,
SpamServices spamServices) {
super(networkManager, queryRequestFactory, queryHandlerFactory,
onDemandUnicaster, headPongFactory, pingReplyFactory,
connectionManager, forMeReplyHandler, queryUnicaster,
fileManager, contentManager, dhtManager, uploadManager,
downloadManager, udpService, searchResultHandler,
socketsManager, hostCatcher, queryReplyFactory,
staticMessages, messageDispatcher, multicastService,
queryDispatcher, activityCallback, connectionServices,
applicationServices, backgroundExecutor, pongCacher,
simppManager, updateHandler, guidMapManager,
udpReplyHandlerCache, inspectionRequestHandlerFactory,
udpCrawlerPingHandlerFactory, statistics,
replyNumberVendorMessageFactory, pingRequestFactory,
messageHandlerBinder, oobHandlerFactory, macManager,
limeACKHandler, outgoingQueryReplyFactory,
sharedFilesKeywordIndex, qrpUpdater, urnFilter,
spamServices);
}
@Override
public void sendDynamicQuery(QueryRequest query) {
if(broadcastLatch != null) {
broadcastLatch.countDown();
}
broadcasts.add(query);
super.sendDynamicQuery(query); //add GUID to route table
}
public void clearBroadcasts() {
broadcasts.clear();
}
}
}