package com.limegroup.gnutella.messages;
import java.io.IOException;
import java.io.OutputStream;
import org.limewire.io.GUID;
import org.limewire.util.ByteUtils;
/**
* A Gnutella message (packet). This class is abstract; subclasses
* implement specific messages such as search requests.<p>
*
* All messages have message IDs, function IDs, TTLs, hops taken, and
* data length. Messages come in two flavors: requests (ping, search)
* and replies (pong, search results). Message are mostly immutable;
* only the TTL, hops, and priority field can be changed.
*/
public abstract class AbstractMessage implements Message {
/** Same as GUID.makeGUID. This exists for backwards compatibility. */
public static byte[] makeGuid() {
return GUID.makeGuid();
}
////////////////////////// Instance Data //////////////////////
private byte[] guid;
private final byte func;
/* We do not support TTLs > 2^7, nor do we support packets
* of length > 2^31 */
private byte ttl;
private byte hops;
private int length;
/** Priority for flow-control. Lower numbers mean higher priority.NOT
* written to network. */
private int priority=0;
/** Time this was created. Not written to network. */
private final long creationTime=System.currentTimeMillis();
/**
* The network that this was received on or is going to be sent to.
*/
private final Network network;
/** Rep. invariant */
protected void repOk() {
assert(guid.length==16);
assert func==F_PING || func==F_PING_REPLY
|| func==F_PUSH
|| func==F_QUERY || func==F_QUERY_REPLY
|| func==F_VENDOR_MESSAGE
|| func == F_VENDOR_MESSAGE_STABLE
|| func == F_UDP_CONNECTION
: "Bad function code";
assert ttl>=0 : "Negative TTL: "+ttl;
assert hops>=0 : "Negative hops: "+hops;
assert length>=0 : "Negative length: "+length;
}
////////////////////// Constructors and Producers /////////////////
/**
* @requires func is a valid functional id (i.e., 0, 1, 64, 128, 129),
* 0 &<;= ttl, 0 &<;= length (i.e., high bit not used)
* @effects Creates a new message with the following data.
* The GUID is set appropriately, and the number of hops is set to 0.
*/
protected AbstractMessage(byte func, byte ttl, int length) {
this(func, ttl, length, Network.UNKNOWN);
}
protected AbstractMessage(byte func, byte ttl, int length, Network network) {
this(makeGuid(), func, ttl, (byte)0, length, network);
}
/**
* Same as above, but caller specifies TTL and number of hops.
* This is used when reading packets off network.
*/
protected AbstractMessage(byte[] guid, byte func, byte ttl,
byte hops, int length) {
this(guid, func, ttl, hops, length, Network.UNKNOWN);
}
/**
* Same as above, but caller specifies the network.
* This is used when reading packets off network.
*/
protected AbstractMessage(byte[] guid, byte func, byte ttl,
byte hops, int length, Network network) {
if (guid.length != 16) {
throw new IllegalArgumentException("invalid guid length: " + guid.length);
}
this.guid = guid;
this.func = func;
this.ttl = ttl;
this.hops = hops;
this.length = length;
this.network = network;
// repOk();
}
/**
* Writes a message quickly, without using temporary buffers or crap.
*/
public void writeQuickly(OutputStream out) throws IOException {
out.write(guid, 0, guid.length /* 16 */);
out.write(func);
out.write(ttl);
out.write(hops);
ByteUtils.int2leb(length, out);
writePayload(out);
}
/**
* Writes a message out, using the buffer as the temporary header.
*/
public void write(OutputStream out, byte[] buf) throws IOException {
System.arraycopy(guid, 0, buf, 0, guid.length /* 16 */);
buf[16]=func;
buf[17]=ttl;
buf[18]=hops;
ByteUtils.int2leb(length, buf, 19);
out.write(buf);
writePayload(out);
}
/**
* @modifies out
* @effects Writes an encoding of this to out. Does NOT flush out.
*/
public void write(OutputStream out) throws IOException {
write(out, new byte[23]);
}
/** @modifies out
* @effects writes the payload specific data to out (the stuff
* following the header). Does NOT flush out.
*/
protected abstract void writePayload(OutputStream out) throws IOException;
////////////////////////////////////////////////////////////////////
public Network getNetwork() {
return network;
}
public boolean isMulticast() {
return network == Network.MULTICAST;
}
public boolean isUDP() {
return network == Network.UDP;
}
public boolean isTCP() {
return network == Network.TCP;
}
public boolean isUnknownNetwork() {
return network == Network.UNKNOWN;
}
public byte[] getGUID() {
return guid;
}
public byte getFunc() {
return func;
}
public byte getTTL() {
return ttl;
}
/**
* If ttl is less than zero, throws IllegalArgumentException. Otherwise sets
* this TTL to the given value. This is useful when you want certain messages
* to travel less than others.
* @modifies this' TTL
*/
public void setTTL(byte ttl) throws IllegalArgumentException {
if (ttl < 0)
throw new IllegalArgumentException("invalid TTL: "+ttl);
this.ttl = ttl;
}
/**
* Sets the guid for this message. Is needed, when we want to cache
* query replies or other messages, and change the GUID as per the
* request
* @param guid the guid to be set
*/
protected void setGUID(GUID guid) {
this.guid = guid.bytes();
}
/**
* If the hops is less than zero, throws IllegalArgumentException.
* Otherwise sets this hops to the given value. This is useful when you
* want certain messages to look as if they've travelled further.
* @modifies this' hops
*/
public void setHops(byte hops) throws IllegalArgumentException {
if(hops < 0)
throw new IllegalArgumentException("invalid hops: " + hops);
this.hops = hops;
}
public byte getHops() {
return hops;
}
/** Returns the length of this' payload, in bytes. */
public int getLength() {
return length;
}
/** Updates length of this' payload, in bytes. */
protected void updateLength(int l) {
length=l;
}
/** Returns the total length of this, in bytes. */
public int getTotalLength() {
//Header is 23 bytes.
return 23+length;
}
/** @modifies this
* @effects increments hops, decrements TTL if > 0, and returns the
* OLD value of TTL.
*/
public byte hop() {
hops++;
if (ttl>0)
return ttl--;
else
return ttl;
}
/**
* Returns the system time (i.e., the result of System.currentTimeMillis())
* this was instantiated.
*/
public long getCreationTime() {
return creationTime;
}
/** Returns this user-defined priority. Lower values are higher priority. */
public int getPriority() {
return priority;
}
/** Set this user-defined priority for flow-control purposes. Lower values
* are higher priority. */
public void setPriority(int priority) {
this.priority=priority;
}
/**
* Returns a negative value if this is of lesser priority than message,
* positive value if of higher priority, or zero if of same priority.
* Remember that lower priority numbers mean HIGHER priority.
*
* @exception ClassCastException message not an instance of Message
*/
public int compareTo(Message m) {
return m.getPriority() - this.getPriority();
}
@Override
public String toString() {
return "{guid="+(new GUID(guid)).toString()
+", ttl="+ttl
+", hops="+hops
+", priority="+getPriority()+"}";
}
/**
* Should return the most specific message interface that his class
* implements.
* <p>
* This is needed since listeners register themselves on the interface class
* id. It can go away once listeners subscribe to the message id or instance
* of checks are used.
* </p>
*/
public Class<? extends Message> getHandlerClass() {
return getClass();
}
}