/*
* This message represents a list of ultrapeer connections that has been
* returned by an ultrapeer. Its payload is a byte indicating how many
* IpPorts are about to follow and their serialized list.
*/
package com.limegroup.gnutella.messages.vendor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import com.limegroup.gnutella.ByteOrder;
import com.limegroup.gnutella.Connection;
import com.limegroup.gnutella.Constants;
import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.ExtendedEndpoint;
import com.limegroup.gnutella.GUID;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.messages.BadPacketException;
import com.limegroup.gnutella.messages.IPPortCombo;
import com.limegroup.gnutella.settings.ApplicationSettings;
import com.limegroup.gnutella.util.CommonUtils;
import com.limegroup.gnutella.util.StringUtils;
import com.limegroup.gnutella.util.IOUtils;
public class UDPCrawlerPong extends VendorMessage {
public static final int VERSION = 1;
public static final String AGENT_SEP = ";";
private String _agents;
private List _ultrapeers, _leaves;
final boolean _connectionTime, _localeInfo, _newOnly, _userAgent;
/**
* the format of the response.
*/
private final byte _format;
//this message is sent only as a reply to a request message, so when
//constructing it we need the object representing the request message
public UDPCrawlerPong(UDPCrawlerPing request){
super(F_LIME_VENDOR_ID,F_ULTRAPEER_LIST, VERSION, derivePayload(request));
setGUID(new GUID(request.getGUID()));
_format = (byte)(request.getFormat() & UDPCrawlerPing.FEATURE_MASK);
_localeInfo = request.hasLocaleInfo();
_connectionTime = request.hasConnectionTime();
_newOnly = request.hasNewOnly();
_userAgent = request.hasUserAgent();
}
private static 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 endpointsUP = new LinkedList();
List endpointsLeaf = new LinkedList();
Iterator iter = RouterService.getConnectionManager()
.getInitializedConnections().iterator();
//add only good ultrapeers or just those who support UDP pinging
//(they support UDP ponging, obviously)
boolean newOnly = request.hasNewOnly();
while(iter.hasNext()) {
Connection c = (Connection)iter.next();
if (newOnly) {
if (c.remoteHostSupportsUDPCrawling() >= 1)
endpointsUP.add(c);
}else
if (c.isGoodUltrapeer())
endpointsUP.add(c);
}
iter = RouterService.getConnectionManager()
.getInitializedClientConnections().iterator();
//add all leaves.. or not?
while(iter.hasNext()) {
Connection c = (Connection)iter.next();
//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.getValue();
//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 prefedcons = RouterService.getConnectionManager().
getInitializedConnectionsMatchLocale(myLocale);
endpointsUP.removeAll(prefedcons);
prefedcons.addAll(endpointsUP);
endpointsUP=prefedcons;
prefedcons = RouterService.getConnectionManager().
getInitializedClientConnectionsMatchLocale(myLocale);
endpointsLeaf.removeAll(prefedcons);
prefedcons.addAll(endpointsLeaf);
endpointsLeaf=prefedcons;
//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;
byte [] result = new byte[(endpointsUP.size()+endpointsLeaf.size())*
bytesPerResult+3];
//write out metainfo
result[0] = (byte)endpointsUP.size();
result[1] = (byte)endpointsLeaf.size();
result[2] = format;
//cat the two lists
endpointsUP.addAll(endpointsLeaf);
//cache the call to currentTimeMillis() cause its not always cheap
long now = System.currentTimeMillis();
int index = 3;
iter = endpointsUP.iterator();
while(iter.hasNext()) {
//pack each entry into a 6 byte array and add it to the result.
Connection c = (Connection)iter.next();
System.arraycopy(
packIPAddress(c.getInetAddress(),c.getPort()),
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);
ByteOrder.short2leb(packed, result, index);
index+=2;
}
if (request.hasLocaleInfo()){
//I'm assuming the language code is always 2 bytes, no?
System.arraycopy(c.getLocalePref().getBytes(),0,result,index,2);
index+=2;
}
}
//if the ping asked for user agents, copy the reported strings verbatim
//in the same order as the results.
if (request.hasUserAgent()) {
StringBuffer agents = new StringBuffer();
iter = endpointsUP.iterator();
while(iter.hasNext()) {
Connection c = (Connection)iter.next();
String agent = c.getUserAgent();
agent = StringUtils.replace(agent,AGENT_SEP,"\\"+AGENT_SEP);
agents.append(agent).append(AGENT_SEP);
}
// append myself at the end
agents.append(CommonUtils.getHttpServer());
//zip the string
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
GZIPOutputStream zout = new GZIPOutputStream(baos);
byte [] length = new byte[2];
ByteOrder.short2leb((short)agents.length(),length,0);
zout.write(length);
zout.write(agents.toString().getBytes());
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);
ByteOrder.short2leb((short)agentsB.length,result,resTemp.length);
System.arraycopy(agentsB,0,result,resTemp.length+2,agentsB.length);
}
return result;
}
/**
* copy/pasted from PushProxyRequest. This should go to NetworkUtils imho
* @param addr address of the other person
* @param port the port
* @return 6-byte value representing the address and port.
*/
private static byte[] packIPAddress(InetAddress addr, int port) {
try {
// i do it during construction....
IPPortCombo combo =
new IPPortCombo(addr.getHostAddress(), port);
return combo.toBytes();
} catch (UnknownHostException uhe) {
throw new IllegalArgumentException(uhe.getMessage());
}
}
/**
* create message with data from network.
*/
protected UDPCrawlerPong(byte[] guid, byte ttl, byte hops,
int version, byte[] payload)
throws BadPacketException {
super(guid, ttl, hops, F_LIME_VENDOR_ID, F_ULTRAPEER_LIST, version, payload);
_ultrapeers = new LinkedList();
_leaves = new LinkedList();
if (getVersion() == VERSION &&
(payload==null || payload.length < 3))
throw new BadPacketException();
int numberUP = payload[0];
int numberLeaves = payload[1];
//we mask the received results with our capabilities mask because
//we should not be receiving more features than we asked for, even
//if the other side supports them.
_format = (byte) (payload[2] & UDPCrawlerPing.FEATURE_MASK);
_connectionTime = ((_format & UDPCrawlerPing.CONNECTION_TIME)
== (int)UDPCrawlerPing.CONNECTION_TIME);
_localeInfo = (_format & UDPCrawlerPing.LOCALE_INFO)
== (int)UDPCrawlerPing.LOCALE_INFO;
_newOnly =(_format & UDPCrawlerPing.NEW_ONLY)
== (int)UDPCrawlerPing.NEW_ONLY;
_userAgent =(_format & UDPCrawlerPing.USER_AGENT)
== (int)UDPCrawlerPing.USER_AGENT;
int bytesPerResult = 6;
if (_connectionTime)
bytesPerResult+=2;
if (_localeInfo)
bytesPerResult+=2;
int agentsOffset=(numberUP+numberLeaves)*bytesPerResult+3;
//check if the payload is legal length
if (getVersion() == VERSION &&
payload.length< agentsOffset)
throw new BadPacketException("size is "+payload.length+
" but should have been at least"+ agentsOffset);
//parse the up ip addresses
for (int i = 3;i<numberUP*bytesPerResult;i+=bytesPerResult) {
int index = i; //the index within the result block.
byte [] current = new byte[6];
System.arraycopy(payload,index,current,0,6);
index+=6;
IPPortCombo combo =
IPPortCombo.getCombo(current);
if (combo == null || combo.getInetAddress() == null)
throw new BadPacketException("parsing of ip:port failed. "+
" dump of current byte block: "+current);
//store the result in an ExtendedEndpoint
ExtendedEndpoint result = new ExtendedEndpoint(combo.getAddress(),combo.getPort());
//add connection lifetime
if(_connectionTime) {
result.setDailyUptime(ByteOrder.leb2short(payload,index));
index+=2;
}
//add locale info.
if (_localeInfo) {
String langCode = new String(payload, index, 2);
result.setClientLocale(langCode);
index+=2;
}
_ultrapeers.add(result);
}
//parse the leaf ip addresses
for (int i = numberUP*bytesPerResult+3;i<agentsOffset;i+=bytesPerResult) {
int index =i;
byte [] current = new byte[6];
System.arraycopy(payload,index,current,0,6);
index+=6;
IPPortCombo combo =
IPPortCombo.getCombo(current);
if (combo == null || combo.getInetAddress() == null)
throw new BadPacketException("parsing of ip:port failed. "+
" dump of current byte block: "+current);
//store the result in an ExtendedEndpoint
ExtendedEndpoint result = new ExtendedEndpoint(combo.getAddress(),combo.getPort());
//add connection lifetime
if(_connectionTime) {
result.setDailyUptime(ByteOrder.leb2short(payload,index));
index+=2;
}
//add locale info.
if (_localeInfo) {
String langCode = new String(payload, index,2);
result.setClientLocale(langCode);
index+=2;
}
_leaves.add(result);
}
//check if the payload is proper size if it contains user agents.
if (_userAgent) {
int agentsSize = ByteOrder.leb2short(payload,agentsOffset);
if (payload.length < agentsSize+agentsOffset+2)
throw new BadPacketException("payload is "+payload.length+
" but should have been at least "+
(agentsOffset+agentsSize+2));
ByteArrayInputStream bais =
new ByteArrayInputStream(payload,agentsOffset+2,agentsSize);
GZIPInputStream gais = null;
try {
gais = new GZIPInputStream(bais);
DataInputStream dais = new DataInputStream(gais);
byte [] length = new byte[2];
dais.readFully(length);
int len = ByteOrder.leb2short(length,0);
byte []agents = new byte[len];
dais.readFully(agents);
_agents = new String(agents);
}catch(IOException bad ) {
throw new BadPacketException("invalid compressed agent data");
} finally {
IOUtils.close(gais);
}
}
//Note: do the check whether we got as many results as requested elsewhere.
}
/**
* @return Returns the List of Ultrapeers contained in the message.
*/
public List getUltrapeers() {
return _ultrapeers;
}
/**
* @return Returns the List of Leaves contained in the message.
*/
public List getLeaves() {
return _leaves;
}
/**
* @return whether the set of results contains connection uptime
*/
public boolean hasConnectionTime() {
return _connectionTime;
}
/**
* @return whether the set of results contains locale information
*/
public boolean hasLocaleInfo() {
return _localeInfo;
}
/**
*
* @return the string containing the user agents. Can be null.
*/
public String getAgents() {
return _agents;
}
}