package lbms.plugins.mldht.kad;
import java.util.*;
import lbms.plugins.mldht.kad.DHT.DHTtype;
import lbms.plugins.mldht.kad.messages.AnnounceRequest;
import lbms.plugins.mldht.kad.messages.GetPeersRequest;
import lbms.plugins.mldht.kad.messages.GetPeersResponse;
import lbms.plugins.mldht.kad.messages.MessageBase;
import lbms.plugins.mldht.kad.messages.MessageBase.Method;
/**
* @author Damokles
*
*/
public class AnnounceTask extends Task {
private int port;
private List<KBucketEntryAndToken> answered; // nodes which have answered with values
private List<KBucketEntry> answered_visited; // nodes which have answered with values which have been visited
private Database db;
private Set<DBItem> returned_items;
private SortedSet<KBucketEntryAndToken> closestSet;
private int validReponsesSinceLastClosestSetModification;
public AnnounceTask (Database db, RPCServerBase rpc, Node node,
Key info_hash, int port) {
super(info_hash, rpc, node);
answered = new ArrayList<KBucketEntryAndToken>();
answered_visited = new ArrayList<KBucketEntry>();
returned_items = new HashSet<DBItem>();
this.db = db;
this.port = port;
this.closestSet = new TreeSet<KBucketEntryAndToken>(new KBucketEntry.DistanceOrder(targetKey));
DHT.logDebug("AnnounceTask started: " + getTaskID());
}
/* (non-Javadoc)
* @see lbms.plugins.mldht.kad.Task#callFinished(lbms.plugins.mldht.kad.RPCCall, lbms.plugins.mldht.kad.messages.MessageBase)
*/
@Override
void callFinished (RPCCallBase c, MessageBase rsp) {
// if we do not have a get peers response, return
// announce_peer's response are just empty anyway
if (c.getMessageMethod() != Method.GET_PEERS) {
return;
}
// it is either a GetPeersNodesRsp or a GetPeersValuesRsp
GetPeersResponse gpr;
if (rsp instanceof GetPeersResponse) {
gpr = (GetPeersResponse) rsp;
} else {
return;
}
for (DHTtype type : DHTtype.values())
{
byte[] nodes = gpr.getNodes(type);
if (nodes == null)
continue;
int nval = nodes.length / type.NODES_ENTRY_LENGTH;
if (type == rpc.getDHT().getType())
{
synchronized (todo)
{
for (int i = 0; i < nval; i++)
{
// add node to todo list
KBucketEntry e = PackUtil.UnpackBucketEntry(nodes, i * type.NODES_ENTRY_LENGTH, type);
if (!todo.contains(e) && !visited.contains(e) && todo.size() < 100)
{
todo.add(e);
}
}
}
} else
{
for (int i = 0; i < nval; i++)
{
KBucketEntry e = PackUtil.UnpackBucketEntry(nodes, i * type.NODES_ENTRY_LENGTH, type);
DHT.getDHT(type).addDHTNode(e.getAddress().getAddress().getHostAddress(), e.getAddress().getPort());
}
}
}
// store the items in the database
List<DBItem> items = gpr.getPeerItems();
for (DBItem item : items)
{
db.store(targetKey, item);
// also add the items to the returned_items list
returned_items.add(item);
}
if (gpr.getToken() != null && !gpr.getPeerItems().isEmpty())
{
// add the peer who responded to the answered list, so we can do an announce
KBucketEntry e = new KBucketEntry(rsp.getOrigin(), rsp.getID());
KBucketEntryAndToken newEntry = new KBucketEntryAndToken(e, gpr.getToken());
synchronized (answered)
{
synchronized (answered_visited)
{
if (!answered.contains(newEntry) && !answered_visited.contains(e))
{
answered.add(newEntry);
}
}
}
}
if (gpr.getToken() != null)
{
// add the peer who responded to the closest nodes list, so we can do an announce
// if no or less then K Nodes respond with peers
KBucketEntry entry = new KBucketEntry(rsp.getOrigin(), rsp.getID());
KBucketEntryAndToken toAdd = new KBucketEntryAndToken(entry, gpr.getToken());
synchronized (closestSet)
{
closestSet.add(toAdd);
if (closestSet.size() > DHTConstants.MAX_ENTRIES_PER_BUCKET)
{
KBucketEntryAndToken last = closestSet.last();
closestSet.remove(last);
if (toAdd == last)
{
validReponsesSinceLastClosestSetModification++;
} else
{
validReponsesSinceLastClosestSetModification = 0;
}
}
}
}
}
/* (non-Javadoc)
* @see lbms.plugins.mldht.kad.Task#callTimeout(lbms.plugins.mldht.kad.RPCCall)
*/
@Override
void callTimeout (RPCCallBase c) {
}
/* (non-Javadoc)
* @see lbms.plugins.mldht.kad.Task#update()
*/
@Override
synchronized void update () {
synchronized (answered) {
while (!answered.isEmpty() && canDoRequest()) {
KBucketEntryAndToken e = answered.remove(0);
synchronized (answered_visited) {
if (!answered_visited.contains(e)) {
AnnounceRequest anr = new AnnounceRequest(node.getOurID(), targetKey, port, e.getToken());
//System.out.println("sending announce to ID:"+e.getID()+" addr:"+e.getAddress());
anr.setOrigin(e.getAddress());
rpcCall(anr);
answered_visited.add(e);
}
}
}
}
synchronized (todo) {
// go over the todo list and send get_peers requests
// until we have nothing left
while (!todo.isEmpty() && canDoRequest()) {
KBucketEntry e = todo.first();
todo.remove(e);
// only send a findNode if we haven't allready visited the node
if (!visited.contains(e)) {
// send a findNode to the node
GetPeersRequest gpr = new GetPeersRequest(node.getOurID(),targetKey);
gpr.setWant4(rpc.getDHT().getType() == DHTtype.IPV4_DHT || DHT.getDHT(DHTtype.IPV4_DHT).getNode().getNumEntriesInRoutingTable() < DHTConstants.BOOTSTRAP_IF_LESS_THAN_X_PEERS);
gpr.setWant6(rpc.getDHT().getType() == DHTtype.IPV6_DHT || DHT.getDHT(DHTtype.IPV6_DHT).getNode().getNumEntriesInRoutingTable() < DHTConstants.BOOTSTRAP_IF_LESS_THAN_X_PEERS);
gpr.setDestination(e.getAddress());
rpcCall(gpr);
visited.add(e);
}
}
}
if (todo.isEmpty() && answered.isEmpty()
&& getNumOutstandingRequests() == 0 && !isFinished()) {
done();
} else if (answered_visited.size() >= DHTConstants.MAX_ENTRIES_PER_BUCKET) {
// if K announces have occurred stop
done();
} else if (validReponsesSinceLastClosestSetModification >= DHTConstants.MAX_CONCURRENT_REQUESTS) {
synchronized (answered_visited)
{
synchronized (answered)
{
synchronized (closestSet)
{
SortedSet<Key> toEstimate = new TreeSet<Key>();
int remainingAnnounces = DHTConstants.MAX_ENTRIES_PER_BUCKET - answered_visited.size();
for(KBucketEntryAndToken e : closestSet)
{
toEstimate.add(e.getID());
if (!answered.contains(e) && !answered_visited.contains(e) && remainingAnnounces > 0)
{
answered.add(e);
remainingAnnounces--;
}
}
rpc.getDHT().getEstimator().update(toEstimate);
}
}
}
validReponsesSinceLastClosestSetModification = 0;
}
}
@Override
protected void done() {
super.done();
if(validReponsesSinceLastClosestSetModification >= DHTConstants.MAX_CONCURRENT_REQUESTS)
synchronized (closestSet)
{
SortedSet<Key> toEstimate = new TreeSet<Key>();
for(KBucketEntryAndToken e : closestSet)
toEstimate.add(e.getID());
rpc.getDHT().getEstimator().update(toEstimate);
}
//System.out.println(returned_items);
}
/**
* @return the returned_items
*/
public Set<DBItem> getReturnedItems () {
return Collections.unmodifiableSet(returned_items);
}
/**
* @return the info_hash
*/
public Key getInfoHash () {
return targetKey;
}
/* (non-Javadoc)
* @see lbms.plugins.mldht.kad.Task#start()
*/
@Override
void start () {
//delay the filling of the todo list until we actually start the task
KClosestNodesSearch kns = new KClosestNodesSearch(targetKey,
DHTConstants.MAX_ENTRIES_PER_BUCKET * 4,rpc.getDHT());
kns.fill();
if (kns.getNumEntries() > 0) {
todo.addAll(kns.getEntries());
}
super.start();
}
}