package com.limegroup.gnutella.bootstrap;
import java.io.IOException;
import java.io.Writer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.core.settings.ConnectionSettings;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.limegroup.gnutella.ConnectionServices;
import com.limegroup.gnutella.ExtendedEndpoint;
import com.limegroup.gnutella.MulticastService;
import com.limegroup.gnutella.messages.PingRequest;
import com.limegroup.gnutella.messages.PingRequestFactory;
@Singleton
class BootstrapperImpl implements Bootstrapper {
private static final Log LOG = LogFactory.getLog(BootstrapperImpl.class);
/** Milliseconds to wait between multicast fetches. */
static int MULTICAST_INTERVAL = 50 * 1000;
/** Milliseconds to wait after trying multicast before falling back to UDP. */
static int UDP_FALLBACK_DELAY = 1000;
/** Milliseconds to wait between UDP fetches. */
static int UDP_INTERVAL = 10 * 1000;
/** Milliseconds to wait after trying UDP before falling back to TCP. */
static int TCP_FALLBACK_DELAY = 40 * 1000;
/** Milliseconds to wait between TCP fetches. */
static int TCP_INTERVAL = 60 * 60 * 1000;
/** Milliseconds to wait before trying TCP if gnutella.net doesn't exist. */
static int ONE_SHOT_TCP_DELAY = 10 * 1000;
/** The time at which we started to think about bootstrapping. */
private long firstBootstrapCheck = 0;
/**
* The next allowed multicast time.
* Incremented after each attempted multicast fetch.
*/
private long nextAllowedMulticastTime = 0; // Immediately
/**
* The next allowed UDP fetch time.
* Incremented after each attempted UDP fetch.
*/
private long nextAllowedUdpTime = Long.MAX_VALUE; // Not just yet
/**
* The next allowed TCP fetch time.
* Incremented after each attempted TCP fetch.
*/
private long nextAllowedTcpTime = Long.MAX_VALUE; // Not just yet
private final ConnectionServices connectionServices;
private final Provider<MulticastService> multicastService;
private final PingRequestFactory pingRequestFactory;
private final Bootstrapper.Listener listener;
private final TcpBootstrap tcpBootstrap;
private final UDPHostCache udpHostCache;
private boolean oneShotTcpAllowed = false, oneShotTcpTried = false;
@Inject
public BootstrapperImpl(ConnectionServices connectionServices,
Provider<MulticastService> multicastService,
PingRequestFactory pingRequestFactory,
Bootstrapper.Listener listener,
TcpBootstrap tcpBootstrap,
UDPHostCache udpHostCache) {
this.connectionServices = connectionServices;
this.multicastService = multicastService;
this.pingRequestFactory = pingRequestFactory;
this.listener = listener;
this.tcpBootstrap = tcpBootstrap;
this.udpHostCache = udpHostCache;
}
/**
* Determines whether or not it is time to get more hosts,
* and if we need them, gets them.
*/
@Override
public synchronized void run() {
if(ConnectionSettings.DO_NOT_BOOTSTRAP.getValue()) {
LOG.trace("Not bootstrapping");
return;
}
long now = System.currentTimeMillis();
if(firstBootstrapCheck == 0)
firstBootstrapCheck = now;
// If we need endpoints, try any bootstrapping methods that
// haven't been tried too recently
if(needsHosts(now)) {
multicastFetch(now);
udpHostCacheFetch(now);
tcpHostCacheFetch(now);
}
}
@Override
public void reset() {
firstBootstrapCheck = 0;
nextAllowedMulticastTime = 0;
nextAllowedUdpTime = Long.MAX_VALUE;
nextAllowedTcpTime = Long.MAX_VALUE;
}
@Override
public boolean addUDPHostCache(ExtendedEndpoint ee) {
return udpHostCache.add(ee);
}
@Override
public boolean isWriteDirty() {
return udpHostCache.isWriteDirty();
}
@Override
public void write(Writer out) throws IOException {
udpHostCache.write(out);
}
/**
* Determines whether or not we need more hosts.
*/
private boolean needsHosts(long now) {
long delay = now - firstBootstrapCheck;
if(listener.needsHosts()) {
if(delay > ONE_SHOT_TCP_DELAY && !oneShotTcpTried) {
if(LOG.isTraceEnabled())
LOG.trace("One-shot TCP allowed after " + delay + " ms");
oneShotTcpAllowed = true;
}
LOG.trace("Need hosts: none known");
return true;
}
if(connectionServices.isConnected()) {
LOG.trace("Connected");
return false;
}
if(delay > ConnectionSettings.BOOTSTRAP_DELAY.getValue()) {
if(LOG.isTraceEnabled())
LOG.trace("Need hosts: not connected after " + delay + " ms");
return true;
}
LOG.trace("Still trying to connect");
return false;
}
/**
* Considers fetching hosts via multicast and returns true if a fetch
* was attempted.
*/
private boolean multicastFetch(long now) {
if(ConnectionSettings.DO_NOT_MULTICAST_BOOTSTRAP.getValue()) {
LOG.trace("Never fetching via multicast");
if(nextAllowedUdpTime == Long.MAX_VALUE)
nextAllowedUdpTime = 0;
return false;
}
if(nextAllowedMulticastTime < now) {
LOG.trace("Fetching via multicast");
PingRequest pr = pingRequestFactory.createMulticastPing();
multicastService.get().send(pr);
nextAllowedMulticastTime = now + MULTICAST_INTERVAL;
// If this is the first multicast fetch, set the UDP fallback time
if(nextAllowedUdpTime == Long.MAX_VALUE)
nextAllowedUdpTime = now + UDP_FALLBACK_DELAY;
return true;
}
LOG.trace("Not fetching via multicast");
return false;
}
/**
* Considers fetching hosts via UDP and returns true if a fetch was
* attempted.
*/
private boolean udpHostCacheFetch(long now) {
if(nextAllowedUdpTime < now) {
LOG.trace("Fetching via UDP");
udpHostCache.fetchHosts();
nextAllowedUdpTime = now + UDP_INTERVAL;
// If this is the first UDP fetch, set the TCP fallback time
if(nextAllowedTcpTime == Long.MAX_VALUE)
nextAllowedTcpTime = now + TCP_FALLBACK_DELAY;
return true;
}
LOG.trace("Not fetching via UDP");
return false;
}
/**
* Considers fetching hosts via TCP and returns true if a fetch was
* attempted.
*/
private boolean tcpHostCacheFetch(long now) {
if(nextAllowedTcpTime < now || oneShotTcpAllowed) {
LOG.trace("Fetching via TCP");
tcpBootstrap.fetchHosts(listener);
nextAllowedTcpTime = now + TCP_INTERVAL;
oneShotTcpAllowed = false;
oneShotTcpTried = true;
return true;
}
LOG.trace("Not fetching via TCP");
return false;
}
}