package com.limegroup.gnutella.handshaking;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.WritableByteChannel;
import java.util.Properties;
import com.limegroup.gnutella.statistics.BandwidthStat;
import com.limegroup.gnutella.statistics.HandshakingStat;
/** Superclass for HandshakeStates that are written out. */
public abstract class WriteHandshakeState extends HandshakeState {
/** The outgoing buffer, if we've made it. (Null if we haven't.) */
private ByteBuffer outgoing;
/** Creates a new WriteHandshakeState using the given support. */
public WriteHandshakeState(HandshakeSupport support) {
super(support);
}
/** Returns true. */
boolean isWriting() {
return true;
}
/** Returns false. */
boolean isReading() {
return false;
}
/**
* Writes output to the channel. This farms out the creation of the output
* to the abstract method createOutgoingData(). That method will only be called once
* to get the initial outgoing data. Once all data has been written, the abstract
* processWrittenHeaders() method will be called, so that subclasses can act upon
* what they've just written.
*
* This will return true if it needs to be called again to continue writing.
* If it returns false, all data has been written and you can proceed to the next state.
*/
boolean process(Channel channel, ByteBuffer buffer) throws IOException {
if(outgoing == null) {
outgoing = createOutgoingData();
}
int written = ((WritableByteChannel)channel).write(outgoing);
BandwidthStat.GNUTELLA_HEADER_UPSTREAM_BANDWIDTH.addData(written);
if(!outgoing.hasRemaining()) {
processWrittenHeaders();
return false;
} else {
return true;
}
}
/** Returns a ByteBuffer of data to write. */
protected abstract ByteBuffer createOutgoingData() throws IOException;
/** Processes the headers we wrote, after writing them. May throw IOException if we need to disco. */
protected abstract void processWrittenHeaders() throws IOException;
/** The second state in an incoming handshake, or the third state in an outgoing handshake. */
static class WriteResponseState extends WriteHandshakeState {
private HandshakeResponder responder;
private HandshakeResponse response;
private boolean outgoing;
/**
* Constructs a new WriteResponseState using the given support, responder,
* and whether or not we're responding to an outgoing or incoming request.
*
* @param support
* @param responder
* @param outgoing
*/
WriteResponseState(HandshakeSupport support, HandshakeResponder responder, boolean outgoing) {
super(support);
this.responder = responder;
this.outgoing = outgoing;
}
/**
* Creates a response using the responder and wraps it into a ByteBuffer.
*/
protected ByteBuffer createOutgoingData() throws IOException {
// The distinction between requests is not necessary for correctness,
// but is useful. The getReadHandshakeRemoteResponse() method will
// contain the correct response status code & msg, whereas
// the getReadHandshakeResponse() method assumes '200 OK'.
HandshakeResponse theirResponse;
if(outgoing)
theirResponse = support.getReadHandshakeRemoteResponse();
else
theirResponse = support.getReadHandshakeResponse();
response = responder.respond(theirResponse, outgoing);
StringBuffer sb = new StringBuffer();
support.appendResponse(response, sb);
return ByteBuffer.wrap(sb.toString().getBytes()); // TODO: conversion??
}
/**
* Throws an IOException if we wrote a code other than 'OK'.
* Increments the appropriate statistics also.
*/
protected void processWrittenHeaders() throws IOException {
if(outgoing) {
switch(response.getStatusCode()) {
case HandshakeResponse.OK:
HandshakingStat.SUCCESSFUL_OUTGOING.incrementStat();
break;
case HandshakeResponse.SLOTS_FULL:
HandshakingStat.OUTGOING_CLIENT_REJECT.incrementStat();
throw NoGnutellaOkException.CLIENT_REJECT;
case HandshakeResponse.LOCALE_NO_MATCH:
//if responder's locale preferencing was set
//and didn't match the locale this code is used.
//(currently in use by the dedicated connectionfetcher)
throw NoGnutellaOkException.CLIENT_REJECT_LOCALE;
default:
HandshakingStat.OUTGOING_CLIENT_UNKNOWN.incrementStat();
throw NoGnutellaOkException.createClientUnknown(response.getStatusCode());
}
} else {
switch(response.getStatusCode()) {
case HandshakeResponse.OK:
case HandshakeResponse.CRAWLER_CODE: // let the crawler IOX in ReadResponse
break;
case HandshakeResponse.SLOTS_FULL:
HandshakingStat.INCOMING_CLIENT_REJECT.incrementStat();
throw NoGnutellaOkException.CLIENT_REJECT;
default:
HandshakingStat.INCOMING_CLIENT_UNKNOWN.incrementStat();
throw NoGnutellaOkException.createClientUnknown(response.getStatusCode());
}
}
}
}
/** The first state in an outgoing handshake. */
static class WriteRequestState extends WriteHandshakeState {
private Properties request;
/** Creates a new WriteRequestState using the given support & initial set of properties. */
WriteRequestState(HandshakeSupport support, Properties request) {
super(support);
this.request = request;
}
/** Returns a ByteBuffer of the initial connect request & headers. */
protected ByteBuffer createOutgoingData() {
StringBuffer sb = new StringBuffer();
support.appendConnectLine(sb);
support.appendHeaders(request, sb);
return ByteBuffer.wrap(sb.toString().getBytes()); // TODO: conversion??
}
/** Does nothing. */
protected void processWrittenHeaders() {}
}
}