package com.limegroup.gnutella.messages; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.List; import java.util.LinkedList; import com.limegroup.gnutella.ErrorService; import com.limegroup.gnutella.RouterService; import com.limegroup.gnutella.GUID; import com.limegroup.gnutella.UDPService; import com.limegroup.gnutella.settings.ApplicationSettings; import com.limegroup.gnutella.settings.ConnectionSettings; import com.limegroup.gnutella.statistics.DroppedSentMessageStatHandler; import com.limegroup.gnutella.statistics.SentMessageStatHandler; import com.limegroup.gnutella.util.DataUtils; import com.limegroup.gnutella.util.NameValue; /** * A Gnutella ping message. */ public class PingRequest extends Message { /** * various flags related to the SCP ggep field */ public static final byte SCP_ULTRAPEER_OR_LEAF_MASK = 0x1; public static final byte SCP_LEAF = 0x0; public static final byte SCP_ULTRAPEER = 0x1; /** * With the Big Ping and Big Pong extensions pings may have a payload */ private byte[] payload = null; /** * The GGEP blocks carried in this ping - parsed when necessary */ private GGEP _ggep; /////////////////Constructors for incoming messages///////////////// /** * Creates a normal ping from data read on the network */ public PingRequest(byte[] guid, byte ttl, byte hops) { super(guid, Message.F_PING, ttl, hops, 0); } /** * Creates an incoming group ping. Used only by boot-strap server */ protected PingRequest(byte[] guid, byte ttl, byte hops, byte length) { super(guid, Message.F_PING, ttl, hops, length); } /** * Creates a big ping request from data read from the network * * @param payload the headers etc. which the big pings contain. */ public PingRequest(byte[] guid, byte ttl, byte hops, byte[] payload) { super(guid, Message.F_PING, ttl, hops, payload.length); this.payload = payload; } //////////////////////Constructors for outgoing Pings///////////// /** * Creates a normal ping with a new GUID * * @param ttl the ttl of the new Ping */ public PingRequest(byte ttl) { super((byte)0x0, ttl, (byte)0); addBasicGGEPs(); } /** * Creates a normal ping with a specified GUID * * @param ttl the ttl of the new Ping */ public PingRequest(byte [] guid,byte ttl) { super(guid,(byte)0x0, ttl, (byte)0,0); addBasicGGEPs(); } /** * Creates a ping with the specified GUID, ttl, and GGEP fields. */ private PingRequest(byte[] guid, byte ttl, List /* of NameValue */ ggeps) { super(guid, (byte)0x0, ttl, (byte)0, 0); addGGEPs(ggeps); } /** * Creates a Query Key ping. */ public static PingRequest createQueryKeyRequest() { List l = new LinkedList(); l.add(new NameValue(GGEP.GGEP_HEADER_QUERY_KEY_SUPPORT)); return new PingRequest(GUID.makeGuid(), (byte)1, l); } /** * Creates a TTL 1 Ping for faster bootstrapping, intended * for sending to UDP hosts. */ public static PingRequest createUDPPing() { List l = new LinkedList(); return new PingRequest(populateUDPGGEPList(l).bytes(), (byte)1, l); } /** * Creates a TTL 1 Ping for faster bootstrapping, intended * for sending to UHCs. */ public static PingRequest createUHCPing() { List ggeps = new LinkedList(); GUID guid = populateUDPGGEPList(ggeps); ggeps.add(new NameValue(GGEP.GGEP_HEADER_UDP_HOST_CACHE)); return new PingRequest(guid.bytes(),(byte)1,ggeps); } /** * @param l list to put the standard extentions we add to UDP pings * @return the guid to use for the ping */ private static GUID populateUDPGGEPList(List l) { GUID guid; if(ConnectionSettings.EVER_ACCEPTED_INCOMING.getValue()) { guid = new GUID(); } else { l.add(new NameValue(GGEP.GGEP_HEADER_IPPORT)); guid = UDPService.instance().getSolicitedGUID(); } byte[] data = new byte[1]; if(RouterService.isSupernode()) data[0] = SCP_ULTRAPEER; else data[0] = SCP_LEAF; l.add(new NameValue(GGEP.GGEP_HEADER_SUPPORT_CACHE_PONGS, data)); return guid; } /** * Creates a TTL 1 Ping for faster bootstrapping, intended * for sending to the multicast network. */ public static PingRequest createMulticastPing() { GUID guid = new GUID(); byte[] data = new byte[1]; if(RouterService.isSupernode()) data[0] = 0x1; else data[0] = 0x0; List l = new LinkedList(); l.add(new NameValue(GGEP.GGEP_HEADER_SUPPORT_CACHE_PONGS, data)); return new PingRequest(guid.bytes(), (byte)1, l); } /////////////////////////////methods/////////////////////////// protected void writePayload(OutputStream out) throws IOException { if(payload != null) { out.write(payload); } // the ping is still written even if there's no payload SentMessageStatHandler.TCP_PING_REQUESTS.addMessage(this); //Do nothing...there is no payload! } public Message stripExtendedPayload() { if (payload==null) return this; else return new PingRequest(this.getGUID(), this.getTTL(), this.getHops()); } // inherit doc comment public void recordDrop() { DroppedSentMessageStatHandler.TCP_PING_REQUESTS.addMessage(this); } public String toString() { return "PingRequest("+super.toString()+")"; } /** * Accessor for whether or not this ping meets the criteria for being a * "heartbeat" ping, namely having ttl=0 and hops=1. * * @return <tt>true</tt> if this ping apears to be a "heartbeat" ping, * otherwise <tt>false</tt> */ public boolean isHeartbeat() { return (getHops() == 1 && getTTL() == 0); } /** * Marks this ping request as requesting a pong carrying * an ip:port info. */ public void addIPRequest() { List l = new LinkedList(); l.add(new NameValue(GGEP.GGEP_HEADER_IPPORT)); addGGEPs(l); } /** * Adds all basic GGEP information to the outgoing ping. * Currently adds a Locale field. */ private void addBasicGGEPs() { List l = new LinkedList(); l.add(new NameValue(GGEP.GGEP_HEADER_CLIENT_LOCALE, ApplicationSettings.LANGUAGE.getValue())); addGGEPs(l); } /** * Adds the specified GGEPs. */ private void addGGEPs(List /* of NameValue */ ggeps) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { if (_ggep == null) _ggep = new GGEP(true); _ggep.putAll(ggeps); _ggep.write(baos); baos.write(0); payload = baos.toByteArray(); updateLength(payload.length); } catch(IOException e) { ErrorService.error(e); } } /** * get locale of this PingRequest */ public String getLocale() { if(payload != null) { try { parseGGEP(); if(_ggep.hasKey(GGEP.GGEP_HEADER_CLIENT_LOCALE)) return _ggep.getString(GGEP.GGEP_HEADER_CLIENT_LOCALE); } catch(BadGGEPBlockException ignored) { } catch(BadGGEPPropertyException ignoredToo) {} } return ApplicationSettings.DEFAULT_LOCALE.getValue(); } /** * Determines if this PingRequest has the 'supports cached pongs' * marking. */ public boolean supportsCachedPongs() { if(payload != null) { try { parseGGEP(); return _ggep.hasKey(GGEP.GGEP_HEADER_SUPPORT_CACHE_PONGS); } catch(BadGGEPBlockException ignored) {} } return false; } /** * Gets the data value for the SCP field, if one exists. * If none exist, null is returned. Else, a byte[] of some * size is returned. */ public byte[] getSupportsCachedPongData() { byte[] ret = null; if(payload != null) { try { parseGGEP(); if(_ggep.hasKey(GGEP.GGEP_HEADER_SUPPORT_CACHE_PONGS)) { ret = DataUtils.EMPTY_BYTE_ARRAY; // this may throw, which is why we first set it to an empty value. return _ggep.getBytes(GGEP.GGEP_HEADER_SUPPORT_CACHE_PONGS); } } catch(BadGGEPBlockException ignored) { } catch(BadGGEPPropertyException ignored) { } } return ret; } public boolean isQueryKeyRequest() { if (!(getTTL() == 0) || !(getHops() == 1)) return false; if(payload != null) { try { parseGGEP(); return _ggep.hasKey(GGEP.GGEP_HEADER_QUERY_KEY_SUPPORT); } catch (BadGGEPBlockException ignored) {} } return false; } /** * @return whether this ping wants a reply carrying IP:Port info. */ public boolean requestsIP() { if(payload != null) { try { parseGGEP(); return _ggep.hasKey(GGEP.GGEP_HEADER_IPPORT); } catch(BadGGEPBlockException ignored) {} } return false; } private void parseGGEP() throws BadGGEPBlockException { if(_ggep == null) _ggep = new GGEP(payload, 0, null); } }