/** * 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.logout; 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>LogoutRequestParser</h1> * <p> * This class parses a Logout Request message defined in the iSCSI Standard (RFC3720). * <p> * The Logout Request is used to perform a controlled closing of a connection. * <p> * An initiator MAY use a Logout Request to remove a connection from a session or to close an entire session. * <p> * After sending the Logout Request PDU, an initiator MUST NOT send any new iSCSI requests on the closing * connection. If the Logout Request is intended to close the session, new iSCSI requests MUST NOT be sent on * any of the connections participating in the session. * <p> * When receiving a Logout Request with the reason code of "close the connection" or "close the session", the * target MUST terminate all pending commands, whether acknowledged via ExpCmdSN or not, on that connection or * session respectively. * <p> * When receiving a Logout Request with the reason code "remove connection for recovery", the target MUST * discard all requests not yet acknowledged via ExpCmdSN that were issued on the specified connection, and * suspend all data/status/R2T transfers on behalf of pending commands on the specified connection. * <p> * The target then issues the Logout Response and half-closes the TCP connection (sends FIN). After receiving * the Logout Response and attempting to receive the FIN (if still possible), the initiator MUST completely * close the logging-out connection. For the terminated commands, no additional responses should be expected. * <p> * A Logout for a CID may be performed on a different transport connection when the TCP connection for the CID * has already been terminated. In such a case, only a logical "closing" of the iSCSI connection for the CID * is implied with a Logout. * <p> * All commands that were not terminated or not completed (with status) and acknowledged when the connection * is closed completely can be reassigned to a new connection if the target supports connection recovery. * <p> * If an initiator intends to start recovery for a failing connection, it MUST use the Logout Request to * "clean-up" the target end of a failing connection and enable recovery to start, or the Login Request with a * non-zero TSIH and the same CID on a new connection for the same effect (see Section 10.14.3 CID). In * sessions with a single connection, the connection can be closed and then a new connection reopened. A * connection reinstatement login can be used for recovery (see Section 5.3.4 Connection Reinstatement). * <p> * A successful completion of a Logout Request with the reason code of "close the connection" or "remove the * connection for recovery" results at the target in the discarding of unacknowledged commands received on the * connection being logged out. These are commands that have arrived on the connection being logged out, but * have not been delivered to SCSI because one or more commands with a smaller CmdSN has not been received by * iSCSI. See Section 3.2.2.1 Command Numbering and Acknowledging. The resulting holes the in command sequence * numbers will have to be handled by appropriate recovery (see Chapter 6) unless the session is also closed. * <p> * The entire logout discussion in this section is also applicable for an implicit Logout realized via a * connection reinstatement or session reinstatement. When a Login Request performs an implicit Logout, the * implicit Logout is performed as if having the reason codes specified below: * <p> * <table border="1"> * <tr> * <th>Reason code</th> * <th>Type of implicit Logout</th> * </tr> * <tr> * <td>0</td> * <td>session reinstatement</td> * </tr> * <tr> * <td>1</td> * <td>connection reinstatement when the operational ErrorRecoveryLevel < 2</td> * </tr> * <tr> * <td>2</td> * <td>connection reinstatement when the operational ErrorRecoveryLevel = 2</td> * </tr> * </table> * <p> * <h4>TotalAHSLength and DataSegmentLength</h4> For this PDU TotalAHSLength and DataSegmentLength MUST be * <code>0</code>. * <p> * <h4>ExpStatSN</h4> This is the last ExpStatSN value for the connection to be closed. * <p> * <h4>Implicit termination of tasks</h4> A target implicitly terminates the active tasks due to the iSCSI * protocol in the following cases: * <p> * <ol type="a"> * <li>When a connection is implicitly or explicitly logged out with the reason code of "Close the connection" * and there are active tasks allegiant to that connection.</li> * <li>When a connection fails and eventually the connection state times out (state transition M1 in Section * 7.2.2 State Transition Descriptions for Initiators and Targets) and there are active tasks allegiant to * that connection.</li> * <li>When a successful recovery Logout is performed while there are active tasks allegiant to that * connection, and those tasks eventually time out after the Time2Wait and Time2Retain periods without * allegiance reassignment.</li> * <li>When a connection is implicitly or explicitly logged out with the reason code of "Close the session" * and there are active tasks in that session.</li> * </ol> * If the tasks terminated in any of the above cases are SCSI tasks, they must be internally terminated as if * with CHECK CONDITION status. This status is only meaningful for appropriately handling the internal SCSI * state and SCSI side effects with respect to ordering because this status is never communicated back as a * terminating status to the initiator. However additional actions may have to be taken at SCSI level * depending on the SCSI context as defined by the SCSI standards (e.g., queued commands and ACA, in cases a), * b), and c), after the tasks are terminated, the target MUST report a Unit Attention condition on the next * command processed on any connection for each affected I_T_L nexus with the status of CHECK CONDITION, and * the ASC/ASCQ value of 47h/7Fh - "SOME COMMANDS CLEARED BY ISCSI PROTOCOL EVENT" - etc. - see [SAM2] and * [SPC3]). * <p> * * @author Volker Wildi */ public final class LogoutRequestParser extends InitiatorMessageParser { // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * This enumeration defines all the Logout Reasons Codes, which are allowed * in a iSCSI Logout Request message (RFC3720). All other values are * reserved. * <p> * All other values are reserved. * <p> */ public static enum LogoutReasonCode { /** * Close the session. All commands associated with the session (if any) * are terminated. */ CLOSE_SESSION((byte)0), /** * Close the connection. All commands associated with connection (if * any) are terminated. */ CLOSE_CONNECTION((byte)1), /** * Remove the connection for recovery. Connection is closed and all * commands associated with it, if any, are to be prepared for a new * allegiance. */ CONNECTION_RECOVERY((byte)2); private final byte value; private static ByteObjectOpenHashMap<LogoutReasonCode> mapping; static { LogoutReasonCode.mapping = new ByteObjectOpenHashMap<LogoutReasonCode>(values().length); for (LogoutReasonCode s : values()) { LogoutReasonCode.mapping.put(s.value, s); } } private LogoutReasonCode(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 LogoutReasonCode valueOf(final byte value) { return LogoutReasonCode.mapping.get(value); } } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** Reason code indicates the reason for the logout. */ private LogoutReasonCode reasonCode; /** The Connection ID. */ private short connectionID; // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * Default constructor, creates a new, empty <code>LogoutRequestParser</code> object. * * @param initProtocolDataUnit * The reference <code>ProtocolDataUnit</code> instance, which * contains this <code>LogoutRequestParser</code> subclass * object. */ public LogoutRequestParser(final ProtocolDataUnit initProtocolDataUnit) { super(initProtocolDataUnit); } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** {@inheritDoc} */ @Override public final String toString() { final StringBuilder sb = new StringBuilder(Constants.LOG_INITIAL_SIZE); Utils.printField(sb, "Reson Code", reasonCode.value(), 1); Utils.printField(sb, "CID", connectionID, 1); sb.append(super.toString()); return sb.toString(); } /** {@inheritDoc} */ @Override public final DataSegmentFormat getDataSegmentFormat() { return DataSegmentFormat.BINARY; } /** {@inheritDoc} */ @Override public final void clear() { super.clear(); reasonCode = LogoutReasonCode.CLOSE_SESSION; connectionID = 0x0000; } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * This is the <em>Connection ID</em> of the connection to be closed * (including closing the TCP stream). This field is only valid if the * reason code is not "close the session". * * @return The <em>Connection ID</em> of this <code>LogoutRequestParser</code> object. */ public final short getConnectionID() { return connectionID; } /** * Returns the reason for the logout. * * @return The reason code for this logout request. * @see LogoutReasonCode */ public final LogoutReasonCode getReasonCode() { return reasonCode; } /** * Sets the new <code>Connection ID</code> of this <code>LogoutRequestParser</code> object. * * @param newCID * The new <code>Connection ID</code>. */ public final void setConnectionID(final short newCID) { connectionID = newCID; } /** * Sets the <code>Reason Code</code> of this <code>LogoutRequestParser</code> object. * * @param newReasonCode * The new <code> Reason Code</code>. */ public final void setReasonCode(final LogoutReasonCode newReasonCode) { reasonCode = newReasonCode; } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** {@inheritDoc} */ @Override protected final void deserializeBytes1to3(final int line) throws InternetSCSIException { reasonCode = LogoutReasonCode .valueOf((byte)((line & Constants.SECOND_BYTE_MASK) >>> Constants.TWO_BYTES_SHIFT)); Utils.isReserved(line & Constants.LAST_TWO_BYTES_MASK); } /** {@inheritDoc} */ @Override protected final void deserializeBytes20to23(final int line) throws InternetSCSIException { connectionID = (short)((line & Constants.FIRST_TWO_BYTES_MASK) >>> Constants.TWO_BYTES_SHIFT); Utils.isReserved(line & Constants.LAST_TWO_BYTES_MASK); } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** {@inheritDoc} */ @Override protected final void checkIntegrity() throws InternetSCSIException { String exceptionMessage; do { Utils.isReserved(logicalUnitNumber); if (reasonCode == LogoutReasonCode.CLOSE_SESSION && connectionID != 0) { exceptionMessage = "The CID field must be zero, if close session is requested."; break; } if (protocolDataUnit.getBasicHeaderSegment().getTotalAHSLength() != 0) { exceptionMessage = "TotalAHSLength must be 0!"; break; } if (protocolDataUnit.getBasicHeaderSegment().getDataSegmentLength() != 0) { exceptionMessage = "DataSegmentLength must be 0!"; break; } // message is checked correctly return; } while (false); throw new InternetSCSIException(exceptionMessage); } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** {@inheritDoc} */ @Override protected final int serializeBytes1to3() { return reasonCode.value() << Constants.TWO_BYTES_SHIFT; } /** {@inheritDoc} */ @Override protected final int serializeBytes20to23() { return connectionID << Constants.TWO_BYTES_SHIFT; } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- }