package com.limegroup.gnutella.messages;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collection;
import org.limewire.collection.BitNumbers;
import org.limewire.core.settings.ApplicationSettings;
import org.limewire.io.BadGGEPBlockException;
import org.limewire.io.BadGGEPPropertyException;
import org.limewire.io.GGEP;
import org.limewire.io.IpPort;
import org.limewire.io.NetworkInstanceUtils;
import org.limewire.io.NetworkUtils;
import org.limewire.security.AddressSecurityToken;
import org.limewire.security.MACCalculatorRepositoryManager;
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.Endpoint;
import com.limegroup.gnutella.HostCatcher;
import com.limegroup.gnutella.NetworkManager;
import com.limegroup.gnutella.Statistics;
import com.limegroup.gnutella.UDPService;
import com.limegroup.gnutella.dht.DHTManager;
import com.limegroup.gnutella.dht.DHTManager.DHTMode;
import com.limegroup.gnutella.messages.Message.Network;
@Singleton
public class PingReplyFactoryImpl implements PingReplyFactory {
private final NetworkManager networkManager;
private final Provider<Statistics> statistics;
private final Provider<UDPService> udpService;
private final Provider<ConnectionManager> connectionManager;
private final Provider<HostCatcher> hostCatcher;
private final Provider<DHTManager> dhtManager;
private final LocalPongInfo localPongInfo;
private final MACCalculatorRepositoryManager macCalculatorRepositoryManager;
private final NetworkInstanceUtils networkInstanceUtils;
// TODO: All these objects should be folded into LocalPongInfo
@Inject
public PingReplyFactoryImpl(NetworkManager networkManager,
Provider<Statistics> statistics, Provider<UDPService> udpService,
Provider<ConnectionManager> connectionManager,
Provider<HostCatcher> hostCatcher,
Provider<DHTManager> dhtManager,
LocalPongInfo localPongInfo,
MACCalculatorRepositoryManager MACCalculatorRepositoryManager,
NetworkInstanceUtils networkInstanceUtils) {
this.networkManager = networkManager;
this.statistics = statistics;
this.udpService = udpService;
this.connectionManager = connectionManager;
this.hostCatcher = hostCatcher;
this.dhtManager = dhtManager;
this.localPongInfo = localPongInfo;
this.macCalculatorRepositoryManager = MACCalculatorRepositoryManager;
this.networkInstanceUtils = networkInstanceUtils;
}
public PingReply create(byte[] guid, byte ttl,
Collection<? extends IpPort> gnutHosts,
Collection<? extends IpPort> dhtHosts) {
return create(
guid,
ttl,
networkManager.getPort(),
networkManager.getAddress(),
localPongInfo.getNumSharedFiles(),
localPongInfo.getSharedFileSize() / 1024,
localPongInfo.isSupernode(),
statistics.get().calculateDailyUptime(),
udpService.get().isGUESSCapable(),
ApplicationSettings.LANGUAGE.get().equals("") ? ApplicationSettings.DEFAULT_LOCALE
.get()
: ApplicationSettings.LANGUAGE.get(),
connectionManager.get().getNumLimeWireLocalePrefSlots(), gnutHosts,
dhtHosts);
}
public PingReply create(byte[] guid, byte ttl) {
return create(guid, ttl, IpPort.EMPTY_LIST, IpPort.EMPTY_LIST);
}
public PingReply create(byte[] guid, byte ttl, IpPort addr) {
return create(guid, ttl, addr, IpPort.EMPTY_LIST, IpPort.EMPTY_LIST);
}
@Override
public PingReply create(byte[] guid, byte ttl, int localPort, byte[] localIp, IpPort addr) {
return create(guid, ttl, localPort, localIp, addr, IpPort.EMPTY_LIST, IpPort.EMPTY_LIST);
}
@Override
public PingReply create(byte[] guid, byte ttl,
IpPort returnAddr,
Collection<? extends IpPort> gnutHosts,
Collection<? extends IpPort> dhtHosts) {
return create(guid, ttl, networkManager.getPort(), networkManager.getAddress(), returnAddr, gnutHosts, dhtHosts);
}
public PingReply create(byte[] guid, byte ttl, int localPort, byte[] localIP,
IpPort returnAddr,
Collection<? extends IpPort> gnutHosts,
Collection<? extends IpPort> dhtHosts) {
GGEP ggep = newGGEP(statistics.get().calculateDailyUptime(),
localPongInfo.isSupernode(), udpService
.get().isGUESSCapable());
String locale = ApplicationSettings.LANGUAGE.get().equals("") ? ApplicationSettings.DEFAULT_LOCALE
.get()
: ApplicationSettings.LANGUAGE.get();
addLocale(ggep, locale, connectionManager
.get().getNumLimeWireLocalePrefSlots());
addAddress(ggep, returnAddr);
addPackedHosts(ggep, gnutHosts, dhtHosts);
return create(guid, ttl, localPort, localIP, localPongInfo.getNumSharedFiles(), localPongInfo
.getSharedFileSize() / 1024, localPongInfo.isSupernode(), ggep);
}
public PingReply createQueryKeyReply(byte[] guid, byte ttl,
AddressSecurityToken key) {
return create(guid, ttl, networkManager.getPort(), networkManager
.getAddress(), localPongInfo.getNumSharedFiles(), localPongInfo
.getSharedFileSize() / 1024, localPongInfo.isSupernode(),
qkGGEP(key));
}
public PingReply createQueryKeyReply(byte[] guid, byte ttl, int port,
byte[] ip, long sharedFiles, long sharedSize, boolean ultrapeer,
AddressSecurityToken key) {
return create(guid, ttl, port, ip, sharedFiles, sharedSize, ultrapeer,
qkGGEP(key));
}
public PingReply create(byte[] guid, byte ttl, int port, byte[] address) {
return create(guid, ttl, port, address, 0, 0, false, -1, false);
}
public PingReply createExternal(byte[] guid, byte ttl, int port,
byte[] address, boolean ultrapeer) {
return create(guid, ttl, port, address, 0, 0, ultrapeer, -1, false);
}
public PingReply createExternal(byte[] guid, byte ttl, int port,
byte[] address, int uptime, boolean ultrapeer) {
return create(guid, ttl, port, address, 0, 0, ultrapeer, uptime, false);
}
public PingReply createGUESSReply(byte[] guid, byte ttl, Endpoint ep)
throws UnknownHostException {
return create(guid, ttl, ep.getPort(), ep.getHostBytes(), 0, 0, true,
-1, true);
}
public PingReply createGUESSReply(byte[] guid, byte ttl, int port,
byte[] address) {
return create(guid, ttl, port, address, 0, 0, true, -1, true);
}
public PingReply create(byte[] guid, byte ttl, int port, byte[] ip,
long files, long kbytes) {
return create(guid, ttl, port, ip, files, kbytes, false, -1, false);
}
public PingReply create(byte[] guid, byte ttl, int port, byte[] ip,
long files, long kbytes, boolean isUltrapeer, int dailyUptime,
boolean isGUESSCapable) {
return create(guid, ttl, port, ip, files, kbytes, isUltrapeer, newGGEP(
dailyUptime, isUltrapeer, isGUESSCapable));
}
public PingReply create(byte[] guid, byte ttl, int port, byte[] ip,
long files, long kbytes, boolean isUltrapeer, int dailyUptime,
boolean isGuessCapable, String locale, int slots) {
return create(guid, ttl, port, ip, files, kbytes, isUltrapeer,
dailyUptime, isGuessCapable, locale, slots, IpPort.EMPTY_LIST,
IpPort.EMPTY_LIST);
}
public PingReply create(byte[] guid, byte ttl, int port, byte[] ip,
long files, long kbytes, boolean isUltrapeer, int dailyUptime,
boolean isGuessCapable, String locale, int slots,
Collection<? extends IpPort> gnutHosts,
Collection<? extends IpPort> dhtHosts) {
GGEP ggep = newGGEP(dailyUptime, isUltrapeer, isGuessCapable);
addLocale(ggep, locale, slots);
addPackedHosts(ggep, gnutHosts, dhtHosts);
return create(guid, ttl, port, ip, files, kbytes, isUltrapeer, ggep);
}
public PingReply create(byte[] guid, byte ttl, int port, byte[] ipBytes,
long files, long kbytes, boolean isUltrapeer, GGEP ggep) {
if (!NetworkUtils.isValidPort(port))
throw new IllegalArgumentException("invalid port: " + port);
if (!NetworkUtils.isValidAddress(ipBytes))
throw new IllegalArgumentException("invalid address: "
+ NetworkUtils.ip2string(ipBytes));
InetAddress ip = null;
try {
ip = InetAddress.getByName(NetworkUtils.ip2string(ipBytes));
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e);
}
byte[] extensions = null;
if (ggep != null) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ggep.write(baos);
} catch (IOException e) {
// this should not happen
ErrorService.error(e);
}
extensions = baos.toByteArray();
}
int length = PingReply.STANDARD_PAYLOAD_SIZE
+ (extensions == null ? 0 : extensions.length);
byte[] payload = new byte[length];
//It's ok if casting port, files, or kbytes turns negative.
ByteUtils.short2leb((short) port, payload, 0);
//payload stores IP in BIG-ENDIAN
payload[2] = ipBytes[0];
payload[3] = ipBytes[1];
payload[4] = ipBytes[2];
payload[5] = ipBytes[3];
ByteUtils.int2leb((int) files, payload, 6);
ByteUtils.int2leb((int) (isUltrapeer ? mark(kbytes) : kbytes), payload,
10);
//Encode GGEP block if included.
if (extensions != null) {
System.arraycopy(extensions, 0, payload,
PingReply.STANDARD_PAYLOAD_SIZE, extensions.length);
}
try {
return new PingReplyImpl(guid, ttl, (byte) 0, payload, ggep, ip,
Network.UNKNOWN, macCalculatorRepositoryManager, networkInstanceUtils);
} catch (BadPacketException e) {
throw new IllegalStateException(e);
}
}
public PingReply createFromNetwork(byte[] guid, byte ttl, byte hops,
byte[] payload) throws BadPacketException {
return createFromNetwork(guid, ttl, hops, payload, Network.UNKNOWN);
}
public PingReply createFromNetwork(byte[] guid, byte ttl, byte hops,
byte[] payload, Network network) throws BadPacketException {
if (guid == null) {
throw new NullPointerException("null guid");
}
if (payload == null) {
throw new NullPointerException("null payload");
}
if (payload.length < PingReply.STANDARD_PAYLOAD_SIZE) {
throw new BadPacketException("invalid payload length");
}
int port = ByteUtils.ushort2int(ByteUtils.leb2short(payload, 0));
if (!NetworkUtils.isValidPort(port)) {
throw new BadPacketException("invalid port: " + port);
}
// this address may get updated if we have the UDPHC extention
// therefore it is checked after checking for that extention.
String ipString = NetworkUtils.ip2string(payload, 2);
InetAddress ip = null;
GGEP ggep = parseGGEP(payload);
if (ggep != null) {
if (ggep.hasKey(GGEPKeys.GGEP_HEADER_CLIENT_LOCALE)) {
try {
ggep.getBytes(GGEPKeys.GGEP_HEADER_CLIENT_LOCALE);
} catch (BadGGEPPropertyException e) {
throw new BadPacketException("GGEP error : creating from"
+ " network : client locale");
}
}
if (ggep.hasKey(GGEPKeys.GGEP_HEADER_PACKED_IPPORTS)) {
byte[] data = null;
try {
data = ggep.getBytes(GGEPKeys.GGEP_HEADER_PACKED_IPPORTS);
} catch (BadGGEPPropertyException bad) {
throw new BadPacketException(bad.getMessage());
}
if (data == null || data.length % 6 != 0)
throw new BadPacketException("invalid data");
}
if (ggep.hasKey(GGEPKeys.GGEP_HEADER_PACKED_HOSTCACHES)) {
try {
ggep.getBytes(GGEPKeys.GGEP_HEADER_PACKED_HOSTCACHES);
} catch (BadGGEPPropertyException bad) {
throw new BadPacketException(bad.getMessage());
}
}
if (ggep.hasKey(GGEPKeys.GGEP_HEADER_UDP_HOST_CACHE)) {
try {
String dns = ggep
.getString(GGEPKeys.GGEP_HEADER_UDP_HOST_CACHE);
ip = InetAddress.getByName(dns);
ipString = ip.getHostAddress();
} catch (BadGGEPPropertyException ignored) {
} catch (UnknownHostException bad) {
throw new BadPacketException(bad.getMessage());
}
}
}
if (!NetworkUtils.isValidAddress(ipString)) {
throw new BadPacketException("invalid address: " + ipString);
}
if (ip == null) {
try {
ip = InetAddress.getByName(NetworkUtils.ip2string(payload, 2));
} catch (UnknownHostException e) {
throw new BadPacketException("bad IP:" + ipString + " "
+ e.getMessage());
}
}
return new PingReplyImpl(guid, ttl, hops, payload, ggep, ip, network,
macCalculatorRepositoryManager, networkInstanceUtils);
}
public PingReply mutateGUID(PingReply pingReply, byte[] guid) {
if (guid.length != 16)
throw new IllegalArgumentException("bad guid size: " + guid.length);
// i can't just call a new constructor, i have to recreate stuff
try {
return createFromNetwork(guid, pingReply.getTTL(), pingReply
.getHops(), pingReply.getPayload(), pingReply.getNetwork());
} catch (BadPacketException ioe) {
throw new IllegalArgumentException("Input pong was bad!");
}
}
/** Returns the GGEP payload bytes to encode the given uptime */
private GGEP newGGEP(int dailyUptime, boolean isUltrapeer,
boolean isGUESSCapable) {
GGEP ggep = new GGEP();
if (dailyUptime >= 0)
ggep.put(GGEPKeys.GGEP_HEADER_DAILY_AVERAGE_UPTIME, dailyUptime);
if (isGUESSCapable && isUltrapeer) {
ggep.put(GGEPKeys.GGEP_HEADER_UNICAST_SUPPORT);
}
// indicate UP support
if (isUltrapeer)
addUltrapeerExtension(ggep);
// add DHT support
addDHTExtension(ggep);
// add our support of TLS
if (networkManager.isIncomingTLSEnabled())
ggep.put(GGEPKeys.GGEP_HEADER_TLS_CAPABLE);
return ggep;
}
/** Returns the GGEP payload bytes to encode the given AddressSecurityToken */
private GGEP qkGGEP(AddressSecurityToken addressSecurityToken) {
try {
GGEP ggep = new GGEP();
// get qk bytes....
ByteArrayOutputStream baos = new ByteArrayOutputStream();
addressSecurityToken.write(baos);
// populate GGEP....
ggep.put(GGEPKeys.GGEP_HEADER_QUERY_KEY_SUPPORT, baos.toByteArray());
return ggep;
} catch (IOException e) {
throw new IllegalStateException(
"Couldn't encode AddressSecurityToken"
+ addressSecurityToken, e);
}
}
/**
* Adds the locale GGEP.
*/
private GGEP addLocale(GGEP ggep, String locale, int slots) {
byte[] payload = new byte[3];
byte[] s = StringUtils.toAsciiBytes(locale);
payload[0] = s[0];
payload[1] = s[1];
payload[2] = (byte) slots;
ggep.put(GGEPKeys.GGEP_HEADER_CLIENT_LOCALE, payload);
return ggep;
}
/**
* Adds the address GGEP.
*/
private GGEP addAddress(GGEP ggep, IpPort address) {
byte[] payload = new byte[6];
System.arraycopy(address.getInetAddress().getAddress(), 0, payload, 0,
4);
ByteUtils.short2leb((short) address.getPort(), payload, 4);
ggep.put(GGEPKeys.GGEP_HEADER_IPPORT, payload);
return ggep;
}
/**
* Adds the packed hosts into this GGEP.
*/
private GGEP addPackedHosts(GGEP ggep,
Collection<? extends IpPort> gnutHosts,
Collection<? extends IpPort> dhtHosts) {
if (gnutHosts != null && !gnutHosts.isEmpty()) {
ggep.put(GGEPKeys.GGEP_HEADER_PACKED_IPPORTS, NetworkUtils
.packIpPorts(gnutHosts));
byte[] data = getTLSData(gnutHosts);
if (data.length != 0)
ggep.put(GGEPKeys.GGEP_HEADER_PACKED_IPPORTS_TLS, data);
}
if (dhtHosts != null && !dhtHosts.isEmpty()) {
ggep.put(GGEPKeys.GGEP_HEADER_DHT_IPPORTS, NetworkUtils
.packIpPorts(dhtHosts));
}
return ggep;
}
/**
* Returns a byte[] of data that indicates if the hosts are TLS capable.
* If no hosts are capable, this returns an empty array.
* Otherwise, it returns a byte[] where each bit in the array
* corresponds to the element (in order) of the hosts. If the bit
* is on, that host supports TLS.
*
* For example, if this is supplied the hosts:
* 1.2.3.4:5, 5.4.3.2.1:1, and 2.3.4.5:6
* and the first and third hosts are TLS capable,
* then this will return a single byte of:
* 10100000
*
*/
private byte[] getTLSData(Collection<? extends IpPort> hosts) {
BitNumbers bn = new BitNumbers(hosts.size());
int i = 0;
for (IpPort ipp : hosts) {
if (hostCatcher.get().isHostTLSCapable(ipp))
bn.set(i);
i++;
}
return bn.toByteArray();
}
/**
* Adds the ultrapeer GGEP extension to the pong. This has the version of
* the Ultrapeer protocol that we support as well as the number of free
* leaf and Ultrapeer slots available.
*
* @param ggep the <tt>GGEP</tt> instance to add the extension to
*/
private void addUltrapeerExtension(GGEP ggep) {
byte[] payload = new byte[3];
// historically, payload[0] contained the major/minor version of ultrapeer support.
// the data was never used, the format used to send it didn't scale well, and
// the version never went beyond the first revision. so, for now we just
// put a blank number there, and in the future if we decide to use the info
// or increment the version, we can include it somewhere else in a more scalable
// form.
payload[0] = 0;
payload[1] = localPongInfo.getNumFreeLimeWireLeafSlots();
payload[2] = localPongInfo.getNumFreeLimeWireNonLeafSlots();
ggep.put(GGEPKeys.GGEP_HEADER_UP_SUPPORT, payload);
}
/**
* Adds the DHT GGEP extension to the pong. This has the version of
* the DHT that we support as well as the mode of this node (active/passive).
* A node only advertises itself as an active node if it is already bootstrapped
* to the network!
*
* @param ggep the <tt>GGEP</tt> instance to add the extension to
*/
private void addDHTExtension(GGEP ggep) {
byte[] payload = new byte[3];
// put version
int version = dhtManager.get().getVersion().shortValue();
ByteUtils.short2beb((short) version, payload, 0);
if (dhtManager.get().isMemberOfDHT()) {
DHTMode mode = dhtManager.get().getDHTMode();
assert (mode != null);
payload[2] = mode.byteValue();
} else {
payload[2] = DHTMode.INACTIVE.byteValue();
}
// add it
ggep.put(GGEPKeys.GGEP_HEADER_DHT_SUPPORT, payload);
}
// TODO : change this to look for multiple GGEP block in the payload....
/** Ensure GGEP data parsed...if possible. */
private GGEP parseGGEP(final byte[] PAYLOAD) {
//Return if this is a plain pong without space for GGEP. If
//this has bad GGEP data, multiple calls to
//parseGGEP will result in multiple parse attempts. While this is
//inefficient, it is sufficiently rare to not justify a parsedGGEP
//variable.
if (PAYLOAD.length <= PingReply.STANDARD_PAYLOAD_SIZE)
return null;
try {
return new GGEP(PAYLOAD, PingReply.STANDARD_PAYLOAD_SIZE, null);
} catch (BadGGEPBlockException e) {
return null;
}
}
/** Marks the given kbytes field */
private long mark(long kbytes) {
int x = ByteUtils.long2int(kbytes);
//Returns the power of two nearest to x. TODO3: faster algorithms are
//possible. At the least, you can do binary search. I imagine some bit
//operations can be done as well. This brute-force approach was
//generated with the help of the the following Python program:
//
// for i in xrange(0, 32):
// low=1<<i
// high=1<<(i+1)
// split=(low+high)/2
// print "else if (x<%d)" % split
// print " return %d; //1<<%d" % (low, i)
if (x < 12)
return 8; //1<<3
else if (x < 24)
return 16; //1<<4
else if (x < 48)
return 32; //1<<5
else if (x < 96)
return 64; //1<<6
else if (x < 192)
return 128; //1<<7
else if (x < 384)
return 256; //1<<8
else if (x < 768)
return 512; //1<<9
else if (x < 1536)
return 1024; //1<<10
else if (x < 3072)
return 2048; //1<<11
else if (x < 6144)
return 4096; //1<<12
else if (x < 12288)
return 8192; //1<<13
else if (x < 24576)
return 16384; //1<<14
else if (x < 49152)
return 32768; //1<<15
else if (x < 98304)
return 65536; //1<<16
else if (x < 196608)
return 131072; //1<<17
else if (x < 393216)
return 262144; //1<<18
else if (x < 786432)
return 524288; //1<<19
else if (x < 1572864)
return 1048576; //1<<20
else if (x < 3145728)
return 2097152; //1<<21
else if (x < 6291456)
return 4194304; //1<<22
else if (x < 12582912)
return 8388608; //1<<23
else if (x < 25165824)
return 16777216; //1<<24
else if (x < 50331648)
return 33554432; //1<<25
else if (x < 100663296)
return 67108864; //1<<26
else if (x < 201326592)
return 134217728; //1<<27
else if (x < 402653184)
return 268435456; //1<<28
else if (x < 805306368)
return 536870912; //1<<29
else
return 1073741824; //1<<30
}
}