package com.limegroup.gnutella;
import java.util.Arrays;
import java.util.Set;
import org.limewire.collection.BitNumbers;
import org.limewire.io.Connectable;
import org.limewire.io.ConnectableImpl;
import org.limewire.io.GUID;
import org.limewire.io.IpPort;
import org.limewire.io.NetworkUtils;
import org.limewire.util.ByteUtils;
import com.limegroup.gnutella.http.HTTPConstants;
import com.limegroup.gnutella.uploader.HTTPHeaderUtils;
/**
* Abstract class that does not store any values but provides default implementations
* for methods that produce a common format.
*/
public abstract class AbstractPushEndpoint implements PushEndpoint {
public byte[] toBytes(boolean includeTLS) {
Set<? extends IpPort> proxies = getProxies();
int payloadSize = getSizeBytes(proxies, includeTLS);
IpPort addr = getValidExternalAddress();
int FWTVersion = getFWTVersion();
if (addr != null && FWTVersion > 0)
payloadSize+=6;
byte [] ret = new byte[payloadSize];
toBytes(ret,0,proxies,addr,FWTVersion, includeTLS);
return ret;
}
public void toBytes(byte[] where, int offset, boolean includeTLS) {
toBytes(where, offset, getProxies(), getValidExternalAddress(),getFWTVersion(), includeTLS);
}
protected void toBytes(byte []where, int offset, Set<? extends IpPort> proxies,
IpPort address, int FWTVersion, boolean includeTLS) {
int neededSpace = getSizeBytes(proxies, includeTLS);
if (address != null) {
if (FWTVersion > 0)
neededSpace+=6;
} else {
FWTVersion = 0;
}
if (where.length-offset < neededSpace)
throw new IllegalArgumentException ("target array too small");
int featureIdx = offset;
// store the number of proxies
where[offset] = (byte)(Math.min(MAX_PROXIES,proxies.size())
| getFeatures()
| FWTVersion << 3);
// store the guid
System.arraycopy(getClientGUID(),0,where,++offset,16);
offset+=16;
// if we know the external address, store that too
// if its valid and not private and port is valid
if (address != null && FWTVersion > 0) {
byte [] addr = address.getInetAddress().getAddress();
int port = address.getPort();
System.arraycopy(addr,0,where,offset,4);
offset+=4;
ByteUtils.short2leb((short)port,where,offset);
offset+=2;
}
// If we're including TLS, then add a byte for which proxies support it.
int pptlsIdx = offset;
int i=0;
if(includeTLS) {
// If any of the proxies are TLS-capable, increment the offset
for(IpPort ppi : proxies) {
if(i >= MAX_PROXIES)
break;
if(ppi instanceof Connectable && ((Connectable)ppi).isTLSCapable()) {
offset++;
break;
}
i++;
}
}
// store the push proxies
i=0;
for(IpPort ppi : proxies) {
if(i >= MAX_PROXIES)
break;
byte [] addr = ppi.getInetAddress().getAddress();
short port = (short)ppi.getPort();
System.arraycopy(addr,0,where,offset,4);
offset+=4;
ByteUtils.short2leb(port,where,offset);
offset+=2;
i++;
}
// insert the tls indices & turn the feature on if TLS should be included
BitNumbers bn = includeTLS ? HTTPHeaderUtils.getTLSIndices(proxies, (Math.min(proxies.size(), MAX_PROXIES))) : BitNumbers.EMPTY_BN;
if(!bn.isEmpty()) {
byte[] tlsIndexes = bn.toByteArray();
assert tlsIndexes.length == 1;
where[pptlsIdx] = tlsIndexes[0];
where[featureIdx] |= PPTLS_BINARY;
} else {
where[featureIdx] &= ~PPTLS_BINARY; // make sure its not in the features!
}
}
@Override
public final boolean equals(Object obj) {
if (obj instanceof PushEndpoint) {
return Arrays.equals(getClientGUID(), ((PushEndpoint)obj).getClientGUID());
}
return false;
}
@Override
public final int hashCode() {
return new GUID(getClientGUID()).hashCode();
}
public String httpStringValue() {
GUID guid = new GUID(getClientGUID());
StringBuilder httpString =new StringBuilder(guid.toHexString()).append(";");
//if version is not 0, append it to the http string
int fwtVersion=getFWTVersion();
if (fwtVersion!=0) {
httpString.append(HTTPConstants.FW_TRANSFER)
.append("/")
.append(fwtVersion)
.append(";");
// append the external address of this endpoint if such exists
// and is valid, non-private and if the port is valid as well.
IpPort address = getValidExternalAddress();
if (address!=null) {
String addr = address.getAddress();
int port = address.getPort();
if (!addr.equals(RemoteFileDesc.BOGUS_IP) &&
NetworkUtils.isValidPort(port)){
httpString.append(port)
.append(":")
.append(addr)
.append(";");
}
}
}
Set<? extends IpPort> proxies = getProxies();
if (!proxies.isEmpty()) {
httpString.append(HTTPHeaderUtils.encodePushProxies(proxies, ";", PushEndpoint.MAX_PROXIES));
} else {
//trim the ; at the end
httpString.deleteCharAt(httpString.length()-1);
}
return httpString.toString();
}
@Override
public String toString() {
String ret = "PE [FEATURES:"+getFeatures()+",\nFWT Version:"+getFWTVersion()+
",\nGUID:"+ new GUID(getClientGUID()) +", address: "+
getAddress()+":"+getPort()+",\nproxies:{ ";
for (IpPort ppi : getProxies()) {
ret = ret+ppi.getInetAddress()+":"+ppi.getPort()+"\n";
}
ret = ret+ "}]";
return ret;
}
/**
* @param proxies the set of proxies for this PE
* @return how many bytes a PE will use when serialized.
*/
public static int getSizeBytes(Set<? extends IpPort> proxies, boolean includeTLS) {
boolean hasTLS = false;
if(includeTLS) {
int i = 0;
for(IpPort ipp : proxies) {
if(i >= MAX_PROXIES)
break;
if(ipp instanceof ConnectableImpl && ((Connectable)ipp).isTLSCapable()) {
hasTLS = true;
break;
}
i++;
}
}
return HEADER_SIZE + (hasTLS ? 1 : 0 ) + Math.min(proxies.size(),MAX_PROXIES) * PROXY_SIZE;
}
}