Coverage Report - org.jscsi.target.connection.stage.fullfeature.WriteStage
 
Classes in this File Line Coverage Branch Coverage Complexity
WriteStage
0%
0/87
0%
0/54
12
 
 1  
 package org.jscsi.target.connection.stage.fullfeature;
 2  
 
 3  
 
 4  
 import static org.jscsi.target.storage.IStorageModule.VIRTUAL_BLOCK_SIZE;
 5  
 
 6  
 import java.io.IOException;
 7  
 import java.security.DigestException;
 8  
 
 9  
 import org.jscsi.exception.InternetSCSIException;
 10  
 import org.jscsi.parser.AbstractMessageParser;
 11  
 import org.jscsi.parser.BasicHeaderSegment;
 12  
 import org.jscsi.parser.ProtocolDataUnit;
 13  
 import org.jscsi.parser.data.DataOutParser;
 14  
 import org.jscsi.parser.nop.NOPOutParser;
 15  
 import org.jscsi.parser.scsi.SCSICommandParser;
 16  
 import org.jscsi.parser.scsi.SCSIResponseParser;
 17  
 import org.jscsi.parser.scsi.SCSIStatus;
 18  
 import org.jscsi.target.TargetServer;
 19  
 import org.jscsi.target.connection.TargetPduFactory;
 20  
 import org.jscsi.target.connection.phase.TargetFullFeaturePhase;
 21  
 import org.jscsi.target.scsi.ScsiResponseDataSegment;
 22  
 import org.jscsi.target.scsi.cdb.ScsiOperationCode;
 23  
 import org.jscsi.target.scsi.cdb.Write10Cdb;
 24  
 import org.jscsi.target.scsi.cdb.Write6Cdb;
 25  
 import org.jscsi.target.scsi.cdb.WriteCdb;
 26  
 import org.jscsi.target.settings.SettingsException;
 27  
 import org.jscsi.target.util.Debug;
 28  
 import org.slf4j.Logger;
 29  
 import org.slf4j.LoggerFactory;
 30  
 
 31  
 
 32  
 /**
 33  
  * A stage for processing <code>WRITE (6)</code> and <code>WRITE (10)</code> SCSI commands.
 34  
  * 
 35  
  * @author Andreas Ergenzinger
 36  
  */
 37  
 public final class WriteStage extends ReadOrWriteStage {
 38  
 
 39  0
     private static final Logger LOGGER = LoggerFactory.getLogger(WriteStage.class);
 40  
 
 41  
     /**
 42  
      * The <code>DataSN</code> value the next Data-Out PDU must carry.
 43  
      */
 44  0
     private int expectedDataSequenceNumber = 0;
 45  
 
 46  
     public WriteStage (TargetFullFeaturePhase targetFullFeaturePhase) {
 47  0
         super(targetFullFeaturePhase);
 48  0
     }
 49  
 
 50  
     /**
 51  
      * Is used for checking if the PDUs received in a Data-Out sequence actually are Data-Out PDU and if the PDUs have
 52  
      * been received in order.
 53  
      * 
 54  
      * @param parser the {@link AbstractMessageParser} subclass instance retrieved from the {@link ProtocolDataUnit}'s
 55  
      *            {@link BasicHeaderSegment}
 56  
      * @throws InternetSCSIException if an unexpected PDU has been received
 57  
      */
 58  
     private void checkDataOutParser (final AbstractMessageParser parser) throws InternetSCSIException {
 59  0
         if (parser instanceof DataOutParser) {
 60  0
             final DataOutParser p = (DataOutParser) parser;
 61  0
             if (p.getDataSequenceNumber() != expectedDataSequenceNumber++) { throw new InternetSCSIException("received erroneous PDU in data-out sequence, expected " + (expectedDataSequenceNumber - 1)); }
 62  0
         } else if (parser instanceof NOPOutParser || parser instanceof SCSICommandParser) {
 63  
 
 64  
         } else {
 65  0
             if (parser != null) {
 66  0
                 throw new InternetSCSIException("received erroneous PDU in data-out sequence, " + parser.getClass().getName());
 67  
             } else {
 68  0
                 throw new InternetSCSIException("received erroneous PDU in data-out sequence, parser is null");
 69  
             }
 70  
         }
 71  
 
 72  0
     }
 73  
 
 74  
     @Override
 75  
     public void execute (ProtocolDataUnit pdu) throws IOException , DigestException , InterruptedException , InternetSCSIException , SettingsException {
 76  
 
 77  0
         if (LOGGER.isDebugEnabled()) LOGGER.debug("Entering WRITE STAGE");
 78  
 
 79  
         // get relevant values from settings
 80  0
         final boolean immediateData = settings.getImmediateData();
 81  0
         final boolean initialR2T = settings.getInitialR2T();
 82  0
         final int firstBurstLength = settings.getFirstBurstLength();
 83  0
         final int maxBurstLength = settings.getMaxBurstLength();
 84  
 
 85  0
         if (LOGGER.isDebugEnabled()) {
 86  0
             LOGGER.debug("immediateData = " + immediateData);
 87  0
             LOGGER.debug("initialR2T = " + initialR2T);
 88  
         }
 89  
 
 90  
         // get relevant values from PDU/CDB
 91  0
         BasicHeaderSegment bhs = pdu.getBasicHeaderSegment();
 92  0
         SCSICommandParser parser = (SCSICommandParser) bhs.getParser();
 93  0
         final int initiatorTaskTag = bhs.getInitiatorTaskTag();
 94  
         WriteCdb cdb;
 95  0
         final ScsiOperationCode scsiOpCode = ScsiOperationCode.valueOf(parser.getCDB().get(0));
 96  0
         if (scsiOpCode == ScsiOperationCode.WRITE_10)
 97  0
             cdb = new Write10Cdb(parser.getCDB());
 98  0
         else if (scsiOpCode == ScsiOperationCode.WRITE_6)
 99  0
             cdb = new Write6Cdb(parser.getCDB());
 100  
         else {
 101  
             // anything else wouldn't be good (programmer error)
 102  
             // close connection
 103  0
             throw new InternetSCSIException("wrong SCSI Operation Code " + scsiOpCode + " in WriteStage");
 104  
         }
 105  0
         final int transferLength = cdb.getTransferLength();
 106  0
         final long logicalBlockAddress = cdb.getLogicalBlockAddress();
 107  
 
 108  
         // transform to from block units to byte units
 109  0
         final int transferLengthInBytes = transferLength * VIRTUAL_BLOCK_SIZE;
 110  0
         long storageIndex = logicalBlockAddress * VIRTUAL_BLOCK_SIZE;
 111  
 
 112  
         // check if requested blocks are out of bounds
 113  
         // (might add FPSKSD to the CDB's list to be detected in the next step)
 114  0
         checkOverAndUnderflow(cdb);
 115  
 
 116  0
         if (cdb.getIllegalFieldPointers() != null) {
 117  
             /*
 118  
              * CDB is invalid, inform initiator by closing the connection. Sending an error status SCSI Response PDU
 119  
              * will not work reliably, since the initiator may not be expecting a response so soon. Also, if the
 120  
              * WriteStage is simply left early (without closing the connection), the initiator may send additional
 121  
              * unsolicited Data-Out PDUs, which the jSCSI Target is currently unable to ignore or process properly.
 122  
              */
 123  0
             LOGGER.debug("illegal field in Write CDB");
 124  0
             LOGGER.debug("CDB:\n" + Debug.byteBufferToString(parser.getCDB()));
 125  
             
 126  
             // Not necessarily close the connection
 127  
 
 128  
             // create and send error PDU and leave stage
 129  0
             final ProtocolDataUnit responsePdu = createFixedFormatErrorPdu(cdb.getIllegalFieldPointers(),// senseKeySpecificData
 130  
                     initiatorTaskTag, parser.getExpectedDataTransferLength());
 131  0
             connection.sendPdu(responsePdu);
 132  0
             return;
 133  
         }
 134  
 
 135  
         // *** start receiving data (or process what has already been sent) ***
 136  0
         int bytesReceived = 0;
 137  
 
 138  
         // *** receive immediate data ***
 139  0
         if (immediateData && bhs.getDataSegmentLength() > 0) {
 140  0
             final byte[] immediateDataArray = pdu.getDataSegment().array();
 141  
 
 142  0
             session.getStorageModule().write(immediateDataArray, storageIndex);
 143  0
             bytesReceived = immediateDataArray.length;
 144  
 
 145  0
             if (LOGGER.isDebugEnabled()) LOGGER.debug("wrote " + immediateDataArray.length + "bytes as immediate data");
 146  
         }
 147  
 
 148  
         // *** receive unsolicited data ***
 149  0
         if (!initialR2T && !bhs.isFinalFlag()) {
 150  
 
 151  0
             if (LOGGER.isDebugEnabled()) LOGGER.debug("receiving unsolicited data");
 152  
 
 153  0
             boolean firstBurstOver = false;
 154  0
             while (!firstBurstOver && bytesReceived <= firstBurstLength) {
 155  
 
 156  
                 // receive and check PDU
 157  0
                 pdu = connection.receivePdu();
 158  0
                 bhs = pdu.getBasicHeaderSegment();
 159  
 
 160  0
                 checkDataOutParser(bhs.getParser());
 161  
 
 162  0
                 final DataOutParser dataOutParser = (DataOutParser) bhs.getParser();
 163  
 
 164  0
                 session.getStorageModule().write(pdu.getDataSegment().array(), storageIndex + dataOutParser.getBufferOffset());
 165  
                 ;
 166  0
                 bytesReceived += bhs.getDataSegmentLength();
 167  
 
 168  0
                 if (bhs.isFinalFlag()) firstBurstOver = true;
 169  0
             }
 170  
         }
 171  
 
 172  
         // *** receive solicited data ***
 173  0
         if (bytesReceived < transferLengthInBytes) {
 174  0
             if (LOGGER.isDebugEnabled()) LOGGER.debug(bytesReceived + "<" + transferLengthInBytes);
 175  
 
 176  0
             int readyToTransferSequenceNumber = 0;
 177  
             int desiredDataTransferLength;
 178  
 
 179  0
             while (bytesReceived < transferLengthInBytes) {
 180  
 
 181  0
                 desiredDataTransferLength = Math.min(maxBurstLength, transferLengthInBytes - bytesReceived);
 182  
 
 183  
                 // send R2T
 184  0
                 pdu = TargetPduFactory.createReadyToTransferPdu(0,// logicalUnitNumber
 185  
                         initiatorTaskTag, TargetServer.getNextTargetTransferTag(),// targetTransferTag
 186  
                         readyToTransferSequenceNumber++, bytesReceived,// bufferOffset
 187  
                         desiredDataTransferLength);
 188  
 
 189  0
                 connection.sendPdu(pdu);
 190  
 
 191  
                 // receive DataOut PDUs
 192  0
                 expectedDataSequenceNumber = 0;// reset sequence counter//FIXME
 193  
                                                // fix in jSCSI Initiator
 194  0
                 boolean solicitedDataCycleOver = false;
 195  0
                 int bytesReceivedThisCycle = 0;
 196  0
                 while (!solicitedDataCycleOver) {
 197  
 
 198  
                     // receive and check PDU
 199  0
                     pdu = connection.receivePdu();
 200  0
                     bhs = pdu.getBasicHeaderSegment();
 201  0
                     checkDataOutParser(bhs.getParser());
 202  
 
 203  0
                     if (bhs.getParser() instanceof NOPOutParser) {
 204  
 
 205  
                         /* send SCSI Response PDU */
 206  0
                         pdu = TargetPduFactory.createSCSIResponsePdu(false,// bidirectionalReadResidualOverflow
 207  
                                 false,// bidirectionalReadResidualUnderflow
 208  
                                 false,// residualOverflow
 209  
                                 false,// residualUnderflow
 210  
                                 SCSIResponseParser.ServiceResponse.COMMAND_COMPLETED_AT_TARGET,// response
 211  
                                 SCSIStatus.GOOD,// status
 212  
                                 initiatorTaskTag, 0,// snackTag
 213  
                                 0,// (ExpDataSN or) Reserved
 214  
                                 0,// bidirectionalReadResidualCount
 215  
                                 0,// residualCount
 216  
                                 ScsiResponseDataSegment.EMPTY_DATA_SEGMENT);// dataSegment
 217  
 
 218  0
                         connection.sendPdu(pdu);
 219  0
                         return;
 220  0
                     } else if (bhs.getParser() instanceof DataOutParser) {
 221  0
                         final DataOutParser dataOutParser = (DataOutParser) bhs.getParser();
 222  
 
 223  0
                         session.getStorageModule().write(pdu.getDataSegment().array(), storageIndex + dataOutParser.getBufferOffset());
 224  
 
 225  0
                         bytesReceivedThisCycle += bhs.getDataSegmentLength();
 226  
 
 227  
                         /*
 228  
                          * Checking the final flag should be enough, but is not, when dealing with the jSCSI Initiator.
 229  
                          * This is also one of the reasons, why the contents of this while loop, though very similar to
 230  
                          * what is happening during the receiving of the unsolicited data PDU sequence, has not been put
 231  
                          * into a dedicated method.
 232  
                          */
 233  0
                         if (bhs.isFinalFlag() || bytesReceivedThisCycle >= desiredDataTransferLength) solicitedDataCycleOver = true;
 234  0
                     }
 235  
                 }
 236  0
                 bytesReceived += bytesReceivedThisCycle;
 237  0
             }
 238  
         }
 239  
 
 240  
         /* send SCSI Response PDU */
 241  0
         pdu = TargetPduFactory.createSCSIResponsePdu(false,// bidirectionalReadResidualOverflow
 242  
                 false,// bidirectionalReadResidualUnderflow
 243  
                 false,// residualOverflow
 244  
                 false,// residualUnderflow
 245  
                 SCSIResponseParser.ServiceResponse.COMMAND_COMPLETED_AT_TARGET,// response
 246  
                 SCSIStatus.GOOD,// status
 247  
                 initiatorTaskTag, 0,// snackTag
 248  
                 0,// (ExpDataSN or) Reserved
 249  
                 0,// bidirectionalReadResidualCount
 250  
                 0,// residualCount
 251  
                 ScsiResponseDataSegment.EMPTY_DATA_SEGMENT);// dataSegment
 252  
 
 253  0
         connection.sendPdu(pdu);
 254  0
     }
 255  
 }