package com.limegroup.gnutella.handshaking;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Properties;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.settings.ConnectionSettings;
import com.limegroup.gnutella.settings.StringSetting;
import com.limegroup.gnutella.util.NetworkUtils;
class HandshakeSupport {
/** Connection string. */
private String GNUTELLA_CONNECT_06 = "GNUTELLA CONNECT/0.6";
/** Valid connect line string. */
private static final String GNUTELLA_06 = "GNUTELLA/0.6";
/** Gnutella 0.6 accept connection string. */
private static final String CONNECT = "CONNECT/";
/** End of line for Gnutella 0.6 */
private static final String CRLF = "\r\n";
/** All headers we've read from the remote side. */
private final Properties readHeaders;
/** All headers we wrote to the remote side. */
private final Properties writtenHeaders;
/** The remote address to use when processing a sent header. */
private final String remoteAddress;
/** The connectLine used in the remote response. */
private String remoteResponse;
HandshakeSupport(String remoteAddress) {
this.remoteAddress = remoteAddress;
this.readHeaders = new Properties();
this.writtenHeaders = new Properties();
}
/** Creates their response, based on the given connectLine. */
HandshakeResponse createRemoteResponse(String connectLine) throws IOException {
this.remoteResponse = connectLine.substring(GNUTELLA_06.length()).trim();
return HandshakeResponse.createRemoteResponse(remoteResponse, readHeaders);
}
/** Appends the connect line to the given StringBuffer. */
void appendConnectLine(StringBuffer sb) {
sb.append(GNUTELLA_CONNECT_06).append(CRLF);
}
/** Appends a response (using the given response) to the given StringBuffer */
void appendResponse(HandshakeResponse response, StringBuffer sb) {
sb.append(GNUTELLA_06).append(" ").append(response.getStatusLine()).append(CRLF);
appendHeaders(response.props(), sb);
}
/**
* Appends the given properties to the given StringBuffer.
*
* @param props The headers to be sent. Note: null argument is acceptable,
* if no headers need to be sent the trailer will be sent.
*/
void appendHeaders(Properties props, StringBuffer sb) {
if (props != null) {
Enumeration names = props.propertyNames();
while (names.hasMoreElements()) {
String key = (String) names.nextElement();
String value = props.getProperty(key);
String toWrite = processKeyValueForWriting(key, value);
sb.append(toWrite);
}
}
sb.append(CRLF);
}
/** Determines if the given connect line is valid. */
boolean isConnectLineValid(String s) {
return s.startsWith(GNUTELLA_06);
}
/**
* Returns true iff line ends with "CONNECT/N", where N is a number greater than or equal "0.6".
*/
boolean notLessThan06(String line) {
int i = line.indexOf(CONNECT);
if (i < 0)
return false;
try {
float f = Float.parseFloat(line.substring(i + CONNECT.length()));
return f >= 0.6f;
} catch (NumberFormatException e) {
return false;
}
}
/**
* Process a single read header.
* Returns true if this wasn't a blank line and more headers are expected,
* returns false if this was a blank line and no more headers are expected.
*/
boolean processReadHeader(String line) {
if(line.equals(""))
return false;
int i = line.indexOf(':');
if (i > 0) {
String key = line.substring(0, i);
String value = line.substring(i + 1).trim();
if (HeaderNames.REMOTE_IP.equals(key))
changeAddress(value);
readHeaders.put(key, value);
}
return true;
}
/**
* Process a key/value pair for writing.
* May change the value.
* Returns what should be sent over the wire.
*/
String processKeyValueForWriting(String key, String value) {
// Ensure we put their remote-ip correctly.
if (HeaderNames.REMOTE_IP.equals(key))
value = remoteAddress;
if (value == null)
value = "";
writtenHeaders.put(key, value);
return key + ": " + value + CRLF;
}
/**
* Determines if the address should be changed and changes it if
* necessary.
*/
private void changeAddress(final String v) {
InetAddress ia = null;
try {
ia = InetAddress.getByName(v);
} catch(UnknownHostException uhe) {
return; // invalid.
}
// invalid or private, exit
if(!NetworkUtils.isValidAddress(ia) ||
NetworkUtils.isPrivateAddress(ia))
return;
// If we're forcing, change that if necessary.
if( ConnectionSettings.FORCE_IP_ADDRESS.getValue() ) {
StringSetting addr = ConnectionSettings.FORCED_IP_ADDRESS_STRING;
if(!v.equals(addr.getValue())) {
addr.setValue(v);
RouterService.addressChanged();
}
}
// Otherwise, if our current address is invalid, change.
else if(!NetworkUtils.isValidAddress(RouterService.getAddress())) {
// will auto-call addressChanged.
RouterService.getAcceptor().setAddress(ia);
}
RouterService.getAcceptor().setExternalAddress(ia);
}
/** Returns the number of headers we've read so far. */
int getHeadersReadSize() {
return readHeaders.size();
}
/** Constructs a HandshakeResponse object using the remote response data. */
HandshakeResponse getReadHandshakeRemoteResponse() throws IOException {
return HandshakeResponse.createRemoteResponse(remoteResponse, readHeaders);
}
/** Constructs a HandshakeResponse object wrapping the headers we've read. */
HandshakeResponse getReadHandshakeResponse() {
return HandshakeResponse.createResponse(readHeaders);
}
/** Constructs a HandshakeResponse object wrapping the headers we've written. */
HandshakeResponse getWrittenHandshakeResponse() {
return HandshakeResponse.createResponse(writtenHeaders);
}
}