package net.i2p.client.streaming.impl; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.channels.SelectableChannel; import java.util.concurrent.atomic.AtomicBoolean; import net.i2p.I2PAppContext; import net.i2p.client.I2PSession; import net.i2p.client.streaming.I2PSocket; import net.i2p.client.streaming.I2PSocketOptions; import net.i2p.data.Destination; import net.i2p.util.Log; /** * Bridge between the full streaming lib and the I2PSocket API * */ class I2PSocketFull implements I2PSocket { private final Log log; private volatile Connection _connection; private final Destination _remotePeer; private final Destination _localPeer; private final AtomicBoolean _closed = new AtomicBoolean(); public I2PSocketFull(Connection con, I2PAppContext context) { log = context.logManager().getLog(I2PSocketFull.class); _connection = con; if (con != null) { _remotePeer = con.getRemotePeer(); _localPeer = con.getSession().getMyDestination(); } else _remotePeer = _localPeer = null; } /** * Closes this socket. * * Nonblocking as of 0.9.9: * Any thread currently blocked in an I/O operation upon this socket will throw an IOException. * Once a socket has been closed, it is not available for further networking use * (i.e. can't be reconnected or rebound). A new socket needs to be created. * Closing this socket will also close the socket's InputStream and OutputStream. */ public void close() throws IOException { if (!_closed.compareAndSet(false,true)) { // log a trace to find out why log.logCloseLoop("I2PSocket",_localPeer,"-->",_remotePeer,_connection); return; } Connection c = _connection; if (c == null) return; if (log.shouldLog(Log.INFO)) log.info("close() called, connected? " + c.getIsConnected() + " : " + c, new Exception()); if (c.getIsConnected()) { MessageInputStream in = c.getInputStream(); in.close(); MessageOutputStream out = c.getOutputStream(); out.closeInternal(); // this will cause any thread waiting in Connection.packetSendChoke() // to throw an IOE c.windowAdjusted(); } else { //throw new IOException("Not connected"); } destroy(); } /** * Resets and closes this socket. Sends a RESET indication to the far-end. * This is the equivalent of setSoLinger(true, 0) followed by close() on a Java Socket. * * Nonblocking. * Any thread currently blocked in an I/O operation upon this socket will throw an IOException. * Once a socket has been reset, it is not available for further networking use * (i.e. can't be reconnected or rebound). A new socket needs to be created. * Resetting this socket will also close the socket's InputStream and OutputStream. * * @since 0.9.30 */ public void reset() throws IOException { Connection c = _connection; if (c == null) return; if (log.shouldLog(Log.INFO)) log.info("reset() called, connected? " + c.getIsConnected() + " : " + c, new Exception()); if (c.getIsConnected()) { c.disconnect(false); // this will cause any thread waiting in Connection.packetSendChoke() // to throw an IOE c.windowAdjusted(); } destroy(); } Connection getConnection() { return _connection; } /** * As of 0.9.9 will throw an IOE if socket is closed. * Prior to that would return null instead of throwing IOE. * @return non-null */ public InputStream getInputStream() throws IOException { Connection c = _connection; if (c != null) return c.getInputStream(); throw new IOException("Socket closed"); } public I2PSocketOptions getOptions() { Connection c = _connection; if (c != null) return c.getOptions(); else return null; } /** * Unimplemented, unlikely to ever be implemented. * * @deprecated * @return null always * @since 0.8.9 */ @Deprecated public synchronized SelectableChannel getChannel() { return null; } /** * As of 0.9.9 will throw an IOE if socket is closed. * Prior to that would return null instead of throwing IOE. * @return non-null */ public OutputStream getOutputStream() throws IOException { Connection c = _connection; if (c != null) return c.getOutputStream(); throw new IOException("Socket closed"); } public Destination getPeerDestination() { return _remotePeer; } public long getReadTimeout() { I2PSocketOptions opts = getOptions(); if (opts != null) return opts.getReadTimeout(); else return -1; } public Destination getThisDestination() { return _localPeer; } public void setOptions(I2PSocketOptions options) { Connection c = _connection; if (c == null) return; if (options instanceof ConnectionOptions) c.setOptions((ConnectionOptions)options); else c.setOptions(new ConnectionOptions(options)); } public void setReadTimeout(long ms) { Connection c = _connection; if (c == null) return; if (ms > Integer.MAX_VALUE) ms = Integer.MAX_VALUE; c.getInputStream().setReadTimeout((int)ms); c.getOptions().setReadTimeout(ms); } /** * Deprecated, unimplemented, does nothing */ public void setSocketErrorListener(I2PSocket.SocketErrorListener lsnr) { } public boolean isClosed() { Connection c = _connection; return ((c == null) || (!c.getIsConnected()) || (c.getResetReceived()) || (c.getResetSent())); } void destroy() { destroy2(); } /** * Call from Connection.disconnectComplete() * instead of destroy() so we don't loop * @since 0.8.13 */ void destroy2() { _connection = null; } /** * The remote port. * @return the port or 0 if unknown * @since 0.8.9 */ public int getPort() { Connection c = _connection; return c == null ? I2PSession.PORT_UNSPECIFIED : c.getPort(); } /** * The local port. * @return the port or 0 if unknown * @since 0.8.9 */ public int getLocalPort() { Connection c = _connection; return c == null ? I2PSession.PORT_UNSPECIFIED : c.getLocalPort(); } @Override public String toString() { Connection c = _connection; if (c == null) return super.toString(); else return c.toString(); } }