View Javadoc

1   package org.jscsi.target.connection;
2   
3   
4   import java.io.IOException;
5   import java.nio.channels.ClosedChannelException;
6   import java.nio.channels.SocketChannel;
7   import java.security.DigestException;
8   
9   import org.jscsi.exception.InternetSCSIException;
10  import org.jscsi.parser.BasicHeaderSegment;
11  import org.jscsi.parser.InitiatorMessageParser;
12  import org.jscsi.parser.OperationCode;
13  import org.jscsi.parser.ProtocolDataUnit;
14  import org.jscsi.parser.ProtocolDataUnitFactory;
15  import org.jscsi.parser.TargetMessageParser;
16  import org.jscsi.parser.scsi.SCSICommandParser;
17  import org.jscsi.target.scsi.cdb.ScsiOperationCode;
18  import org.jscsi.target.settings.Settings;
19  import org.jscsi.target.settings.SettingsException;
20  import org.jscsi.target.settings.TextKeyword;
21  import org.jscsi.target.util.Debug;
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  
25  
26  /**
27   * Instances of this class are used by {@link Connection} objects for sending and receiving {@link ProtocolDataUnit}
28   * objects.
29   * 
30   * @author Andreas Ergenzinger, University of Konstanz
31   */
32  public class TargetSenderWorker {
33  
34      private static final Logger LOGGER = LoggerFactory.getLogger(TargetSenderWorker.class);
35  
36      /**
37       * The connection which uses this object for sending and receiving PDUs.
38       */
39      final private Connection connection;
40  
41      /**
42       * The session to which {@link #connection} belongs to.
43       */
44      private TargetSession session;
45  
46      /**
47       * Used for writing serialized PDUs to and reading serialized PDUs from.
48       */
49      final private SocketChannel socketChannel;
50  
51      /**
52       * Will be used to create {@link ProtocolDataUnit} objects from the byte stream read from the {@link #socketChannel}
53       * .
54       */
55      final private ProtocolDataUnitFactory protocolDataUnitFactory;
56  
57      /**
58       * If this is <code>true</code>, then the next PDU read from the {@link #socketChannel} will be the first PDU
59       * received in the {@link #session}.
60       * <p>
61       * Will be initializes to <code>true</code> if and only if the {@link #connection} is the leading connection of its
62       * session.
63       * <p>
64       * PDUs identified by this variable as the first PDU in a session will not have their counters (i.e. CmdSN and
65       * ExpStatSN) checked. Instead the values of these counters will be used to initialize the targets local copies of
66       * these counters that will be used to ensure that no PDUs have been lost in transit.
67       */
68      private boolean initialPdu;
69  
70      /**
71       * Creates a new {@link TargetSenderWorker} object.
72       * 
73       * @param connection the connection that will use this object for sending and receiving PDUs
74       * @param socketChannel used for sending and receiving serialized PDU to and from the target
75       */
76      public TargetSenderWorker (final Connection connection, final SocketChannel socketChannel) {
77          this.connection = connection;
78          this.socketChannel = socketChannel;
79          protocolDataUnitFactory = new ProtocolDataUnitFactory();
80          initialPdu = connection.isLeadingConnection();
81      }
82  
83      /**
84       * Sets the {@link #session} variable.
85       * <p>
86       * During the time this object is initialized, the {@link Connection#getSession()} method will return
87       * <code>null</code>. Therefore {@link #session} must be set manually once the {@link TargetSession} object has been
88       * created.
89       * 
90       * @param session the session of the {@link #connection}
91       */
92      void setSession (final TargetSession session) {
93          this.session = session;
94      }
95  
96      /**
97       * This method does all the necessary steps, which are needed when a connection should be closed.
98       * 
99       * @throws IOException if an I/O error occurs.
100      */
101     public final void close () throws IOException {
102         socketChannel.close();
103     }
104 
105     /**
106      * Receives a <code>ProtocolDataUnit</code> from the socket and appends it to the end of the receiving queue of this
107      * connection.
108      * 
109      * @return Queue with the resulting units
110      * @throws IOException if an I/O error occurs.
111      * @throws InternetSCSIException if any violation of the iSCSI-Standard emerge.
112      * @throws DigestException if a mismatch of the digest exists.
113      * @throws SettingsException
114      */
115     ProtocolDataUnit receiveFromWire () throws DigestException , InternetSCSIException , IOException , SettingsException {
116 
117         ProtocolDataUnit pdu;
118         if (initialPdu) {
119             /*
120              * The connection's ConnectionSettingsNegotiator has not been initialized, hence getSettings() would throw a
121              * NullPointerException. Initialize PDU with default values, i.e. no digests.
122              */
123             pdu = protocolDataUnitFactory.create(TextKeyword.NONE,// header
124                                                                   // digest
125                     TextKeyword.NONE);// data digest
126         } else {
127             // use negotiated or (now available) default settings
128             final Settings settings = connection.getSettings();
129             pdu = protocolDataUnitFactory.create(settings.getHeaderDigest(), settings.getDataDigest());
130         }
131 
132         try {
133             pdu.read(socketChannel);
134         } catch (ClosedChannelException e) {
135             throw new InternetSCSIException(e);
136         }
137 
138         if (LOGGER.isDebugEnabled()) LOGGER.debug("Receiving this PDU:\n" + pdu);
139 
140         // parse sequence counters
141         final BasicHeaderSegment bhs = pdu.getBasicHeaderSegment();
142         final InitiatorMessageParser parser = (InitiatorMessageParser) bhs.getParser();
143         // final int commandSequenceNumber = parser.getCommandSequenceNumber();
144         // final int expectedStatusSequenceNumber = parser.getExpectedStatusSequenceNumber();
145 
146         if (LOGGER.isDebugEnabled()) {
147             // sharrajesh
148             // Needed to debug, out of order receiving of StatusSN and ExpStatSN
149             if (bhs.getOpCode() == OperationCode.SCSI_COMMAND) {
150                 final SCSICommandParser scsiParser = (SCSICommandParser) bhs.getParser();
151                 ScsiOperationCode scsiOpCode = ScsiOperationCode.valueOf(scsiParser.getCDB().get(0));
152                 LOGGER.debug("scsiOpCode = " + scsiOpCode);
153                 LOGGER.debug("CDB bytes: \n" + Debug.byteBufferToString(scsiParser.getCDB()));
154             }
155             // LOGGER.debug("parser.expectedStatusSequenceNumber: " + expectedStatusSequenceNumber);
156             if (connection == null)
157                 LOGGER.debug("connection: null");
158             else if (connection.getStatusSequenceNumber() == null)
159                 LOGGER.debug("connection.getStatusSequenceNumber: null");
160             else
161                 LOGGER.debug("connection.getStatusSequenceNumber: " + connection.getStatusSequenceNumber().getValue());
162         }
163 
164         // if this is the first PDU in the leading connection, then
165         // initialize the session's ExpectedCommandSequenceNumber
166         if (initialPdu) {
167             initialPdu = false;
168             // PDU is immediate Login PDU, checked in Target.main(),
169             // ExpCmdSN of this PDU will be used to initialize the
170             // respective session and connection parameters (sequence numbers)
171             // see TargetSession and TargetConnection initialization in
172             // Target.main()
173         } else {
174             // check sequence counters
175             // if (session.getMaximumCommandSequenceNumber().lessThan(commandSequenceNumber))
176             // throw new InternetSCSIException("received CmdSN > local MaxCmdSN");
177 
178             // verified, is working with Windows 8 initiator
179             // if (!connection.getStatusSequenceNumber().equals(expectedStatusSequenceNumber)
180             // && expectedStatusSequenceNumber != 0)// required by MS iSCSI
181             // // initiator DATA-OUT
182             // // PDU sequence
183             // throw new InternetSCSIException("received ExpStatusSN != local StatusSN + 1");
184         }
185 
186         // increment CmdSN if not immediate PDU (or Data-Out PDU)
187         try {
188             if (parser.incrementSequenceNumber()) session.getExpectedCommandSequenceNumber().increment();
189         } catch (NullPointerException exc) {
190 
191         }
192 
193         return pdu;
194     }
195 
196     /**
197      * Sends the given <code>ProtocolDataUnit</code> instance over the socket to the connected iSCSI Target.
198      * 
199      * @param pdu The <code>ProtocolDataUnit</code> instances to send.
200      * @throws InternetSCSIException if any violation of the iSCSI-Standard emerge.
201      * @throws IOException if an I/O error occurs.
202      * @throws InterruptedException if another caller interrupted the current caller before or while the current caller
203      *             was waiting for a notification. The interrupted status of the current caller is cleared when this
204      *             exception is thrown.
205      */
206 
207     final void sendOverWire (final ProtocolDataUnit pdu) throws InternetSCSIException , IOException , InterruptedException {
208 
209         // set sequence counters
210         final TargetMessageParser parser = (TargetMessageParser) pdu.getBasicHeaderSegment().getParser();
211         parser.setExpectedCommandSequenceNumber(session.getExpectedCommandSequenceNumber().getValue());
212         parser.setMaximumCommandSequenceNumber(session.getMaximumCommandSequenceNumber().getValue());
213         final boolean incrementSequenceNumber = parser.incrementSequenceNumber();
214         if (incrementSequenceNumber) // set StatSN only if field is not reserved
215         parser.setStatusSequenceNumber(connection.getStatusSequenceNumber().getValue());
216 
217         if (LOGGER.isDebugEnabled()) LOGGER.debug("Sending this PDU:\n" + pdu);
218 
219         // send pdu
220         pdu.write(socketChannel);
221 
222         // increment StatusSN if this was a Response PDU (with status)
223         // or if special cases apply
224         if (incrementSequenceNumber) connection.getStatusSequenceNumber().increment();
225 
226     }
227 }