com.limegroup.gnutella
Class Connection

java.lang.Object
  extended bycom.limegroup.gnutella.Connection
Direct Known Subclasses:
ManagedConnection

public class Connection
extends java.lang.Object

A Gnutella messaging connection. Provides handshaking functionality and routines for reading and writing of Gnutella messages. A connection is either incoming (created from a Socket) or outgoing (created from an address). This class does not provide sophisticated buffering or routing logic; use ManagedConnection for that.

You will note that the constructors don't actually involve the network and hence never throw exceptions or block. To actual initialize a connection, you must call initialize(). While this is somewhat awkward, it is intentional. It makes it easier, for example, for the GUI to show uninitialized connections.

Connection supports only 0.6 handshakes. Gnutella 0.6 connections have a list of properties read and written during the handshake sequence. Typical property/value pairs might be "Query-Routing: 0.3" or "User-Agent: LimeWire".

This class augments the basic 0.6 handshaking mechanism to allow authentication via "401" messages. Authentication interactions can take multiple rounds.

This class supports reading and writing streams using 'deflate' compression. The HandshakeResponser is what actually determines whether or not deflate will be used. This class merely looks at what the responses are in order to set up the appropriate streams. Compression is implemented by chaining the input and output streams, meaning that even if an extending class implements getInputStream() and getOutputStream(), the actual input and output stream used may not be an instance of the expected class. However, the information is still chained through the appropriate stream.

The amount of bytes written and received are maintained by this class. This is necessary because of compression and decompression are considered implementation details in this class.

Finally, Connection also handles setting the SOFT_MAX_TTL on a per-connection basis. The SOFT_MAX TTL is the limit for hops+TTL on all incoming traffic, with the exception of query hits. If an incoming message has hops+TTL greater than SOFT_MAX, we set the TTL to SOFT_MAX-hops. We do this on a per-connection basis because on newer connections that understand X-Max-TTL, we can regulate the TTLs they send us. This helps prevent malicious hosts from using headers like X-Max-TTL to simply get connections. This way, they also have to abide by the contract of the X-Max-TTL header, illustrated by sending lower TTL traffic generally.


Field Summary
static java.lang.String _200_OK
           
protected  boolean _closed
          Trigger an opening connection to close after it opens.
protected  MessagesSupportedVendorMessage _messagesSupported
          The possibly non-null VendorMessagePayload which describes what VendorMessages the guy on the other side of this connection supports.
static java.lang.String CONNECT
           
protected static java.io.IOException CONNECTION_CLOSED
          Cache the 'connection closed' exception, so we have to allocate one for every closed connection.
static java.lang.String CRLF
          End of line for Gnutella 0.6
static java.lang.String GNUTELLA_06
           
static java.lang.String GNUTELLA_06_200
           
static java.lang.String GNUTELLA_OK_06
          Gnutella 0.6 accept connection string.
static int MAX_HANDSHAKE_ATTEMPTS
          The number of times we will respond to a given challenge from the other side, or otherwise, during connection handshaking
static int USER_INPUT_WAIT_TIME
          Time to wait for inut from user at the remote end.
 
Constructor Summary
Connection(java.net.Socket socket, HandshakeResponder responseHeaders)
          Creates an uninitialized incoming 0.6 Gnutella connection.
Connection(java.lang.String host, int port, java.util.Properties requestHeaders, HandshakeResponder responseHeaders)
          Creates an uninitialized outgoing Gnutella 0.6 connection with the desired outgoing properties, possibly reverting to Gnutella 0.4 if needed.
 
Method Summary
 boolean allowNewPings()
          Returns whether or not we should allow new pings on this connection.
 boolean allowNewPongs()
          Returns whether or not we should allow new pongs on this connection.
 void close()
          Closes the Connection's socket and thus the connection itself.
 void flush()
          Flushes any buffered messages sent through the send method.
 long getBytesReceived()
          Returns the number of bytes received on this connection.
 long getBytesSent()
          Returns the number of bytes sent on this connection.
 long getConnectionTime()
          Returns the time this connection was established, in milliseconds since January 1, 1970.
 java.lang.String getDomainsAuthenticated()
          Returns the authenticated domains listed in the connection headers for this connection.
 java.net.InetAddress getInetAddress()
          Returns the address of the foreign host this is connected to.
