package lbms.plugins.mldht.kad.tasks;
import lbms.plugins.mldht.kad.DHT;
import lbms.plugins.mldht.kad.DHT.DHTtype;
import lbms.plugins.mldht.kad.DHT.LogLevel;
import lbms.plugins.mldht.kad.DHTConstants;
import lbms.plugins.mldht.kad.GenericStorage;
import lbms.plugins.mldht.kad.GenericStorage.StorageItem;
import lbms.plugins.mldht.kad.KBucketEntry;
import lbms.plugins.mldht.kad.KClosestNodesSearch;
import lbms.plugins.mldht.kad.Key;
import lbms.plugins.mldht.kad.Node;
import lbms.plugins.mldht.kad.RPCCall;
import lbms.plugins.mldht.kad.RPCServer;
import lbms.plugins.mldht.kad.messages.GetRequest;
import lbms.plugins.mldht.kad.messages.GetResponse;
import lbms.plugins.mldht.kad.messages.MessageBase;
import lbms.plugins.mldht.kad.utils.AddressUtils;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class GetLookupTask extends IteratingTask {
Map<KBucketEntry, byte[]> tokens = new ConcurrentHashMap<>();
long expectedSequence = -1;
byte[] salt;
Consumer<StorageItem> valueHandler;
public GetLookupTask(Key target, RPCServer srv, Node n) {
super(target, srv, n);
}
public void setValueConsumer(Consumer<StorageItem> handler) {
valueHandler = handler;
}
public void setSequence(long i) {
expectedSequence = i;
}
public void expectedSalt(byte[] s) {
salt = s;
}
AtomicReference<StorageItem> result = new AtomicReference<>();
@Override
public void start() {
KClosestNodesSearch kns = new KClosestNodesSearch(targetKey, DHTConstants.MAX_ENTRIES_PER_BUCKET * 4, node.getDHT());
kns.fill();
todo.addCandidates(null, kns.getEntries());
super.start();
}
@Override
void update() {
for(;;) {
RequestPermit p = checkFreeSlot();
if(p == RequestPermit.NONE_ALLOWED)
return;
KBucketEntry e = todo.next().orElse(null);
if(e == null)
return;
if(!new RequestCandidateEvaluator(this, closest, todo, e, inFlight).goodForRequest(p))
return;
GetRequest r = new GetRequest(targetKey);
r.setWant4(node.getDHT().getType() == DHTtype.IPV4_DHT);
r.setWant6(node.getDHT().getType() == DHTtype.IPV6_DHT);
r.setDestination(e.getAddress());
if(expectedSequence != -1)
r.setSeq(expectedSequence);
if(!rpcCall(r, e.getID(), c -> {
c.builtFromEntry(e);
todo.addCall(c, e);
int rtt = e.getRTT() * 2;
if(rtt < DHTConstants.RPC_CALL_TIMEOUT_MAX && rtt < rpc.getTimeoutFilter().getStallTimeout())
c.setExpectedRTT(rtt);
})) {
break;
}
}
}
@Override
void callFinished(RPCCall c, MessageBase rsp) {
if(rsp.getType() != MessageBase.Type.RSP_MSG || rsp.getMethod() != MessageBase.Method.GET)
return;
GetResponse get = (GetResponse) rsp;
KBucketEntry e = todo.acceptResponse(c);
if(e == null)
return;
StorageItem data = null;
if(get.getRawValue() != null) {
Key k = GenericStorage.fingerprint(get.getPubkey(), salt, get.getRawValue());
if(!k.equals(targetKey)) {
DHT.log("get response fingerprint mismatch " + rsp , LogLevel.Error);
return;
}
if(expectedSequence < 0 || get.getSequenceNumber() >= expectedSequence) {
data = new StorageItem(get, salt);
if(data.mutable() && !data.validateSig()) {
DHT.log("signature mismatch", LogLevel.Error);
return;
}
}
}
if(data != null) {
final StorageItem newData = data;
StorageItem merged = result.updateAndGet(current -> current == null || newData.seq() > current.seq() ? newData : current);
if(valueHandler != null && merged == newData) {
valueHandler.accept(merged);
}
}
Collection<KBucketEntry> returnedNodes = get.getNodes(node.getDHT().getType()).entries().filter(ne -> !AddressUtils.isBogon(ne.getAddress()) && !node.isLocalId(ne.getID())).collect(Collectors.toList());
todo.addCandidates(e, returnedNodes);
if(get.getToken() != null) {
closest.insert(e);
tokens.put(e, get.getToken());
}
}
public Map<KBucketEntry, byte[]> getTokens() {
return tokens;
}
@Override
void callTimeout(RPCCall c) {
// TODO Auto-generated method stub
}
@Override
protected boolean isDone() {
int waitingFor = getNumOutstandingRequests();
if(waitingFor > 0)
return false;
KBucketEntry next = todo.next().orElse(null);
if(next == null)
return true;
return new RequestCandidateEvaluator(this, closest, todo, next, inFlight).terminationPrecondition();
}
}