package com.limegroup.gnutella;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.nio.observer.Shutdownable;
import org.limewire.io.IP;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
/**
* A sanity checker for many different in-network verification
* requests.
*
* If we cannot verify the message as received from a number
* of hosts from different areas, message the user to hit
* the website as the installation may possible be corrupted.
*/
@Singleton
public class NetworkUpdateSanityCheckerImpl implements NetworkUpdateSanityChecker {
private static final Set<RequestType> allTypes = EnumSet.allOf(RequestType.class);
private static final Log LOG = LogFactory.getLog(NetworkUpdateSanityCheckerImpl.class);
private static final int MAXIMUM_FAILURES = 20;
private static final String MASK = "/255.255.0.0";
private final Map<RequestType, Map<ReplyHandler, Boolean>> requests =
new HashMap<RequestType, Map<ReplyHandler, Boolean>>(RequestType.values().length);
private final List<IP> failures = new ArrayList<IP>(MAXIMUM_FAILURES);
private boolean finished = false;
private final Set<RequestType> successes = EnumSet.noneOf(RequestType.class);
private final Provider<ActivityCallback> activityCallback;
@Inject
public NetworkUpdateSanityCheckerImpl(
Provider<ActivityCallback> activityCallback) {
this.activityCallback = activityCallback;
}
/**
* Stores knowledge that we've requested a network-updatable component
* from the given source.
*/
public void handleNewRequest(ReplyHandler handler, RequestType type) {
if(LOG.isDebugEnabled())
LOG.debug("Adding request to handler: " + handler);
synchronized(requests) {
// We're done.
if(finished) {
LOG.debug("Already reached maximum failure point, ignoring.");
return;
}
addRequest(handler, type);
}
}
/**
* Acknowledge we received a valid response from the source.
*/
public void handleValidResponse(ReplyHandler handler, RequestType type) {
if(LOG.isDebugEnabled())
LOG.debug("Received valid response from handler: " + handler + "of type: " + type);
synchronized(requests) {
if(!finished) {
// If we've gotten all kinds of valid responses,
// assume we're okay.
successes.add(type);
if(successes.containsAll(allTypes)) {
LOG.debug("Received every kind of success!");
finished = true;
requests.clear();
failures.clear();
} else {
removeRequest(handler, type);
}
}
}
}
/**
* Acknowledge we received an invalid response from the source.
*/
public void handleInvalidResponse(ReplyHandler handler, RequestType type) {
synchronized(requests) {
// If we had a request for this handler...
if(!finished && removeRequest(handler, type)) {
if(LOG.isDebugEnabled())
LOG.debug("Had a request from handler, adding as failure... " + handler);
IP ip = new IP(handler.getAddress() + MASK);
boolean contained = false;
// See if we already had a failure in this range, if so
// then ignore it.
for(IP current : failures) {
if(current.contains(ip)) {
if(LOG.isDebugEnabled())
LOG.debug("Already had a failure from range: " +
current + ", ignoring handler ip: " + ip);
contained = true;
break;
}
}
if(!contained) {
// If no failure already here, add it.
failures.add(ip);
if(LOG.isDebugEnabled())
LOG.debug("Current failure size: " + failures.size());
if(failures.size() == MAXIMUM_FAILURES) {
LOG.debug("Reached failure threshold!");
finished = true;
requests.clear();
failures.clear();
activityCallback.get().installationCorrupted();
}
}
}
}
if(handler instanceof Shutdownable)
((Shutdownable)handler).shutdown();
}
/** Adds a single incoming request to the maps. */
private void addRequest(ReplyHandler handler, RequestType type) {
Map<ReplyHandler, Boolean> inner = requests.get(type);
if(inner == null) {
inner = new WeakHashMap<ReplyHandler, Boolean>();
requests.put(type, inner);
}
inner.put(handler, Boolean.TRUE);
}
/**
* Removes a request from the maps, returning an object if it was
* contained. Null if it wasn't.
*/
private boolean removeRequest(ReplyHandler handler, RequestType type) {
Map<ReplyHandler, Boolean> inner = requests.get(type);
if(inner == null)
return false;
else
return inner.remove(handler) != null;
}
}