protected  java.io.InputStream getInputStream()
          Returns the stream to use for reading from s.
 java.lang.String getIPString()
          Returns the IP address of the remote host as a string.
 int getListeningPort()
          Accessor for the port number this connection is listening on.
 int getNumIntraUltrapeerConnections()
          Returns the number of intra-Ultrapeer connections this node maintains.
protected  java.io.OutputStream getOutputStream()
          Returns the stream to use for writing to s.
 java.lang.String getPropertyWritten(java.lang.String name)
          Returns the value of the given outgoing (written) connection property, or null if no such property.
 float getReadSavedFromCompression()
          Returns the percentage saved from having the incoming data compressed.
 float getSentSavedFromCompression()
          Returns the percentage saved through compressing the outgoing data.
 java.net.Socket getSocket()
          Accessor for the Socket for this connection.
 byte getSoftMax()
          Accessor for the soft max TTL to use for this connection.
 long getUncompressedBytesReceived()
          Returns the number of uncompressed bytes read on this connection.
 long getUncompressedBytesSent()
          Returns the number of uncompressed bytes sent on this connection.
 java.lang.String getUserAgent()
          Returns the vendor string reported by this connection, i.e., the USER_AGENT property, or null if it wasn't set.
 java.lang.String getVersion()
          Accessor for the LimeWire version reported in the connection headers for this node.
protected  void handleVendorMessage(VendorMessage vm)
          Call this method when you want to handle us to handle a VM.
 HandshakeResponse headers()
          Accessor for the HandshakeResponse instance containing all of the Gnutella connection headers passed by this node.
 void initialize()
          Initializes this without timeout; exactly like initialize(0).
 void initialize(int timeout)
          Initialize the connection by doing the handshake.
 boolean isClientSupernodeConnection()
          Returns true iff the connection is an Ultrapeer and I am a leaf, i.e., if I wrote "X-Ultrapeer: false", this connection wrote "X-Ultrapeer: true" (not necessarily in that order).
 boolean isConnectBackCapable()
          Returns true if the this connection is potentially on the 'same' network.
 boolean isGoodLeaf()
           
 boolean isGoodUltrapeer()
           
 boolean isGUESSCapable()
          Returns whether or not this connection is to a client supporting GUESS.
 boolean isGUESSUltrapeer()
          Returns whether or not this connection is to a ultrapeer supporting GUESS.
 boolean isHighDegreeConnection()
           
 boolean isInitialized()
          Accessor for whether or not this connection has been initialized.
 boolean isLeafConnection()
          Returns true iff this connection wrote "Ultrapeer: false".
protected  boolean isLocal()
          Returns whether or not this connection represents a local address.
 boolean isOpen()
           
 boolean isOutgoing()
          Used to determine whether the connection is incoming or outgoing.
 boolean isReadDeflated()
          Returns true if the incoming stream is deflated.
 boolean isStable()
          Checks whether this connection is considered a stable connection, meaning it has been up for enough time to be considered stable.
 boolean isStable(long millis)
          Checks whether this connection is considered a stable connection, by comparing the time it was established with the millis argument.
 boolean isSupernodeClientConnection()
          Returns true iff I am a supernode shielding the given connection, i.e., if I wrote "X-Ultrapeer: true" and this connection wrote "X-Ultrapeer: false, and both support query routing.
 boolean isSupernodeConnection()
          Returns true iff this connection wrote "Supernode: true".
 boolean isSupernodeSupernodeConnection()
          Returns true iff the connection is an Ultrapeer and I am a Ultrapeer, ie: if I wrote "X-Ultrapeer: true", this connection wrote "X-Ultrapeer: true" (not necessarily in that order).
 boolean isTempConnection()
          Returns true iff this connection is a temporary connection as per the headers.
 boolean isUltrapeerQueryRoutingConnection()
          Returns whether or not this connection is to an Ultrapeer that supports query routing between Ultrapeers at 1 hop.
 boolean isWriteDeflated()
          Returns true if the outgoing stream is deflated.
