/** * 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.ByteBufferCache; import org.jscsi.utils.Utils; import com.carrotsearch.hppc.ByteObjectOpenHashMap; /** * <h1>AdditionalHeaderSegment</h1> * <p> * This class encapsulate an Additional Header Segment (AHS) defined in iSCSI Protocol (RFC3720). * <p> * It provides all methods to serialize and deserialize such an AHS. Further there are getter methods to * access the specific data, which is contained in this AHS. * * @author Volker Wildi */ final class AdditionalHeaderSegment { // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * This enumeration defines all valid types of additional header segments, * which are defined by the iSCSI standard (RFC3720). * <p> * <table border="1"> * <tr> * <th>Value</th> * <th>Meaning</th> * </tr> * <tr> * <td>0</td> * <td>Reserved</td> * </tr> * <tr> * <td>1</td> * <td>Extended CDB</td> * </tr> * <tr> * <td>2</td> * <td>Expected Bidirectional Read Data Length</td> * </tr> * <tr> * <td>3 - 63</td> * <td>Reserved</td> * </tr> * </table> */ enum AdditionalHeaderSegmentType { /** * This type of AHS MUST NOT be used if the <code>CDBLength</code> is * less than <code>17</code>. The length includes the reserved byte <code>3</code>. */ EXTENDED_CDB((byte)1), /** * The Expected Bidirectional Read Data Length. But this is not good * documented in the iSCSI Protocol (RFC3720). */ EXPECTED_BIDIRECTIONAL_READ_DATA_LENGTH((byte)2); private final byte value; private static ByteObjectOpenHashMap<AdditionalHeaderSegmentType> mapping; static { AdditionalHeaderSegmentType.mapping = new ByteObjectOpenHashMap<AdditionalHeaderSegmentType>(values().length); for (AdditionalHeaderSegmentType s : values()) { AdditionalHeaderSegmentType.mapping.put(s.value, s); } } private AdditionalHeaderSegmentType(final byte newValue) { value = newValue; } /** * Returns the value of this enumeration. * * @return The value of this enumeration. */ private 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. */ private static final AdditionalHeaderSegmentType valueOf(final byte value) { return AdditionalHeaderSegmentType.mapping.get(value); } } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * Factor, which must be muliplied with the <code>totalAHSLength</code> contained in a * <code>BasicHeaderSegment</code> object. */ static final int AHS_FACTOR = 4; // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** Offset of the first complete line in the AHS specific field. */ private static final int EXTENDED_CDB_OFFSET = 1; /** * The length of AHS, if the type of the AHS is the Bidirectional Expected * Read-Data Length. */ private static final int EXPECTED_BIDIRECTIONAL_LENGTH = 0x0005; /** * Length of the specific field <code>ByteBuffer</code>, which is expected, * if the AHS type is the <code>AdditionalHeaderSegmentType.EXPECTED_BIDIRECTIONAL_READ_DATA_LENGTH</code> * . */ private static final int EXPECTED_BIDIRECTIONAL_SPECIFIC_FIELD_LENGTH = 5; /** * This is the size (in bytes) of the <code>AHSLength</code> and the <code>AHSType</code>, which are also * included in the serialized AHS form * of this object. */ private static final int FIX_SIZE_OVERHEAD = 3; // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * This field contains the effective length in bytes of the AHS excluding * AHSType and AHSLength and padding, if any. The AHS is padded to the * smallest integer number of 4 byte words (i.e., from 0 up to 3 padding * bytes). */ private short length; /** * The type of this AHS. <br/> * <br/> * <table border="1"> * <tr> * <th>Bits</th> * <th>Meaning</th> * </tr> * <tr> * <td>0-1</td> * <td>Reserved</td> * </tr> * <tr> * <td>2-7</td> * <td>AHS code</td> * </tr> * </table> * <br/> * * @see AdditionalHeaderSegmentType */ private AdditionalHeaderSegmentType type; /** * This array contains the informations, which are type specific fields. */ private ByteBuffer specificField; // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * Default constructor, creates a new, empty AdditionalHeaderSegment object. */ AdditionalHeaderSegment() { } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * This method serializes the informations of this AHS 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 of used integers of the serialized form of this AHS * object. * @throws InternetSCSIException * If any violation of the iSCSI-Standard emerge. */ final int serialize(final ByteBuffer dst, final int offset) throws InternetSCSIException { dst.position(offset); if (dst.remaining() < length) { throw new IllegalArgumentException("Destination array is too small."); } dst.putShort(length); dst.put(type.value()); dst.put(specificField.get()); while (specificField.hasRemaining()) { dst.putInt(specificField.getInt()); } return length + FIX_SIZE_OVERHEAD; } /** * Extract the informations given by the int array to this Additional Header * Segment object. * * @param pdu * The Protocol Data Unit to be parsed. * @param offset * The offset, where to start in the pdu. * @throws InternetSCSIException * If any violation of the iSCSI-Standard emerge. */ final void deserialize(final ByteBuffer pdu, final int offset) throws InternetSCSIException { pdu.position(offset); length = pdu.getShort(); type = AdditionalHeaderSegmentType.valueOf(pdu.get()); // allocate the needed memory specificField = ByteBufferCache.allocate(specificField, length); specificField.put(pdu.get()); // deserialize the type specific fields while (specificField.hasRemaining()) { specificField.putInt(pdu.getInt()); } checkIntegrity(); } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * Returns the length of this AHS object. Expected values are greater than <code>0</code> and a maximum of * <code>65536</code> * * @return The length of this AHS object. */ final short getLength() { return length; } /** * Returns an array with the type specific fields of this AHS object. * * @return The type specific fields. */ final ByteBuffer getSpecificField() { return (ByteBuffer)specificField.rewind(); } /** * Returns the type of this AHS object. Expected values are defined as * constants in the class AdditionalHeaderSegmentTypes. * * @return The value of this AHS object. */ final AdditionalHeaderSegmentType getType() { return type; } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * Creates a string object with all values for easy debugging. * * @return The string with all informations of this AHS. */ public final String toString() { final StringBuilder sb = new StringBuilder(Constants.LOG_INITIAL_SIZE); sb.append("--------------------------\n"); sb.append("Additional Header Segment:\n"); Utils.printField(sb, "Length", length, 1); Utils.printField(sb, "Type", type.value(), 1); getSpecificField().position(EXTENDED_CDB_OFFSET); switch (type) { case EXTENDED_CDB: while (specificField.hasRemaining()) { Utils.printField(sb, "Extended CDB", specificField.getInt(), 1); } break; case EXPECTED_BIDIRECTIONAL_READ_DATA_LENGTH: Utils.printField(sb, "Expected Data Length", specificField.getInt(), 1); break; default: // do nothing } sb.append("--------------------------\n"); specificField.rewind(); return sb.toString(); } /** * Clears all the stored content of this <code>AdditionalHeaderSegment</code> object. */ final void clear() { ByteBufferCache.release(specificField); specificField = null; length = 0; type = null; } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** {@inheritDoc} */ @Override final public boolean equals(Object o) { if (o instanceof AdditionalHeaderSegment == false) return false; AdditionalHeaderSegment oAhs = (AdditionalHeaderSegment)o; if (oAhs.getType().equals(this.getType()) && oAhs.getSpecificField().equals(this.getSpecificField()) && oAhs.getLength() == this.getLength()) return true; return false; } /** * This method checks the integrity of the this Additional Header Segment * object to garantee a valid specification. * * @throws InternetSCSIException * If the fields are not valid for this AHS type. */ private final void checkIntegrity() throws InternetSCSIException { switch (type) { case EXTENDED_CDB: case EXPECTED_BIDIRECTIONAL_READ_DATA_LENGTH: break; default: throw new InternetSCSIException("AHS Package is not valid."); } // this field is AHSType independent specificField.rewind(); Utils.isReserved(specificField.get()); switch (type) { case EXTENDED_CDB: break; case EXPECTED_BIDIRECTIONAL_READ_DATA_LENGTH: Utils.isExpected(specificField.limit(), EXPECTED_BIDIRECTIONAL_SPECIFIC_FIELD_LENGTH); Utils.isExpected(length, EXPECTED_BIDIRECTIONAL_LENGTH); break; default: throw new InternetSCSIException("Unknown additional header segment type."); } specificField.rewind(); } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- }