package com.limegroup.gnutella.handshaking;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Enumeration;
import java.util.Properties;
import com.limegroup.gnutella.ByteReader;
import com.limegroup.gnutella.Constants;
import com.limegroup.gnutella.settings.ConnectionSettings;
import com.limegroup.gnutella.statistics.BandwidthStat;
/**
* Contains some convenience methods for handshaking.
*/
class BlockingHandshakeSupport extends HandshakeSupport {
/** Socket we're basing this I/O on. */
private Socket socket;
/** InputStream we're reading from */
private InputStream in;
/** OutputStream we're writing to. */
private OutputStream out;
/** Constructs a new Support object based on the given Socket, InputStream & OutputStream. */
BlockingHandshakeSupport(Socket socket, InputStream in, OutputStream out) {
super(socket.getInetAddress().getHostAddress());
this.socket = socket;
this.in = in;
this.out = out;
}
/**
* Reads and returns one line from the network. A line is defined as a
* maximal sequence of characters without '\n', with '\r''s removed. If the
* characters cannot be read within TIMEOUT milliseconds (as defined by the
* property manager), throws IOException. This includes EOF.
* @return The line of characters read
* @exception IOException if the characters cannot be read within
* the specified timeout
*/
String readLine() throws IOException {
return readLine(Constants.TIMEOUT);
}
/**
* Reads and returns one line from the network. A line is defined as a
* maximal sequence of characters without '\n', with '\r''s removed. If the
* characters cannot be read within the specified timeout milliseconds,
* throws IOException. This includes EOF.
* @param timeout The time to wait on the socket to read data before
* IOException is thrown
* @return The line of characters read
* @exception IOException if the characters cannot be read within
* the specified timeout
*/
String readLine(int timeout) throws IOException {
int oldTimeout=socket.getSoTimeout();
// _in.read can throw an NPE if we closed the connection,
// so we must catch NPE and throw the CONNECTION_CLOSED.
try {
socket.setSoTimeout(timeout);
// TODO: don't read over max line size.
String line=(new ByteReader(in)).readLine();
if (line==null)
throw new IOException("read null line");
BandwidthStat.GNUTELLA_HEADER_DOWNSTREAM_BANDWIDTH.addData(line.length());
return line;
} catch(NullPointerException npe) {
throw new IOException();
} finally {
//Restore socket timeout.
socket.setSoTimeout(oldTimeout);
}
}
/**
* Reads all headers.
*/
void readHeaders() throws IOException {
readHeaders(Constants.TIMEOUT);
}
/**
* Reads the properties from the network and stores them locally.
*
* @param timeout The time to wait on the socket to read data before
* IOException is thrown
* @exception IOException if the characters cannot be read within
* the specified timeout
*/
void readHeaders(int timeout) throws IOException {
while (true) {
// This doesn't distinguish between \r and \n. That's fine.
String line = readLine(timeout);
if (line == null)
throw new IOException("unexpected end of file"); // unexpected EOF
if(!processReadHeader(line))
break;
if(getHeadersReadSize() > ConnectionSettings.MAX_HANDSHAKE_HEADERS.getValue())
throw new IOException("too many headers");
}
}
/** Writes the initial connection line. */
void writeConnectLine() throws IOException {
StringBuffer sb = new StringBuffer();
appendConnectLine(sb);
writeLine(sb.toString());
}
/** Writes a response using the given HandshakeResponse. */
void writeResponse(HandshakeResponse response) throws IOException {
StringBuffer sb = new StringBuffer();
appendResponse(response, sb);
writeLine(sb.toString());
}
/**
* Writes s to out, with no trailing linefeeds. Called only from initialize().
*/
void writeLine(String s) throws IOException {
if (s == null || s.equals("")) {
throw new NullPointerException("null or empty string: " + s);
}
// TODO: character encodings?
byte[] bytes = s.getBytes();
BandwidthStat.GNUTELLA_HEADER_UPSTREAM_BANDWIDTH.addData(bytes.length);
out.write(bytes);
out.flush();
}
/**
* Writes the properties in props to network, including the blank line at the end.
* Throws IOException if there are any problems.
*
* @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 sendHeaders(Properties props) throws IOException {
StringBuffer sb = new StringBuffer();
appendHeaders(props, sb);
writeLine(sb.toString());
}
}