package com.limegroup.gnutella.messages.vendor;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import com.limegroup.gnutella.ByteOrder;
import com.limegroup.gnutella.ErrorService;
import com.limegroup.gnutella.messages.BadPacketException;
import com.limegroup.gnutella.messages.Message;
import com.limegroup.gnutella.statistics.ReceivedErrorStat;
/** Vendor Messages are Gnutella Messages that are NEVER forwarded after
* recieved.
* This message is abstract because it provides common methods for ALL
* VendorMessages, but it makes no sense to instantiate a VendorMessage.
*/
public abstract class VendorMessage extends Message {
//Functional IDs defined by Gnutella VendorMessage protocol....
protected static final int F_MESSAGES_SUPPORTED = 0;
protected static final int F_HOPS_FLOW = 4;
protected static final int F_TCP_CONNECT_BACK = 7;
protected static final int F_UDP_CONNECT_BACK = 7;
protected static final int F_UDP_CONNECT_BACK_REDIR = 8;
protected static final int F_CAPABILITIES = 10;
protected static final int F_LIME_ACK = 11;
protected static final int F_REPLY_NUMBER = 12;
protected static final int F_PUSH_PROXY_REQ = 21;
protected static final int F_PUSH_PROXY_ACK = 22;
protected static final int F_GIVE_STATS = 14;
protected static final int F_STATISTICS = 15;
protected static final int F_GIVE_ULTRAPEER = 5;
protected static final int F_ULTRAPEER_LIST = 6;
protected static final int F_SIMPP_REQ = 16;
protected static final int F_SIMPP = 17;
protected static final int F_UDP_HEAD_PING = 23;
protected static final int F_UDP_HEAD_PONG = 24;
protected static final int F_HEADER_UPDATE = 25;
protected static final int F_UPDATE_REQ = 26;
protected static final int F_UPDATE_RESP = 27;
protected static final int F_CONTENT_REQ = 28;
protected static final int F_CONTENT_RESP = 29;
protected static final byte[] F_LIME_VENDOR_ID = {(byte) 76, (byte) 73,
(byte) 77, (byte) 69};
protected static final byte[] F_BEAR_VENDOR_ID = {(byte) 66, (byte) 69,
(byte) 65, (byte) 82};
protected static final byte[] F_GTKG_VENDOR_ID = {(byte) 71, (byte) 84,
(byte) 75, (byte) 71};
protected static final byte[] F_NULL_VENDOR_ID = {(byte) 0, (byte) 0,
(byte) 0, (byte) 0};
private static final int LENGTH_MINUS_PAYLOAD = 8;
private static final BadPacketException UNRECOGNIZED_EXCEPTION =
new BadPacketException("Unrecognized Vendor Message");
/**
* 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 VendorMessage(byte[] vendorIDBytes, int selector, int version,
byte[] payload) {
this(vendorIDBytes, selector, version, payload, Message.N_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 VendorMessage(byte[] vendorIDBytes, int selector, int version,
byte[] payload, int 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 below constructor that takes a
* network parameter) and use getPayload() to parse the payload and do
* anything else they need to.
*/
protected VendorMessage(byte[] guid, byte ttl, byte hops, byte[] vendorID,
int selector, int version, byte[] payload)
throws BadPacketException {
this(guid,ttl,hops,vendorID,selector,version,payload,Message.N_UNKNOWN);
}
/**
* 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 VendorMessage(byte[] guid, byte ttl, byte hops,byte[] vendorID,
int selector, int version, byte[] payload,
int network) throws BadPacketException {
super(guid, (byte)0x31, ttl, hops, LENGTH_MINUS_PAYLOAD+payload.length,
network);
// set the instance params....
if ((vendorID.length != 4)) {
ReceivedErrorStat.VENDOR_INVALID_ID.incrementStat();
throw new BadPacketException("Vendor ID Invalid!");
}
if ((selector & 0xFFFF0000) != 0) {
ReceivedErrorStat.VENDOR_INVALID_SELECTOR.incrementStat();
throw new BadPacketException("Selector Invalid!");
}
if ((version & 0xFFFF0000) != 0) {
ReceivedErrorStat.VENDOR_INVALID_VERSION.incrementStat();
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 += (int) 17*vendorID[i];
for (int i = 0; i < payload.length; i++)
hashCode += (int) 17*payload[i];
return hashCode;
}
//----------------------------------
//----------------------------------
// ACCESSOR methods
//----------------------------------
/** Allows subclasses to make changes gain access to the payload. They
* can:
* 1) change the contents
* 2) parse the contents.
* In general, 1) is discouraged, 2) is necessary. Subclasses CANNOT
* re-init the payload.
*/
protected byte[] getPayload() {
return _payload;
}
protected int getVersion() {
return _version;
}
//----------------------------------
//----------------------
// Methods for all subclasses....
//----------------------
/**
* Constructs a vendor message with the specified network data.
* The actual vendor message constructed is determined by the value
* of the selector within the message.
*/
public static VendorMessage deriveVendorMessage(byte[] guid, byte ttl,
byte hops,
byte[] fromNetwork,
int network)
throws BadPacketException {
// sanity check
if (fromNetwork.length < LENGTH_MINUS_PAYLOAD) {
ReceivedErrorStat.VENDOR_INVALID_PAYLOAD.incrementStat();
throw new BadPacketException("Not enough bytes for a VM!!");
}
// get very necessary parameters....
ByteArrayInputStream bais = new ByteArrayInputStream(fromNetwork);
byte[] vendorID = null, restOf = null;
int selector = -1, version = -1;
try {
// first 4 bytes are vendor ID
vendorID = new byte[4];
bais.read(vendorID, 0, vendorID.length);
// get the selector....
selector = ByteOrder.ushort2int(ByteOrder.leb2short(bais));
// get the version....
version = ByteOrder.ushort2int(ByteOrder.leb2short(bais));
// get the rest....
restOf = new byte[bais.available()];
bais.read(restOf, 0, restOf.length);
} catch (IOException ioe) {
ErrorService.error(ioe); // impossible.
}
// now switch on them to get the appropriate message....
if ((selector == F_HOPS_FLOW) &&
(Arrays.equals(vendorID, F_BEAR_VENDOR_ID)))
// HOPS FLOW MESSAGE
return new HopsFlowVendorMessage(guid, ttl, hops, version,
restOf);
if ((selector == F_LIME_ACK) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
// LIME ACK MESSAGE
return new LimeACKVendorMessage(guid, ttl, hops, version,
restOf);
if ((selector == F_REPLY_NUMBER) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
// REPLY NUMBER MESSAGE
return new ReplyNumberVendorMessage(guid, ttl, hops, version,
restOf);
if ((selector == F_TCP_CONNECT_BACK) &&
(Arrays.equals(vendorID, F_BEAR_VENDOR_ID)))
// TCP CONNECT BACK
return new TCPConnectBackVendorMessage(guid, ttl, hops, version,
restOf);
if ((selector == F_MESSAGES_SUPPORTED) &&
(Arrays.equals(vendorID, F_NULL_VENDOR_ID)))
// Messages Supported Message
return new MessagesSupportedVendorMessage(guid, ttl, hops, version,
restOf);
if ((selector == F_UDP_CONNECT_BACK) &&
(Arrays.equals(vendorID, F_GTKG_VENDOR_ID)))
// UDP CONNECT BACK
return new UDPConnectBackVendorMessage(guid, ttl, hops, version,
restOf);
if ((selector == F_PUSH_PROXY_REQ) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
// Push Proxy Request
return new PushProxyRequest(guid, ttl, hops, version, restOf);
if ((selector == F_PUSH_PROXY_ACK) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
// Push Proxy Acknowledgement
return new PushProxyAcknowledgement(guid, ttl, hops, version,
restOf);
if ((selector == F_LIME_ACK) &&
(Arrays.equals(vendorID, F_BEAR_VENDOR_ID)))
// Query Status Request
return new QueryStatusRequest(guid, ttl, hops, version, restOf);
if ((selector == F_REPLY_NUMBER) &&
(Arrays.equals(vendorID, F_BEAR_VENDOR_ID)))
// Query Status Response
return new QueryStatusResponse(guid, ttl, hops, version, restOf);
if ((selector == F_TCP_CONNECT_BACK) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
return new TCPConnectBackRedirect(guid, ttl, hops, version, restOf);
if ((selector == F_UDP_CONNECT_BACK_REDIR) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
return new UDPConnectBackRedirect(guid, ttl, hops, version, restOf);
if ((selector == F_CAPABILITIES) &&
(Arrays.equals(vendorID, F_NULL_VENDOR_ID)))
return new CapabilitiesVM(guid, ttl, hops, version, restOf);
if ((selector == F_GIVE_STATS) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
return new GiveStatsVendorMessage(guid, ttl, hops, version, restOf,
network);
if ((selector == F_STATISTICS) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
return new StatisticVendorMessage(guid, ttl, hops, version, restOf);
if((selector == F_SIMPP_REQ) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
return new SimppRequestVM(guid, ttl, hops, version, restOf);
if((selector == F_SIMPP) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
return new SimppVM(guid, ttl, hops, version, restOf);
if ((selector == F_GIVE_ULTRAPEER) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
return new UDPCrawlerPing(guid,ttl,hops,version,restOf);
if ((selector == F_ULTRAPEER_LIST) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
return new UDPCrawlerPong(guid,ttl,hops,version,restOf);
if ((selector == F_UDP_HEAD_PING) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
return new HeadPing(guid,ttl,hops,version,restOf);
if ((selector == F_UDP_HEAD_PONG) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
return new HeadPong(guid,ttl,hops,version,restOf);
if((selector == F_UPDATE_REQ) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
return new UpdateRequest(guid, ttl, hops, version, restOf);
if((selector == F_UPDATE_RESP) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
return new UpdateResponse(guid, ttl, hops, version, restOf);
if((selector == F_CONTENT_REQ) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
return new ContentRequest(guid, ttl, hops, version, restOf);
if((selector == F_CONTENT_RESP) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
return new ContentResponse(guid, ttl, hops, version, restOf);
if((selector == F_HEADER_UPDATE) &&
(Arrays.equals(vendorID, F_LIME_VENDOR_ID)))
return new HeaderUpdateVendorMessage(guid, ttl, hops, version, restOf);
ReceivedErrorStat.VENDOR_UNRECOGNIZED.incrementStat();
throw UNRECOGNIZED_EXCEPTION;
}
/**
* @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.
*/
public boolean equals(Object other) {
if (other instanceof VendorMessage) {
VendorMessage vmp = (VendorMessage) other;
return ((_selector == vmp._selector) &&
(Arrays.equals(_vendorID, vmp._vendorID)) &&
(Arrays.equals(_payload, vmp._payload))
);
}
return false;
}
public int hashCode() {
return _hashCode;
}
//----------------------
//----------------------
// ABSTRACT METHODS
//----------------------
//----------------------
//----------------------------------
// FULFILL abstract Message methods
//----------------------------------
// INHERIT COMMENT
protected void writePayload(OutputStream out) throws IOException {
out.write(_vendorID);
ByteOrder.short2leb((short)_selector, out);
ByteOrder.short2leb((short)_version, out);
out.write(getPayload());
}
// INHERIT COMMENT
public Message stripExtendedPayload() {
// doesn't make sense for VendorMessage to strip anything....
return this;
}
// INHERIT COMMENT
public void recordDrop() {
}
//----------------------------------
}