package com.limegroup.gnutella.handshaking;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.ReadableByteChannel;
import com.limegroup.gnutella.io.BufferUtils;
import com.limegroup.gnutella.settings.ConnectionSettings;
import com.limegroup.gnutella.statistics.BandwidthStat;
import com.limegroup.gnutella.statistics.HandshakingStat;
/**
* Superclass for HandshakeStates that are reading.
*/
abstract class ReadHandshakeState extends HandshakeState {
/** Whether or not we've finished reading the initial connect line. */
protected boolean doneConnect;
/** The current header we're in the process of reading. */
protected StringBuffer currentHeader = new StringBuffer(1024);
/** The connect line. */
protected String connectLine;
/** Constructs a new ReadHandshakeState using the given HandshakeSupport. */
ReadHandshakeState(HandshakeSupport support) {
super(support);
}
/**
* Reads as much data as it can from the buffer, farming the processing of the
* connect line (same as response line) and headers out to the methods:
* processConnectLine(String line)
* processHeaders()
*
* This will return true if it needs to be called again for more processing,
* otherwise it will return false indiciating it's time to move on to the next
* state.
*/
boolean process(Channel channel, ByteBuffer buffer) throws IOException {
ReadableByteChannel rc = (ReadableByteChannel)channel;
boolean allDone = false;
while(!allDone) {
int read = 0;
while(buffer.hasRemaining() && (read = rc.read(buffer)) > 0)
BandwidthStat.GNUTELLA_HEADER_DOWNSTREAM_BANDWIDTH.addData(read);
if(buffer.position() == 0) {
if(read == -1)
throw new IOException("EOF");
break;
}
buffer.flip();
if(!doneConnect) {
if(BufferUtils.readLine(buffer, currentHeader)) {
connectLine = currentHeader.toString();
currentHeader.delete(0, currentHeader.length());
processConnectLine();
doneConnect = true;
}
}
if(doneConnect) {
while(true) {
if(!BufferUtils.readLine(buffer, currentHeader))
break;
if(!support.processReadHeader(currentHeader.toString())) {
allDone = true;
break; // we finished reading this set of headers!
}
currentHeader.delete(0, currentHeader.length()); // reset for the next header.
// Make sure we don't try and read forever.
if(support.getHeadersReadSize() > ConnectionSettings.MAX_HANDSHAKE_HEADERS.getValue())
throw new IOException("too many headers");
}
}
buffer.compact();
// Don't allow someone to send us a header so big that we blow up.
// Note that we don't check this after immediately after creating the
// header, because it's not really so important there. We know the
// data cannot be bigger than the buffer's size, and the buffer's size isn't
// too extraordinarily large, so this works out okay.
if(currentHeader.length() > ConnectionSettings.MAX_HANDSHAKE_LINE_SIZE.getValue())
throw new IOException("header too big");
}
if(allDone) {
processHeaders();
return false;
} else {
return true;
}
}
/** Returns false. */
boolean isWriting() {
return false;
}
/** Returns true. */
boolean isReading() {
return true;
}
/**
* Reacts to the connect line, either throwing an IOException if it was invalid
* or doing nothing if it was valid.
*/
abstract protected void processConnectLine() throws IOException;
/**
* Reacts to the event of headers being finished processing. Throws an IOException
* if the connection wasn't allowed.
*
* @throws IOException
*/
abstract protected void processHeaders() throws IOException;
/** The first state in an incoming handshake. */
static class ReadRequestState extends ReadHandshakeState {
ReadRequestState(HandshakeSupport support) {
super(support);
}
/**
* Ensures the initial connect line is GNUTELLA/0.6
* or a higher version of the protocol.
*/
protected void processConnectLine() throws IOException {
if (!support.notLessThan06(connectLine))
throw new IOException("not a valid connect string!");
}
/** Does nothing. */
protected void processHeaders() throws IOException {}
}
/** The third state in an incoming handshake, or the second state in an outgoing handshake. */
static class ReadResponseState extends ReadHandshakeState {
ReadResponseState(HandshakeSupport support) {
super(support);
}
/** Ensures that the connect line began with GNUTELLA/0.6 */
protected void processConnectLine() throws IOException {
// We do this here, as opposed to in other states, so that
// our response to the crawler can go through the wire prior
// to closing the connection.
// In the case of a crawler, this will normally go:
// ReadRequestState -> WriteResponseState -> ReadResponseState
// Normally, ReadResponseState will never get hit because the
// crawler won't respond & the connection will timeout.
// However, if it does get hit, we need to close the connection
if(support.getReadHandshakeResponse().isCrawler())
throw new IOException("crawler");
if (!support.isConnectLineValid(connectLine)) {
HandshakingStat.INCOMING_BAD_CONNECT.incrementStat();
throw new IOException("Bad connect string");
}
}
/** Ensures that the response contained a valid status code. */
protected void processHeaders() throws IOException {
HandshakeResponse theirResponse = support.createRemoteResponse(connectLine);
switch(theirResponse.getStatusCode()) {
case HandshakeResponse.OK:
HandshakingStat.SUCCESSFUL_INCOMING.incrementStat();
break;
default:
HandshakingStat.INCOMING_SERVER_UNKNOWN.incrementStat();
throw NoGnutellaOkException.createServerUnknown(theirResponse.getStatusCode());
}
}
}
}