View Javadoc

1   package org.jscsi.target.scsi;
2   
3   
4   import java.nio.ByteBuffer;
5   
6   import org.jscsi.target.connection.TargetPduFactory;
7   import org.jscsi.target.scsi.sense.SenseData;
8   import org.jscsi.target.util.Debug;
9   import org.jscsi.target.util.ReadWrite;
10  import org.slf4j.Logger;
11  import org.slf4j.LoggerFactory;
12  
13  
14  /**
15   * Instances of this class represent data that may be sent in a SCSI Command Response PDU.
16   * <p>
17   * It may contain either {@link SenseData}, {@link IResponseData}, or (rarely) both. Some SCSI commands require that no
18   * response data is sent (i.e. data segment length = 0) - in these cases the {@link #EMPTY_DATA_SEGMENT} shall be used.
19   * <p>
20   * Since the target must honor the buffer size the initiator has allocated for data returned in the data segment, the
21   * {@link #serialize()} method will only write as many bytes to the passed {@link ByteBuffer} as specified during
22   * initialization.
23   * 
24   * @see TargetPduFactory
25   * @author Andreas Ergenzinger
26   */
27  public final class ScsiResponseDataSegment {
28  
29      private static final Logger LOGGER = LoggerFactory.getLogger(ScsiResponseDataSegment.class);
30  
31      /**
32       * A {@link ScsiResponseDataSegment} of length zero.
33       */
34      public static final ScsiResponseDataSegment EMPTY_DATA_SEGMENT = new ScsiResponseDataSegment();
35  
36      /**
37       * The length in bytes of the SENSE LENGTH field.
38       */
39      private static final int SENSE_LENGTH_FIELD_LENGTH = 2;
40  
41      /**
42       * The contained {@link SenseData}.
43       */
44      private final SenseData senseData;
45  
46      /**
47       * The contained {@link IResponseData}.
48       */
49      private final IResponseData responseData;
50  
51      /**
52       * The number of bytes the initiator has allocated for data sent in the data segment, i.e. the maximum number of
53       * bytes of this object that may be transmitted.
54       */
55      private final int allocationLength;
56  
57      /**
58       * The length of the {@link ScsiResponseDataSegment}, without considering any limitations due to
59       * {@link #allocationLength}, or <code>-1</code>.
60       * 
61       * @see #uncroppedSize()
62       */
63      private int uncroppedSize = -1;
64  
65      /**
66       * Constructor for creating an empty {@link ScsiResponseDataSegment}.
67       * 
68       * @see ScsiResponseDataSegment#EMPTY_DATA_SEGMENT
69       */
70      private ScsiResponseDataSegment () {
71          this(null, null, 0);
72      }
73  
74      /**
75       * Creates a {@link ScsiResponseDataSegment} with {@link SenseData}.
76       * 
77       * @param senseData the sense data sent as the result of SCSI error
78       * @param allocationLength number of bytes the initiator has allocated for data sent in the data segment
79       */
80      public ScsiResponseDataSegment (final SenseData senseData, final int allocationLength) {
81          this(senseData, null, allocationLength);
82      }
83  
84      /**
85       * Creates a {@link ScsiResponseDataSegment} with {@link IResponseData}.
86       * 
87       * @param responseData the data requested by the initiator
88       * @param allocationLength number of bytes the initiator has allocated for data sent in the data segment
89       */
90      public ScsiResponseDataSegment (final IResponseData responseData, final int allocationLength) {
91          this(null, responseData, allocationLength);
92      }
93  
94      /**
95       * Creates a {@link ScsiResponseDataSegment} with both {@link SenseData} and {@link IResponseData}.
96       * 
97       * @param senseData senseData the sense data sent as the result of SCSI error
98       * @param responseData data requested by the initiator
99       * @param allocationLength number of bytes the initiator has allocated for data sent in the data segment
100      */
101     private ScsiResponseDataSegment (final SenseData senseData, final IResponseData responseData, final int allocationLength) {
102         this.senseData = senseData;
103         this.responseData = responseData;
104         this.allocationLength = allocationLength;
105     }
106 
107     /**
108      * Returns a {@link ByteBuffer} containing a serialized representation of this object.
109      * 
110      * @return a {@link ByteBuffer} containing a serialized representation of this object
111      */
112     public ByteBuffer serialize () {
113 
114         final int size = uncroppedSize();
115         if (size == 0) return ByteBuffer.allocate(0);// empty data segment
116 
117         // calculate sense length
118         int senseLength;
119         if (senseData == null)
120             senseLength = 0;
121         else
122             senseLength = senseData.size();
123 
124         // allocate buffer of appropriate size
125         final ByteBuffer buffer = ByteBuffer.allocate(size);
126 
127         // write sense length field
128         ReadWrite.writeTwoByteInt(buffer,// buffer
129                 senseLength,// value
130                 0);// index
131 
132         // write sense data
133         if (senseData != null) senseData.serialize(buffer, SENSE_LENGTH_FIELD_LENGTH);
134 
135         // write and SCSI response data
136         if (responseData != null) responseData.serialize(buffer, SENSE_LENGTH_FIELD_LENGTH + senseLength);
137 
138         if (allocationLength > 0 && buffer.capacity() > allocationLength) {
139             /*
140              * If the allocation length variable is valid and the response data segment is larger than the data-in
141              * buffer allocated by the initiator, limit the number of bytes returned.
142              */
143             buffer.position(0);
144             buffer.limit(allocationLength);
145             final ByteBuffer croppedBuffer = ByteBuffer.allocate(allocationLength);
146             croppedBuffer.put(buffer);
147             return croppedBuffer;
148         }
149 
150         if (LOGGER.isDebugEnabled()) LOGGER.debug("SCSI Response Data Segment:\n" + Debug.byteBufferToString(buffer));
151 
152         return buffer;
153     }
154 
155     /**
156      * The length of the {@link ScsiResponseDataSegment}, without considering any limitations due to
157      * {@link #allocationLength}.
158      * <p>
159      * The returned value is calculated just once, and then stored in {@link #uncroppedSize}. This summation is
160      * performed only if {@link #uncroppedSize} is negative.
161      * 
162      * @return length of the {@link ScsiResponseDataSegment}, without considering any limitations due to
163      *         {@link #allocationLength}
164      */
165     public int uncroppedSize () {
166         if (uncroppedSize < 0) {
167             if (senseData == null && responseData == null) return 0;
168             int size = SENSE_LENGTH_FIELD_LENGTH;
169             if (senseData != null) size += senseData.size();
170             if (responseData != null) size += responseData.size();
171             uncroppedSize = size;
172         }
173         return uncroppedSize;
174     }
175 
176     /**
177      * Indicates if any bytes have to be cropped during {@link #serialize()} calls. The method returns <code>true</code>
178      * if and only if the serialized length of this objects exceeds {@link #allocationLength}.
179      * 
180      * @return <code>true</code> if and only if the serialized length of this objects exceeds {@link #allocationLength}
181      */
182     public boolean getResidualOverflow () {
183         if (uncroppedSize() > allocationLength) return true;
184         return false;
185     }
186 
187     /**
188      * Returns the number of bytes that had to be cropped due to the total length of all contained fields exceeding the
189      * {@link #allocationLength}.
190      * 
191      * @return the total number of bytes that have to be cropped during serialization
192      * @see #serialize()
193      */
194     public int getResidualCount () {
195         if (getResidualOverflow()) return uncroppedSize() - allocationLength;
196         return 0;
197     }
198 }