All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jscsi.parser.ProtocolDataUnit Maven / Gradle / Ivy

The newest version!
/**
 * 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  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.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.security.DigestException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Iterator;

import org.jscsi.exception.InternetSCSIException;
import org.jscsi.parser.datasegment.AbstractDataSegment;
import org.jscsi.parser.datasegment.IDataSegmentIterator.IDataSegmentChunk;
import org.jscsi.parser.digest.IDigest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * 

ProtocolDataUnit

*

* This class encapsulates a Protocol Data Unit (PDU), which is defined in the iSCSI Standard (RFC 3720). * * @author Volker Wildi */ public final class ProtocolDataUnit { // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** The initial size of the Additional Header Segment. */ private static final int AHS_INITIAL_SIZE = 0; /** The Log interface. */ private static final Logger LOGGER = LoggerFactory.getLogger(ProtocolDataUnit.class); // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** The Basic Header Segment of this PDU. */ private final BasicHeaderSegment basicHeaderSegment; /** The Additional Header Segment 1...n (optional) of this PDU. */ private final AbstractList additionalHeaderSegments; /** * The (optional) Data Segment contains PDU associated data. Its payload effective length is provided in the BHS * field - DataSegmentLength. The Data Segment is also padded to multiple of a 4 byte * words. */ private ByteBuffer dataSegment; /** * Optional header and data digests protect the integrity of the header and data, respectively. The digests, if * present, are located, respectively, after the header and PDU-specific data, and cover respectively the header and * the PDU data, each including the padding bytes, if any. *

* The existence and type of digests are negotiated during the Login Phase. *

* The separation of the header and data digests is useful in iSCSI routing applications, in which only the header * changes when a message is forwarded. In this case, only the header digest should be recalculated. *

* Digests are not included in data or header length fields. *

* A zero-length Data Segment also implies a zero-length data-digest. */ /** Digest of the header of this PDU. */ private IDigest headerDigest; /** Digest of the data segment of this PDU. */ private IDigest dataDigest; // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * Default constructor, creates a new, empty ProtcolDataUnit object. * * @param initHeaderDigest The instance of the digest to use for the Basic Header Segment protection. * @param initDataDigest The instance of the digest to use for the Data Segment protection. */ public ProtocolDataUnit (final IDigest initHeaderDigest, final IDigest initDataDigest) { basicHeaderSegment = new BasicHeaderSegment(); headerDigest = initHeaderDigest; additionalHeaderSegments = new ArrayList(AHS_INITIAL_SIZE); dataSegment = ByteBuffer.allocate(0); dataDigest = initDataDigest; } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * Serialize all informations of this PDU object to its byte representation. * * @return The byte representation of this PDU. * @throws InternetSCSIException If any violation of the iSCSI-Standard emerge. * @throws IOException if an I/O error occurs. */ public final ByteBuffer serialize () throws InternetSCSIException , IOException { basicHeaderSegment.getParser().checkIntegrity(); final ByteBuffer pdu = ByteBuffer.allocate(calcSize()); int offset = 0; offset += basicHeaderSegment.serialize(pdu, offset); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Serialized Basic Header Segment:\n" + toString()); } offset += serializeAdditionalHeaderSegments(pdu, offset); // write header digest // TODO: Move CRC calculation in BasicHeaderSegment.serialize? if (basicHeaderSegment.getParser().canHaveDigests()) { offset += serializeDigest(pdu, headerDigest); } // serialize data segment offset += serializeDataSegment(pdu, offset); // write data segment digest // TODO: Move CRC calculation in BasicHeaderSegment.serialize? if (basicHeaderSegment.getParser().canHaveDigests()) { offset += serializeDigest(pdu, dataDigest); } return (ByteBuffer) pdu.rewind(); } /** * Deserializes (parses) a given byte representation of a PDU to an PDU object. * * @param pdu The byte representation of an PDU to parse. * @return The number of bytes, which are serialized. * @throws InternetSCSIException If any violation of the iSCSI-Standard emerge. * @throws IOException if an I/O error occurs. * @throws DigestException There is a mismatch of the digest. */ public final int deserialize (final ByteBuffer pdu) throws InternetSCSIException , IOException , DigestException { int offset = deserializeBasicHeaderSegment(pdu); offset += deserializeAdditionalHeaderSegments(pdu, offset); offset += deserializeDataSegment(pdu, offset); basicHeaderSegment.getParser().checkIntegrity(); return offset; } /** * Deserializes a given array starting from offset 0 and store the informations in the * BasicHeaderSegment object.. * * @param bhs The array to read from. * @throws InternetSCSIException If any violation of the iSCSI-Standard emerge. * @throws DigestException There is a mismatch of the digest. */ private final int deserializeBasicHeaderSegment (final ByteBuffer bhs) throws InternetSCSIException , DigestException { int len = basicHeaderSegment.deserialize(this, bhs); // read header digest and validate if (basicHeaderSegment.getParser().canHaveDigests()) { len += deserializeDigest(bhs, bhs.position() - BasicHeaderSegment.BHS_FIXED_SIZE, BasicHeaderSegment.BHS_FIXED_SIZE, headerDigest); } if (LOGGER.isTraceEnabled()) { LOGGER.trace("Deserialized Basic Header Segment:\n" + toString()); } return len; } /** * Deserializes a array (starting from offset 0) and store the informations to the * AdditionalHeaderSegment object. * * @param pdu The array to read from. * @return The length of the read bytes. * @throws InternetSCSIException If any violation of the iSCSI-Standard emerge. */ private final int deserializeAdditionalHeaderSegments (final ByteBuffer pdu) throws InternetSCSIException { return deserializeAdditionalHeaderSegments(pdu, 0); } /** * Deserializes a array (starting from the given offset) and store the informations to the * AdditionalHeaderSegment object. * * @param pdu The ByteBuffer to read from. * @param offset The offset to start from. * @return The length of the written bytes. * @throws InternetSCSIException If any violation of the iSCSI-Standard emerge. */ private final int deserializeAdditionalHeaderSegments (final ByteBuffer pdu, final int offset) throws InternetSCSIException { // parsing Additional Header Segment int off = offset; int ahsLength = basicHeaderSegment.getTotalAHSLength(); while (ahsLength != 0) { final AdditionalHeaderSegment tmpAHS = new AdditionalHeaderSegment(); tmpAHS.deserialize(pdu, off); additionalHeaderSegments.add(tmpAHS); ahsLength -= tmpAHS.getLength(); off += tmpAHS.getSpecificField().position(); } return off - offset; } /** * Serialize all the contained additional header segments to the destination array starting from the given offset. * * @param dst The destination array to write in. * @param offset The offset to start to write in dst. * @return The written length. * @throws InternetSCSIException If any violation of the iSCSI-Standard emerge. */ private final int serializeAdditionalHeaderSegments (final ByteBuffer dst, final int offset) throws InternetSCSIException { int off = offset; for (AdditionalHeaderSegment ahs : additionalHeaderSegments) { off += ahs.serialize(dst, off); } return off - offset; } /** * Serializes the data segment (binary or key-value pairs) to a destination array, staring from offset to write. * * @param dst The array to write in. * @param offset The start offset to start from in dst. * @return The written length. * @throws InternetSCSIException If any violation of the iSCSI-Standard emerge. */ public final int serializeDataSegment (final ByteBuffer dst, final int offset) throws InternetSCSIException { dataSegment.rewind(); dst.position(offset); dst.put(dataSegment); return dataSegment.limit(); } /** * Deserializes a array (starting from the given offset) and store the informations to the Data Segment. * * @param pdu The array to read from. * @param offset The offset to start from. * @return The length of the written bytes. * @throws IOException if an I/O error occurs. * @throws InternetSCSIException If any violation of the iSCSI-Standard emerge. * @throws DigestException There is a mismatch of the digest. */ private final int deserializeDataSegment (final ByteBuffer pdu, final int offset) throws IOException , InternetSCSIException , DigestException { final int length = basicHeaderSegment.getDataSegmentLength(); if (dataSegment == null || dataSegment.limit() < length) { dataSegment = ByteBuffer.allocate(AbstractDataSegment.getTotalLength(length)); } dataSegment.put(pdu); dataSegment.flip(); // read data segment digest and validate if (basicHeaderSegment.getParser().canHaveDigests()) { deserializeDigest(pdu, offset, length, dataDigest); } if (dataSegment == null) { return 0; } else { return dataSegment.limit(); } } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * Writes this ProtocolDataUnit object to the given SocketChannel. * * @param sChannel SocketChannel to write to. * @return The number of bytes written, possibly zero. * @throws InternetSCSIException if any violation of the iSCSI-Standard emerge. * @throws IOException if an I/O error occurs. */ public final int write (final SocketChannel sChannel) throws InternetSCSIException , IOException { // print debug informations if (LOGGER.isTraceEnabled()) { LOGGER.trace(basicHeaderSegment.getParser().getShortInfo()); } final ByteBuffer src = serialize(); int length = 0; while (length < src.limit()) { length += sChannel.write(src); } return length; } /** * Reads from the given SocketChannel all the neccassary bytes to fill this PDU. * * @param sChannel SocketChannel to read from. * @return The number of bytes, possibly zero,or -1 if the channel has reached end-of-stream * @throws IOException if an I/O error occurs. * @throws InternetSCSIException if any violation of the iSCSI-Standard emerge. * @throws DigestException if a mismatch of the digest exists. */ public final int read (final SocketChannel sChannel) throws InternetSCSIException , IOException , DigestException { // read Basic Header Segment first to determine the total length of this // Protocol Data Unit. clear(); final ByteBuffer bhs = ByteBuffer.allocate(BasicHeaderSegment.BHS_FIXED_SIZE); int len = 0; while (len < BasicHeaderSegment.BHS_FIXED_SIZE) { int lens = sChannel.read(bhs); if (lens == -1) { // The Channel was closed at the Target (e.g. the Target does // not support Multiple Connections) // throw new ClosedChannelException(); return lens; } len += lens; LOGGER.trace("Receiving through SocketChannel: " + len + " of maximal " + BasicHeaderSegment.BHS_FIXED_SIZE); } bhs.flip(); deserializeBasicHeaderSegment(bhs); // check for further reading if (getBasicHeaderSegment().getTotalAHSLength() > 0) { final ByteBuffer ahs = ByteBuffer.allocate(basicHeaderSegment.getTotalAHSLength()); int ahsLength = 0; while (ahsLength < getBasicHeaderSegment().getTotalAHSLength()) { ahsLength += sChannel.read(ahs); } len += ahsLength; ahs.flip(); deserializeAdditionalHeaderSegments(ahs); } if (basicHeaderSegment.getDataSegmentLength() > 0) { dataSegment = ByteBuffer.allocate(AbstractDataSegment.getTotalLength(basicHeaderSegment.getDataSegmentLength())); int dataSegmentLength = 0; while (dataSegmentLength < basicHeaderSegment.getDataSegmentLength()) { dataSegmentLength += sChannel.read(dataSegment); } len += dataSegmentLength; dataSegment.flip(); } // print debug informations if (LOGGER.isTraceEnabled()) { LOGGER.trace(basicHeaderSegment.getParser().getShortInfo()); } return len; } /** * Clears all stored content of this ProtocolDataUnit object. */ public final void clear () { basicHeaderSegment.clear(); headerDigest.reset(); additionalHeaderSegments.clear(); dataSegment.clear(); dataSegment.flip(); dataDigest.reset(); } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * Returns an iterator to all contained Additional Header Segment in this PDU. * * @return The iterator to the contained Additional Header Segment. * @see AdditionalHeaderSegment */ public final Iterator getAdditionalHeaderSegments () { return additionalHeaderSegments.iterator(); } /** * Returns the Basic Header Segment contained in this PDU. * * @return The Basic Header Segment. * @see BasicHeaderSegment */ public final BasicHeaderSegment getBasicHeaderSegment () { return basicHeaderSegment; } /** * Gets the data segment in this PDU. * * @return The data segment of this ProtocolDataUnit object. */ public final ByteBuffer getDataSegment () { return dataSegment; } public final void setDataSegment (final ByteBuffer dataSegment) { dataSegment.clear(); this.dataSegment = dataSegment; basicHeaderSegment.setDataSegmentLength(dataSegment.capacity()); } /** * Sets a new data segment in this PDU. * * @param chunk The new data segment of this ProtocolDataUnit object. */ public final void setDataSegment (final IDataSegmentChunk chunk) { if (chunk == null) { throw new NullPointerException(); } dataSegment = ByteBuffer.allocate(chunk.getTotalLength()); dataSegment.put(chunk.getData()); basicHeaderSegment.setDataSegmentLength(chunk.getLength()); } /** * Returns the instance of the used digest algorithm for the header. * * @return The instance of the header digest. */ public final IDigest getHeaderDigest () { return headerDigest; } /** * Sets the digest of the header to use for data integrity. * * @param newHeaderDigest An instance of the new header digest. */ public final void setHeaderDigest (final IDigest newHeaderDigest) { headerDigest = newHeaderDigest; } /** * Returns the instance of the used digest algorithm for the data segment. * * @return The instance of the data digest. */ public final IDigest getDataDigest () { return dataDigest; } /** * Sets the digest of the data segment to use for data integrity. * * @param newDataDigest An instance of the new data segment digest. */ public final void setDataDigest (final IDigest newDataDigest) { dataDigest = newDataDigest; } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** {@inheritDoc} */ @Override public final String toString () { final StringBuilder sb = new StringBuilder(Constants.LOG_INITIAL_SIZE); sb.append(basicHeaderSegment.toString()); for (AdditionalHeaderSegment ahs : additionalHeaderSegments) { sb.append(ahs.toString()); } return sb.toString(); } // -------------------------------------------------------------------------- /** {@inheritDoc} */ @Override public final boolean equals (Object o) { if (o instanceof ProtocolDataUnit == false) return false; ProtocolDataUnit oPdu = (ProtocolDataUnit) o; Iterator ahs1 = oPdu.getAdditionalHeaderSegments(); Iterator ahs2 = this.getAdditionalHeaderSegments(); while (ahs1.hasNext()) { if (!ahs1.equals(ahs2)) return false; ahs1.next(); ahs2.next(); } if (oPdu.getBasicHeaderSegment().equals(this.getBasicHeaderSegment()) && oPdu.getDataDigest().equals(this.getDataDigest()) && oPdu.getHeaderDigest().equals(this.getHeaderDigest()) && oPdu.getDataSegment().equals(this.getDataSegment())) return true; return false; } // -------------------------------------------------------------------------- @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (basicHeaderSegment != null ? basicHeaderSegment.hashCode() : 0); result = 31 * result + (additionalHeaderSegments != null ? additionalHeaderSegments.hashCode() : 0); result = 31 * result + (dataSegment != null ? dataSegment.hashCode() : 0); result = 31 * result + (headerDigest != null ? headerDigest.hashCode() : 0); result = 31 * result + (dataDigest != null ? dataDigest.hashCode() : 0); return result; } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- /** * Calculates the needed size (in bytes) of serializing this object. * * @return The needed size to store this object. */ private final int calcSize () { int size = BasicHeaderSegment.BHS_FIXED_SIZE; size += basicHeaderSegment.getTotalAHSLength() * AdditionalHeaderSegment.AHS_FACTOR; // plus the sizes of the used digests size += headerDigest.getSize(); size += dataDigest.getSize(); size += AbstractDataSegment.getTotalLength(basicHeaderSegment.getDataSegmentLength()); return size; } private final int serializeDigest (final ByteBuffer pdu, final IDigest digest) { final int size = digest.getSize(); if (size > 0) { digest.reset(); pdu.mark(); digest.update(pdu, 0, BasicHeaderSegment.BHS_FIXED_SIZE); pdu.putInt((int) digest.getValue()); pdu.reset(); } return size; } private final int deserializeDigest (final ByteBuffer pdu, final int offset, final int length, final IDigest digest) throws DigestException { pdu.mark(); digest.update(pdu, offset, length); digest.validate(); pdu.reset(); return digest.getSize(); } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy