package org.jscsi.target.connection.stage.fullfeature;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.DigestException;
import org.jscsi.exception.InternetSCSIException;
import org.jscsi.parser.AbstractMessageParser;
import org.jscsi.parser.BasicHeaderSegment;
import org.jscsi.parser.ProtocolDataUnit;
import org.jscsi.parser.data.DataOutParser;
import org.jscsi.parser.nop.NOPOutParser;
import org.jscsi.parser.scsi.SCSICommandParser;
import org.jscsi.parser.scsi.SCSIResponseParser;
import org.jscsi.parser.scsi.SCSIStatus;
import org.jscsi.target.TargetServer;
import org.jscsi.target.connection.TargetPduFactory;
import org.jscsi.target.connection.phase.TargetFullFeaturePhase;
import org.jscsi.target.scsi.ScsiResponseDataSegment;
import org.jscsi.target.scsi.cdb.ScsiOperationCode;
import org.jscsi.target.scsi.cdb.Write10Cdb;
import org.jscsi.target.scsi.cdb.Write6Cdb;
import org.jscsi.target.scsi.cdb.WriteCdb;
import org.jscsi.target.settings.SettingsException;
import org.jscsi.target.util.Debug;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A stage for processing <code>WRITE (6)</code> and <code>WRITE (10)</code> SCSI commands.
*
* @author Andreas Ergenzinger
*/
public final class WriteStage extends ReadOrWriteStage {
private static final Logger LOGGER = LoggerFactory.getLogger(WriteStage.class);
/**
* The <code>DataSN</code> value the next Data-Out PDU must carry.
*/
private int expectedDataSequenceNumber = 0;
// OODRIVE
private int initiatorTaskTag;
// OODRIVE
private int bytesReceived = 0;
// OODRIVE
private long storageIndex = 0;
// OODRIVE
private int blockSize = 0;
// OODRIVE
private int transferLengthInBytes = 0;
// OODRIVE
private int bytesReceivedThisCycle = 0;
// OODRIVE
private int readyToTransferSequenceNumber = 0;
// OODRIVE
private int desiredDataTransferLength = 0;
// OODRIVE
private boolean firstBurstOver = false;
public WriteStage(TargetFullFeaturePhase targetFullFeaturePhase) {
super(targetFullFeaturePhase);
}
/**
* Is used for checking if the PDUs received in a Data-Out sequence actually
* are Data-Out PDU and if the PDUs have been received in order.
*
* @param parser
* the {@link AbstractMessageParser} subclass instance retrieved
* from the {@link ProtocolDataUnit}'s {@link BasicHeaderSegment}
* @throws InternetSCSIException
* if an unexpected PDU has been received
*/
private void checkDataOutParser(final AbstractMessageParser parser) throws InternetSCSIException {
if (parser instanceof DataOutParser) {
final DataOutParser p = (DataOutParser)parser;
if (p.getDataSequenceNumber() != expectedDataSequenceNumber++) {
throw new InternetSCSIException("received erroneous PDU in data-out sequence, expected "
+ (expectedDataSequenceNumber - 1));
}
} else if (parser instanceof NOPOutParser || parser instanceof SCSICommandParser) {
} else {
throw new InternetSCSIException("received erroneous PDU in data-out sequence, "
+ parser.getClass().getName());
}
}
@Override
public void execute(ProtocolDataUnit pdu) throws IOException, DigestException, InterruptedException,
InternetSCSIException, SettingsException {
if (LOGGER.isDebugEnabled())
LOGGER.debug("Entering WRITE STAGE");
// get relevant values from settings
final boolean immediateData = settings.getImmediateData();
final boolean initialR2T = settings.getInitialR2T();
final int firstBurstLength = settings.getFirstBurstLength();
final int maxBurstLength = settings.getMaxBurstLength();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("immediateData = " + immediateData);
LOGGER.debug("initialR2T = " + initialR2T);
}
// get relevant values from PDU/CDB
BasicHeaderSegment bhs = pdu.getBasicHeaderSegment();
// OODRIVE
boolean sendR2T = false ;
if (bhs.getParser() instanceof SCSICommandParser){
SCSICommandParser parser = (SCSICommandParser)bhs.getParser();
initiatorTaskTag = bhs.getInitiatorTaskTag();
WriteCdb cdb;
final ScsiOperationCode scsiOpCode = ScsiOperationCode.valueOf(parser.getCDB().get(0));
if (scsiOpCode == ScsiOperationCode.WRITE_10)
cdb = new Write10Cdb(parser.getCDB());
else if (scsiOpCode == ScsiOperationCode.WRITE_6)
cdb = new Write6Cdb(parser.getCDB());
else {
// anything else wouldn't be good (programmer error)
// close connection
throw new InternetSCSIException("wrong SCSI Operation Code " + scsiOpCode + " in WriteStage");
}
final int transferLength = cdb.getTransferLength();
final long logicalBlockAddress = cdb.getLogicalBlockAddress();
// transform to from block units to byte units
blockSize = session.getStorageModule().getBlockSize();
transferLengthInBytes = transferLength * blockSize;
storageIndex = logicalBlockAddress * blockSize;
// check if requested blocks are out of bounds
// (might add FPSKSD to the CDB's list to be detected in the next step)
checkOverAndUnderflow(cdb);
if (cdb.getIllegalFieldPointers() != null) {
/*
* CDB is invalid, inform initiator by closing the connection.
*
* Sending an error status SCSI Response PDU will not work reliably,
* since the initiator may not be expecting a response so soon.
* Also, if the WriteStage is simply left early (without closing the
* connection), the initiator may send additional unsolicited
* Data-Out PDUs, which the jSCSI Target is currently unable to
* ignore or process properly.
*/
LOGGER.error("illegal field in Write CDB");
LOGGER.error("CDB:\n" + Debug.byteBufferToString(parser.getCDB()));
throw new InternetSCSIException();// leads to connection closing
}
/* OODRIVE
// *** start receiving data (or process what has already been sent) ***
int bytesReceived = 0;
*/
// *** receive immediate data ***
if (immediateData && bhs.getDataSegmentLength() > 0) {
final ByteBuffer immediateDataArray = pdu.getDataSegment();
session.getStorageModule().write(immediateDataArray, storageIndex);
bytesReceived = immediateDataArray.capacity();
if (LOGGER.isDebugEnabled())
LOGGER.debug("wrote " + immediateDataArray.capacity() + "bytes as immediate data");
}
// *** receive unsolicited data ***
if (!initialR2T && !bhs.isFinalFlag()) {
if (LOGGER.isDebugEnabled())
LOGGER.debug("receiving unsolicited data");
/* OODRIVE
boolean firstBurstOver = false;
while (!firstBurstOver && bytesReceived <= firstBurstLength) {
*/
// OODRIVE
sendR2T = false ;
if (LOGGER.isDebugEnabled()){
LOGGER.debug("will receive unsolicited data");
}
}
// OODRIVE
else if (bytesReceived < transferLengthInBytes) {
sendR2T = true;
}
}
// OODRIVE
else if (bhs.getParser() instanceof DataOutParser){
// *** receive unsolicited data ***
if (!initialR2T && !firstBurstOver && bytesReceived <= firstBurstLength) {
/* OODRIVE
// receive and check PDU
pdu = connection.receivePdu();
*/
bhs = pdu.getBasicHeaderSegment();
checkDataOutParser(bhs.getParser());
final DataOutParser dataOutParser = (DataOutParser)bhs.getParser();
session.getStorageModule().write(pdu.getDataSegment(),
storageIndex + dataOutParser.getBufferOffset());
;
bytesReceived += bhs.getDataSegmentLength();
if (bhs.isFinalFlag()){
firstBurstOver = true;
// OODRIVE
if (bytesReceived < transferLengthInBytes){
sendR2T = true ;
}
}
}
else {
// *** receive solicited data ***
if (bytesReceived < transferLengthInBytes) {
if (LOGGER.isDebugEnabled())
LOGGER.debug(bytesReceived + "<" + transferLengthInBytes);
/* OODRIVE
int readyToTransferSequenceNumber = 0;
int desiredDataTransferLength;
while (bytesReceived < transferLengthInBytes) {
desiredDataTransferLength = Math.min(maxBurstLength, transferLengthInBytes - bytesReceived);
// send R2T
pdu = TargetPduFactory.createReadyToTransferPdu(0,// logicalUnitNumber
initiatorTaskTag, TargetServer.getNextTargetTransferTag(),// targetTransferTag
readyToTransferSequenceNumber++, bytesReceived,// bufferOffset
desiredDataTransferLength);
connection.sendPdu(pdu);
// receive DataOut PDUs
expectedDataSequenceNumber = 0;// reset sequence counter//FIXME
// fix in jSCSI Initiator
boolean solicitedDataCycleOver = false;
int bytesReceivedThisCycle = 0;
while (!solicitedDataCycleOver) {
// receive and check PDU
pdu = connection.receivePdu();
bhs = pdu.getBasicHeaderSegment();
checkDataOutParser(bhs.getParser());
if (bhs.getParser() instanceof NOPOutParser) {
/* send SCSI Response PDU
pdu = TargetPduFactory.createSCSIResponsePdu(false,// bidirectionalReadResidualOverflow
false,// bidirectionalReadResidualUnderflow
false,// residualOverflow
false,// residualUnderflow
SCSIResponseParser.ServiceResponse.COMMAND_COMPLETED_AT_TARGET,// response
SCSIStatus.GOOD,// status
initiatorTaskTag, 0,// snackTag
0,// (ExpDataSN or) Reserved
0,// bidirectionalReadResidualCount
0,// residualCount
ScsiResponseDataSegment.EMPTY_DATA_SEGMENT);// dataSegment
connection.sendPdu(pdu);
return;
} else if (bhs.getParser() instanceof DataOutParser) {
*/
final DataOutParser dataOutParser = (DataOutParser)bhs.getParser();
checkDataOutParser(dataOutParser);
session.getStorageModule().write(pdu.getDataSegment(),
storageIndex + dataOutParser.getBufferOffset());
bytesReceivedThisCycle += bhs.getDataSegmentLength();
// OODRIVE
bytesReceived += bhs.getDataSegmentLength();
/*
* Checking the final flag should be enough, but is not,
* when dealing with the jSCSI Initiator. This is also one
* of the reasons, why the contents of this while loop,
* though very similar to what is happening during the
* receiving of the unsolicited data PDU sequence, has not
* been put into a dedicated method.
*/
if ((bhs.isFinalFlag() || bytesReceivedThisCycle >= desiredDataTransferLength) && bytesReceived < transferLengthInBytes){
/* OODRIVE
solicitedDataCycleOver = true;
*/
sendR2T = true ;
}
}
/* OODRIVE
}
bytesReceived += bytesReceivedThisCycle;
*/
}
}
// OODRIVE
if (sendR2T){
desiredDataTransferLength = Math.min(maxBurstLength, transferLengthInBytes - bytesReceived);
// send R2T
pdu = TargetPduFactory.createReadyToTransferPdu(0,// logicalUnitNumber
initiatorTaskTag, TargetServer.getNextTargetTransferTag(),// targetTransferTag
readyToTransferSequenceNumber++, bytesReceived,// bufferOffset
desiredDataTransferLength);
connection.sendPdu(pdu);
// receive DataOut PDUs
expectedDataSequenceNumber = 0;// reset sequence counter//FIXME
// fix in jSCSI Initiator
bytesReceivedThisCycle = 0;
}
if (bytesReceived >= transferLengthInBytes){
/* send SCSI Response PDU */
pdu = TargetPduFactory.createSCSIResponsePdu(false,// bidirectionalReadResidualOverflow
false,// bidirectionalReadResidualUnderflow
false,// residualOverflow
false,// residualUnderflow
SCSIResponseParser.ServiceResponse.COMMAND_COMPLETED_AT_TARGET,// response
SCSIStatus.GOOD,// status
initiatorTaskTag, 0,// snackTag
0,// (ExpDataSN or) Reserved
0,// bidirectionalReadResidualCount
0,// residualCount
ScsiResponseDataSegment.EMPTY_DATA_SEGMENT);// dataSegment
connection.sendPdu(pdu);
// OODRIVE
connection.removeWriteStage(initiatorTaskTag);
LOGGER.debug("Received all the bytes: "+transferLengthInBytes);
}
}
}