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.statistics.SentMessageStatHandler; /** The message that lets other know what messages you support. Everytime you * add a subclass of VendorMessage you should modify this class (assuming your * message is delivered over TCP). */ public final class MessagesSupportedVendorMessage extends VendorMessage { public static final int VERSION = 0; private final Set _messagesSupported = new HashSet(); private static MessagesSupportedVendorMessage _instance; /** * Constructs a new MSVM message with data from the network. */ MessagesSupportedVendorMessage(byte[] guid, byte ttl, byte hops, int version, byte[] payload) throws BadPacketException { super(guid, ttl, hops, F_NULL_VENDOR_ID, F_MESSAGES_SUPPORTED, version, payload); if (getVersion() > VERSION) throw new BadPacketException("UNSUPPORTED VERSION"); // populate the Set of supported messages.... try { ByteArrayInputStream bais = new ByteArrayInputStream(getPayload()); int vectorSize = ByteOrder.ushort2int(ByteOrder.leb2short(bais)); for (int i = 0; i < vectorSize; i++) _messagesSupported.add(new SupportedMessageBlock(bais)); } catch (IOException ioe) { ErrorService.error(ioe); // impossible. } } /** * Private constructor for creating the sole MSVM message of all our * supported messages. */ private MessagesSupportedVendorMessage() { super(F_NULL_VENDOR_ID, F_MESSAGES_SUPPORTED, VERSION, derivePayload()); addSupportedMessages(_messagesSupported); } /** * Constructs the payload for supporting all of the messages. */ private static byte[] derivePayload() { Set hashSet = new HashSet(); addSupportedMessages(hashSet); try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteOrder.short2leb((short)hashSet.size(), baos); Iterator iter = hashSet.iterator(); while (iter.hasNext()) { SupportedMessageBlock currSMP = (SupportedMessageBlock) iter.next(); currSMP.encode(baos); } return baos.toByteArray(); } catch (IOException ioe) { ErrorService.error(ioe); // impossible. return null; } } // ADD NEW MESSAGES HERE AS YOU BUILD THEM.... // you should only add messages supported over TCP private static void addSupportedMessages(Set hashSet) { SupportedMessageBlock smp = null; // TCP Connect Back smp = new SupportedMessageBlock(F_BEAR_VENDOR_ID, F_TCP_CONNECT_BACK, TCPConnectBackVendorMessage.VERSION); hashSet.add(smp); // UDP Connect Back smp = new SupportedMessageBlock(F_GTKG_VENDOR_ID, F_UDP_CONNECT_BACK, UDPConnectBackVendorMessage.VERSION); hashSet.add(smp); // Hops Flow smp = new SupportedMessageBlock(F_BEAR_VENDOR_ID, F_HOPS_FLOW, HopsFlowVendorMessage.VERSION); hashSet.add(smp); // Give Stats Request smp = new SupportedMessageBlock(F_LIME_VENDOR_ID, F_GIVE_STATS, GiveStatsVendorMessage.VERSION); hashSet.add(smp); // Push Proxy Request smp = new SupportedMessageBlock(F_LIME_VENDOR_ID, F_PUSH_PROXY_REQ, PushProxyRequest.VERSION); hashSet.add(smp); // Leaf Guidance Support smp = new SupportedMessageBlock(F_BEAR_VENDOR_ID, F_LIME_ACK, QueryStatusRequest.VERSION); hashSet.add(smp); // TCP CB Redirect smp = new SupportedMessageBlock(F_LIME_VENDOR_ID, F_TCP_CONNECT_BACK, TCPConnectBackRedirect.VERSION); hashSet.add(smp); // UDP CB Redirect smp = new SupportedMessageBlock(F_LIME_VENDOR_ID, F_UDP_CONNECT_BACK_REDIR, UDPConnectBackRedirect.VERSION); hashSet.add(smp); // UDP Crawl support smp = new SupportedMessageBlock(F_LIME_VENDOR_ID, F_ULTRAPEER_LIST, UDPCrawlerPong.VERSION); hashSet.add(smp); //Simpp Request message smp = new SupportedMessageBlock(F_LIME_VENDOR_ID, F_SIMPP_REQ, SimppRequestVM.VERSION); hashSet.add(smp); //Simpp Message smp = new SupportedMessageBlock(F_LIME_VENDOR_ID, F_SIMPP, SimppVM.VERSION); hashSet.add(smp); //Header update smp = new SupportedMessageBlock(F_LIME_VENDOR_ID, F_HEADER_UPDATE, HeaderUpdateVendorMessage.VERSION); hashSet.add(smp); } /** @return A MessagesSupportedVendorMessage with the set of messages * this client supports. */ public static MessagesSupportedVendorMessage instance() { if (_instance == null) _instance = new MessagesSupportedVendorMessage(); return _instance; } /** * @return -1 if the message isn't supported, else it returns the version * of the message supported. */ public int supportsMessage(byte[] vendorID, int selector) { Iterator iter = _messagesSupported.iterator(); while (iter.hasNext()) { SupportedMessageBlock currSMP = (SupportedMessageBlock) iter.next(); int version = currSMP.matches(vendorID, selector); if (version > -1) return version; } return -1; } /** * @return -1 if the message isn't supported, else it returns the version * of the message supported. */ public int supportsTCPConnectBack() { return supportsMessage(F_BEAR_VENDOR_ID, F_TCP_CONNECT_BACK); } /** * @return -1 if the message isn't supported, else it returns the version * of the message supported. */ public int supportsUDPConnectBack() { return supportsMessage(F_GTKG_VENDOR_ID, F_UDP_CONNECT_BACK); } /** * @return -1 if the message isn't supported, else it returns the version * of the message supported. */ public int supportsTCPConnectBackRedirect() { return supportsMessage(F_LIME_VENDOR_ID, F_TCP_CONNECT_BACK); } /** * @return -1 if the message isn't supported, else it returns the version * of the message supported. */ public int supportsUDPConnectBackRedirect() { return supportsMessage(F_LIME_VENDOR_ID, F_UDP_CONNECT_BACK_REDIR); } /** * @return -1 if the message isn't supported, else it returns the version * of the message supported. */ public int supportsHopsFlow() { return supportsMessage(F_BEAR_VENDOR_ID, F_HOPS_FLOW); } /** * @return -1 if the message isn't supported, else it returns the version * of the message supported. */ public int supportsPushProxy() { return supportsMessage(F_LIME_VENDOR_ID, F_PUSH_PROXY_REQ); } /** * @return -1 if the message is not supported, else returns the version of * the message supported. */ public int supportsGiveStatsVM() { return supportsMessage(F_LIME_VENDOR_ID, F_GIVE_STATS); } /** * @return -1 if the message isn't supported, else it returns the version * of the message supported. */ public int supportsLeafGuidance() { return supportsMessage(F_BEAR_VENDOR_ID, F_LIME_ACK); } /** * @return -1 if the remote host does not support UDP crawling, * else it returns the version. */ public int supportsUDPCrawling() { return supportsMessage(F_LIME_VENDOR_ID, F_ULTRAPEER_LIST); } public int supportsHeaderUpdate() { return supportsMessage(F_LIME_VENDOR_ID,F_HEADER_UPDATE); } // override super public boolean equals(Object other) { // basically two of these messages are the same if the support the same // messages if (other instanceof MessagesSupportedVendorMessage) { MessagesSupportedVendorMessage vmp = (MessagesSupportedVendorMessage) other; return (_messagesSupported.equals(vmp._messagesSupported)); } return false; } // override super public int hashCode() { return 17*_messagesSupported.hashCode(); } /** Container for vector elements. */ static class SupportedMessageBlock { final byte[] _vendorID; final int _selector; final int _version; final int _hashCode; /** * Constructs a new SupportedMessageBlock with the given vendorID, * selector, and version. */ public SupportedMessageBlock(byte[] vendorID, int selector, int version) { _vendorID = vendorID; _selector = selector; _version = version; _hashCode = computeHashCode(_vendorID, _selector, _version); } /** * Constructs a new SupportedMessageBlock from the input stream. * Throws BadPacketException if the data is invalid. */ public SupportedMessageBlock(InputStream encodedBlock) throws BadPacketException, IOException { if (encodedBlock.available() < 8) throw new BadPacketException("invalid data."); // first 4 bytes are vendor ID _vendorID = new byte[4]; encodedBlock.read(_vendorID, 0, _vendorID.length); _selector =ByteOrder.ushort2int(ByteOrder.leb2short(encodedBlock)); _version = ByteOrder.ushort2int(ByteOrder.leb2short(encodedBlock)); _hashCode = computeHashCode(_vendorID, _selector, _version); } /** * Encodes this SMB to the OutputStream. */ public void encode(OutputStream out) throws IOException { out.write(_vendorID); ByteOrder.short2leb((short)_selector, out); 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[] vendorID, int selector) { if ((Arrays.equals(_vendorID, vendorID)) && (_selector == selector)) return _version; else return -1; } public boolean equals(Object other) { if (other instanceof SupportedMessageBlock) { SupportedMessageBlock vmp = (SupportedMessageBlock) other; return ((_selector == vmp._selector) && (_version == vmp._version) && (Arrays.equals(_vendorID, vmp._vendorID)) ); } return false; } public int hashCode() { return _hashCode; } private static int computeHashCode(byte[] vendorID, int selector, int version) { int hashCode = 0; hashCode += 37*version; hashCode += 37*selector; for (int i = 0; i < vendorID.length; i++) hashCode += (int) 37*vendorID[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(); } }