package com.limegroup.gnutella.handshaking;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.settings.ApplicationSettings;
import com.limegroup.gnutella.settings.ConnectionSettings;
import com.limegroup.gnutella.util.CommonUtils;
import com.limegroup.gnutella.util.IpPort;
/**
* This class contains the necessary information to form a response to a
* connection handshake. It contains a status code, a status message, and
* the headers to use in the response.
*
* There are only two ways to create a HandshakeResponse.
*
* 1) Create an instance which defaults the status code and status message to
* be "200 OK". Only the headers used in the response need to be passed in.
*
* 2) Create an instance with a custom status code, status message, and the
* headers used in the response.
*/
public class HandshakeResponse {
/**
* The "default" status code in a connection handshake indicating that
* the handshake was successful and the connection can be established.
*/
public static final int OK = 200;
/**
* The "default" status message in a connection handshake indicating that
* the handshake was successful and the connection can be established.
*/
public static final String OK_MESSAGE = "OK";
/**
* HTTP response code for the crawler.
*/
public static final int CRAWLER_CODE = 593;
/**
* HTTP response message for the crawler.
*/
public static final String CRAWLER_MESSAGE = "Hi";
/** The error code that a shielded leaf node should give to incoming
* connections. */
public static final int SHIELDED = 503;
/** The error message that a shielded leaf node should give to incoming
* connections. */
public static final String SHIELDED_MESSAGE = "I am a shielded leaf node";
/** The error code that a node with no slots should give to incoming
* connections. */
public static final int SLOTS_FULL = 503;
/** The error message that a node with no slots should give to incoming
* connections. */
public static final String SLOTS_FULL_MESSAGE = "Service unavailable";
/**
* Default bad status code to be used while rejecting connections
*/
public static final int DEFAULT_BAD_STATUS_CODE = 503;
/**
* Default bad status message to be used while rejecting connections
*/
public static final String DEFAULT_BAD_STATUS_MESSAGE
= "Service Not Available";
/**
* ??? TODO: check about this error code...
*/
public static final int LOCALE_NO_MATCH = 577;
public static final String LOCALE_NO_MATCH_MESSAGE
= "Service Not Available";
/**
* HTTP-like status code used when handshaking (e.g., 200, 401, 503).
*/
private final int STATUS_CODE;
/**
* Message used with status code when handshaking (e.g., "OK, "Service Not
* Available"). The status message together with the status code make up
* the status line (i.e., first line) of an HTTP-like response to a
* connection handshake.
*/
private final String STATUS_MESSAGE;
/**
* Headers to use in the response to a connection handshake.
*/
private final Properties HEADERS;
/**
* is the GGEP header set?
*/
private Boolean _supportsGGEP;
/**
* Cached boolean for whether or not this is considered a considered a
* "good" leaf connection.
*/
private final boolean GOOD_LEAF;
/**
* Cached boolean for whether or not this is considered a considered a
* "good" ultrapeer connection.
*/
private final boolean GOOD_ULTRAPEER;
/**
* Cached value for the number of Ultrapeers this Ultrapeer attempts
* to connect to.
*/
private final int DEGREE;
/**
* Cached value for whether or not this is a high degree connection.
*/
private final boolean HIGH_DEGREE;
/**
* Cached value for whether or not this is an Ultrapeer connection that
* supports Ultrapeer query routing.
*/
private final boolean ULTRAPEER_QRP;
/**
* Cached value for the maximum TTL to use along this connection.
*/
private final byte MAX_TTL;
/**
* Cached value for whether or not this connection supports dynamic
* querying.
*/
private final boolean DYNAMIC_QUERY;
/**
* Cached value for whether or not this connection reported
* X-Ultrapeer: true in it's handshake headers.
*/
private final boolean ULTRAPEER;
/**
* Cached value for whether or not this connection reported
* X-Ultrapeer: false in it's handshake headers.
*/
private final boolean LEAF;
/**
* Cached value for whether or not the connection reported
* Content-Encoding: deflate
*/
private final boolean DEFLATE_ENCODED;
/**
* Constant for whether or not this connection supports probe
* queries.
*/
private final boolean PROBE_QUERIES;
/**
* Constant for whether or not this node supports pong caching.
*/
private final boolean PONG_CACHING;
/**
* Constant for whether or not this node supports GUESS.
*/
private final boolean GUESS_CAPABLE;
/**
* Constant for whether or not this is a crawler.
*/
private final boolean IS_CRAWLER;
/**
* Constant for whether or not this node is a LimeWire (or derivative)
*/
private final boolean IS_LIMEWIRE;
/**
* Constant for whether or nor this node is an older limewire.
*/
private final boolean IS_OLD_LIMEWIRE;
/**
* Constant for whether or not the client claims to do no requerying.
*/
private final boolean NO_REQUERYING;
/**
* Locale
*/
private final String LOCALE_PREF;
/**
* Constant for the number of hosts to return in X-Try-Ultrapeer headers.
*/
private static final int NUM_X_TRY_ULTRAPEER_HOSTS = 10;
/**
* Creates a <tt>HandshakeResponse</tt> which defaults the status code and
* status message to be "200 Ok" and uses the desired headers in the
* response.
*
* @param headers the headers to use in the response.
*/
protected HandshakeResponse(Properties headers) {
this(OK, OK_MESSAGE, headers);
}
/**
* Creates a new <tt>HandshakeResponse</tt> instance with the specified
* response code and message and with no extra connection headers.
*
* @param code the status code for the response
* @param message the status message
*/
protected HandshakeResponse(int code, String message) {
this(code, message, new Properties());
}
/**
* Creates a HandshakeResponse with the desired status code, status message,
* and headers to respond with.
* @param code the response code to use.
* @param message the response message to use.
* @param headers the headers to use in the response.
*/
HandshakeResponse(int code, String message, Properties headers) {
STATUS_CODE = code;
STATUS_MESSAGE = message;
HEADERS = headers;
DEGREE = extractIntHeaderValue(HEADERS, HeaderNames.X_DEGREE, 6);
HIGH_DEGREE = getNumIntraUltrapeerConnections() >= 15;
ULTRAPEER_QRP =
isVersionOrHigher(HEADERS,
HeaderNames.X_ULTRAPEER_QUERY_ROUTING, 0.1F);
MAX_TTL = extractByteHeaderValue(HEADERS, HeaderNames.X_MAX_TTL,
(byte)4);
DYNAMIC_QUERY =
isVersionOrHigher(HEADERS, HeaderNames.X_DYNAMIC_QUERY, 0.1F);
PROBE_QUERIES =
isVersionOrHigher(HEADERS, HeaderNames.X_PROBE_QUERIES, 0.1F);
NO_REQUERYING = isFalseValue(HEADERS, HeaderNames.X_REQUERIES);
IS_LIMEWIRE =
extractStringHeaderValue(headers, HeaderNames.USER_AGENT).
toLowerCase().startsWith("limewire");
GOOD_ULTRAPEER = isHighDegreeConnection() &&
isUltrapeerQueryRoutingConnection() &&
(getMaxTTL() < 5) &&
isDynamicQueryConnection();
GOOD_LEAF = GOOD_ULTRAPEER && (IS_LIMEWIRE || NO_REQUERYING);
ULTRAPEER = isTrueValue(HEADERS, HeaderNames.X_ULTRAPEER);
LEAF = isFalseValue(HEADERS, HeaderNames.X_ULTRAPEER);
DEFLATE_ENCODED = isStringValue(HEADERS,
HeaderNames.CONTENT_ENCODING, HeaderNames.DEFLATE_VALUE);
PONG_CACHING =
isVersionOrHigher(headers, HeaderNames.X_PONG_CACHING, 0.1F);
GUESS_CAPABLE =
isVersionOrHigher(headers, HeaderNames.X_GUESS, 0.1F);
IS_CRAWLER =
isVersionOrHigher(headers, HeaderNames.CRAWLER, 0.1F);
IS_OLD_LIMEWIRE = IS_LIMEWIRE &&
oldVersion(extractStringHeaderValue(headers, HeaderNames.USER_AGENT));
String loc = extractStringHeaderValue(headers,
HeaderNames.X_LOCALE_PREF);
LOCALE_PREF = (loc.equals(""))?
ApplicationSettings.DEFAULT_LOCALE.getValue():
loc;
}
/**
* @return true if the version of limewire we are connected to is old
*/
private boolean oldVersion(String userAgent) {
StringTokenizer tok = new StringTokenizer(userAgent,"/.");
int major = -1;
int minor = -1;
boolean ret = false;
boolean error = false;
if(tok.countTokens() < 3) //not limewire
return false;
try {
String str = tok.nextToken();//"limewire"
str = tok.nextToken();
major = Integer.parseInt(str);
str = tok.nextToken();
minor = Integer.parseInt(str);
} catch (NumberFormatException nfx) {
error = true;
}
if(!error && (major<3 || (major==3 && minor < 4)) )
ret = true;
return ret;
}
private static final HandshakeResponse EMPTY_RESPONSE = new HandshakeResponse(new Properties());
/**
* Creates an empty response with no headers. This is useful, for
* example, during connection handshaking when we haven't yet read
* any headers.
*
* @return a new, empty <tt>HandshakeResponse</tt> instance
*/
public static HandshakeResponse createEmptyResponse() {
return EMPTY_RESPONSE;
}
/**
* Constructs the response from the other host during connection
* handshaking.
*
* @return a new <tt>HandshakeResponse</tt> instance with the headers
* sent by the other host
*/
public static HandshakeResponse createResponse(Properties headers) {
return new HandshakeResponse(headers);
}
/**
* Constructs the response from the other host during connection
* handshaking. The returned response contains the connection headers
* sent by the remote host.
*
* @param line the status line received from the connecting host
* @param headers the headers received from the other host
* @return a new <tt>HandshakeResponse</tt> instance with the headers
* sent by the other host
* @throws <tt>IOException</tt> if the status line could not be parsed
*/
public static HandshakeResponse createRemoteResponse(String line,Properties headers) throws IOException {
int code = extractCode(line);
if(code == -1)
throw new IOException("could not parse status code: "+line);
String message = extractMessage(line);
if(message == null)
throw new IOException("could not parse status message: "+line);
return new HandshakeResponse(code, message, headers);
}
/**
* Creates a new <tt>HandshakeResponse</tt> instance that accepts the
* potential connection.
*
* @param headers the <tt>Properties</tt> instance containing the headers
* to send to the node we're accepting
*/
static HandshakeResponse createAcceptIncomingResponse(HandshakeResponse response, Properties headers) {
return new HandshakeResponse(addXTryHeader(response, headers));
}
/**
* Creates a new <tt>HandshakeResponse</tt> instance that accepts the outgoing connection -- the final third step
* in the handshake. This passes no headers, as all necessary headers have already been exchanged. The only possible
* exception is the potential inclusion of X-Ultrapeer: false.
*
* @param headers the <tt>Properties</tt> instance containing the headers to send to the node we're accepting
*/
static HandshakeResponse createAcceptOutgoingResponse(Properties headers) {
return new HandshakeResponse(headers);
}
/**
* Creates a new <tt>HandshakeResponse</tt> instance that responds to a
* special crawler connection with connected leaves and Ultrapeers. See the
* Files>>Development section on the GDF.
*
* @param headers the <tt>Properties</tt> instance containing the headers
* to send to the node we're rejecting
*/
static HandshakeResponse createCrawlerResponse() {
Properties headers = new Properties();
// add our user agent
headers.put(HeaderNames.USER_AGENT, CommonUtils.getHttpServer());
headers.put(HeaderNames.X_ULTRAPEER, ""+RouterService.isSupernode());
// add any leaves
List leaves =
RouterService.getConnectionManager().
getInitializedClientConnections();
headers.put(HeaderNames.LEAVES,
createEndpointString(leaves, leaves.size()));
// add any Ultrapeers
List ultrapeers =
RouterService.getConnectionManager().getInitializedConnections();
headers.put(HeaderNames.PEERS,
createEndpointString(ultrapeers, ultrapeers.size()));
return new HandshakeResponse(HandshakeResponse.CRAWLER_CODE,
HandshakeResponse.CRAWLER_MESSAGE, headers);
}
/**
* Creates a new <tt>HandshakeResponse</tt> instance that rejects the
* potential connection. This includes the X-Try-Ultrapeers header to
* tell the remote host about other nodes to connect to. We return the
* hosts we most recently knew to have free leaf or ultrapeer connection
* slots.
*
* @param hr the <tt>HandshakeResponse</tt> containing the connection
* headers of the connecting host
* @return a <tt>HandshakeResponse</tt> with the appropriate response
* headers
*/
static HandshakeResponse
createUltrapeerRejectIncomingResponse(HandshakeResponse hr) {
return new HandshakeResponse(HandshakeResponse.SLOTS_FULL,
HandshakeResponse.SLOTS_FULL_MESSAGE,
addXTryHeader(hr, new Properties()));
}
/**
* Creates a new <tt>HandshakeResponse</tt> instance that rejects the
* potential connection. The returned <tt>HandshakeResponse</tt> DOES
* NOT include the X-Try-Ultrapeers header because this is an outgoing
* connection, and we should not send host data that the remote client
* does not request.
*
* @param headers the <tt>Properties</tt> instance containing the headers
* to send to the node we're rejecting
*/
static HandshakeResponse createRejectOutgoingResponse() {
return new HandshakeResponse(HandshakeResponse.SLOTS_FULL,
HandshakeResponse.SLOTS_FULL_MESSAGE,
new Properties());
}
/**
* Creates a new <tt>HandshakeResponse</tt> instance that rejects the
* potential connection to a leaf. We add hosts that we know about with
* free connection slots to the X-Try-Ultrapeers header.
*
* @param headers the <tt>Properties</tt> instance containing the headers
* to send to the node we're rejecting
* @param hr the <tt>HandshakeResponse</tt> containing the headers of the
* remote host
* @return a new <tt>HandshakeResponse</tt> instance rejecting the
* connection and with the specified connection headers
*/
static HandshakeResponse
createLeafRejectIncomingResponse(HandshakeResponse hr) {
return new HandshakeResponse(HandshakeResponse.SLOTS_FULL,
HandshakeResponse.SHIELDED_MESSAGE,
addXTryHeader(hr, new Properties()));
}
/**
* Creates a new <tt>HandshakeResponse</tt> instance that rejects an
* outgoing leaf connection. This occurs when we, as a leaf, reject a
* connection on the third stage of the handshake.
*
* @return a new <tt>HandshakeResponse</tt> instance rejecting the
* connection and with no extra headers
*/
static HandshakeResponse createLeafRejectOutgoingResponse() {
return new HandshakeResponse(HandshakeResponse.SLOTS_FULL,
HandshakeResponse.SHIELDED_MESSAGE);
}
static HandshakeResponse createLeafRejectLocaleOutgoingResponse() {
return new HandshakeResponse(HandshakeResponse.LOCALE_NO_MATCH,
HandshakeResponse.LOCALE_NO_MATCH_MESSAGE);
}
/**
* Creates a new String of hosts, limiting the number of hosts to add to
* the default value of 10. This is particularly used for the
* X-Try-Ultrapeers header.
*
* @param iter a <tt>Collection</tt> of <tt>IpPort</tt> instances
* @return a string of the form IP:port,IP:port,... from the given list of
* hosts
*/
private static String createEndpointString(Collection hosts) {
return createEndpointString(hosts, NUM_X_TRY_ULTRAPEER_HOSTS);
}
/**
* Utility method that takes the specified list of hosts and returns a
* string of the form:<p>
*
* IP:port,IP:port,IP:port
*
* @param iter a <tt>Collection</tt> of <tt>IpPort</tt> instances
* @return a string of the form IP:port,IP:port,... from the given list of
* hosts
*/
private static String createEndpointString(Collection hosts, int limit) {
StringBuffer sb = new StringBuffer();
int i = 0;
Iterator iter = hosts.iterator();
while(iter.hasNext() && i<limit) {
IpPort host = (IpPort)iter.next();
sb.append(host.getAddress());
sb.append(":");
sb.append(host.getPort());
if(iter.hasNext()) {
sb.append(",");
}
i++;
}
return sb.toString();
}
/**
* Utility method to extract the connection code from the connect string,
* such as "200" in a "200 OK" message.
*
* @param line the full connection string, such as "200 OK."
* @return the status code for the connection string, or -1 if the code
* could not be parsed
*/
private static int extractCode(String line) {
//get the status code and message out of the status line
int statusMessageIndex = line.indexOf(" ");
if(statusMessageIndex == -1) return -1;
try {
return Integer.parseInt(line.substring(0, statusMessageIndex).trim());
} catch(NumberFormatException e) {
return -1;
}
}
/**
* Utility method to extract the connection message from the connect string,
* such as "OK" in a "200 OK" message.
*
* @param line the full connection string, such as "200 OK."
* @return the status message for the connection string
*/
private static String extractMessage(String line) {
//get the status code and message out of the status line
int statusMessageIndex = line.indexOf(" ");
if(statusMessageIndex == -1) return null;
return line.substring(statusMessageIndex).trim();
}
/**
* Utility method for creating a set of headers with the X-Try-Ultrapeers
* header set according to the headers from the remote host.
*
* @param hr the <tt>HandshakeResponse</tt> of the incoming request
* @return a new <tt>Properties</tt> instance with the X-Try-Ultrapeers
* header set according to the incoming headers from the remote host
*/
private static Properties addXTryHeader(HandshakeResponse hr, Properties headers) {
Collection hosts = RouterService.getPreferencedHosts(hr.isUltrapeer(), hr.getLocalePref(), 10);
headers.put(HeaderNames.X_TRY_ULTRAPEERS, createEndpointString(hosts));
return headers;
}
/**
* Returns the response code.
*/
public int getStatusCode() {
return STATUS_CODE;
}
/**
* Returns the status message.
* @return the status message (e.g. "OK" , "Service Not Available" etc.)
*/
public String getStatusMessage(){
return STATUS_MESSAGE;
}
/**
* Tells if the status returned was OK or not.
* @return true, if the status returned was not the OK status, false
* otherwise
*/
public boolean notOKStatusCode(){
if(STATUS_CODE != OK)
return true;
else
return false;
}
/**
* Returns whether or not this connection was accepted -- whether
* or not the connection returned Gnutella/0.6 200 OK
*
* @return <tt>true</tt> if the server returned Gnutella/0.6 200 OK,
* otherwise <tt>false</tt>
*/
public boolean isAccepted() {
return STATUS_CODE == OK;
}
/**
* Returns the status code and status message together used in a
* status line. (e.g., "200 OK", "503 Service Not Available")
*/
public String getStatusLine() {
return new String(STATUS_CODE + " " + STATUS_MESSAGE);
}
/**
* Returns the headers as a <tt>Properties</tt> instance.
*/
public Properties props() {
return HEADERS;
}
/**
* Accessor for an individual property.
*/
public String getProperty(String prop) {
return HEADERS.getProperty(prop);
}
/** Returns the vendor string reported by this connection, i.e.,
* the USER_AGENT property, or null if it wasn't set.
* @return the vendor string, or null if unknown */
public String getUserAgent() {
return HEADERS.getProperty(HeaderNames.USER_AGENT);
}
/**
* Returns the maximum TTL that queries originating from us and
* sent from this connection should have. If the max TTL header is
* not present, the default TTL is assumed.
*
* @return the maximum TTL that queries sent to this connection
* should have -- this will always be 5 or less
*/
public byte getMaxTTL() {
return MAX_TTL;
}
/**
* Accessor for the X-Try-Ultrapeers header. If the header does not
* exist or is empty, this returns the emtpy string.
*
* @return the string of X-Try-Ultrapeer hosts, or the empty string
* if they do not exist
*/
public String getXTryUltrapeers() {
return extractStringHeaderValue(HEADERS, HeaderNames.X_TRY_ULTRAPEERS);
}
/**
* This is a convenience method to see if the connection passed
* the X-Try-Ultrapeer header. This simply checks the existence of the
* header -- if the header was sent but is empty, this still returns
* <tt>true</tt>.
*
* @return <tt>true</tt> if this connection sent the X-Try-Ultrapeer
* header, otherwise <tt>false</tt>
*/
public boolean hasXTryUltrapeers() {
return headerExists(HEADERS, HeaderNames.X_TRY_ULTRAPEERS);
}
/**
* Returns whether or not this host included leaf guidance, i.e.,
* whether or not the host wrote:
*
* X-Ultrapeer-Needed: false
*
* @return <tt>true</tt> if the other host returned
* X-Ultrapeer-Needed: false, otherwise <tt>false</tt>
*/
public boolean hasLeafGuidance() {
return isFalseValue(HEADERS, HeaderNames.X_ULTRAPEER_NEEDED);
}
/**
* Returns the number of intra-Ultrapeer connections this node maintains.
*
* @return the number of intra-Ultrapeer connections this node maintains
*/
public int getNumIntraUltrapeerConnections() {
return DEGREE;
}
// implements ReplyHandler interface -- inherit doc comment
public boolean isHighDegreeConnection() {
return HIGH_DEGREE;
}
/**
* Returns whether or not we think this connection is from a LimeWire
* or a derivative of LimeWire
*/
public boolean isLimeWire() {
return IS_LIMEWIRE;
}
/**
* @return true if we consider this an older version of limewire, false
* otherwise
*/
public boolean isOldLimeWire() {
return IS_OLD_LIMEWIRE;
}
/**
* Returns whether or not this is connection passed the headers to be
* considered a "good" leaf.
*
* @return <tt>true</tt> if this is considered a "good" leaf, otherwise
* <tt>false</tt>
*/
public boolean isGoodLeaf() {
return GOOD_LEAF;
}
/**
* Returns whether or not this connnection is encoded in deflate.
*/
public boolean isDeflateEnabled() {
//this does NOT check the setting because we have already told the
//outgoing side we support encoding, and they're expecting us to use it
return DEFLATE_ENCODED;
}
/**
* Returns whether or not this connection accepts deflate as an encoding.
*/
public boolean isDeflateAccepted() {
//Note that we check the ENCODE_DEFLATE setting, and NOT the
//ACCEPT_DEFLATE setting. This is a trick to prevent the
//HandshakeResponders from thinking they can encode
//the via deflate if we do not want to encode in deflate.
return ConnectionSettings.ENCODE_DEFLATE.getValue() &&
containsStringValue(HEADERS, // the headers to look through
HeaderNames.ACCEPT_ENCODING,// the header to look for
HeaderNames.DEFLATE_VALUE); // the value to look for
}
/**
* Returns whether or not this is connection passed the headers to be
* considered a "good" ultrapeer.
*
* @return <tt>true</tt> if this is considered a "good" ultrapeer, otherwise
* <tt>false</tt>
*/
public boolean isGoodUltrapeer() {
return GOOD_ULTRAPEER;
}
/**
* Returns whether or not this connection supports query routing
* between Ultrapeers at 1 hop.
*
* @return <tt>true</tt> if this is an Ultrapeer connection that
* exchanges query routing tables with other Ultrapeers at 1 hop,
* otherwise <tt>false</tt>
*/
public boolean isUltrapeerQueryRoutingConnection() {
return ULTRAPEER_QRP;
}
/** Returns true iff this connection wrote "X-Ultrapeer: false".
* This does NOT necessarily mean the connection is shielded. */
public boolean isLeaf() {
return LEAF;
}
/** Returns true iff this connection wrote "X-Ultrapeer: true". */
public boolean isUltrapeer() {
return ULTRAPEER;
}
/**
* Returns whether or not this connection is to a client supporting
* GUESS.
*
* @return <tt>true</tt> if the node on the other end of this
* connection supports GUESS, <tt>false</tt> otherwise
*/
public boolean isGUESSCapable() {
return GUESS_CAPABLE;
}
/**
* Returns whether or not this connection is to a ultrapeer supporting
* GUESS.
*
* @return <tt>true</tt> if the node on the other end of this
* Ultrapeer connection supports GUESS, <tt>false</tt> otherwise
*/
public boolean isGUESSUltrapeer() {
return isGUESSCapable() && isUltrapeer();
}
/** Returns true iff this connection is a temporary connection as per
the headers. */
public boolean isTempConnection() {
//get the X-Temp-Connection from either the headers received
String value=HEADERS.getProperty(HeaderNames.X_TEMP_CONNECTION);
//if X-Temp-Connection header is not received, return false, else
//return the value received
if(value == null)
return false;
else
return Boolean.valueOf(value).booleanValue();
}
/** Returns true if this supports GGEP'ed messages. GGEP'ed messages (e.g.,
* big pongs) should only be sent along connections for which
* supportsGGEP()==true. */
public boolean supportsGGEP() {
if (_supportsGGEP==null) {
String value =
HEADERS.getProperty(HeaderNames.GGEP);
//Currently we don't care about the version number.
_supportsGGEP = new Boolean(value != null);
}
return _supportsGGEP.booleanValue();
}
/**
* Determines whether or not this node supports vendor messages.
*
* @return <tt>true</tt> if this node supports vendor messages, otherwise
* <tt>false</tt>
*/
public float supportsVendorMessages() {
String value =
HEADERS.getProperty(HeaderNames.X_VENDOR_MESSAGE);
if ((value != null) && !value.equals("")) {
try {
return Float.parseFloat(value);
}catch(NumberFormatException nfe) {
return 0;
}
}
return 0;
}
/**
* Returns whether or not this node supports pong caching.
*
* @return <tt>true</tt> if this node supports pong caching, otherwise
* <tt>false</tt>
*/
public boolean supportsPongCaching() {
return PONG_CACHING;
}
public String getVersion() {
return HEADERS.getProperty(HeaderNames.X_VERSION);
}
/** True if the remote host supports query routing (QRP). This is only
* meaningful in the context of leaf-supernode relationships. */
public boolean isQueryRoutingEnabled() {
return isVersionOrHigher(HEADERS, HeaderNames.X_QUERY_ROUTING, 0.1F);
}
/**
* Returns whether or not the node on the other end of this connection
* uses dynamic querying.
*
* @return <tt>true</tt> if this node uses dynamic querying, otherwise
* <tt>false</tt>
*/
public boolean isDynamicQueryConnection() {
return DYNAMIC_QUERY;
}
/**
* Accessor for whether or not this connection supports TTL=1 probe
* queries. These queries are treated separately from other queries.
* In particular, if a second query with the same GUID is received,
* it is not considered a duplicate.
*
* @return <tt>true</tt> if this connection supports probe queries,
* otherwise <tt>false</tt>
*/
public boolean supportsProbeQueries() {
return PROBE_QUERIES;
}
/**
* Determines whether or not this handshake is from the crawler.
*
* @return <tt>true</tt> if this handshake is from the crawler, otherwise
* <tt>false</tt>
*/
public boolean isCrawler() {
return IS_CRAWLER;
}
/**
* access the locale pref. advertised by the client
*/
public String getLocalePref() {
return LOCALE_PREF;
}
/**
* Convenience method that returns whether or not the given header
* exists.
*
* @return <tt>true</tt> if the header exists, otherwise <tt>false</tt>
*/
private static boolean headerExists(Properties headers,
String headerName) {
String value = headers.getProperty(headerName);
return value != null;
}
/**
* Utility method for checking whether or not a given header
* value is true.
*
* @param headers the headers to check
* @param headerName the header name to look for
*/
private static boolean isTrueValue(Properties headers, String headerName) {
String value = headers.getProperty(headerName);
if(value == null) return false;
return Boolean.valueOf(value).booleanValue();
}
/**
* Utility method for checking whether or not a given header
* value is false.
*
* @param headers the headers to check
* @param headerName the header name to look for
*/
private static boolean isFalseValue(Properties headers, String headerName) {
String value = headers.getProperty(headerName);
if(value == null) return false;
return value.equalsIgnoreCase("false");
}
/**
* Utility method for determing whether or not a given header
* is a given string value. Case-insensitive.
*
* @param headers the headers to check
* @param headerName the headerName to look for
* @param headerValue the headerValue to check against
*/
private static boolean isStringValue(Properties headers,
String headerName, String headerValue) {
String value = headers.getProperty(headerName);
if(value == null) return false;
return value.equalsIgnoreCase(headerValue);
}
/**
* Utility method for determing whether or not a given header
* contains a given string value within a comma-delimited list.
* Case-insensitive.
*
* @param headers the headers to check
* @param headerName the headerName to look for
* @param headerValue the headerValue to check against
*/
private static boolean containsStringValue(Properties headers,
String headerName, String headerValue) {
String value = headers.getProperty(headerName);
if(value == null) return false;
//As a small optimization, we first check to see if the value
//by itself is what we want, so we don't have to create the
//StringTokenizer.
if(value.equalsIgnoreCase(headerValue))
return true;
StringTokenizer st = new StringTokenizer(value, ",");
while(st.hasMoreTokens()) {
if(st.nextToken().equalsIgnoreCase(headerValue))
return true;
}
return false;
}
/**
* Utility method that checks the headers to see if the advertised
* version for a specified feature is greater than or equal to the version
* we require (<tt>minVersion</tt>.
*
* @param headers the connection headers to evaluate
* @param headerName the header name for the feature to check
* @param minVersion the minimum version that we require for this feature
*
* @return <tt>true</tt> if the version number for the specified feature
* is greater than or equal to <tt>minVersion</tt>, otherwise
* <tt>false</tt>.
*/
private static boolean isVersionOrHigher(Properties headers,
String headerName,
float minVersion) {
String value = headers.getProperty(headerName);
if(value == null)
return false;
try {
Float f = new Float(value);
return f.floatValue() >= minVersion;
} catch (NumberFormatException e) {
return false;
}
}
/**
* Helper method for returning an int header value. If the header name
* is not found, or if the header value cannot be parsed, the default
* value is returned.
*
* @param headers the connection headers to search through
* @param headerName the header name to look for
* @param defaultValue the default value to return if the header value
* could not be properly parsed
* @return the int value for the header
*/
private static int extractIntHeaderValue(Properties headers,
String headerName,
int defaultValue) {
String value = headers.getProperty(headerName);
if(value == null) return defaultValue;
try {
return Integer.valueOf(value).intValue();
} catch(NumberFormatException e) {
return defaultValue;
}
}
/**
* Helper method for returning a byte header value. If the header name
* is not found, or if the header value cannot be parsed, the default
* value is returned.
*
* @param headers the connection headers to search through
* @param headerName the header name to look for
* @param defaultValue the default value to return if the header value
* could not be properly parsed
* @return the byte value for the header
*/
private static byte extractByteHeaderValue(Properties headers,
String headerName,
byte defaultValue) {
String value = headers.getProperty(headerName);
if(value == null) return defaultValue;
try {
return Byte.valueOf(value).byteValue();
} catch(NumberFormatException e) {
return defaultValue;
}
}
/**
* Helper method for returning a string header value. If the header name
* is not found, or if the header value cannot be parsed, the default
* value is returned.
*
* @param headers the connection headers to search through
* @param headerName the header name to look for
* @return the string value for the header, or the empty string if
* the header could not be found
*/
private static String extractStringHeaderValue(Properties headers,
String headerName) {
String value = headers.getProperty(headerName);
if(value == null) return "";
return value;
}
public String toString() {
return "<"+STATUS_CODE+", "+STATUS_MESSAGE+">"+HEADERS;
}
}