package com.limegroup.gnutella.messages; import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; import com.limegroup.gnutella.Assert; import com.limegroup.gnutella.ByteOrder; import com.limegroup.gnutella.statistics.DroppedSentMessageStatHandler; import com.limegroup.gnutella.statistics.ReceivedErrorStat; import com.limegroup.gnutella.statistics.SentMessageStatHandler; import com.limegroup.gnutella.util.NetworkUtils; /** * A Gnutella push request, used to download files behind a firewall. */ public class PushRequest extends Message implements Serializable { private static final int STANDARD_PAYLOAD_SIZE=26; public static final long FW_TRANS_INDEX = Integer.MAX_VALUE - 2; /** The unparsed payload--because I don't care what's inside. * NOTE: IP address is BIG-endian. */ private byte[] payload; /** * Wraps a PushRequest around stuff snatched from the network. * @exception BadPacketException the payload length is wrong */ public PushRequest(byte[] guid, byte ttl, byte hops, byte[] payload, int network) throws BadPacketException { super(guid, Message.F_PUSH, ttl, hops, payload.length, network); if (payload.length < STANDARD_PAYLOAD_SIZE) { ReceivedErrorStat.PUSH_INVALID_PAYLOAD.incrementStat(); throw new BadPacketException("Payload too small: "+payload.length); } this.payload=payload; if(!NetworkUtils.isValidPort(getPort())) { ReceivedErrorStat.PUSH_INVALID_PORT.incrementStat(); throw new BadPacketException("invalid port"); } String ip = NetworkUtils.ip2string(payload, 20); if(!NetworkUtils.isValidAddress(ip)) { ReceivedErrorStat.PUSH_INVALID_ADDRESS.incrementStat(); throw new BadPacketException("invalid address: " + ip); } } /** * Creates a new PushRequest from scratch. * * @requires clientGUID.length==16, * 0 < index < 2^32 (i.e., can fit in 4 unsigned bytes), * ip.length==4 and ip is in <i>BIG-endian</i> byte order, * 0 < port < 2^16 (i.e., can fit in 2 unsigned bytes), */ public PushRequest(byte[] guid, byte ttl, byte[] clientGUID, long index, byte[] ip, int port) { this(guid, ttl, clientGUID, index, ip, port, Message.N_UNKNOWN); } /** * Creates a new PushRequest from scratch. Allows the caller to * specify the network. * * @requires clientGUID.length==16, * 0 < index < 2^32 (i.e., can fit in 4 unsigned bytes), * ip.length==4 and ip is in <i>BIG-endian</i> byte order, * 0 < port < 2^16 (i.e., can fit in 2 unsigned bytes), */ public PushRequest(byte[] guid, byte ttl, byte[] clientGUID, long index, byte[] ip, int port, int network) { super(guid, Message.F_PUSH, ttl, (byte)0, STANDARD_PAYLOAD_SIZE,network); if(clientGUID.length != 16) { throw new IllegalArgumentException("invalid guid length: "+ clientGUID.length); } else if((index&0xFFFFFFFF00000000l)!=0) { throw new IllegalArgumentException("invalid index: "+index); } else if(ip.length!=4) { throw new IllegalArgumentException("invalid ip length: "+ ip.length); } else if(!NetworkUtils.isValidAddress(ip)) { throw new IllegalArgumentException("invalid ip "+NetworkUtils.ip2string(ip)); } else if(!NetworkUtils.isValidPort(port)) { throw new IllegalArgumentException("invalid port: "+port); } payload=new byte[STANDARD_PAYLOAD_SIZE]; System.arraycopy(clientGUID, 0, payload, 0, 16); ByteOrder.int2leb((int)index,payload,16); //downcast ok payload[20]=ip[0]; //big endian payload[21]=ip[1]; payload[22]=ip[2]; payload[23]=ip[3]; ByteOrder.short2leb((short)port,payload,24); //downcast ok } protected void writePayload(OutputStream out) throws IOException { out.write(payload); SentMessageStatHandler.TCP_PUSH_REQUESTS.addMessage(this); } public byte[] getClientGUID() { byte[] ret=new byte[16]; System.arraycopy(payload, 0, ret, 0, 16); return ret; } public long getIndex() { return ByteOrder.uint2long(ByteOrder.leb2int(payload, 16)); } public boolean isFirewallTransferPush() { return (getIndex() == FW_TRANS_INDEX); } public byte[] getIP() { byte[] ret=new byte[4]; ret[0]=payload[20]; ret[1]=payload[21]; ret[2]=payload[22]; ret[3]=payload[23]; return ret; } public int getPort() { return ByteOrder.ushort2int(ByteOrder.leb2short(payload, 24)); } public Message stripExtendedPayload() { //TODO: if this is too slow, we can alias parts of this, as as the //payload. In fact we could even return a subclass of PushRequest that //simply delegates to this. byte[] newPayload=new byte[STANDARD_PAYLOAD_SIZE]; System.arraycopy(payload, 0, newPayload, 0, STANDARD_PAYLOAD_SIZE); try { return new PushRequest(this.getGUID(), this.getTTL(), this.getHops(), newPayload, this.getNetwork()); } catch (BadPacketException e) { Assert.that(false, "Standard packet length not allowed!"); return null; } } // inherit doc comment public void recordDrop() { DroppedSentMessageStatHandler.TCP_PUSH_REQUESTS.addMessage(this); } public String toString() { return "PushRequest("+super.toString()+" "+ NetworkUtils.ip2string(getIP())+":"+getPort()+")"; } //Unit tests: tests/com/limegroup/gnutella/messages/PushRequestTest }