/**
* 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;
import java.nio.ByteBuffer;
import org.jscsi.exception.InternetSCSIException;
import org.jscsi.utils.Utils;
/**
* <h1>BasicHeaderSegment</h1>
* <p>
* This class encapsulate a Basic Header Segment (BHS), which is defined in the iSCSI Protocol (RFC3720). It
* provides methods for serializing or deserializing such an object. The contained data can be accessed
* seperately by the getter methods.
* <p>
* The BHS has a fixed size, which is stored in the variable <code>BHS_FIXED_SIZE</code>. And these must the
* first bytes in a valid iSCSI Protocol Data Unit (PDU). org.jscsi.utils
*
* @author Volker Wildi
*/
public final class BasicHeaderSegment {
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** The BHS has a fixed size of <code>48</code> bytes. */
static final int BHS_FIXED_SIZE = 48;
/** Offset of the byte <code>8</code> till <code>11</code> in the BHS. */
static final int BYTES_8_11 = 8;
/** Offset of the byte <code>16</code> till <code>19</code> in the BHS. */
static final int BYTES_16_19 = 16;
/** Offset of the byte <code>20</code> till <code>23</code> in the BHS. */
static final int BYTES_20_23 = 20;
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** Bit mask to extract the immediate flag of a <code>32</code> bit number. */
private static final int IMMEDIATE_FLAG_MASK = 0x40000000;
/** Bit mask to extract the operation code of a <code>32</code> bit number. */
private static final int OPERATION_CODE_MASK = 0x3F000000;
/** Bit mask to extract the final flag of a <code>32</code> bit number. */
private static final int FINAL_FLAG_MASK = 0x00800000;
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* For request PDUs, the <code>I</code> bit set to <code>1</code> is an
* immediate delivery marker.
*/
private boolean immediateFlag;
/**
* The operation code indicates the type of iSCSI PDU the header
* encapsulates. The operation codes are divided into two categories: <em>initiator</em> opcodes and
* <em>target</em> opcodes. Initiator opcodes
* are in PDUs sent by the initiator (request PDUs). Target opcodes are in
* PDUs sent by the target (response PDUs).
* <p>
* Initiators MUST NOT use target opcodes and targets MUST NOT use initiator opcodes.
*/
private OperationCode operationCode;
/**
* When set to <code>1</code> it indicates the final (or only) PDU of a
* sequence.
*/
private boolean finalFlag;
/**
* Total length of all AHS header segments in units of <code>four</code> byte words including padding, if
* any.
* <p>
* The <code>TotalAHSLength</code> is only used in PDUs that have an AHS and MUST be <code>0</code> in all
* other PDUs.
*/
private byte totalAHSLength;
/**
* This is the data segment payload length in bytes (excluding padding). The
* DataSegmentLength MUST be <code>0</code> whenever the PDU has no data
* segment.
*/
private int dataSegmentLength;
/**
* The initiator assigns a Task Tag to each iSCSI task it issues. While a
* task exists, this tag MUST uniquely identify the task session-wide. SCSI
* may also use the initiator task tag as part of the SCSI task identifier
* when the timespan during which an iSCSI initiator task tag must be unique
* extends over the timespan during which a SCSI task tag must be unique.
* However, the iSCSI Initiator Task Tag must exist and be unique even for
* untagged SCSI commands.
*/
private int initiatorTaskTag;
/** The used parser for the messages. */
private AbstractMessageParser parser;
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/** Default constructor, creates new, empty BasicHeaderSegment object. */
BasicHeaderSegment() {
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* This method serializes the informations of this BHS object to the byte
* representation defined by the iSCSI Standard.
*
* @param dst
* The destination array to write in.
* @param offset
* The start offset in <code>dst</code>.
* @return The length (in bytes) of the serialized form of this BHS object.
* @throws InternetSCSIException
* If any violation of the iSCSI-Standard emerge.
*/
final int serialize(final ByteBuffer dst, final int offset) throws InternetSCSIException {
// has the destination array enough space to store this basic header
// segment
dst.position(offset);
if (dst.remaining() < BHS_FIXED_SIZE) {
throw new IllegalArgumentException("Destination array is too small.");
}
int line = 0;
if (immediateFlag) {
line |= IMMEDIATE_FLAG_MASK;
}
line |= operationCode.value() << Constants.THREE_BYTES_SHIFT;
if (finalFlag) {
line |= FINAL_FLAG_MASK;
}
dst.putInt(line);
dst.putInt(dataSegmentLength | (totalAHSLength << Constants.THREE_BYTES_SHIFT));
dst.putInt(BYTES_16_19, initiatorTaskTag);
parser.serializeBasicHeaderSegment(dst, offset);
return BHS_FIXED_SIZE;
}
/**
* Extract from the given Protocol Data Unit the BHS. After an successful
* extraction this methods and setreturns the right message parser object
* for this kind of message.
*
* @param protocolDataUnit
* The reference <code>ProtocolDataUnit</code> instance, which
* contains this <code>BasicHeaderSegment</code> object.
* @param src
* The bytes representation of a Protocol Data Unit.
* @return The length (in bytes), which are read from <code>pdu</code>.
* @throws InternetSCSIException
* If any violation of the iSCSI-Standard emerge.
*/
final int deserialize(final ProtocolDataUnit protocolDataUnit, final ByteBuffer src)
throws InternetSCSIException {
if (src.remaining() < BHS_FIXED_SIZE) {
throw new InternetSCSIException("This Protocol Data Unit does not contain"
+ " an valid Basic Header Segment.");
}
final int firstLine = src.getInt();
immediateFlag = Utils.isBitSet(firstLine & IMMEDIATE_FLAG_MASK);
final int code = (firstLine & OPERATION_CODE_MASK) >> Constants.THREE_BYTES_SHIFT;
operationCode = OperationCode.valueOf((byte)code);
finalFlag = Utils.isBitSet(firstLine & FINAL_FLAG_MASK);
totalAHSLength = src.get();
dataSegmentLength = Utils.getUnsignedInt(src.get()) << Constants.TWO_BYTES_SHIFT;
dataSegmentLength += Utils.getUnsignedInt(src.get()) << Constants.ONE_BYTE_SHIFT;
dataSegmentLength += Utils.getUnsignedInt(src.get());
initiatorTaskTag = src.getInt(BYTES_16_19);
src.rewind();
parser = MessageParserFactory.getParser(protocolDataUnit, operationCode);
parser.deserializeBasicHeaderSegment(src);
return BHS_FIXED_SIZE;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* The Length of the Data Segment.
*
* @return length of the data segment
*/
public final int getDataSegmentLength() {
return dataSegmentLength;
}
/**
* When this flag is set it indicates the final (or only) PDU of a sequence.
* In some kinds of messages (PDU) this methods has the meaning of the <code>TransitFlag</code>.
*
* @return The state of the final flag.
*/
public final boolean isFinalFlag() {
return finalFlag;
}
/**
* For request PDUs, the immediate flag can be set as an immediate delivery
* marker.
*
* @return The state of the immediate flag.
*/
public final boolean isImmediateFlag() {
return immediateFlag;
}
/**
* The initiator assigns a Task Tag to each iSCSI task it issues. While a
* task exists, this tag MUST uniquely identify the task session-wide. SCSI
* may also use the initiator task tag as part of the SCSI task identifier
* when the timespan during which an iSCSI initiator task tag must be unique
* extends over the timespan during which a SCSI task tag must be unique.
* However, the iSCSI Initiator Task Tag must exist and be unique even for
* untagged SCSI commands.
*
* @return the initiator task tag.
*/
public final int getInitiatorTaskTag() {
return initiatorTaskTag;
}
/**
* The length of the Additional Header Segment.
*
* @return The length of the contained AHSs
*/
public final byte getTotalAHSLength() {
return totalAHSLength;
}
/**
* Returns the operation code, which is used in this BHS.
*
* @return The operation code of this BHS.
*/
public final OperationCode getOpCode() {
return operationCode;
}
/**
* Returns a object of the used parser of this BHS.
*
* @return The parser object to use for this PDU.
*/
public final AbstractMessageParser getParser() {
return parser;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Set a new length for the data segment.
*
* @param initDataSegmentLength
* The new length of this BasicHeaderSegment object.
*/
final void setDataSegmentLength(final int initDataSegmentLength) {
dataSegmentLength = initDataSegmentLength;
}
/**
* Changes the state of the final flag.
*
* @param initFinalFlag
* The new state of the final flag.
*/
final void setFinal(final boolean initFinalFlag) {
finalFlag = initFinalFlag;
}
/**
* Changes the state of the immediate flag.
*
* @param initImmediateFlag
* The new state of the immediate flag.
*/
final void setImmediate(final boolean initImmediateFlag) {
immediateFlag = initImmediateFlag;
}
/**
* Changes the value of the initiator task tag.
*
* @param initInitiatorTaskTag
* The new value of the initiator task tag.
*/
public final void setInitiatorTaskTag(final int initInitiatorTaskTag) {
// FIXME: Change to allow fixed values
initiatorTaskTag = initInitiatorTaskTag;
}
/**
* This sets the length (in units of four bytes) of the total length of the
* given AHS.
*
* @param initTotalAHSLength
* The new length.
*/
final void setTotalAHSLength(final byte initTotalAHSLength) {
totalAHSLength = initTotalAHSLength;
}
/**
* Set a new operation code for this BHS object.
*
* @param protocolDataUnit
* The reference <code>ProtocolDataUnit</code> instance, which
* contains this <code>BasicHeaderSegment</code> object.
* @param initOperationCode
* The new operation code.
*/
final void
setOperationCode(final ProtocolDataUnit protocolDataUnit, final OperationCode initOperationCode) {
operationCode = initOperationCode;
parser = MessageParserFactory.getParser(protocolDataUnit, initOperationCode);
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
/**
* Creates a string object with all values for easy debugging.
*
* @return The string with all informations of this BHS.
*/
public final String toString() {
if (parser == null)
return "Empty parser";
final StringBuilder sb = new StringBuilder(Constants.LOG_INITIAL_SIZE);
Utils.printField(sb, "ParserClass", parser.getClass().getSimpleName(), 1);
Utils.printField(sb, "ImmediateFlag", immediateFlag, 1);
Utils.printField(sb, "OpCode", operationCode.value(), 1);
Utils.printField(sb, "FinalFlag", finalFlag, 1);
Utils.printField(sb, "TotalAHSLength", totalAHSLength, 1);
Utils.printField(sb, "DataSegmentLength", dataSegmentLength, 1);
Utils.printField(sb, "InitiatorTaskTag", initiatorTaskTag, 1);
sb.append(parser.toString());
return sb.toString();
}
/** {@inheritDoc} */
@Override
final public boolean equals(Object o) {
if (o instanceof BasicHeaderSegment == false)
return false;
BasicHeaderSegment oBhs = (BasicHeaderSegment)o;
if (oBhs.isFinalFlag() == this.isFinalFlag() && oBhs.isImmediateFlag() == this.isImmediateFlag()
&& oBhs.getParser().getClass() == this.getParser().getClass()
&& oBhs.getDataSegmentLength() == this.getDataSegmentLength()
&& oBhs.getInitiatorTaskTag() == this.getInitiatorTaskTag()
&& oBhs.getOpCode().compareTo(this.getOpCode()) == 0
&& oBhs.getTotalAHSLength() == this.getTotalAHSLength())
return true;
return false;
}
/**
* Clears all the stored content of this BasicHeaderSegment object.
*/
final void clear() {
immediateFlag = false;
operationCode = OperationCode.LOGIN_REQUEST;
finalFlag = false;
totalAHSLength = 0x00;
dataSegmentLength = 0x00000000;
initiatorTaskTag = 0x00000000;
parser = null;
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
}