protected  void postInit()
          Call this method when the Connection has been initialized and accepted as 'long-lived'.
protected  Message receive()
          Receives a message.
 Message receive(int timeout)
          Receives a message with timeout.
 boolean receivedHeaders()
          Accessor for whether or not this connection has received any headers.
 int remoteHostSupportsHopsFlow()
           
 int remoteHostSupportsPushProxy()
           
 int remoteHostSupportsTCPConnectBack()
           
 int remoteHostSupportsUDPConnectBack()
           
 void send(Message m)
          Sends a message.
 boolean supportsGGEP()
          Returns true if this supports GGEP'ed messages.
 boolean supportsPongCaching()
           
 boolean supportsProbeQueries()
          Returns whether or not this connections supports "probe" queries, or queries sent at TTL=1 that should not block the send path of subsequent, higher TTL queries.
 int supportsVendorMessage(byte[] vendorID, int selector)
           
 java.lang.String toString()
           
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
 

Field Detail

_messagesSupported

protected MessagesSupportedVendorMessage _messagesSupported
The possibly non-null VendorMessagePayload which describes what VendorMessages the guy on the other side of this connection supports.


_closed

protected volatile boolean _closed
Trigger an opening connection to close after it opens. This flag is set in shutdown() and then checked in initialize() to insure the _socket.close() happens if shutdown is called asynchronously before initialize() completes. Note that the connection may have been remotely closed even if _closed==true. Protected (instead of private) for testing purposes only. This also protects us from calling methods on the Inflater/Deflater objects after end() has been called on them.


GNUTELLA_OK_06

public static final java.lang.String GNUTELLA_OK_06
Gnutella 0.6 accept connection string.

See Also:
Constant Field Values

GNUTELLA_06

public static final java.lang.String GNUTELLA_06
See Also:
Constant Field Values

_200_OK

public static final java.lang.String _200_OK
See Also:
Constant Field Values

GNUTELLA_06_200

public static final java.lang.String GNUTELLA_06_200
See Also:
Constant Field Values

CONNECT

public static final java.lang.String CONNECT
See Also:
Constant Field Values

CRLF

public static final java.lang.String CRLF
End of line for Gnutella 0.6

See Also:
Constant Field Values

USER_INPUT_WAIT_TIME

public static final int USER_INPUT_WAIT_TIME
Time to wait for inut from user at the remote end. (in milliseconds)

See Also:
Constant Field Values

MAX_HANDSHAKE_ATTEMPTS

public static final int MAX_HANDSHAKE_ATTEMPTS
The number of times we will respond to a given challenge from the other side, or otherwise, during connection handshaking

See Also:
Constant Field Values

CONNECTION_CLOSED

protected static final java.io.IOException CONNECTION_CLOSED
Cache the 'connection closed' exception, so we have to allocate one for every closed connection.

Constructor Detail

Connection

public Connection(java.lang.String host,
                  int port,
                  java.util.Properties requestHeaders,
                  HandshakeResponder responseHeaders)
Creates an uninitialized outgoing Gnutella 0.6 connection with the desired outgoing properties, possibly reverting to Gnutella 0.4 if needed. If properties1 and properties2 are null, forces connection at the 0.4 level. This is a bit of a hack to make implementation in this and subclasses easier; outside classes are discouraged from using it.

Parameters:
host - the name of the host to connect to
port - the port of the remote host
requestHeaders - the headers to be sent after "GNUTELLA CONNECT"
responseHeaders - a function returning the headers to be sent after the server's "GNUTELLA OK". Typically this returns only vendor-specific properties.
Throws:
NullPointerException - if any of the arguments are null
IllegalArgumentException - if the port is invalid

Connection

public Connection(java.net.Socket socket,
                  HandshakeResponder responseHeaders)
Creates an uninitialized incoming 0.6 Gnutella connection. If the client is attempting to connect using an 0.4 handshake, it is rejected.

Parameters:
socket - the socket accepted by a ServerSocket. The word "GNUTELLA " and nothing else must have been read from the socket.
responseHeaders - the headers to be sent in response to the client's "GNUTELLA CONNECT".
Throws:
NullPointerException - if any of the arguments are null
Method Detail

