/**
* 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.snack;
import org.jscsi.exception.InternetSCSIException;
import org.jscsi.parser.Constants;
import org.jscsi.parser.InitiatorMessageParser;
import org.jscsi.parser.ProtocolDataUnit;
import org.jscsi.parser.datasegment.DataSegmentFactory.DataSegmentFormat;
import org.jscsi.utils.Utils;
import com.carrotsearch.hppc.ByteObjectOpenHashMap;
/**
* <h1>SNACKRequestParser</h1>
* <p>
* This class parses a SNACK Request message defined in the iSCSI Standard (RFC3720).
* <p>
* If the implementation supports ErrorRecoveryLevel greater than zero, it MUST support all SNACK types.
* <p>
* The SNACK is used by the initiator to request the retransmission of numbered-responses, data, or R2T PDUs
* from the target. The SNACK request indicates the numbered-responses or data "runs" whose retransmission is
* requested by the target, where the run starts with the first StatSN, DataSN, or R2TSN whose retransmission
* is requested and indicates the number of Status, Data, or R2T PDUs requested including the first.
* <code>0</code> has special meaning when used as a starting number and length:
* <p>
* <ul>
* <li>When used in RunLength, it means all PDUs starting with the initial.</li>
* <li>When used in both BegRun and RunLength, it means all unacknowledged PDUs.</li>
* </ul>
* The numbered-response(s) or R2T(s), requested by a SNACK, MUST be delivered as exact replicas of the ones
* that the target transmitted originally except for the fields ExpCmdSN, MaxCmdSN, and ExpDataSN, which MUST
* carry the current values. R2T(s)requested by SNACK MUST also carry the current value of StatSN.
* <p>
* The numbered Data-In PDUs, requested by a Data SNACK MUST be delivered as exact replicas of the ones that
* the target transmitted originally except for the fields ExpCmdSN and MaxCmdSN, which MUST carry the current
* values and except for resegmentation (see Section 10.16.3 Resegmentation).
* <p>
* Any SNACK that requests a numbered-response, Data, or R2T that was not sent by the target or was already
* acknowledged by the initiator, MUST be rejected with a reason code of "Protocol error".
* <p>
* <h4>Data Acknowledgement</h4> If an initiator operates at ErrorRecoveryLevel <code>1</code> or higher, it
* MUST issue a SNACK of type DataACK after receiving a Data-In PDU with the A bit set to <code>1</code>.
* However, if the initiator has detected holes in the input sequence, it MUST postpone issuing the SNACK of
* type DataACK until the holes are filled. An initiator MAY ignore the A bit if it deems that the bit is
* being set aggressively by the target (i.e., before the MaxBurstLength limit is reached).
* <p>
* The DataACK is used to free resources at the target and not to request or imply data retransmission.
* <p>
* An initiator MUST NOT request retransmission for any data it had already acknowledged.
* <p>
* <h4>Resegmentation</h4> If the initiator MaxRecvDataSegmentLength changed between the original transmission
* and the time the initiator requests retransmission, the initiator MUST issue a R-Data SNACK (see Section
* 10.16.1 Type). With R-Data SNACK, the initiator indicates that it discards all the unacknowledged data and
* expects the target to resend it. It also expects resegmentation. In this case, the retransmitted Data-In
* PDUs MAY be different from the ones originally sent in order to reflect changes in
* MaxRecvDataSegmentLength. Their DataSN starts with the BegRun of the last DataACK received by the target if
* any was received; otherwise it starts with 0 and is increased by 1 for each resent Data-In PDU.
* <p>
* A target that has received a R-Data SNACK MUST return a SCSI Response that contains a copy of the SNACK Tag
* field from the R-Data SNACK in the SCSI Response SNACK Tag field as its last or only Response. For example,
* if it has already sent a response containing another value in the SNACK Tag field or had the status
* included in the last Data-In PDU, it must send a new SCSI Response PDU. If a target sends more than one
* SCSI Response PDU due to this rule, all SCSI responses must carry the same StatSN (see Section 10.4.4 SNACK
* Tag). If an initiator attempts to recover a lost SCSI Response (with a Status SNACK, see Section 10.16.1
* Type) when more than one response has been sent, the target will send the SCSI Response with the latest
* content known to the target, including the last SNACK Tag for the command.
* <p>
* For considerations in allegiance reassignment of a task to a connection with a different
* MaxRecvDataSegmentLength, refer to Section 6.2.2 Allegiance Reassignment.
* <p>
* <h4>Initiator Task Tag</h4> For Status SNACK and DataACK, the Initiator Task Tag MUST be set to the
* reserved value <code>0xffffffff</code>. In all other cases, the Initiator Task Tag field MUST be set to the
* Initiator Task Tag of the referenced command.
* <p>
*
* @author Volker Wildi
*/
public final class SNACKRequestParser extends InitiatorMessageParser {
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* This enumeration defines all valid SNACK types.
* <p>
* <table border="1">
* <tr>
* <th>Type</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>0</td>
* <td>Data/R2T SNACK - requesting retransmission of one or more Data- In or R2T PDUs.</td>
* </tr>
* <tr>
* <td>1</td>
* <td>Status SNACK - requesting retransmission of one or more numbered responses.</td>
* </tr>
* <tr>
* <td>2</td>
* <td>DataACK - positively acknowledges Data-In PDUs.</td>
* </tr>
* <tr>
* <td>3</td>
* <td>R-Data SNACK - requesting retransmission of Data-In PDUs with possible resegmentation and status
* tagging.</td>
* </tr>
* </table>
* <p>
* All other values are reserved.
* <p>
* Data/R2T SNACK, Status SNACK, or R-Data SNACK for a command MUST precede status acknowledgement for the
* given command.
*/
public static enum SNACKType {
/** The Data/R2T-Snack. */
DATA_R2T_SNACK((byte)0),
/** The Status-SNACK. */
STATUS_SNACK((byte)1),
/** The DataACK. */
DATA_ACK((byte)2),
/** The R-Data SNACK. */
R_DATA_SNACK((byte)3);
private final byte value;
private static ByteObjectOpenHashMap<SNACKType> mapping;
static {
SNACKType.mapping = new ByteObjectOpenHashMap<SNACKType>(values().length);
for (SNACKType s : values()) {
SNACKType.mapping.put(s.value, s);
}
}
private SNACKType(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 SNACKType valueOf(final byte value) {
return SNACKType.mapping.get(value);
}
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** Bit mask to extract the SNACK type. */
private static final int TYPE_MASK = 0x000F0000;
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** The SNACK type. */
private SNACKType type;
/** The target transfer tag. */
private int targetTransferTag;
/** The first PDU number to start from with the retransmission. */
private int begRun;
/** The run length of requested PDUs to retransmit. */
private int runLength;
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Default constructor, creates a new, empty <code>SNACKRequestParser</code> object.
*
* @param initProtocolDataUnit
* The reference <code>ProtocolDataUnit</code> instance, which
* contains this <code>SNACKRequestParser</code> subclass object.
*/
public SNACKRequestParser(final ProtocolDataUnit initProtocolDataUnit) {
super(initProtocolDataUnit);
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* The <code>DataSN</code>, <code>R2TSN</code>, or <code>StatSN</code> of
* the first PDU whose retransmission is requested (Data/R2T and Status
* SNACK), or the next expected DataSN (DataACK SNACK).
* <p>
* <code>BegRun</code> <code>0</code> when used in conjunction with <code>RunLength</code> <code>0</code>
* means resend all unacknowledged Data-In, R2T or Response PDUs.
* <p>
* <code>BegRun</code> MUST be <code>0</code> for a R-Data SNACK.
*
* @return The BegRun of this <code>SNACKRequestParser</code> obejct.
*/
public final int getBegRun() {
return begRun;
}
/**
* The number of PDUs whose retransmission is requested.
* <p>
* <code>RunLength</code> <code>0</code> signals that all Data-In, R2T, or Response PDUs carrying the
* numbers equal to or greater than BegRun have to be resent.
* <p>
* The <code>RunLength</code> MUST also be <code>0</code> for a DataACK SNACK in addition to R-Data SNACK.
*
* @return The RunLength of this <code>SNACKRequestParser</code> object.
*/
public final int getRunLength() {
return runLength;
}
/**
* For an R-Data SNACK, this field MUST contain a value that is different
* from <code>0</code> or <code>0xffffffff</code> and is unique for the task
* (identified by the Initiator Task Tag). This value MUST be copied by the
* iSCSI target in the last or only SCSI Response PDU it issues for the
* command.
* <p>
* For <code>DataACK</code>, the Target Transfer Tag MUST contain a copy of the Target Transfer Tag and
* LUN provided with the SCSI Data-In PDU with the <code>A</code> bit set to <code>1</code>.
* <p>
* In all other cases, the Target Transfer Tag field MUST be set to the reserved value of
* <code>0xffffffff</code>.
*
* @return The target transfer tag of this <code>SNACKRequestParser</code> object.
*/
public final int getTargetTransferTag() {
return targetTransferTag;
}
/**
* Returns the SNACK Function Code of this <code>SNACKRequestParser</code> object.
*
* @return The SNACK Function code of this <code>SNACKRequestParser</code> object.
* @see org.jscsi.parser.snack.SNACKRequestParser.SNACKType
*/
public final SNACKType getType() {
return type;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Sets the <code>begRun</code> variable to the given new value.
*
* @param newBegRun
* The new value.
*/
public final void setBegRun(final int newBegRun) {
begRun = newBegRun;
}
/**
* Sets the Run Length to the given value.
*
* @param newRunLength
* The new value.
*/
public final void setRunLength(final int newRunLength) {
runLength = newRunLength;
}
/**
* Sets the Target Transfer Tag to the given value.
*
* @param newTargetTransferTag
* The new value.
*/
public final void setTargetTransferTag(final int newTargetTransferTag) {
targetTransferTag = newTargetTransferTag;
}
/**
* Sets the type of this SNACKRequest to the given value.
*
* @param newType
* The new value.
*/
public final void setType(final SNACKType newType) {
type = newType;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** {@inheritDoc} */
@Override
public final String toString() {
final StringBuilder sb = new StringBuilder(Constants.LOG_INITIAL_SIZE);
Utils.printField(sb, "Type", type.value(), 1);
Utils.printField(sb, "LUN", logicalUnitNumber, 1);
Utils.printField(sb, "Target Transfer Tag", targetTransferTag, 1);
sb.append(super.toString());
Utils.printField(sb, "BegRun", begRun, 1);
Utils.printField(sb, "Run Length", runLength, 1);
return sb.toString();
}
/** {@inheritDoc} */
@Override
public final DataSegmentFormat getDataSegmentFormat() {
return DataSegmentFormat.NONE;
}
/** {@inheritDoc} */
@Override
public final void clear() {
super.clear();
type = SNACKType.DATA_ACK;
targetTransferTag = 0x00000000;
begRun = 0x00000000;
runLength = 0x00000000;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** {@inheritDoc} */
@Override
protected final void deserializeBytes1to3(final int line) throws InternetSCSIException {
type = SNACKType.valueOf((byte)((line & TYPE_MASK) >> Constants.TWO_BYTES_SHIFT));
Utils.isReserved(line & Constants.LAST_TWO_BYTES_MASK);
}
/** {@inheritDoc} */
@Override
protected final void deserializeBytes20to23(final int line) throws InternetSCSIException {
targetTransferTag = line;
}
/** {@inheritDoc} */
@Override
protected final void deserializeBytes40to43(final int line) throws InternetSCSIException {
begRun = line;
}
/** {@inheritDoc} */
@Override
protected final void deserializeBytes44to47(final int line) throws InternetSCSIException {
runLength = line;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** {@inheritDoc} */
@Override
protected final void checkIntegrity() throws InternetSCSIException {
// FIXME: Sure?
// do nothing...
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** {@inheritDoc} */
@Override
protected final int serializeBytes1to3() {
return type.value() << Constants.TWO_BYTES_SHIFT;
}
/** {@inheritDoc} */
@Override
protected final int serializeBytes20to23() {
return targetTransferTag;
}
/** {@inheritDoc} */
@Override
protected final int serializeBytes24to27() {
return 0;
}
/** {@inheritDoc} */
@Override
protected final int serializeBytes40to43() {
return begRun;
}
/** {@inheritDoc} */
@Override
protected final int serializeBytes44to47() {
return runLength;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
}