package org.jscsi.target.connection; import java.io.IOException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.security.DigestException; import java.util.concurrent.Callable; import javax.naming.OperationNotSupportedException; import org.jscsi.exception.InternetSCSIException; import org.jscsi.parser.ProtocolDataUnit; import org.jscsi.target.TargetServer; import org.jscsi.target.connection.phase.TargetFullFeaturePhase; import org.jscsi.target.connection.phase.TargetLoginPhase; import org.jscsi.target.connection.phase.TargetPhase; import org.jscsi.target.connection.stage.fullfeature.ReadStage; import org.jscsi.target.connection.stage.fullfeature.WriteStage; import org.jscsi.target.settings.ConnectionSettingsNegotiator; import org.jscsi.target.settings.SessionSettingsNegotiator; import org.jscsi.target.settings.Settings; import org.jscsi.target.settings.SettingsException; import org.jscsi.target.util.FastByteArrayProvider; import org.jscsi.target.util.SerialArithmeticNumber; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.carrotsearch.hppc.IntObjectOpenHashMap; /** * A class for objects representing an iSCSI connection with all necessary * variables. * <p> * Each {@link TargetConnection} runs in a separate {@link Thread}. The conceptually most important parts of * its behavior can be likened to a finite state machine (FSM), in which the most basic states (stages) are * grouped into more general states (phases). Commands send by the initiator are carried out in these stages, * usually without transitioning to a different phase. A connection's current phase determines which stages * are reachable, limiting the kind of commands the initiator may issue at any given moment. * * @author Andreas Ergenzinger */ public interface Connection extends Callable<Void> { Settings getSettings(); SerialArithmeticNumber getStatusSequenceNumber(); boolean isLeadingConnection(); ProtocolDataUnit receivePdu() throws DigestException, InternetSCSIException, IOException, SettingsException; ProtocolDataUnit receivePdu(int attemptCount) throws DigestException, InternetSCSIException, IOException, SettingsException; void sendPdu(ProtocolDataUnit pDataUnit) throws InterruptedException, IOException, InternetSCSIException; ConnectionSettingsNegotiator getConnectionSettingsNegotiator(); void setSession(TargetSession pSession); TargetSession getTargetSession(); // OODRIVE String getSenderWorkerPeerInfo(); // OODRIVE SocketChannel getSocketChannel(); // OODRIVE public void setConnectionID(int connectionID); // OODRIVE public int getConnectionID(); void setStatusSequenceNumber(int pStatusSequenceNumber); void initializeConnectionSettingsNegotiator(SessionSettingsNegotiator pSettingsNegotiator); byte[] getDataInArray(int pLength); // OODRIVE public void close(); // OODRIVE public String getLocalAddress(); // OODRIVE TargetServer getTargetServer(); // OODRIVE public void setPhase(TargetPhase targetPhase); // OODRIVE public void enableRead(); // OODRIVE public void addWriteStage(final int taskTag, final WriteStage stage); // OODRIVE public void removeWriteStage(final int taskTag); // OODRIVE public WriteStage getWriteStage (final int taskTag); public static class TargetConnection implements Connection { private static final Logger LOGGER = LoggerFactory.getLogger(TargetConnection.class); /** * The {@link TargetSession} this connection belongs to. */ private TargetSession targetSession; /** * The {@link TargetSenderWorker} used by this connection for sending and * receiving {@link ProtocolDataUnit}s. */ private final TargetSenderWorker senderWorker; /** * The {@link ConnectionSettingsNegotiator} of this connection responsible * for negotiating and storing connection parameters which have been * negotiated with or declared by the initiator. */ private ConnectionSettingsNegotiator connectionSettingsNegotiator; /** * The current {@link TargetPhase} describing a general state of the * connection. */ private TargetPhase phase; /** * A counter for the <code>StatSN</code> field of sent {@link ProtocolDataUnit} objects with * Status. */ private SerialArithmeticNumber statusSequenceNumber; /** * Will manage and serve as a source of byte arrays to be used for sending * Data In PDUs in the {@link ReadStage}. */ private FastByteArrayProvider dataInArrayProvider = new FastByteArrayProvider(4); /** * <code>true</code> if and only if this connection is the first connection * to be associated with its parent session. * <p> * This distinction is necessary because some parameters may only be declared over the leading * connection. */ private final boolean isLeadingConnection; /** * The last {@link ProtocolDataUnit} received on this connection. */ /* OODRIVE : not used private ProtocolDataUnit lastReceivedPDU; */ // OODRIVE /** * The {@link SelectionKey} this connection belongs to. */ private final SelectionKey selectionKey; // OODRIVE /** * The target server this phase is a part of. */ private final TargetServer targetServer; // OODRIVE private int connectionID ; // OODRIVE /** * The map which contains all the current write stages. */ private IntObjectOpenHashMap<WriteStage> writeStages = new IntObjectOpenHashMap<>() ; /** * The {@link TargetConnection} constructor. * * @param socketChannel * used for sending and receiving PDUs * @param isLeadingConnection * <code>true</code> if and only if this connection is the first * connection associated with its enclosing session */ public TargetConnection(SocketChannel socketChannel, final boolean isLeadingConnection, SelectionKey selectionKey, TargetServer targetServer) { this.isLeadingConnection = isLeadingConnection; senderWorker = new TargetSenderWorker(this, socketChannel); this.selectionKey = selectionKey; this.phase = new TargetLoginPhase(this); this.targetServer = targetServer; } // OODRIVE public String getLocalAddress() { return senderWorker.getLocalAddress(); } /** * Returns a byte array that can be used for holding data segment data of * Data In PDUs sent during the {@link ReadStage}. * * @param length * the length of the array * @return a byte array of the specified length */ public byte[] getDataInArray(final int length) { return dataInArrayProvider.getArray(length); } /** * Returns the {@link TargetSession} this connection belongs to. * * @return the {@link TargetSession} this connection belongs to */ TargetSession getSession() { return targetSession; } /** * Sets the {@link TargetSession} this connection belongs to. * * @param session * the {@link TargetSession} this connection belongs to */ public void setSession(TargetSession session) { this.targetSession = session; senderWorker.setSession(session); } /** * Returns the next {@link ProtocolDataUnit} to be received on the * connection. * <p> * The method will block until a PDU has been completely received. * * @return the next received PDU * @throws DigestException * if a digest error has occured * @throws InternetSCSIException * if a general iSCSI protocol error has been detected * @throws IOException * if the connection was closed * @throws SettingsException * will not happen */ public ProtocolDataUnit receivePdu() throws DigestException, InternetSCSIException, IOException, SettingsException { return receivePdu(Integer.MAX_VALUE); } public ProtocolDataUnit receivePdu(int emptyReadAttemptCount) throws DigestException, InternetSCSIException, IOException, SettingsException { return senderWorker.receiveFromWire(emptyReadAttemptCount); } /** * Serializes and sends a {@link ProtocolDataUnit} over the connection. * * @param pdu * the PDU to send * @throws InterruptedException * @throws IOException * @throws InternetSCSIException */ public void sendPdu(ProtocolDataUnit pdu) throws InterruptedException, IOException, InternetSCSIException { senderWorker.sendOverWire(pdu); // OODRIVE pdu.release(); } // OODRIVE /** * The {@link ConnectionSettingsNegotiator} of this connection responsible */ public void close() { try { senderWorker.close(); } catch (Throwable t) { LOGGER.warn("Error during close", t); } try { targetServer.removeTargetConnection(this); } catch (Throwable t) { LOGGER.warn("Error when removing connecting", t); } try { selectionKey.cancel(); } catch (Throwable t) { LOGGER.warn("Error during selection key cancelation", t); } } /** * Starts the processing of PDUs by this connection. * <p> * For this method to work properly, the leading PDU send by the initiator over this connection must * have been received via {@link #receivePdu()}. */ public Void call() {// OODRIVE UNUSED // OODRIVE: change the name of the current thread final Thread myThread = Thread.currentThread(); final String prevThreadName = myThread.getName(); myThread.setName("iSCSI connection from " + senderWorker.getPeerInfo()); try { try { // *** login phase *** phase = new TargetLoginPhase(this); /* if (phase.execute(lastReceivedPDU)) { */ if (true) { LOGGER.debug("Login Phase successful"); // if this is the leading connection, set the session type final Settings settings = getSettings(); if (isLeadingConnection) targetSession.setSessionType(SessionType.getSessionType(settings.getSessionType())); targetSession.setTargetName(settings.getTargetName()); // *** full feature phase *** phase = new TargetFullFeaturePhase(this); phase.execute(); } // OODRIVE senderWorker.close(); } catch (OperationNotSupportedException | IOException | InterruptedException | InternetSCSIException | DigestException | SettingsException e) { LOGGER.error("Exception throws", e); } catch (Throwable t) { LOGGER.error("Exception throws", t); } finally { close(); targetSession.removeTargetConnection(this); LOGGER.debug("closed connection"); } return null; } finally {myThread.setName(prevThreadName);} } public TargetSession getTargetSession() { return targetSession; } // OODRIVE @Override public final String getSenderWorkerPeerInfo() { return senderWorker.getPeerInfo(); } // OODRIVE @Override public final SocketChannel getSocketChannel() { return senderWorker.getSocketChannel(); } /** * Returns <code>true</code> if this is the leading connection, i.e. the * first TargetConnection in the connection's {@link TargetSession}. * Otherwise <code>false</code> is returned. * * @return <code>true</code> if this is the leading connection */ public boolean isLeadingConnection() { return isLeadingConnection; } /** * Initializes {@link #connectionSettingsNegotiator}. * <p> * This method must be be called after the this connection has been added to its session. */ public void initializeConnectionSettingsNegotiator( final SessionSettingsNegotiator sessionSettingsNegotiator) { connectionSettingsNegotiator = new ConnectionSettingsNegotiator(sessionSettingsNegotiator); } /** * Returns a {@link Settings} object with a snapshot of the current * connection and session parameters. * * @return the current {@link Settings} */ public Settings getSettings() { return connectionSettingsNegotiator.getSettings(); } public ConnectionSettingsNegotiator getConnectionSettingsNegotiator() { return connectionSettingsNegotiator; } public SerialArithmeticNumber getStatusSequenceNumber() { return statusSequenceNumber; } public void setStatusSequenceNumber(final int statusSequenceNumber) { this.statusSequenceNumber = new SerialArithmeticNumber(statusSequenceNumber); } // OODRIVE public void setPhase(TargetPhase targetPhase) { this.phase = targetPhase; } // OODRIVE public TargetPhase getPhase() { return phase; } // OODRIVE @Override public TargetServer getTargetServer() { return targetServer; } // OODRIVE public void setConnectionID(int connectionID) { this.connectionID = connectionID; } // OODRIVE public int getConnectionID() { return connectionID; } // OODRIVE public void enableRead() { int opsMask = selectionKey.interestOps(); opsMask |= (SelectionKey.OP_READ); selectionKey.interestOps(opsMask); // wake up the main thread blocked in the select Selector selector = targetServer.getSelector(); if (selector != null) { selector.wakeup(); } } // OODRIVE public void addWriteStage(final int taskTag, final WriteStage stage){ writeStages.put(taskTag, stage); } // OODRIVE public void removeWriteStage(final int taskTag){ writeStages.remove(taskTag); } // OODRIVE public WriteStage getWriteStage (final int taskTag){ return writeStages.get(taskTag); } } }