package com.limegroup.gnutella.dht;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.limewire.core.settings.DHTSettings;
import org.limewire.io.IOUtils;
import org.limewire.io.IpPort;
import org.limewire.io.SecureInputStream;
import org.limewire.io.SecureOutputStream;
import org.limewire.mojito.KUID;
import org.limewire.mojito.MojitoDHT;
import org.limewire.mojito.MojitoFactory;
import org.limewire.mojito.routing.Contact;
import org.limewire.mojito.routing.Vendor;
import org.limewire.mojito.routing.Version;
import org.limewire.mojito.util.ContactUtils;
import org.limewire.util.CommonUtils;
import com.limegroup.gnutella.connection.Connection;
import com.limegroup.gnutella.connection.ConnectionLifecycleEvent;
import com.limegroup.gnutella.dht.DHTManager.DHTMode;
import com.limegroup.gnutella.util.EventDispatcher;
/**
* Controls a passive DHT node (an {@link ActiveDHTNodeController} but is a
* Gnutella Ultrapeer).
* <p>
* The passive node controller also maintains the list of this ultrapeer's
* leafs in a separate list. These leaves are this node's most accurate knowledge
* of the DHT, as leaf connections are state-full TCP, and they are therefore
* propagated in priority in the Gnutella network.
* <p>
* Persistence is implemented in order to be able to bootstrap the next session
* by saving a few Most Recently Seen (MRS) nodes. It is not necessary
* to persist the entire DHT, because we don't want to keep the same node ID at
* the next session, and accuracy of the contacts in the route table is not guaranteed
* when a node is passive (as it does not get contacted by the DHT).
*/
public class PassiveDHTNodeController extends AbstractDHTController {
/**
* The file to persist the list of host
*/
private static final File FILE = new File(CommonUtils.getUserSettingsDir(), "passive.mojito");
/**
* A RouteTable for passive Nodes.
*/
private PassiveDHTNodeRouteTable routeTable;
PassiveDHTNodeController(Vendor vendor, Version version,
EventDispatcher<DHTEvent, DHTEventListener> dispatcher,
DHTControllerFacade dhtControllerFacade) {
super(vendor, version, dispatcher, DHTMode.PASSIVE, dhtControllerFacade);
}
@Override
protected MojitoDHT createMojitoDHT(Vendor vendor, Version version) {
MojitoDHT dht = MojitoFactory.createFirewalledDHT("PassiveUltrapeerDHT", vendor, version);
routeTable = new PassiveDHTNodeRouteTable(dht);
dht.setRouteTable(routeTable);
// Load the small list of MRS Nodes for bootstrap
if (DHTSettings.PERSIST_PASSIVE_DHT_ROUTETABLE.getValue()
&& FILE.exists() && FILE.isFile()) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(
new BufferedInputStream(
new SecureInputStream(
new FileInputStream(FILE))));
int routeTableVersion = ois.readInt();
if (routeTableVersion >= getRouteTableVersion()) {
Contact node = null;
while((node = (Contact)ois.readObject()) != null) {
routeTable.add(node);
}
}
} catch (Throwable ignored) {
LOG.error("Throwable", ignored);
} finally {
IOUtils.close(ois);
}
}
return dht;
}
/**
* This method first adds the given host to the list of bootstrap nodes and
* then adds it to this passive node's routing table.
* <p>
* Note: This method makes sure the DHT is running already, as adding a node
* as a leaf involves sending it a DHT ping (in order to get its KUID).
*/
protected void addLeafDHTNode(String host, int port) {
if(!isRunning()) {
return;
}
SocketAddress addr = new InetSocketAddress(host, port);
//add to bootstrap nodes if we need to.
addActiveDHTNode(addr, false);
//add to our DHT leaves
if(LOG.isDebugEnabled()) {
LOG.debug("Adding host: "+addr+" to leaf dht nodes");
}
routeTable.addLeafDHTNode(host, port);
}
protected SocketAddress removeLeafDHTNode(String host, int port) {
if(!isRunning()) {
return null;
}
SocketAddress removed = routeTable.removeLeafDHTNode(host, port);
if(LOG.isDebugEnabled() && removed != null) {
LOG.debug("Removed host: "+removed+" from leaf dht nodes");
}
return removed;
}
@Override
public void stop() {
if (!isRunning()) {
return;
}
super.stop();
if (DHTSettings.PERSIST_PASSIVE_DHT_ROUTETABLE.getValue()) {
Collection<Contact> contacts = routeTable.getActiveContacts();
if (contacts.size() >= 2) {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(
new BufferedOutputStream(
new SecureOutputStream(
new FileOutputStream(FILE))));
oos.writeInt(getRouteTableVersion());
// Sort by MRS and save only some Nodes
contacts = ContactUtils.sort(contacts,
DHTSettings.MAX_PERSISTED_NODES.getValue());
KUID localNodeID = getMojitoDHT().getLocalNodeID();
for(Contact node : contacts) {
if(!node.getNodeID().equals(localNodeID)) {
oos.writeObject(node);
}
}
// EOF Terminator
oos.writeObject(null);
oos.flush();
} catch (IOException ignored) {
} finally {
IOUtils.close(oos);
}
}
}
}
/**
* This method return this passive node's leafs first (they have the highest timestamp)
* <p>
* Note: Although a passive node does not have accurate info in its route table
* (except for direct leafs), we still return nodes.
*/
@Override
public List<IpPort> getActiveDHTNodes(int maxNodes) {
if(!isRunning() || !getMojitoDHT().isBootstrapped()) {
return Collections.emptyList();
}
return getMRSNodes(maxNodes, true);
}
/**
* Handle connection-specific life cycle events only.
*/
@Override
public void handleConnectionLifecycleEvent(ConnectionLifecycleEvent evt) {
//handle connection specific events
Connection c = evt.getConnection();
if( c == null) {
return;
}
String host = c.getAddress();
int port = c.getPort();
if(evt.isConnectionClosedEvent()) {
if(LOG.isDebugEnabled()) {
LOG.debug("Got a connection closed event for connection: "+ c);
}
removeLeafDHTNode( host , port );
} else if(evt.isConnectionCapabilitiesEvent()){
if(c.getConnectionCapabilities().remostHostIsActiveDHTNode() > -1) {
if(LOG.isDebugEnabled()) {
LOG.debug("Connection is active dht node: "+ c);
}
addLeafDHTNode( host , port );
} else if(c.getConnectionCapabilities().remostHostIsPassiveDHTNode() > -1) {
if(LOG.isDebugEnabled()) {
LOG.debug("Connection is passive dht node: "+ c);
}
addPassiveDHTNode(new InetSocketAddress(host, port));
} else {
if(LOG.isDebugEnabled()) {
LOG.debug("Connection is node not connected to the DHT network: "+ c);
}
removeLeafDHTNode( host , port );
}
}
}
}