package com.limegroup.gnutella.messages.vendor; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; import org.limewire.util.ByteUtils; import com.limegroup.gnutella.messages.AbstractMessage; import com.limegroup.gnutella.messages.BadPacketException; public abstract class AbstractVendorMessage extends AbstractMessage implements VendorMessage { /** * Bytes 0-3 of the Vendor Message. Something like "LIME".getBytes(). */ private final byte[] _vendorID; /** * The Sub-Selector for this message. Bytes 4-5 of the Vendor Message. */ private final int _selector; /** * The Version number of the message. Bytes 6-7 of the Vendor Message. */ private final int _version; /** * The payload of this VendorMessage. This usually holds data that is * interpreted by the type of message determined by _vendorID, _selector, * and (to a lesser extent) _version. */ private final byte[] _payload; /** Cache the hashcode cuz it isn't cheap to compute. */ private final int _hashCode; //---------------------------------- // CONSTRUCTORS //---------------------------------- /** * Constructs a new VendorMessage with the given data. * Each Vendor Message class delegates to this constructor (or the one * also taking a network parameter) to construct new locally generated * VMs. * @param vendorIDBytes the Vendor ID of this message (bytes). * @param selector the selector of the message. * @param version the version of this message. * @param payload the payload (not including vendorIDBytes, selector, and * version. * @exception NullPointerException Thrown if payload or vendorIDBytes are * null. */ protected AbstractVendorMessage(byte[] vendorIDBytes, int selector, int version, byte[] payload) { this(vendorIDBytes, selector, version, payload, Network.UNKNOWN); } /** * Constructs a new VendorMessage with the given data. * Each Vendor Message class delegates to this constructor (or the one that * doesn't take the network parameter) to construct new locally generated * VMs. * @param vendorIDBytes the Vendor ID of this message (bytes). * @param selector the selector of the message. * @param version the version of this message. * @param payload the payload (not including vendorIDBytes, selector, and * version. * @param network the network this VM is to be written on. * @exception NullPointerException thrown if payload or vendorIDBytes are * null. */ protected AbstractVendorMessage(byte[] vendorIDBytes, int selector, int version, byte[] payload, Network network) { super(F_VENDOR_MESSAGE, (byte)1, LENGTH_MINUS_PAYLOAD + payload.length, network); if ((vendorIDBytes.length != 4)) throw new IllegalArgumentException("wrong vendorID length: " + vendorIDBytes.length); if ((selector & 0xFFFF0000) != 0) throw new IllegalArgumentException("invalid selector: " + selector); if ((version & 0xFFFF0000) != 0) throw new IllegalArgumentException("invalid version: " + version); // set the instance params.... _vendorID = vendorIDBytes; _selector = selector; _version = version; _payload = payload; // lastly compute the hash _hashCode = computeHashCode(_version, _selector, _vendorID, _payload); } /** * Constructs a new VendorMessage with data from the network. * Primarily built for the convenience of the class Message. * Subclasses must extend this (or the above constructor that doesn't * takes a network parameter) and use getPayload() to parse the payload * and do anything else they need to. */ protected AbstractVendorMessage(byte[] guid, byte ttl, byte hops,byte[] vendorID, int selector, int version, byte[] payload, Network network) throws BadPacketException { super(guid, (byte)0x31, ttl, hops, LENGTH_MINUS_PAYLOAD+payload.length, network); // set the instance params.... if ((vendorID.length != 4)) { throw new BadPacketException("Vendor ID Invalid!"); } if ((selector & 0xFFFF0000) != 0) { throw new BadPacketException("Selector Invalid!"); } if ((version & 0xFFFF0000) != 0) { throw new BadPacketException("Version Invalid!"); } _vendorID = vendorID; _selector = selector; _version = version; _payload = payload; // lastly compute the hash _hashCode = computeHashCode(_version, _selector, _vendorID, _payload); } /** * Computes the hash code for a vendor message. */ private static int computeHashCode(int version, int selector, byte[] vendorID, byte[] payload) { int hashCode = 0; hashCode += 17*version; hashCode += 17*selector; for (int i = 0; i < vendorID.length; i++) hashCode += 17*vendorID[i]; for (int i = 0; i < payload.length; i++) hashCode += 17*payload[i]; return hashCode; } //---------------------------------- //---------------------------------- // ACCESSOR methods //---------------------------------- /** Allows subclasses to make changes gain access to the payload. They * can: * <pre> * 1) change the contents * 2) parse the contents. * </pre> * In general, 1) is discouraged, 2) is necessary. Subclasses CANNOT * re-init the payload. */ protected byte[] getPayload() { return _payload; } /* (non-Javadoc) * @see com.limegroup.gnutella.messages.vendor.VendorMessageI#getVersion() */ public int getVersion() { return _version; } //---------------------------------- //---------------------- // Methods for all subclasses.... //---------------------- /** * @return true if the two VMPs have identical signatures - no more, no * less. Does not take version into account, but if different versions * have different payloads, they'll differ. */ @Override public boolean equals(Object other) { if (other instanceof AbstractVendorMessage) { AbstractVendorMessage vmp = (AbstractVendorMessage) other; return ((_selector == vmp._selector) && (Arrays.equals(_vendorID, vmp._vendorID)) && (Arrays.equals(_payload, vmp._payload)) ); } return false; } @Override public int hashCode() { return _hashCode; } //---------------------- //---------------------- // ABSTRACT METHODS //---------------------- //---------------------- //---------------------------------- // FULFILL abstract Message methods //---------------------------------- // INHERIT COMMENT @Override protected void writePayload(OutputStream out) throws IOException { out.write(_vendorID); ByteUtils.short2leb((short)_selector, out); ByteUtils.short2leb((short)_version, out); writeVendorPayload(out); } protected void writeVendorPayload(OutputStream out) throws IOException { out.write(getPayload()); } //---------------------------------- }