/**
* Copyright (c) 2012, University of Konstanz, Distributed Systems Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the University of Konstanz nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jscsi.parser.scsi;
import org.jscsi.exception.InternetSCSIException;
import org.jscsi.parser.Constants;
import org.jscsi.parser.ProtocolDataUnit;
import org.jscsi.parser.TargetMessageParser;
import org.jscsi.parser.datasegment.DataSegmentFactory.DataSegmentFormat;
import org.jscsi.utils.Utils;
import com.carrotsearch.hppc.ByteObjectOpenHashMap;
/**
* <h1>SCSIResponseParser</h1>
* <p>
* This class parses a SCSI Response message defined in the iSCSI Standard (RFC3720).
* <p>
* <h4>StatSN - Status Sequence Number</h4> StatSN is a Sequence Number that the target iSCSI layer generates
* per connection and that in turn, enables the initiator to acknowledge status reception. StatSN is
* incremented by <code>1</code> for every response/status sent on a connection except for responses sent as a
* result of a retry or SNACK. In the case of responses sent due to a retransmission request, the StatSN MUST
* be the same as the first time the PDU was sent unless the connection has since been restarted.
* <p>
* <h4>ExpCmdSN - Next Expected CmdSN from this Initiator</h4> ExpCmdSN is a Sequence Number that the target
* iSCSI returns to the initiator to acknowledge command reception. It is used to update a local variable with
* the same name. An ExpCmdSN equal to <code>MaxCmdSN + 1</code> indicates that the target cannot accept new
* commands.
* <p>
* <h4>MaxCmdSN - Maximum CmdSN from this Initiator</h4> MaxCmdSN is a Sequence Number that the target iSCSI
* returns to the initiator to indicate the maximum CmdSN the initiator can send. It is used to update a local
* variable with the same name. If MaxCmdSN is equal to <code>ExpCmdSN - 1</code>, this indicates to the
* initiator that the target cannot receive any additional commands. When MaxCmdSN changes at the target while
* the target has no pending PDUs to convey this information to the initiator, it MUST generate a NOP-IN to
* carry the new MaxCmdSN.
* <p/>
* iSCSI targets MUST support and enable autosense. If Status is CHECK CONDITION (<code>0x02</code>), then the
* Data Segment MUST contain sense data for the failed command. <br/>
* For some iSCSI responses, the response data segment MAY contain some response related information, (e.g.,
* for a target failure, it may contain a vendor specific detailed description of the failure).
*
* @author Volker Wildi, University of Konstanz
*/
public class SCSIResponseParser extends TargetMessageParser {
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* This enumerations defines all valid service responses, which are defined
* in the iSCSI Standard (RFC 3720).
* <p>
* 0x80-0xff - Vendor specific
*/
public static enum ServiceResponse {
/** Command completed at target. */
COMMAND_COMPLETED_AT_TARGET((byte)0x00),
/** Target Failure. */
TARGET_FAILURE((byte)0x01);
private final byte value;
private static ByteObjectOpenHashMap<ServiceResponse> mapping;
static {
ServiceResponse.mapping = new ByteObjectOpenHashMap<ServiceResponse>(values().length);
for (ServiceResponse s : values()) {
ServiceResponse.mapping.put(s.value, s);
}
}
private ServiceResponse(final byte newValue) {
value = newValue;
}
/**
* Returns the value of this enumeration.
*
* @return The value of this enumeration.
*/
public final byte value() {
return value;
}
/**
* Returns the constant defined for the given <code>value</code>.
*
* @param value
* The value to search for.
* @return The constant defined for the given <code>value</code>. Or <code>null</code>, if this value
* is not defined by this
* enumeration.
*/
public static final ServiceResponse valueOf(final byte value) {
return ServiceResponse.mapping.get(value);
}
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** Bit mask for the bits <code>1,2, and 7</code>. These bits are reserved. */
private static final int RESERVED_FLAGS_MASK = 0x610000;
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** The o-bit. */
private boolean bidirectionalReadResidualOverflow;
/** The u-bit. */
private boolean bidirectionalReadResidualUnderflow;
/** The O-bit. */
private boolean residualOverflow;
/** The U-bit. */
private boolean residualUnderflow;
private ServiceResponse response;
/** The Status code. */
private SCSIStatus status;
/** The SNACK Tag. */
private int snackTag;
/** The Expected Data Sequence Number. */
private int expectedDataSequenceNumber;
private int bidirectionalReadResidualCount;
private int residualCount;
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Default constructor, creates a new, empty <code>SCSIResponseParser</code> object.
*
* @param initProtocolDataUnit
* The reference <code>ProtocolDataUnit</code> instance, which
* contains this <code>SCSIResponseParser</code> subclass object.
*/
public SCSIResponseParser(final ProtocolDataUnit initProtocolDataUnit) {
super(initProtocolDataUnit);
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** {@inheritDoc} */
@Override
public final String toString() {
final StringBuilder sb = new StringBuilder(Constants.LOG_INITIAL_SIZE);
Utils.printField(sb, "Response", response.value(), 1);
Utils.printField(sb, "SNACK TAG", snackTag, 1);
sb.append(super.toString());
Utils.printField(sb, "ExpDataSN", expectedDataSequenceNumber, 1);
Utils.printField(sb, "BidirectionalReadResidualOverflow", bidirectionalReadResidualOverflow, 1);
Utils.printField(sb, "BidirectionalReadResidualUnderflow", bidirectionalReadResidualUnderflow, 1);
Utils.printField(sb, "ResidualOverflow", residualOverflow, 1);
Utils.printField(sb, "ResidualUnderflow", residualUnderflow, 1);
Utils.printField(sb, "ResidualCount", residualCount, 1);
Utils.printField(sb, "Bidirectional Read Residual Count", bidirectionalReadResidualCount, 1);
return sb.toString();
}
/** {@inheritDoc} */
@Override
public final DataSegmentFormat getDataSegmentFormat() {
return DataSegmentFormat.SCSI_RESPONSE;
}
/** {@inheritDoc} */
@Override
public final void clear() {
super.clear();
bidirectionalReadResidualOverflow = false;
bidirectionalReadResidualUnderflow = false;
residualOverflow = false;
residualUnderflow = false;
response = null;
status = null;
snackTag = 0x00000000;
bidirectionalReadResidualCount = 0x00000000;
residualCount = 0x00000000;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* The Bidirectional Read Residual Count field MUST be valid in the case
* where either the u bit or the o bit is set. If neither bit is set, the
* Bidirectional Read Residual Count field is reserved. Targets may set the
* Bidirectional Read Residual Count and initiators may use it when the
* response code is "completed at target". If the o bit is set, the
* Bidirectional Read Residual Count indicates the number of bytes that were
* not transferred to the initiator because the initiator’s Expected
* Bidirectional Read Transfer Length was not sufficient. If the u bit is
* set, the Bidirectional Read Residual Count indicates the number of bytes
* that were not transferred to the initiator out of the number of bytes
* expected to be transferred.
*
* @return The bidirectional read residual count of this <code>SCSIResponseParser</code> object.
*/
public final int getBidirectionalReadResidualCount() {
return bidirectionalReadResidualCount;
}
/**
* The number of R2T and Data-In (read) PDUs the target has sent for the
* command.
* <p>
* This field MUST be <code>0</code> if the response code is not Command Completed at Target or the target
* sent no Data-In PDUs for the command.
*
* @return The expected data sequence number of this <code>SCSIResponseParser</code> object.
*/
public final int getExpectedDataSequenceNumber() {
return expectedDataSequenceNumber;
}
/**
* Returns the status of the Bidirectional Read Residual Overflow flag. In
* this case, the Bidirectional Read Residual Count indicates the number of
* bytes that were not transferred to the initiator because the initiator’s
* Expected Bidirectional Read Data Transfer Length was not sufficient.
*
* @return <code>true</code>, if it is set; else <code>false</code>.
*/
public final boolean isBidirectionalReadResidualOverflow() {
return bidirectionalReadResidualOverflow;
}
/**
* Returns the status of the Bidirectional Read Residual Underflow flag. In
* this case, the Bidirectional Read Residual Count indicates the number of
* bytes that were not transferred to the initiator out of the number of
* bytes expected to be transferred.
*
* @return <code>true</code>, if it is set; else <code>false</code>.
*/
public final boolean isBidirectionalReadResidualUnderflow() {
return bidirectionalReadResidualUnderflow;
}
/**
* The Residual Count field MUST be valid in the case where either the U bit
* or the O bit is set. If neither bit is set, the Residual Count field is
* reserved. Targets may set the residual count and initiators may use it
* when the response code is "completed at target" (even if the status
* returned is not GOOD). If the O bit is set, the Residual Count indicates
* the number of bytes that were not transferred because the initiator’s
* Expected Data Transfer Length was not sufficient. If the U bit is set,
* the Residual Count indicates the number of bytes that were not
* transferred out of the number of bytes expected to be transferred.
*
* @return The residual count of this <code>SCSIResponseParser</code> object.
*/
public final int getResidualCount() {
return residualCount;
}
/**
* Returns the status of the Residual Overflow flag. In this case, the
* Residual Count indicates the number of bytes that were not transferred
* because the initiator’s Expected Data Transfer Length was not sufficient.
* For a bidirectional operation, the Residual Count contains the residual
* for the write operation.
*
* @return <code>true</code>, if it is set; else <code>false</code>.
*/
public final boolean isResidualOverflow() {
return residualOverflow;
}
/**
* Returns the status of the Residual Underflow flag. In this case, the
* Residual Count indicates the number of bytes that were not transferred
* out of the number of bytes that were expected to be transferred. For a
* bidirectional operation, the Residual Count contains the residual for the
* write operation.
*
* @return <code>true</code>, if it is set; else <code>false</code>.
*/
public final boolean isResidualUnderflow() {
return residualUnderflow;
}
/**
* This field contains the iSCSI service response.
* <p>
* All other response codes are reserved.
* <p>
* The Response is used to report a Service Response. The mapping of the response code into a SCSI service
* response code value, if needed, is outside the scope of this document. However, in symbolic terms
* response value 0x00 maps to the SCSI service response (see [SAM2] and [SPC3]) of TASK COMPLETE or
* LINKED COMMAND COMPLETE. All other Response values map to the SCSI service response of SERVICE DELIVERY
* OR TARGET FAILURE.
* <p>
* If a PDU that includes SCSI status (Response PDU or Data-In PDU including status) does not arrive
* before the session is terminated, the SCSI service response is SERVICE DELIVERY OR TARGET FAILURE. A
* non-zero Response field indicates a failure to execute the command in which case the Status and Flag
* fields are undefined.
*
* @return The service response of this <code>SCSIResponseParser</code> object.
* @see ServiceResponse
*/
public final ServiceResponse getResponse() {
return response;
}
/**
* This field contains a copy of the SNACK Tag of the last SNACK Tag
* accepted by the target on the same connection and for the command for
* which the response is issued. Otherwise it is reserved and should be set
* to <code>0</code>.
* <p>
* After issuing a R-Data SNACK the initiator must discard any SCSI status unless contained in an SCSI
* Response PDU carrying the same SNACK Tag as the last issued R-Data SNACK for the SCSI command on the
* current connection.
* <p>
* For a detailed discussion on R-Data SNACK see Section 10.16 SNACK Request.
*
* @return The SNACK Tag of this <code>SCSIResponseParser</code> object.
*/
public final int getSNACKTag() {
return snackTag;
}
/**
* The Status field is used to report the SCSI status of the command (as
* specified in [SAM2]) and is only valid if the Response Code is Command
* Completed at target.
*
* @return The status field of this <code>SCSIResponseParser</code> object.
* @see org.jscsi.parser.scsi.SCSIStatus
*/
public final SCSIStatus getStatus() {
return status;
}
public final void setBidirectionalReadResidualCount(int bidirectionalReadResidualCount) {
this.bidirectionalReadResidualCount = bidirectionalReadResidualCount;
}
public final void setBidirectionalReadResidualOverflow(boolean bidirectionalReadResidualOverflow) {
this.bidirectionalReadResidualOverflow = bidirectionalReadResidualOverflow;
}
public final void setBidirectionalReadResidualUnderflow(boolean bidirectionalReadResidualUnderflow) {
this.bidirectionalReadResidualUnderflow = bidirectionalReadResidualUnderflow;
}
public final void setExpectedDataSequenceNumber(int expectedDataSequenceNumber) {
this.expectedDataSequenceNumber = expectedDataSequenceNumber;
}
public final void setResidualCount(int residualCount) {
this.residualCount = residualCount;
}
public final void setResidualOverflow(boolean residualOverflow) {
this.residualOverflow = residualOverflow;
}
public final void setResidualUnderflow(boolean residualUnderflow) {
this.residualUnderflow = residualUnderflow;
}
public void setResponse(SCSIResponseParser.ServiceResponse response) {
this.response = response;
}
public final void setSNACKTag(int snackTag) {
this.snackTag = snackTag;
}
public final void setStatus(SCSIStatus status) {
this.status = status;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** {@inheritDoc} */
@Override
protected final void deserializeBytes1to3(final int line) throws InternetSCSIException {
Utils.isReserved(line & RESERVED_FLAGS_MASK);
bidirectionalReadResidualOverflow = Utils.isBitSet(line & Constants.READ_RESIDUAL_OVERFLOW_FLAG_MASK);
bidirectionalReadResidualUnderflow =
Utils.isBitSet(line & Constants.READ_RESIDUAL_UNDERFLOW_FLAG_MASK);
residualOverflow = Utils.isBitSet(line & Constants.RESIDUAL_OVERFLOW_FLAG_MASK);
residualUnderflow = Utils.isBitSet(line & Constants.RESIDUAL_UNDERFLOW_FLAG_MASK);
response =
ServiceResponse.valueOf((byte)((line & Constants.THIRD_BYTE_MASK) >>> Constants.ONE_BYTE_SHIFT));
status = SCSIStatus.valueOf((byte)(line & Constants.FOURTH_BYTE_MASK));
}
/** {@inheritDoc} */
@Override
protected final void deserializeBytes20to23(final int line) throws InternetSCSIException {
snackTag = line;
}
/** {@inheritDoc} */
@Override
protected final void deserializeBytes36to39(final int line) throws InternetSCSIException {
expectedDataSequenceNumber = line;
}
/** {@inheritDoc} */
@Override
protected final void deserializeBytes40to43(final int line) throws InternetSCSIException {
bidirectionalReadResidualCount = line;
}
/** {@inheritDoc} */
@Override
protected final void deserializeBytes44to47(final int line) throws InternetSCSIException {
residualCount = line;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** {@inheritDoc} */
@Override
protected final void checkIntegrity() throws InternetSCSIException {
String exceptionMessage;
do {
Utils.isReserved(logicalUnitNumber);
if (response != ServiceResponse.COMMAND_COMPLETED_AT_TARGET) {
if (bidirectionalReadResidualOverflow || bidirectionalReadResidualUnderflow
|| residualOverflow || residualUnderflow) {
exceptionMessage =
"Theses bits must to be 0, because the command is not completed at the target.";
break;
}
if (status != SCSIStatus.GOOD) {
exceptionMessage =
"Status Code is only valid, because the command is not completed at the target.";
break;
}
}
if (bidirectionalReadResidualOverflow && bidirectionalReadResidualUnderflow) {
exceptionMessage = "The 'o' and 'u' bits must be set mutal exclusion.";
break;
}
if (residualOverflow && residualUnderflow) {
exceptionMessage = "The 'O' and 'U' bits must be set mutal exclusion.";
break;
}
if ((!residualOverflow && !residualUnderflow) && residualCount != 0) {
exceptionMessage =
"ResidualCount is only valid either the ResidualOverflow or ResidualUnderflow-Flag is set.";
break;
}
if ((!bidirectionalReadResidualOverflow && !bidirectionalReadResidualUnderflow)
&& bidirectionalReadResidualCount != 0) {
exceptionMessage =
"BidirectionalResidualCount is only valid either the "
+ "BidirectionalResidualOverflow or BidirectionalResidualUnderflow-Flag is set.";
break;
}
if (response != ServiceResponse.COMMAND_COMPLETED_AT_TARGET && expectedDataSequenceNumber != 0) {
exceptionMessage =
"The ExpectedDataSequenceNumber is not valid, because the command is not "
+ "completed at the target.";
break;
}
// message is checked correctly
return;
} while (false);
throw new InternetSCSIException(exceptionMessage);
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** {@inheritDoc} */
@Override
protected final int serializeBytes1to3() {
int line = status.value();
line |= response.value() << Constants.ONE_BYTE_SHIFT;
if (residualUnderflow) {
line |= Constants.RESIDUAL_UNDERFLOW_FLAG_MASK;
}
if (residualOverflow) {
line |= Constants.RESIDUAL_OVERFLOW_FLAG_MASK;
}
if (bidirectionalReadResidualUnderflow) {
line |= Constants.READ_RESIDUAL_UNDERFLOW_FLAG_MASK;
}
if (bidirectionalReadResidualOverflow) {
line |= Constants.READ_RESIDUAL_OVERFLOW_FLAG_MASK;
}
return line;
}
/** {@inheritDoc} */
@Override
protected final int serializeBytes20to23() {
return snackTag;
}
/** {@inheritDoc} */
@Override
protected final int serializeBytes36to39() {
return expectedDataSequenceNumber;
}
/** {@inheritDoc} */
@Override
protected final int serializeBytes40to43() {
return bidirectionalReadResidualCount;
}
/** {@inheritDoc} */
@Override
protected final int serializeBytes44to47() {
return residualCount;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
}