package com.limegroup.gnutella.messages; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; import org.limewire.collection.Buffer; import org.limewire.inspection.Inspectable; import org.limewire.util.ByteUtils; /** * Defines the interface for a Gnutella message (packet). See * <a href="http://rfc-gnutella.sourceforge.net/developer/testing/messageArchitecture.html"> * Gnutella message architecture</a> for more information. * */ public interface Message extends Comparable<Message> { /** The network a message came from or will travel through. */ public static enum Network { UNKNOWN, TCP, UDP, MULTICAST; } // Functional IDs defined by Gnutella protocol. public static final byte F_PING = (byte) 0x0; public static final byte F_PING_REPLY = (byte) 0x1; public static final byte F_PUSH = (byte) 0x40; public static final byte F_QUERY = (byte) 0x80; public static final byte F_QUERY_REPLY = (byte) 0x81; public static final byte F_ROUTE_TABLE_UPDATE = (byte) 0x30; public static final byte F_VENDOR_MESSAGE = (byte) 0x31; public static final byte F_VENDOR_MESSAGE_STABLE = (byte) 0x32; public static final byte F_UDP_CONNECTION = (byte) 0x41; /** * Writes a message quickly, without using temporary buffers or crap. */ public void writeQuickly(OutputStream out) throws IOException; /** * Writes a message out, using the buffer as the temporary header. */ public void write(OutputStream out, byte[] buf) throws IOException; /** * @modifies out * @effects Writes an encoding of this to out. Does NOT flush out. */ public void write(OutputStream out) throws IOException; //////////////////////////////////////////////////////////////////// public Network getNetwork(); public boolean isMulticast(); public boolean isUDP(); public boolean isTCP(); public boolean isUnknownNetwork(); public byte[] getGUID(); public byte getFunc(); public byte getTTL(); /** * If Time To Live (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 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; public byte getHops(); /** Returns the length of this' payload, in bytes. */ public int getLength(); /** Returns the total length of this, in bytes. */ public int getTotalLength(); /** @modifies this * @effects increments hops, decrements TTL if > 0, and returns the * OLD value of TTL. */ public byte hop(); /** * Returns the system time (i.e., the result of System.currentTimeMillis()) * this was instantiated. */ public long getCreationTime(); /** Returns this user-defined priority. Lower values are higher priority. */ public int getPriority(); /** Set this user-defined priority for flow-control purposes. Lower values * are higher priority. */ public void setPriority(int priority); /** * Returns the class that message handlers for it should register upon, i.e. * the interface class that it implements. */ public Class<? extends Message> getHandlerClass(); //********************************* /// Inspection-related code follows //********************************* /** * Counts messages by type and network they came on. * Also counts size in bytes, hops, TTLs. */ public static class MessageCounter implements Inspectable { private final Map<Class, EnumMap<Network, MessageTypeCounter>> counts = new HashMap<Class, EnumMap<Network,MessageTypeCounter>>(); private final int history; /** * @param history how many messages to record history for each type/network */ public MessageCounter(int history) { this.history = history; } public synchronized void countMessage(Message msg) { EnumMap<Network, MessageTypeCounter> m = counts.get(msg.getClass()); if (m == null) { m = new EnumMap<Network, MessageTypeCounter>(Network.class); counts.put(msg.getClass(),m); } MessageTypeCounter count = m.get(msg.getNetwork()); if (count == null) { count = new MessageTypeCounter(msg.getClass(), msg.getNetwork(), history); m.put(msg.getNetwork(),count); } count.countMessage(msg); } @Override public synchronized Object inspect() { List<Map<String,Object>> ret = new ArrayList<Map<String,Object>>(counts.size()); for (EnumMap<Network, MessageTypeCounter> e : counts.values()) { for (MessageTypeCounter mtc : e.values()) ret.add(mtc.inspect()); } return ret; } /** * Keeps track of traffic information about a specific type of message */ private static class MessageTypeCounter { private final Class clazz; private final Network net; private long num, size; private Buffer<Long> timestamps; private Buffer<Integer> sizes; private Buffer<Byte> hops; private Buffer<Byte> ttls; private long[] totalTtls = new long[5]; private long[] totalHops = new long[5]; /** * @param clazz the message class this is counting * @param net the network this message travels on * @param history how many messages to record history for */ MessageTypeCounter(Class<? extends Message> clazz, Network net, int history) { this.clazz = clazz; this.net = net; timestamps = new Buffer<Long>(history); // each entry 6 bytes on network sizes = new Buffer<Integer>(history); // each entry 2 bytes on network hops = new Buffer<Byte>(history); ttls = new Buffer<Byte>(history); } void countMessage(Message m) { num++; size += m.getLength(); timestamps.add(System.currentTimeMillis()); sizes.add(m.getLength()); hops.add(m.getHops()); ttls.add(m.getTTL()); // the last element is "or more" totalTtls[Math.min(4,m.getTTL())]++; totalHops[Math.min(4,m.getHops())]++; } Map<String,Object> inspect() { Map<String,Object> ret = new HashMap<String,Object>(); ret.put("class",clazz.toString()); ret.put("net",net.toString()); ret.put("num",num); ret.put("size",size); byte [] timesByte = new byte[timestamps.getSize() * 6]; // 6 bytes per timestamp byte [] sizesByte = new byte[sizes.getSize() * 2]; // 2 bytes per size byte [] hopsByte = new byte[hops.getSize()]; byte [] ttlsByte = new byte[ttls.getSize()]; for (int i = 0; i < timestamps.getSize(); i++) { long timestamp = timestamps.get(i); timesByte[i * 6] = (byte)((timestamp >> 40) & 0xFF); timesByte[i * 6 + 1] = (byte)((timestamp >> 32) & 0xFF); ByteUtils.int2beb((int)timestamp, timesByte, i * 6 + 2); } for (int i = 0; i < sizes.getSize(); i++) { short size = (short) Math.min(0xFFFF,sizes.get(i)); ByteUtils.short2beb(size, sizesByte, i * 2); } for (int i = 0; i < hops.getSize(); i++) hopsByte[i] = hops.get(i); for (int i = 0; i < ttls.getSize(); i++) ttlsByte[i] = ttls.get(i); ret.put("times",timesByte); ret.put("sizes",sizesByte); ret.put("hops", hopsByte); ret.put("ttls", ttlsByte); ret.put("totalTttls",totalTtls); ret.put("totalHops",totalHops); return ret; } } } }