package com.limegroup.gnutella.messages.vendor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import com.limegroup.gnutella.ByteOrder;
import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.messages.BadPacketException;
import com.limegroup.gnutella.messages.FeatureSearchData;
import com.limegroup.gnutella.simpp.SimppManager;
import com.limegroup.gnutella.statistics.SentMessageStatHandler;
import com.limegroup.gnutella.version.UpdateHandler;
/**
* The message that lets other know what capabilities you support. Everytime
* you add a capability you should modify this class.
*
*/
public final class CapabilitiesVM extends VendorMessage {
/**
* Bytes for advertising that we support a 'feature' search.
* The value is 'WHAT' for legacy reasons, because 'what is new'
* was the first feature search supported.
*/
static final byte[] FEATURE_SEARCH_BYTES = {(byte)87, (byte)72,
(byte)65, (byte)84};
/**
* The bytes for supporting SIMPP. This used to be 'SIMP', but that
* implementation was broken. We now use 'IMPP' to advertise support.
*/
private static final byte[] SIMPP_CAPABILITY_BYTES = {'I', 'M', 'P', 'P' };
/**
* The bytes for the LMUP message.
*/
private static final byte[] LIME_UPDATE_BYTES = { 'L', 'M', 'U', 'P' };
/**
* The current version of this message.
*/
public static final int VERSION = 0;
/**
* The capabilities supported.
*/
private final Set _capabilitiesSupported = new HashSet();
/**
* The current instance of this CVM that this node will forward to others
*/
private static CapabilitiesVM _instance;
/**
* Constructs a new CapabilitiesVM from data read off the network.
*/
CapabilitiesVM(byte[] guid, byte ttl, byte hops,
int version, byte[] payload) throws BadPacketException {
super(guid, ttl, hops, F_NULL_VENDOR_ID, F_CAPABILITIES, version,
payload);
// populate the Set of supported messages....
try {
ByteArrayInputStream bais = new ByteArrayInputStream(getPayload());
int vectorSize = ByteOrder.ushort2int(ByteOrder.leb2short(bais));
// constructing the SMB will cause a BadPacketException if the
// network data is invalid
for (int i = 0; i < vectorSize; i++)
_capabilitiesSupported.add(new SupportedMessageBlock(bais));
} catch (IOException ioe) {
ErrorService.error(ioe); // impossible.
}
}
/**
* Internal constructor for creating the sole instance of our
* CapabilitiesVM.
*/
private CapabilitiesVM() {
super(F_NULL_VENDOR_ID, F_CAPABILITIES, VERSION, derivePayload());
addSupportedMessages(_capabilitiesSupported);
}
/**
* Generates the default payload, using all our supported messages.
*/
private static byte[] derivePayload() {
Set hashSet = new HashSet();
addSupportedMessages(hashSet);
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ByteOrder.short2leb((short)hashSet.size(), baos);
for(Iterator i = hashSet.iterator(); i.hasNext(); ) {
SupportedMessageBlock currSMP = (SupportedMessageBlock)i.next();
currSMP.encode(baos);
}
return baos.toByteArray();
} catch (IOException ioe) {
ErrorService.error(ioe); // impossible.
return null;
}
}
// ADD NEW CAPABILITIES HERE AS YOU BUILD THEM....
/**
* Adds all supported capabilities to the given set.
*/
private static void addSupportedMessages(Set hashSet) {
SupportedMessageBlock smp = null;
smp = new SupportedMessageBlock(FEATURE_SEARCH_BYTES,
FeatureSearchData.FEATURE_SEARCH_MAX_SELECTOR);
hashSet.add(smp);
smp = new SupportedMessageBlock(SIMPP_CAPABILITY_BYTES,
SimppManager.instance().getVersion());
hashSet.add(smp);
smp = new SupportedMessageBlock(LIME_UPDATE_BYTES,
UpdateHandler.instance().getLatestId());
hashSet.add(smp);
}
/** @return A CapabilitiesVM with the set of messages
* this client supports.
*/
public static CapabilitiesVM instance() {
if (_instance == null)
_instance = new CapabilitiesVM();
return _instance;
}
/**
* @return -1 if the ability isn't supported, else it returns the version
* of the message supported.
*/
public int supportsCapability(byte[] capabilityName) {
Iterator iter = _capabilitiesSupported.iterator();
while (iter.hasNext()) {
SupportedMessageBlock currSMP = (SupportedMessageBlock) iter.next();
int version = currSMP.matches(capabilityName);
if (version > -1)
return version;
}
return -1;
}
/** @return 1 or higher if capability queries are supported. the version
* number gives some indication about what exactly is a supported. if no
* support, returns -1.
*/
public int supportsFeatureQueries() {
return supportsCapability(FEATURE_SEARCH_BYTES);
}
/** @return true if 'what is new' capability query feature is supported.
*/
public boolean supportsWhatIsNew() {
return FeatureSearchData.supportsWhatIsNew(
supportsCapability(FEATURE_SEARCH_BYTES));
}
/**
* Returns the current SIMPP version.
*/
public int supportsSIMPP() {
return supportsCapability(SIMPP_CAPABILITY_BYTES);
}
/**
* Returns the current Update version.
*/
public int supportsUpdate() {
return supportsCapability(LIME_UPDATE_BYTES);
}
// override super
public boolean equals(Object other) {
if(other == this)
return true;
// two of these messages are the same if the support the same messages
if (other instanceof CapabilitiesVM) {
CapabilitiesVM vmp = (CapabilitiesVM) other;
return _capabilitiesSupported.equals(vmp._capabilitiesSupported);
}
return false;
}
/**
* Constructs a new instance for this node to advertise,
* using the latest version numbers of supported messages.
*/
public static void reconstructInstance() {
//replace _instance with a newer one, which will be created with the
//correct simppVersion, a new _capabilitiesSupported will be created
_instance = new CapabilitiesVM();
}
// override super
public int hashCode() {
return 17*_capabilitiesSupported.hashCode();
}
/** Container for vector elements.
*/
static class SupportedMessageBlock {
final byte[] _capabilityName;
final int _version;
final int _hashCode;
public String toString() {
return new String(_capabilityName) + "/" + _version;
}
public SupportedMessageBlock(byte[] capabilityName, int version) {
_capabilityName = capabilityName;
_version = version;
_hashCode = computeHashCode(_capabilityName, _version);
}
/**
* Constructs a new SupportedMessageBlock with data from the
* InputStream. If not enough data is available,
* throws BadPacketException.
*/
public SupportedMessageBlock(InputStream encodedBlock)
throws BadPacketException, IOException {
if (encodedBlock.available() < 6)
throw new BadPacketException("invalid block.");
// first 4 bytes are capability name
_capabilityName = new byte[4];
encodedBlock.read(_capabilityName, 0, _capabilityName.length);
_version = ByteOrder.ushort2int(ByteOrder.leb2short(encodedBlock));
_hashCode = computeHashCode(_capabilityName, _version);
}
/**
* Writes this capability (and version) to the OutputStream.
*/
public void encode(OutputStream out) throws IOException {
out.write(_capabilityName);
ByteOrder.short2leb((short)_version, out);
}
/** @return 0 or more if this matches the message you are looking for.
* Otherwise returns -1;
*/
public int matches(byte[] capabilityName) {
if (Arrays.equals(_capabilityName, capabilityName))
return _version;
else
return -1;
}
public boolean equals(Object other) {
if (other instanceof SupportedMessageBlock) {
SupportedMessageBlock vmp = (SupportedMessageBlock) other;
return ((_version == vmp._version) &&
(Arrays.equals(_capabilityName, vmp._capabilityName))
);
}
return false;
}
public int hashCode() {
return _hashCode;
}
private static int computeHashCode(byte[] capabilityName, int version) {
int hashCode = 0;
hashCode += 37*version;
for (int i = 0; i < capabilityName.length; i++)
hashCode += (int) 37*capabilityName[i];
return hashCode;
}
}
/** Overridden purely for stats handling.
*/
protected void writePayload(OutputStream out) throws IOException {
super.writePayload(out);
SentMessageStatHandler.TCP_MESSAGES_SUPPORTED.addMessage(this);
}
/** Overridden purely for stats handling.
*/
public void recordDrop() {
super.recordDrop();
}
public String toString() {
return "{CapabilitiesVM:"+super.toString()+"; supporting: " + _capabilitiesSupported + "}";
}
}