package com.limegroup.gnutella.handshaking; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import com.limegroup.gnutella.io.BufferUtils; import com.limegroup.gnutella.io.ChannelReadObserver; import com.limegroup.gnutella.io.ChannelWriter; import com.limegroup.gnutella.io.InterestReadChannel; import com.limegroup.gnutella.io.InterestWriteChannel; /** * Handshaking class that iterates through the available states and sets the * appropriate interest on the channel depending on the state. */ class AsyncHandshaker implements ChannelReadObserver, ChannelWriter, InterestReadChannel { /** The Handshaker controlling this. */ private Handshaker shaker; /** Observer to notify when this finishes or fails. */ private HandshakeObserver handshakeObserver; /** The states this will use while handshaking.*/ private List /* of HandshakeState */ states; /** The current state. */ private HandshakeState currentState; /** The sink we write to. */ private InterestWriteChannel writeSink; /** The sink we read from. */ private InterestReadChannel readSink; /** The ByteBuffer to use for reading. */ private ByteBuffer readBuffer; /** Whether or not we've shutdown this handshaker. */ private volatile boolean shutdown; /** Constructs a new AsyncHandker using the given Handshaker, HandshakeObserver, and List of states. */ AsyncHandshaker(Handshaker shaker, HandshakeObserver observer, List states) { this.shaker = shaker; this.handshakeObserver = observer; this.states = states; this.readBuffer = ByteBuffer.allocate(2048); this.currentState = (HandshakeState)states.remove(0); } /** * Notification that a read can be performed. If our current state is for writing, * we'll turn off future interest events. Otherwise we'll tell the current state * to process. */ public void handleRead() { if(currentState.isWriting()) { readSink.interest(false); } else { processCurrentState(true); } } /** * Notification that a write can be performed. If our current state is for reading, * we'll turn off future interest events. Otherwise we'll tell the current state to process. */ public boolean handleWrite() { if(currentState.isReading()) { writeSink.interest(this, false); return false; } else { return processCurrentState(false); } } /** * Process the current state. If any exceptions occur while procesing, * we'll notify the observer of them. If the state indicated it needs to be * processed again, we do not move to the next state. Otherwise, if the state * indicated that it's done, we move to the next state. * * This will return true if we're writing and we have more to write. * @param reading * @return */ private boolean processCurrentState(boolean reading) { if(!shutdown) { try { if (reading) { if (!currentState.process(readSink, readBuffer)) nextState(true); } else { if (!currentState.process(writeSink, null)) nextState(false); else return true; } } catch (NoGnutellaOkException ex) { shutdown = true; handshakeObserver.handleNoGnutellaOk(ex.getCode(), ex.getMessage()); } catch (IOException iox) { shutdown = true; handshakeObserver.handleBadHandshake(); } } return false; } /** * Moves to the next state. * If there are no states left, we notify the observer that we're finished. * Otherwise, we'll move to the next state and change interest on our channels * depending on what we're currently doing and what's needed next. * * @param reading */ private void nextState(boolean reading) { if(states.isEmpty()) { readSink.interest(false); writeSink.interest(this, false); handshakeObserver.handleHandshakeFinished(shaker); } else { currentState = (HandshakeState)states.remove(0); if(currentState.isReading() && !reading) { readSink.interest(true); writeSink.interest(this, false); } else if(currentState.isWriting() && reading) { readSink.interest(false); writeSink.interest(this, true); } } } public InterestWriteChannel getWriteChannel() { return writeSink; } public void setWriteChannel(InterestWriteChannel newChannel) { this.writeSink = newChannel; writeSink.interest(this, true); } public InterestReadChannel getReadChannel() { return readSink; } public void setReadChannel(InterestReadChannel newChannel) { this.readSink = newChannel; readSink.interest(true); } public boolean isOpen() { return readSink.isOpen() && writeSink.isOpen(); } public void close() throws IOException { readSink.close(); writeSink.close(); } /** * Notification that this Handshaker is being shut down. * This may be because message looping is taking over and is notifying us that * we're done. If that's the case, we do not notify the observer that we were shutdown. * Otherwise, we notify the observer that we were shut down. */ public void shutdown() { synchronized(this) { if(shutdown) return; shutdown = true; } // this may be called when the message reading is installed if(!isOpen()) { handshakeObserver.shutdown(); } } public void interest(boolean status) { readSink.interest(status); } /** * Allows another channel to read from this, passing any unread bytes to that channel. * This is typically used for the MessageReader to read any bytes that this AsyncHandshaker * read from the network but did not process during handshaking. */ public int read(ByteBuffer toBuffer) { return BufferUtils.transfer(readBuffer, toBuffer); } // unused. public void handleIOException(IOException iox) {} }