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.Map; import java.util.TreeMap; import org.limewire.collection.Comparators; import org.limewire.service.ErrorService; import org.limewire.util.ByteUtils; import com.limegroup.gnutella.dht.DHTManager.DHTMode; import com.limegroup.gnutella.messages.BadPacketException; import com.limegroup.gnutella.messages.FeatureSearchData; import com.limegroup.gnutella.messages.Message; public class CapabilitiesVMImpl extends AbstractVendorMessage implements CapabilitiesVM { /** The capabilities supported. */ private final Map<byte[], Integer> capabilities; /** * Constructs a new CapabilitiesVM from data read off the network. */ CapabilitiesVMImpl(byte[] guid, byte ttl, byte hops, int version, byte[] payload, Network network) throws BadPacketException { super(guid, ttl, hops, F_NULL_VENDOR_ID, F_CAPABILITIES, version, payload, network); capabilities = new TreeMap<byte[], Integer>(new Comparators.ByteArrayComparator()); // populate the Set of supported messages.... try { ByteArrayInputStream bais = new ByteArrayInputStream(payload); int vectorSize = ByteUtils.ushort2int(ByteUtils.leb2short(bais)); // constructing the SMB will cause a BadPacketException if the // network data is invalid for (int i = 0; i < vectorSize; i++) { readCapability(bais, false); } if(bais.available() > 0) { vectorSize = ByteUtils.ushort2int(ByteUtils.leb2short(bais)); for(int i = 0; i < vectorSize; i++) { readCapability(bais, true); } } } catch (IOException ioe) { throw new BadPacketException(ioe); } } /** * Internal constructor for creating the sole instance of our * CapabilitiesVM. */ CapabilitiesVMImpl(Map<byte[], Integer> _capabilitiesSupported) { super(F_NULL_VENDOR_ID, F_CAPABILITIES, VERSION, derivePayload(_capabilitiesSupported)); this.capabilities = _capabilitiesSupported; } /** * Generates the default payload, using all our supported messages. */ private static byte[] derivePayload(Map<byte[], Integer> allCapabilities) { try { Map<byte[], Integer> capsNeedingInt = new TreeMap<byte[], Integer>(new Comparators.ByteArrayComparator()); ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteUtils.short2leb((short)allCapabilities.size(), out); for(Map.Entry<byte[], Integer> entry : allCapabilities.entrySet()) { writeCapability(out, entry.getKey(), entry.getValue(), false); if(entry.getValue() > 65535) capsNeedingInt.put(entry.getKey(), entry.getValue()); } if(capsNeedingInt.size() > 0) { ByteUtils.short2leb((short)capsNeedingInt.size(), out); for(Map.Entry<byte[], Integer> entry : capsNeedingInt.entrySet()) { writeCapability(out, entry.getKey(), entry.getValue(), true); } } return out.toByteArray(); } catch (IOException ioe) { ErrorService.error(ioe); // impossible. return null; } } /** * @return -1 if the ability isn't supported, else it returns the version * of the message supported. */ public int supportsCapability(byte[] capabilityName) { Integer version = capabilities.get(capabilityName); if(version == null || version <= -1) return -1; else return version; } /** * Return 1 or higher if TLS is supported by the connection. * This does not necessarily mean the connection is over * TLS though. */ public int supportsTLS() { return supportsCapability(TLS_SUPPORT_BYTES); } /** @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 DHT version if this node is an ACTIVE DHT node. */ public int isActiveDHTNode() { return supportsCapability(DHTMode.ACTIVE.getCapabilityName()); } /** * Returns the current DHT version if this node is an PASSIVE DHT node. */ public int isPassiveDHTNode() { return supportsCapability(DHTMode.PASSIVE.getCapabilityName()); } /** * Returns the current DHT version if this node is an PASSIVE_LEAF DHT node. */ public int isPassiveLeafNode() { return supportsCapability(DHTMode.PASSIVE_LEAF.getCapabilityName()); } /** * @return true unless the remote host indicated they can't accept * incoming tcp. If they didn't say anything we assume they can */ public boolean canAcceptIncomingTCP() { return supportsCapability(INCOMING_TCP_BYTES) != 0; } /** * @return true unless the remote host indicated they can't do * firewall-to-firewall transfers. If they didn't say anything we assume they can */ public boolean canDoFWT() { return supportsCapability(FWT_SUPPORT_BYTES) != 0; } // override super @Override 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 CapabilitiesVMImpl) { CapabilitiesVMImpl vmp = (CapabilitiesVMImpl) other; return capabilities.equals(vmp.capabilities); } return false; } // override super @Override public int hashCode() { return capabilities.hashCode(); } private void readCapability(InputStream input, boolean allow4ByteVersion) throws IOException { int required = allow4ByteVersion ? 8 : 6; if (input.available() < required) throw new IOException("invalid block."); byte[] name = new byte[4]; input.read(name, 0, name.length); int version = allow4ByteVersion ? ByteUtils.leb2int(input) : ByteUtils.ushort2int(ByteUtils.leb2short(input)); capabilities.put(name, version); } static void writeCapability(OutputStream out, byte[] name, int version, boolean allow4ByteVersion) throws IOException { out.write(name); if(allow4ByteVersion) ByteUtils.int2leb(version, out); else ByteUtils.short2leb((short)version, out); } /** Overridden purely for stats handling. */ @Override protected void writePayload(OutputStream out) throws IOException { super.writePayload(out); } @Override public String toString() { return "{CapabilitiesVM:"+super.toString()+"; supporting: " + capabilities + "}"; } @Override public Class<? extends Message> getHandlerClass() { return CapabilitiesVM.class; } }