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()); } }