package com.limegroup.gnutella.messages.vendor;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPOutputStream;
import org.limewire.core.settings.ApplicationSettings;
import org.limewire.io.NetworkUtils;
import org.limewire.service.ErrorService;
import org.limewire.util.ByteUtils;
import org.limewire.util.StringUtils;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.limegroup.gnutella.ConnectionManager;
import com.limegroup.gnutella.Constants;
import com.limegroup.gnutella.connection.Connection;
import com.limegroup.gnutella.connection.RoutedConnection;
import com.limegroup.gnutella.dht.DHTManager;
import com.limegroup.gnutella.util.LimeWireUtils;
@Singleton
public class UDPCrawlerPongFactoryImpl implements UDPCrawlerPongFactory {
private final Provider<DHTManager> dhtManager;
private final Provider<ConnectionManager> connectionManager;
@Inject
public UDPCrawlerPongFactoryImpl(Provider<DHTManager> dhtManager, Provider<ConnectionManager> connectionManager) {
this.dhtManager = dhtManager;
this.connectionManager = connectionManager;
}
public UDPCrawlerPong createUDPCrawlerPong(UDPCrawlerPing request) {
return new UDPCrawlerPong(request, derivePayload(request));
}
private byte [] derivePayload(UDPCrawlerPing request) {
//local copy of the requested format
byte format = (byte)(request.getFormat() & UDPCrawlerPing.FEATURE_MASK);
//get a list of all ultrapeers and leafs we have connections to
List<RoutedConnection> endpointsUP = new LinkedList<RoutedConnection>();
List<RoutedConnection> endpointsLeaf = new LinkedList<RoutedConnection>();
//add only good ultrapeers or just those who support UDP pinging
//(they support UDP ponging, obviously)
boolean newOnly = request.hasNewOnly();
for(RoutedConnection c : connectionManager.get().getInitializedConnections()) {
if (newOnly) {
if (c.getConnectionCapabilities().remoteHostSupportsUDPCrawling() >= 1)
endpointsUP.add(c);
} else if (c.isGoodUltrapeer()) {
endpointsUP.add(c);
}
}
//add all leaves.. or not?
for(RoutedConnection c : connectionManager.get().getInitializedClientConnections()) {
//if (c.isGoodLeaf()) //uncomment if you decide you want only good leafs
endpointsLeaf.add(c);
}
//the ping does not carry info about which locale to preference to, so we'll just
//preference any locale. In reality we will probably have only connections only to
//this host's pref'd locale so they will end up in the pong.
if (!request.hasLocaleInfo()) {
//do a randomized trim.
if (request.getNumberUP() != UDPCrawlerPing.ALL &&
request.getNumberUP() < endpointsUP.size()) {
//randomized trim
int index = (int) Math.floor(Math.random()*
(endpointsUP.size()-request.getNumberUP()));
endpointsUP = endpointsUP.subList(index,index+request.getNumberUP());
}
if (request.getNumberLeaves() != UDPCrawlerPing.ALL &&
request.getNumberLeaves() < endpointsLeaf.size()) {
//randomized trim
int index = (int) Math.floor(Math.random()*
(endpointsLeaf.size()-request.getNumberLeaves()));
endpointsLeaf = endpointsLeaf.subList(index,index+request.getNumberLeaves());
}
} else {
String myLocale = ApplicationSettings.LANGUAGE.get();
//move the connections with the locale pref to the head of the lists
//we prioritize these disregarding the other criteria (such as isGoodUltrapeer, etc.)
List<RoutedConnection> prefedcons =
connectionManager.get().getInitializedConnectionsMatchLocale(myLocale);
for(RoutedConnection c : prefedcons) {
endpointsUP.remove(c);
endpointsUP.add(0, c);
}
prefedcons =
connectionManager.get().getInitializedClientConnectionsMatchLocale(myLocale);
for(RoutedConnection c : prefedcons) {
endpointsLeaf.remove(c);
endpointsLeaf.add(0, c);
}
//then trim down to the requested number
if (request.getNumberUP() != UDPCrawlerPing.ALL &&
request.getNumberUP() < endpointsUP.size())
endpointsUP = endpointsUP.subList(0,request.getNumberUP());
if (request.getNumberLeaves() != UDPCrawlerPing.ALL &&
request.getNumberLeaves() < endpointsLeaf.size())
endpointsLeaf = endpointsLeaf.subList(0,request.getNumberLeaves());
}
//serialize the Endpoints to a byte []
int bytesPerResult = 6;
if (request.hasConnectionTime())
bytesPerResult+=2;
if (request.hasLocaleInfo())
bytesPerResult+=2;
if (request.hasReplies())
bytesPerResult += 4;
int index = 3;
if(request.hasNodeUptime()) {
index += 4;
}
if(request.hasDHTStatus()) {
index++;
}
byte [] result = new byte[(endpointsUP.size()+endpointsLeaf.size())*
bytesPerResult+index];
//write out metainfo
result[0] = (byte)endpointsUP.size();
result[1] = (byte)endpointsLeaf.size();
result[2] = format;
if(request.hasNodeUptime()) {
long currentAverage = connectionManager.get().getCurrentAverageUptime()/1000L;//in sec
if(currentAverage > Integer.MAX_VALUE)
currentAverage = Integer.MAX_VALUE;
ByteUtils.int2leb((int)currentAverage, result, 3);
}
if(request.hasDHTStatus()) {
byte dhtStatus = 0x00;
DHTManager manager = dhtManager.get();
if(manager.isRunning()) {
switch (manager.getDHTMode()) {
case ACTIVE:
dhtStatus |= UDPCrawlerPong.DHT_ACTIVE_MASK;
break;
case PASSIVE:
dhtStatus |= UDPCrawlerPong.DHT_PASSIVE_MASK;
break;
case PASSIVE_LEAF:
dhtStatus |= UDPCrawlerPong.DHT_PASSIVE_LEAF_MASK;
break;
}
if(!manager.isMemberOfDHT()) {
dhtStatus |= UDPCrawlerPong.DHT_WAITING_MASK;
}
}
result[index-1] = dhtStatus;
}
//cat the two lists
endpointsUP.addAll(endpointsLeaf);
//cache the call to currentTimeMillis() cause its not always cheap
long now = System.currentTimeMillis();
for(RoutedConnection c : endpointsUP) {
//pack each entry into a 6 byte array and add it to the result.
System.arraycopy(
NetworkUtils.getBytes(c.getInetAddress(), c.getPort(), java.nio.ByteOrder.LITTLE_ENDIAN),
0,
result,
index,
6);
index+=6;
//add connection time if asked for
//represent it as a short with the # of minutes
if (request.hasConnectionTime()) {
long uptime = now - c.getConnectionTime();
short packed = (short) ( uptime / Constants.MINUTE);
ByteUtils.short2leb(packed, result, index);
index+=2;
}
if (request.hasLocaleInfo()){
//I'm assuming the language code is always 2 bytes, no?
System.arraycopy(StringUtils.toAsciiBytes(c.getLocalePref()),0,result,index,2);
index+=2;
}
if (request.hasReplies()) {
// pack the # of replies as reported up to Integer.MAX_VALUE
ByteUtils.int2leb(ByteUtils.long2int(c.getConnectionMessageStatistics().getNumQueryReplies()),
result,index);
index += 4;
}
}
//if the ping asked for user agents, copy the reported strings verbatim
//in the same order as the results.
if (request.hasUserAgent()) {
StringBuilder agents = new StringBuilder();
for(Connection c : endpointsUP) {
String agent = c.getConnectionCapabilities().getUserAgent();
agent = StringUtils.replace(agent,UDPCrawlerPong.AGENT_SEP,"\\"+UDPCrawlerPong.AGENT_SEP);
agents.append(agent).append(UDPCrawlerPong.AGENT_SEP);
}
// append myself at the end
agents.append(LimeWireUtils.getHttpServer());
//zip the string
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
GZIPOutputStream zout = new GZIPOutputStream(baos);
byte [] length = new byte[2];
ByteUtils.short2leb((short)agents.length(),length,0);
zout.write(length);
zout.write(StringUtils.toAsciiBytes(agents.toString()));
zout.flush();
zout.close();
}catch(IOException huh) {
ErrorService.error(huh);
}
//put in the return payload.
byte [] agentsB = baos.toByteArray();
byte [] resTemp = result;
result = new byte[result.length+agentsB.length+2];
System.arraycopy(resTemp,0,result,0,resTemp.length);
ByteUtils.short2leb((short)agentsB.length,result,resTemp.length);
System.arraycopy(agentsB,0,result,resTemp.length+2,agentsB.length);
}
return result;
}
}