package com.limegroup.gnutella.messages; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.LinkedList; import java.util.List; import org.limewire.core.settings.ApplicationSettings; import org.limewire.io.BadGGEPBlockException; import org.limewire.io.BadGGEPPropertyException; import org.limewire.io.GGEP; import org.limewire.service.ErrorService; import org.limewire.util.NameValue; import com.limegroup.gnutella.util.DataUtils; /** * A Gnutella ping message. */ public class PingRequestImpl extends AbstractMessage implements PingRequest { /** * 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. */ PingRequestImpl(byte[] guid, byte ttl, byte hops) { super(guid, Message.F_PING, ttl, hops, 0); } /** * Creates a big ping request from data read from the network. * * @param payload the headers etc. which the big pings contain */ PingRequestImpl(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 */ PingRequestImpl(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 */ PingRequestImpl(byte [] guid,byte ttl) { super(guid,(byte)0x0, ttl, (byte)0,0); addBasicGGEPs(); } /** * Creates a ping with the specified GUID, ttl, and GGEP fields. */ PingRequestImpl(byte[] guid, byte ttl, List<NameValue<?>> ggeps) { super(guid, (byte)0x0, ttl, (byte)0, 0); addGGEPs(ggeps); } /////////////////////////////methods/////////////////////////// @Override protected void writePayload(OutputStream out) throws IOException { if(payload != null && payload.length > 0 ) { out.write(payload); } } @Override 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 appears 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<NameValue<?>> l = new LinkedList<NameValue<?>>(); l.add(new NameValue(GGEPKeys.GGEP_HEADER_IPPORT)); addGGEPs(l); } /** * Adds all basic GGEP information to the outgoing ping. * Currently adds a Locale field. */ private void addBasicGGEPs() { List<NameValue<?>> l = new LinkedList<NameValue<?>>(); l.add(new NameValue<String>(GGEPKeys.GGEP_HEADER_CLIENT_LOCALE, ApplicationSettings.LANGUAGE.get())); addGGEPs(l); } /** * Adds the specified GGEPs. */ private void addGGEPs(List<? extends NameValue<?>> ggeps) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { if (_ggep == null) _ggep = new GGEP(); _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 && payload.length > 0) { try { parseGGEP(); if(_ggep.hasKey(GGEPKeys.GGEP_HEADER_CLIENT_LOCALE)) return _ggep.getString(GGEPKeys.GGEP_HEADER_CLIENT_LOCALE); } catch(BadGGEPBlockException ignored) { } catch(BadGGEPPropertyException ignoredToo) {} } return ApplicationSettings.DEFAULT_LOCALE.get(); } /** * Determines if this PingRequest has the 'supports cached pongs' * marking. */ public boolean supportsCachedPongs() { if(payload != null && payload.length > 0) { try { parseGGEP(); return _ggep.hasKey(GGEPKeys.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 && payload.length > 0) { try { parseGGEP(); if(_ggep.hasKey(GGEPKeys.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(GGEPKeys.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 && payload.length > 0) { try { parseGGEP(); return _ggep.hasKey(GGEPKeys.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 && payload.length > 0) { try { parseGGEP(); return _ggep.hasKey(GGEPKeys.GGEP_HEADER_IPPORT); } catch(BadGGEPBlockException ignored) {} } return false; } /** * @return whether this ping wants a reply carrying DHT IPP info */ public boolean requestsDHTIPP() { if(payload != null && payload.length > 0) { try { parseGGEP(); return _ggep.hasKey(GGEPKeys.GGEP_HEADER_DHT_IPPORTS); } catch(BadGGEPBlockException ignored) {} } return false; } @Override public Class<? extends Message> getHandlerClass() { return PingRequest.class; } private void parseGGEP() throws BadGGEPBlockException { if(_ggep == null) _ggep = new GGEP(payload, 0, null); } }