package com.limegroup.gnutella.messages; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import org.limewire.io.BadGGEPBlockException; import org.limewire.io.GGEP; import org.limewire.io.GUID; import org.limewire.io.NetworkUtils; import org.limewire.service.ErrorService; import org.limewire.util.ByteUtils; import com.limegroup.gnutella.util.DataUtils; /** A Gnutella push request, used to download files behind a firewall. */ public class PushRequestImpl extends AbstractMessage implements PushRequest { private static final int STANDARD_PAYLOAD_SIZE=26; /** A null GGEP to mark a failed parsing. */ private static final GGEP NULL_GGEP = new GGEP(); /** The unparsed payload--because I don't care what's inside. * NOTE: IP address is BIG-endian. */ private byte[] payload; /** The GGEP, if parsed. */ private GGEP ggep; /** * Wraps a PushRequest around stuff snatched from the network. * @exception BadPacketException the payload length is wrong */ public PushRequestImpl(byte[] guid, byte ttl, byte hops, byte[] payload, Network network) throws BadPacketException { super(guid, Message.F_PUSH, ttl, hops, payload.length, network); if (payload.length < STANDARD_PAYLOAD_SIZE) { throw new BadPacketException("Payload too small: "+payload.length); } this.payload=payload; if(!NetworkUtils.isValidPort(getPort())) { throw new BadPacketException("invalid port"); } String ip = NetworkUtils.ip2string(payload, 20); if(!NetworkUtils.isValidAddress(ip)) { 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 PushRequestImpl(byte[] guid, byte ttl, byte[] clientGUID, long index, byte[] ip, int port) { this(guid, ttl, clientGUID, index, ip, port, Network.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 PushRequestImpl(byte[] guid, byte ttl, byte[] clientGUID, long index, byte[] ip, int port, Network network) { this(guid, ttl, clientGUID, index, ip, port, network, false); } /** Constructs a new PushRequest that optionally includes TLS data. */ public PushRequestImpl(byte[] guid, byte ttl, byte[] clientGUID, long index, byte[] ip, int port, Network network, boolean useTLS) { super(guid, Message.F_PUSH, ttl, (byte)0, 0,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); } byte[] extra = DataUtils.EMPTY_BYTE_ARRAY; if(useTLS) extra = PushGGEPHelper.TLS_GGEP; int payloadSize = STANDARD_PAYLOAD_SIZE + extra.length; payload=new byte[payloadSize]; System.arraycopy(clientGUID, 0, payload, 0, 16); ByteUtils.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]; ByteUtils.short2leb((short)port,payload,24); //downcast ok System.arraycopy(extra, 0, payload, STANDARD_PAYLOAD_SIZE, extra.length); updateLength(payloadSize); } /* (non-Javadoc) * @see com.limegroup.gnutella.messages.PushRequest#isTLSCapable() */ public boolean isTLSCapable() { parseGGEP(); if(ggep != null && ggep != NULL_GGEP) { return ggep.hasKey(GGEPKeys.GGEP_HEADER_TLS_CAPABLE); } else { return false; } } /** Parses the GGEP block in a push, if one exists. */ private void parseGGEP() { if(ggep == null && payload.length > STANDARD_PAYLOAD_SIZE) { try { ggep = new GGEP(payload, STANDARD_PAYLOAD_SIZE); } catch (BadGGEPBlockException e) { ggep = NULL_GGEP; } } } @Override protected void writePayload(OutputStream out) throws IOException { out.write(payload); } /* (non-Javadoc) * @see com.limegroup.gnutella.messages.PushRequest#getClientGUID() */ public byte[] getClientGUID() { byte[] ret=new byte[16]; System.arraycopy(payload, 0, ret, 0, 16); return ret; } /* (non-Javadoc) * @see com.limegroup.gnutella.messages.PushRequest#getIndex() */ public long getIndex() { return ByteUtils.uint2long(ByteUtils.leb2int(payload, 16)); } /* (non-Javadoc) * @see com.limegroup.gnutella.messages.PushRequest#isFirewallTransferPush() */ public boolean isFirewallTransferPush() { return (getIndex() == FW_TRANS_INDEX); } /* (non-Javadoc) * @see com.limegroup.gnutella.messages.PushRequest#getIP() */ 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; } /* (non-Javadoc) * @see com.limegroup.gnutella.messages.PushRequest#getPort() */ public int getPort() { return ByteUtils.ushort2int(ByteUtils.leb2short(payload, 24)); } @Override public Class<? extends Message> getHandlerClass() { return PushRequest.class; } @Override public String toString() { StringBuilder builder = new StringBuilder("PushRequest("); builder.append(super.toString()).append("\n"); builder.append(NetworkUtils.ip2string(getIP())+":"+getPort()).append("\n"); builder.append("FWT push: ").append(isFirewallTransferPush()).append("\n"); builder.append("TLS: ").append(isTLSCapable()).append("\n"); builder.append("Client GUID: ").append(GUID.toHexString(getClientGUID())).append("\n"); return builder.toString(); } /** A simple GGEP helper that precaches commonly used GGEPs. */ private static class PushGGEPHelper { private static final byte[] TLS_GGEP; static { GGEP tlsGGEP = new GGEP(); tlsGGEP.put(GGEPKeys.GGEP_HEADER_TLS_CAPABLE); ByteArrayOutputStream out = new ByteArrayOutputStream(); try { tlsGGEP.write(out); } catch(IOException impossible) { ErrorService.error(impossible); } TLS_GGEP = out.toByteArray(); } private PushGGEPHelper() {} } }