postInit

protected void postInit()
Call this method when the Connection has been initialized and accepted as 'long-lived'.


handleVendorMessage

protected void handleVendorMessage(VendorMessage vm)
Call this method when you want to handle us to handle a VM. We may....


initialize

public void initialize()
                throws java.io.IOException,
                       NoGnutellaOkException,
                       BadHandshakeException
Initializes this without timeout; exactly like initialize(0).

Throws:
java.io.IOException
NoGnutellaOkException
BadHandshakeException
See Also:
initialize(int)

initialize

public void initialize(int timeout)
                throws java.io.IOException,
                       NoGnutellaOkException,
                       BadHandshakeException
Initialize the connection by doing the handshake. Throws IOException if we were unable to establish a normal messaging connection for any reason. Do not call send or receive if this happens.

Parameters:
timeout - for outgoing connections, the timeout in milliseconds to use in establishing the socket, or 0 for no timeout. If the platform does not support native timeouts, it will be emulated with threads.
Throws:
java.io.IOException - we were unable to connect to the host
NoGnutellaOkException - one of the participants responded with an error code other than 200 OK (possibly after several rounds of 401's)
BadHandshakeException - some other problem establishing the connection, e.g., the server responded with HTTP, closed the the connection during handshaking, etc.

isInitialized

public boolean isInitialized()
Accessor for whether or not this connection has been initialized. Several methods of this class require that the connection is initialized, particularly that the socket is established. These methods should verify that the connection is initialized before being called.

Returns:
true if the connection has been initialized and the socket established, otherwise false

getOutputStream

protected java.io.OutputStream getOutputStream()
                                        throws java.io.IOException
Returns the stream to use for writing to s. By default this is a BufferedOutputStream. Subclasses may override to decorate the stream.

Throws:
java.io.IOException

getInputStream

protected java.io.InputStream getInputStream()
                                      throws java.io.IOException
Returns the stream to use for reading from s. By default this is a BufferedInputStream. Subclasses may override to decorate the stream.

Throws:
java.io.IOException

isOutgoing

public boolean isOutgoing()
Used to determine whether the connection is incoming or outgoing.


receive

protected Message receive()
                   throws java.io.IOException,
                          BadPacketException
Receives a message. This method is NOT thread-safe. Behavior is undefined if two threads are in a receive call at the same time for a given connection.

Throws:
java.io.IOException
BadPacketException

receive

public Message receive(int timeout)
                throws java.io.IOException,
                       BadPacketException,
                       java.io.InterruptedIOException
Receives a message with timeout. This method is NOT thread-safe. Behavior is undefined if two threads are in a receive call at the same time for a given connection.

Throws:
java.io.IOException
BadPacketException
java.io.InterruptedIOException

send

public void send(Message m)
          throws java.io.IOException
Sends a message. The message may be buffered, so call flush() to guarantee that the message is sent synchronously. This method is NOT thread-safe. Behavior is undefined if two threads are in a send call at the same time for a given connection.

Throws:
java.io.IOException

flush

public void flush()
           throws java.io.IOException
Flushes any buffered messages sent through the send method.

Throws:
java.io.IOException

getBytesSent

public long getBytesSent()
Returns the number of bytes sent on this connection. If the outgoing stream is compressed, the return value indicates the compressed number of bytes sent.


getUncompressedBytesSent

public long getUncompressedBytesSent()
Returns the number of uncompressed bytes sent on this connection. If the outgoing stream is not compressed, this is effectively the same as calling getBytesSent()


getBytesReceived

public long getBytesReceived()
Returns the number of bytes received on this connection. If the incoming stream is compressed, the return value indicates the number of compressed bytes received.


getUncompressedBytesReceived

public long getUncompressedBytesReceived()
Returns the number of uncompressed bytes read on this connection. If the incoming stream is not compressed, this is effectively the same as calling getBytesReceived()


getSentSavedFromCompression

public float getSentSavedFromCompression()
Returns the percentage saved through compressing the outgoing data. The value may be slightly off until the output stream is flushed, because the value of the compressed bytes is not calculated until then.


getReadSavedFromCompression

public float getReadSavedFromCompression()
Returns the percentage saved from having the incoming data compressed.


getIPString

public java.lang.String getIPString()
Returns the IP address of the remote host as a string.

Returns:
the IP address of the remote host as a string

getListeningPort

public int getListeningPort()
Accessor for the port number this connection is listening on. Note that this is NOT the port of the socket itself. For incoming connections, the getPort method of the java.net.Socket class returns the ephemeral port that the host connected with. This port, however, is the port the remote host is listening on for new connections, which we set using Gnutella connection headers in the case of incoming connections. For outgoing connections, this is the port we used to connect to them -- their listening port.

Returns:
the listening port for the remote host

getInetAddress

public java.net.InetAddress getInetAddress()
                                    throws java.lang.IllegalStateException
Returns the address of the foreign host this is connected to.

Throws:
java.lang.IllegalStateException - this is not initialized

getSocket

public java.net.Socket getSocket()
                          throws java.lang.IllegalStateException
Accessor for the Socket for this connection.

Returns:
the Socket for this connection
Throws:
java.lang.IllegalStateException - if this connection is not yet initialized

isConnectBackCapable

public boolean isConnectBackCapable()
                             throws java.lang.IllegalStateException
Returns true if the this connection is potentially on the 'same' network.

Throws:
java.lang.IllegalStateException

getConnectionTime

public long getConnectionTime()
Returns the time this connection was established, in milliseconds since January 1, 1970.

Returns:
the time this connection was established

getSoftMax

public byte getSoftMax()
Accessor for the soft max TTL to use for this connection.

Returns:
the soft max TTL for this connection

isStable

public boolean isStable()
Checks whether this connection is considered a stable connection, meaning it has been up for enough time to be considered stable.

Returns:
true if the connection is considered stable, otherwise false

isStable

public boolean isStable(long millis)
Checks whether this connection is considered a stable connection, by comparing the time it was established with the millis argument.

Returns:
true if the connection is considered stable, otherwise false

supportsVendorMessage

public int supportsVendorMessage(byte[] vendorID,
                                 int selector)
Returns:
-1 if the message isn't supported, else the version number supported.

remoteHostSupportsUDPConnectBack

public int remoteHostSupportsUDPConnectBack()
Returns:
-1 if the message isn't supported, else the version number supported.

remoteHostSupportsTCPConnectBack

public int remoteHostSupportsTCPConnectBack()
Returns:
-1 if the message isn't supported, else the version number supported.

remoteHostSupportsHopsFlow

public int remoteHostSupportsHopsFlow()
Returns:
-1 if the message isn't supported, else the version number supported.

remoteHostSupportsPushProxy

public int remoteHostSupportsPushProxy()
Returns:
-1 if the message isn't supported, else the version number supported.

isLocal

protected boolean isLocal()
Returns whether or not this connection represents a local address.

Returns:
true if this connection is a local address, otherwise false

getPropertyWritten

public java.lang.String getPropertyWritten(java.lang.String name)
Returns the value of the given outgoing (written) connection property, or null if no such property. For example, getProperty("X-Supernode") tells whether I am a supernode or a leaf node. If I wrote a property multiple time during connection, returns the latest.


isOpen

public boolean isOpen()
Returns:
true until close() is called on this Connection

close

public void close()
Closes the Connection's socket and thus the connection itself.


getUserAgent

public java.lang.String getUserAgent()
Returns the vendor string reported by this connection, i.e., the USER_AGENT property, or null if it wasn't set.

Returns:
the vendor string, or null if unknown

isWriteDeflated

public boolean isWriteDeflated()
Returns true if the outgoing stream is deflated.

Returns:
true if the outgoing stream is deflated.

isReadDeflated

public boolean isReadDeflated()
Returns true if the incoming stream is deflated.

Returns:
true if the incoming stream is deflated.

isGoodUltrapeer

public boolean isGoodUltrapeer()

isGoodLeaf

public boolean isGoodLeaf()

supportsPongCaching

public boolean supportsPongCaching()

allowNewPings

public boolean allowNewPings()
Returns whether or not we should allow new pings on this connection. If we have recently received a ping, we will likely not allow the second ping to go through to avoid flooding the network with ping traffic.

Returns:
true if new pings are allowed along this connection, otherwise false

allowNewPongs

public boolean allowNewPongs()
Returns whether or not we should allow new pongs on this connection. If we have recently received a pong, we will likely not allow the second pong to go through to avoid flooding the network with pong traffic. In practice, this is only used to limit pongs sent to leaves.

Returns:
true if new pongs are allowed along this connection, otherwise false

getNumIntraUltrapeerConnections

public int getNumIntraUltrapeerConnections()
Returns the number of intra-Ultrapeer connections this node maintains.

Returns:
the number of intra-Ultrapeer connections this node maintains

isHighDegreeConnection

public boolean isHighDegreeConnection()

isUltrapeerQueryRoutingConnection

public boolean isUltrapeerQueryRoutingConnection()
Returns whether or not this connection is to an Ultrapeer that supports query routing between Ultrapeers at 1 hop.

Returns:
true if this is an Ultrapeer connection that exchanges query routing tables with other Ultrapeers at 1 hop, otherwise false

supportsProbeQueries

public boolean supportsProbeQueries()
Returns whether or not this connections supports "probe" queries, or queries sent at TTL=1 that should not block the send path of subsequent, higher TTL queries.

Returns:
true if this connection supports probe queries, otherwise false

getDomainsAuthenticated

public java.lang.String getDomainsAuthenticated()
Returns the authenticated domains listed in the connection headers for this connection.

Returns:
the string of authenticated domains for this connection

receivedHeaders

public boolean receivedHeaders()
Accessor for whether or not this connection has received any headers.

Returns:
true if this connection has finished initializing and therefore has headers, otherwise false

headers

public HandshakeResponse headers()
Accessor for the HandshakeResponse instance containing all of the Gnutella connection headers passed by this node.

Returns:
the HandshakeResponse instance containing all of the Gnutella connection headers passed by this node

getVersion

public java.lang.String getVersion()
Accessor for the LimeWire version reported in the connection headers for this node.


isLeafConnection

public boolean isLeafConnection()
Returns true iff this connection wrote "Ultrapeer: false". This does NOT necessarily mean the connection is shielded.


isSupernodeConnection

public boolean isSupernodeConnection()
Returns true iff this connection wrote "Supernode: true".


isClientSupernodeConnection

public boolean isClientSupernodeConnection()
Returns true iff the connection is an Ultrapeer and I am a leaf, i.e., if I wrote "X-Ultrapeer: false", this connection wrote "X-Ultrapeer: true" (not necessarily in that order). Does NOT require that QRP is enabled between the two; the Ultrapeer could be using reflector indexing, for example.


isSupernodeSupernodeConnection

public boolean isSupernodeSupernodeConnection()
Returns true iff the connection is an Ultrapeer and I am a Ultrapeer, ie: if I wrote "X-Ultrapeer: true", this connection wrote "X-Ultrapeer: true" (not necessarily in that order). Does NOT require that QRP is enabled between the two; the Ultrapeer could be using reflector indexing, for example.


isGUESSCapable

public boolean isGUESSCapable()
Returns whether or not this connection is to a client supporting GUESS.

Returns:
true if the node on the other end of this connection supports GUESS, false otherwise

isGUESSUltrapeer

public boolean isGUESSUltrapeer()
Returns whether or not this connection is to a ultrapeer supporting GUESS.

Returns:
true if the node on the other end of this Ultrapeer connection supports GUESS, false otherwise

isTempConnection

public boolean isTempConnection()
Returns true iff this connection is a temporary connection as per the headers.


isSupernodeClientConnection

public boolean isSupernodeClientConnection()
Returns true iff I am a supernode shielding the given connection, i.e., if I wrote "X-Ultrapeer: true" and this connection wrote "X-Ultrapeer: false, and both support query routing.


supportsGGEP

public boolean supportsGGEP()
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.


toString

public java.lang.String toString()