package net.i2p.sam; /* * free (adj.): unencumbered; not under the control of others * Written by human in 2004 and released into the public domain * with no warranty of any kind, either expressed or implied. * It probably won't make your computer catch on fire, or eat * your children, but it might. Use at your own risk. * */ import java.io.IOException; import java.nio.channels.SocketChannel; import java.nio.ByteBuffer; import java.util.Properties; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; import net.i2p.util.I2PAppThread; import net.i2p.util.Log; /** * Base class for SAM protocol handlers. It implements common * methods, but is not able to actually parse the protocol itself: * this task is delegated to subclasses. * * @author human */ abstract class SAMHandler implements Runnable, Handler { protected final Log _log; protected I2PAppThread thread; protected final SAMBridge bridge; private final Object socketWLock = new Object(); // Guards writings on socket protected final SocketChannel socket; public final int verMajor; public final int verMinor; /** I2CP options configuring the I2CP connection (port, host, numHops, etc) */ protected final Properties i2cpProps; protected final Object stopLock = new Object(); protected boolean stopHandler; /** * SAMHandler constructor (to be called by subclasses) * * @param s Socket attached to a SAM client * @param verMajor SAM major version to manage * @param verMinor SAM minor version to manage * @param i2cpProps properties to configure the I2CP connection (host, port, etc) * @throws IOException */ protected SAMHandler(SocketChannel s, int verMajor, int verMinor, Properties i2cpProps, SAMBridge parent) throws IOException { _log = I2PAppContext.getGlobalContext().logManager().getLog(getClass()); socket = s; this.verMajor = verMajor; this.verMinor = verMinor; this.i2cpProps = i2cpProps; bridge = parent; } /** * Start handling the SAM connection, detaching an handling thread. * */ public final void startHandling() { thread = new I2PAppThread(this, getClass().getSimpleName()); thread.start(); } /** * Actually handle the SAM protocol. * */ protected abstract void handle(); /** * Get the channel of the socket connected to the SAM client * * @return channel */ protected final SocketChannel getClientSocket() { return socket ; } /** * Write a byte array on the handler's socket. This method must * always be used when writing data, unless you really know what * you're doing. * * @param data A byte array to be written * @throws IOException */ protected final void writeBytes(ByteBuffer data) throws IOException { synchronized (socketWLock) { writeBytes(data, socket); } } /** * Caller must synch */ private static void writeBytes(ByteBuffer data, SocketChannel out) throws IOException { while (data.hasRemaining()) out.write(data); out.socket().getOutputStream().flush(); } /** * If you're crazy enough to write to the raw socket, grab the write lock * with getWriteLock(), synchronize against it, and write to the getOut() * * @return socket Write lock object */ protected Object getWriteLock() { return socketWLock; } /** * Write a string to the handler's socket. This method must * always be used when writing strings, unless you really know what * you're doing. * * @param str A byte array to be written * * @return True if the string was successfully written, false otherwise */ protected final boolean writeString(String str) { synchronized (socketWLock) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Sending the client: [" + str + "]"); return writeString(str, socket); } } /** * Unsynchronized, use with caution * @return success */ public static boolean writeString(String str, SocketChannel out) { try { writeBytes(ByteBuffer.wrap(DataHelper.getUTF8(str)), out); } catch (IOException e) { //_log.debug("Caught IOException", e); return false; } return true ; } /** * Close the socket connected to the SAM client. * * @throws IOException */ protected final void closeClientSocket() throws IOException { socket.close(); } /** * Stop the SAM handler, close the client socket, * unregister with the bridge. */ public void stopHandling() { if (_log.shouldInfo()) _log.info("Stopping: " + this, new Exception("I did it")); synchronized (stopLock) { stopHandler = true; } try { closeClientSocket(); } catch (IOException e) {} bridge.unregister(this); } /** * Should the handler be stopped? * * @return True if the handler should be stopped, false otherwise */ protected final boolean shouldStop() { synchronized (stopLock) { return stopHandler; } } /** * Get a string describing the handler. * * @return A String describing the handler; */ @Override public final String toString() { return (this.getClass().getSimpleName() + "; SAM version: " + verMajor + "." + verMinor + "; client: " + this.socket.socket().getInetAddress().toString() + ":" + this.socket.socket().getPort() + ")"); } /** * Register with the bridge, call handle(), * unregister with the bridge. */ public final void run() { bridge.register(this); try { handle(); } finally { bridge.unregister(this); } } }