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