package org.jscsi.target.connection;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SocketChannel;
import java.security.DigestException;
import org.jscsi.exception.InternetSCSIException;
import org.jscsi.parser.BasicHeaderSegment;
import org.jscsi.parser.InitiatorMessageParser;
import org.jscsi.parser.OperationCode;
import org.jscsi.parser.ProtocolDataUnit;
import org.jscsi.parser.ProtocolDataUnitFactory;
import org.jscsi.parser.TargetMessageParser;
import org.jscsi.parser.scsi.SCSICommandParser;
import org.jscsi.target.scsi.cdb.ScsiOperationCode;
import org.jscsi.target.settings.Settings;
import org.jscsi.target.settings.SettingsException;
import org.jscsi.target.settings.TextKeyword;
import org.jscsi.target.util.Debug;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Instances of this class are used by {@link Connection} objects for
* sending and receiving {@link ProtocolDataUnit} objects.
*
* @author Andreas Ergenzinger, University of Konstanz
*/
public class TargetSenderWorker {
private static final Logger LOGGER = LoggerFactory.getLogger(TargetSenderWorker.class);
/**
* The connection which uses this object for sending and receiving PDUs.
*/
final private Connection connection;
/**
* The session to which {@link #connection} belongs to.
*/
private TargetSession session;
/**
* Used for writing serialized PDUs to and reading serialized PDUs from.
*/
final private SocketChannel socketChannel;
/**
* Will be used to create {@link ProtocolDataUnit} objects from the byte
* stream read from the {@link #socketChannel}.
*/
final private ProtocolDataUnitFactory protocolDataUnitFactory;
/**
* If this is <code>true</code>, then the next PDU read from the {@link #socketChannel} will be the first
* PDU received in the {@link #session}.
* <p>
* Will be initializes to <code>true</code> if and only if the {@link #connection} is the leading
* connection of its session.
* <p>
* PDUs identified by this variable as the first PDU in a session will not have their counters (i.e. CmdSN
* and ExpStatSN) checked. Instead the values of these counters will be used to initialize the targets
* local copies of these counters that will be used to ensure that no PDUs have been lost in transit.
*/
private boolean initialPdu;
/**
* The local address the client reached. Address only, do not include the port. Null if the initialization of the
* value failed.
*/
private final String localAddr;
/**
* Creates a new {@link TargetSenderWorker} object.
*
* @param connection
* the connection that will use this object for sending and
* receiving PDUs
* @param socketChannel
* used for sending and receiving serialized PDU to and from the
* target
*/
public TargetSenderWorker(final Connection connection, final SocketChannel socketChannel) {
this.connection = connection;
this.socketChannel = socketChannel;
protocolDataUnitFactory = new ProtocolDataUnitFactory();
initialPdu = connection.isLeadingConnection();
// Init local address reached, null if not found or on error
String localAddrTmp = null;
try {
final SocketAddress localAddrSocket = socketChannel.getLocalAddress();
if (localAddrSocket instanceof InetSocketAddress) {
final InetAddress localAddrInet = ((InetSocketAddress) localAddrSocket).getAddress();
if (localAddrInet != null) {
localAddrTmp = localAddrInet.getHostAddress();
}
}
}
catch (Throwable t) {
// ignored
LOGGER.debug("Failed to get local address", t);
}
localAddr = localAddrTmp;
}
/**
* Sets the {@link #session} variable.
* <p>
* During the time this object is initialized, the {@link Connection#getSession()} method will return
* <code>null</code>. Therefore {@link #session} must be set manually once the {@link TargetSession}
* object has been created.
*
* @param session
* the session of the {@link #connection}
*/
void setSession(final TargetSession session) {
this.session = session;
}
/**
* This method does all the necessary steps, which are needed when a
* connection should be closed.
*
* @throws IOException
* if an I/O error occurs.
*/
public final void close() throws IOException {
socketChannel.close();
}
// OODRIVE
final String getPeerInfo() {
try {
return socketChannel.getRemoteAddress().toString();
}
catch (Throwable e) {
return "<unknown>";
}
}
// OODRIVE
final SocketChannel getSocketChannel() {
return socketChannel ;
}
/**
* Gets the local address the client reached.
*
* @return the local address or <code>null</code> if the address can not be determined
*/
final String getLocalAddress() {
return localAddr;
}
/**
* Receives a <code>ProtocolDataUnit</code> from the socket and appends it
* to the end of the receiving queue of this connection.
*
* @return Queue with the resulting units
* @throws IOException
* if an I/O error occurs.
* @throws InternetSCSIException
* if any violation of the iSCSI-Standard emerge.
* @throws DigestException
* if a mismatch of the digest exists.
* @throws SettingsException
*/
ProtocolDataUnit receiveFromWire(int emptyReadAttemptCount) throws DigestException, InternetSCSIException, IOException,
SettingsException {
ProtocolDataUnit pdu;
if (initialPdu) {
/*
* The connection's ConnectionSettingsNegotiator has not been
* initialized, hence getSettings() would throw a
* NullPointerException.
*
* Initialize PDU with default values, i.e. no digests.
*/
pdu = protocolDataUnitFactory.create(TextKeyword.NONE,// header
// digest
TextKeyword.NONE);// data digest
} else {
// use negotiated or (now available) default settings
final Settings settings = connection.getSettings();
pdu = protocolDataUnitFactory.create(settings.getHeaderDigest(), settings.getDataDigest());
}
try {
if (pdu.read(socketChannel, emptyReadAttemptCount) == 0) {
return null;
}
} catch (ClosedChannelException e) {
throw new InternetSCSIException(e);
}
if (LOGGER.isDebugEnabled())
LOGGER.debug("Receiving this PDU:\n" + pdu);
// parse sequence counters
final BasicHeaderSegment bhs = pdu.getBasicHeaderSegment();
final InitiatorMessageParser parser = (InitiatorMessageParser)bhs.getParser();
// final int commandSequenceNumber = parser.getCommandSequenceNumber();
// final int expectedStatusSequenceNumber = parser.getExpectedStatusSequenceNumber();
if (LOGGER.isDebugEnabled()) {
// sharrajesh
// Needed to debug, out of order receiving of StatusSN and ExpStatSN
if (bhs.getOpCode() == OperationCode.SCSI_COMMAND) {
final SCSICommandParser scsiParser = (SCSICommandParser)bhs.getParser();
ScsiOperationCode scsiOpCode = ScsiOperationCode.valueOf(scsiParser.getCDB().get(0));
LOGGER.debug("scsiOpCode = " + scsiOpCode);
LOGGER.debug("CDB bytes: \n" + Debug.byteBufferToString(scsiParser.getCDB()));
}
// LOGGER.debug("parser.expectedStatusSequenceNumber: " + expectedStatusSequenceNumber);
if (connection == null)
LOGGER.debug("connection: null");
else if (connection.getStatusSequenceNumber() == null)
LOGGER.debug("connection.getStatusSequenceNumber: null");
else
LOGGER.debug("connection.getStatusSequenceNumber: "
+ connection.getStatusSequenceNumber().getValue());
}
// if this is the first PDU in the leading connection, then
// initialize the session's ExpectedCommandSequenceNumber
if (initialPdu) {
initialPdu = false;
// PDU is immediate Login PDU, checked in Target.main(),
// ExpCmdSN of this PDU will be used to initialize the
// respective session and connection parameters (sequence numbers)
// see TargetSession and TargetConnection initialization in
// Target.main()
} else {
// check sequence counters
// if (session.getMaximumCommandSequenceNumber().lessThan(commandSequenceNumber))
// throw new InternetSCSIException("received CmdSN (" + commandSequenceNumber + " > local MaxCmdSN (" + session.getMaximumCommandSequenceNumber().getValue() + ")");
// verified, is working with Windows 8 initiator
// if (!connection.getStatusSequenceNumber().equals(expectedStatusSequenceNumber)
// && expectedStatusSequenceNumber != 0)// required by MS iSCSI
// // initiator DATA-OUT
// // PDU sequence
// throw new InternetSCSIException("received ExpStatusSN ("+ expectedStatusSequenceNumber + ") != local StatusSN + 1 ("+connection.getStatusSequenceNumber().getValue() + ")");
}
// increment CmdSN if not immediate PDU (or Data-Out PDU)
try {
if (parser.incrementSequenceNumber())
session.getExpectedCommandSequenceNumber().increment();
} catch (NullPointerException exc) {
}
return pdu;
}
/**
* Sends the given <code>ProtocolDataUnit</code> instance over the socket to
* the connected iSCSI Target.
*
* @param pdu
* The <code>ProtocolDataUnit</code> instances to send.
* @throws InternetSCSIException
* if any violation of the iSCSI-Standard emerge.
* @throws IOException
* if an I/O error occurs.
* @throws InterruptedException
* if another caller interrupted the current caller before or
* while the current caller was waiting for a notification. The
* interrupted status of the current caller is cleared when this
* exception is thrown.
*/
final void sendOverWire(final ProtocolDataUnit pdu) throws InternetSCSIException, IOException,
InterruptedException {
// set sequence counters
final TargetMessageParser parser = (TargetMessageParser)pdu.getBasicHeaderSegment().getParser();
parser.setExpectedCommandSequenceNumber(session.getExpectedCommandSequenceNumber().getValue());
parser.setMaximumCommandSequenceNumber(session.getMaximumCommandSequenceNumber().getValue());
final boolean incrementSequenceNumber = parser.incrementSequenceNumber();
if (incrementSequenceNumber)// set StatSN only if field is not reserved
parser.setStatusSequenceNumber(connection.getStatusSequenceNumber().getValue());
if (LOGGER.isDebugEnabled())
LOGGER.debug("Sending this PDU:\n" + pdu);
// send pdu
pdu.write(socketChannel);
// increment StatusSN if this was a Response PDU (with status)
// or if special cases apply
if (incrementSequenceNumber)
connection.getStatusSequenceNumber().increment();
}
}