package org.jscsi.target.scsi;
import java.nio.ByteBuffer;
import org.jscsi.target.connection.TargetPduFactory;
import org.jscsi.target.scsi.sense.SenseData;
import org.jscsi.target.util.Debug;
import org.jscsi.target.util.ReadWrite;
import org.jscsi.utils.ByteBufferCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Instances of this class represent data that may be sent in a SCSI Command
* Response PDU.
* <p>
* It may contain either {@link SenseData}, {@link IResponseData}, or (rarely) both. Some SCSI commands
* require that no response data is sent (i.e. data segment length = 0) - in these cases the
* {@link #EMPTY_DATA_SEGMENT} shall be used.
* <p>
* Since the target must honor the buffer size the initiator has allocated for data returned in the data
* segment, the {@link #serialize()} method will only write as many bytes to the passed {@link ByteBuffer} as
* specified during initialization.
*
* @see TargetPduFactory
* @author Andreas Ergenzinger
*/
public final class ScsiResponseDataSegment {
private static final Logger LOGGER = LoggerFactory.getLogger(ScsiResponseDataSegment.class);
/**
* A {@link ScsiResponseDataSegment} of length zero.
*/
public static final ScsiResponseDataSegment EMPTY_DATA_SEGMENT = new ScsiResponseDataSegment();
/**
* The length in bytes of the SENSE LENGTH field.
*/
private static final int SENSE_LENGTH_FIELD_LENGTH = 2;
/**
* The contained {@link SenseData}.
*/
private final SenseData senseData;
/**
* The contained {@link IResponseData}.
*/
private final IResponseData responseData;
/**
* The number of bytes the initiator has allocated for data sent in the data
* segment, i.e. the maximum number of bytes of this object that may be
* transmitted.
*/
private final int allocationLength;
/**
* The length of the {@link ScsiResponseDataSegment}, without considering
* any limitations due to {@link #allocationLength}, or <code>-1</code>.
*
* @see #uncroppedSize()
*/
private int uncroppedSize = -1;
/**
* Constructor for creating an empty {@link ScsiResponseDataSegment}.
*
* @see ScsiResponseDataSegment#EMPTY_DATA_SEGMENT
*/
private ScsiResponseDataSegment() {
this(null, null, 0);
}
/**
* Creates a {@link ScsiResponseDataSegment} with {@link SenseData}.
*
* @param senseData
* the sense data sent as the result of SCSI error
* @param allocationLength
* number of bytes the initiator has allocated for data sent in
* the data segment
*/
public ScsiResponseDataSegment(final SenseData senseData, final int allocationLength) {
this(senseData, null, allocationLength);
}
/**
* Creates a {@link ScsiResponseDataSegment} with {@link IResponseData}.
*
* @param responseData
* the data requested by the initiator
* @param allocationLength
* number of bytes the initiator has allocated for data sent in
* the data segment
*/
public ScsiResponseDataSegment(final IResponseData responseData, final int allocationLength) {
this(null, responseData, allocationLength);
}
/**
* Creates a {@link ScsiResponseDataSegment} with both {@link SenseData} and {@link IResponseData}.
*
* @param senseData
* senseData the sense data sent as the result of SCSI error
* @param responseData
* data requested by the initiator
* @param allocationLength
* number of bytes the initiator has allocated for data sent in
* the data segment
*/
private ScsiResponseDataSegment(final SenseData senseData, final IResponseData responseData,
final int allocationLength) {
this.senseData = senseData;
this.responseData = responseData;
this.allocationLength = allocationLength;
}
/**
* Returns a {@link ByteBuffer} containing a serialized representation of
* this object.
*
* @return a {@link ByteBuffer} containing a serialized representation of
* this object
*/
public ByteBuffer serialize() {
final int size = uncroppedSize();
if (size == 0)
return ByteBufferCache.allocate(0);// empty data segment
// calculate sense length
int senseLength;
if (senseData == null)
senseLength = 0;
else
senseLength = senseData.size();
// allocate buffer of appropriate size
final ByteBuffer buffer = ByteBufferCache.allocate(size);
// write sense length field
ReadWrite.writeTwoByteInt(buffer,// buffer
senseLength,// value
0);// index
// write sense data
if (senseData != null)
senseData.serialize(buffer, SENSE_LENGTH_FIELD_LENGTH);
// write and SCSI response data
if (responseData != null)
responseData.serialize(buffer, SENSE_LENGTH_FIELD_LENGTH + senseLength);
if (allocationLength > 0 && buffer.capacity() > allocationLength) {
/*
* If the allocation length variable is valid and the response data
* segment is larger than the data-in buffer allocated by the
* initiator, limit the number of bytes returned.
*/
buffer.position(0);
buffer.limit(allocationLength);
final ByteBuffer croppedBuffer = ByteBufferCache.allocate(allocationLength);
croppedBuffer.put(buffer);
return croppedBuffer;
}
if (LOGGER.isDebugEnabled())
LOGGER.debug("SCSI Response Data Segment:\n" + Debug.byteBufferToString(buffer));
return buffer;
}
/**
* The length of the {@link ScsiResponseDataSegment}, without considering
* any limitations due to {@link #allocationLength}.
* <p>
* The returned value is calculated just once, and then stored in {@link #uncroppedSize}. This summation
* is performed only if {@link #uncroppedSize} is negative.
*
* @return length of the {@link ScsiResponseDataSegment}, without
* considering any limitations due to {@link #allocationLength}
*/
public int uncroppedSize() {
if (uncroppedSize < 0) {
if (senseData == null && responseData == null)
return 0;
int size = SENSE_LENGTH_FIELD_LENGTH;
if (senseData != null)
size += senseData.size();
if (responseData != null)
size += responseData.size();
uncroppedSize = size;
}
return uncroppedSize;
}
/**
* Indicates if any bytes have to be cropped during {@link #serialize()} calls. The method returns
* <code>true</code> if and only if the serialized
* length of this objects exceeds {@link #allocationLength}.
*
* @return <code>true</code> if and only if the serialized length of this
* objects exceeds {@link #allocationLength}
*/
public boolean getResidualOverflow() {
if (uncroppedSize() > allocationLength)
return true;
return false;
}
/**
* Returns the number of bytes that had to be cropped due to the total
* length of all contained fields exceeding the {@link #allocationLength}.
*
* @return the total number of bytes that have to be cropped during
* serialization
* @see #serialize()
*/
public int getResidualCount() {
if (getResidualOverflow())
return uncroppedSize() - allocationLength;
return 0;
}
}