View Javadoc

1   package org.jscsi.target.settings;
2   
3   
4   import java.util.List;
5   import java.util.Vector;
6   
7   import org.jscsi.parser.ProtocolDataUnit;
8   import org.jscsi.parser.login.LoginStage;
9   import org.jscsi.target.TargetServer;
10  import org.jscsi.target.connection.TargetSession;
11  import org.jscsi.target.settings.entry.BooleanEntry;
12  import org.jscsi.target.settings.entry.Entry;
13  import org.jscsi.target.settings.entry.NumericalEntry;
14  import org.jscsi.target.settings.entry.NumericalRangeEntry;
15  import org.jscsi.target.settings.entry.StringEntry;
16  import org.jscsi.target.settings.entry.Use;
17  
18  
19  /**
20   * In addition to offering all methods of their {@link SettingsNegotiator} parent class,
21   * {@link ConnectionSettingsNegotiator} instances provide all methods necessary for text parameter negotiation and for
22   * creating {@link Settings} objects, which allow access to the current parameters.
23   * <p>
24   * Each instance of this class belongs to one <code>Connection</code>, for which it manages all connection-specific
25   * parameters. A similar association exists between the connection's enclosing {@link TargetSession} and the
26   * {@link #sessionSettingsNegotiator} member variable.
27   * <p>
28   * A negotiation sequence consists of the following steps, performed in the given order:
29   * <p>
30   * <ol>
31   * <li>Call {@link #beginNegotiation()} until it returns <code>true</code>.</li>
32   * <li>One or, if the initiator is using multiple PDU sequences, multiple calls of
33   * {@link #negotiate(TargetServer, LoginStage, boolean, boolean, List, List)}. The method will return <code>false</code>
34   * if there was a problem.</li>
35   * <li>Call {@link #checkConstraints()} to check more complex requirements. The method will return <code>false</code> if
36   * there was a problem.</li>
37   * <li>Call {@link #finishNegotiation(boolean)} with the appropriate parameter. This step is mandatory.</li>
38   * </ol>
39   * 
40   * @author Andreas Ergenzinger, University of Konstanz
41   */
42  public final class ConnectionSettingsNegotiator extends SettingsNegotiator {
43  
44      /**
45       * The {@link SessionSettingsNegotiator} maintaining all {@link Entry} objects for session-wide parameters.
46       */
47      private final SessionSettingsNegotiator sessionSettingsNegotiator;
48  
49      /**
50       * A current snapshot of all connection-specific parameters.
51       */
52      private ConnectionSettingsBuilderComponent connectionSettingBuilderComponent;
53  
54      /**
55       * A current snapshot of all parameters, both connection-specific and session-wide.
56       */
57      private Settings settings;
58  
59      /**
60       * The {@link ConnectionSettingsNegotiator} constructor.
61       * 
62       * @param sessionSettingsNegotiator the {@link SessionSettingsNegotiator} maintaining all {@link Entry} objects for
63       *            session-wide parameters
64       */
65      public ConnectionSettingsNegotiator (final SessionSettingsNegotiator sessionSettingsNegotiator) {
66          super();// initializes entries
67          this.sessionSettingsNegotiator = sessionSettingsNegotiator;
68          // initialize settings
69          updateSettingsBuilderComponent();
70          updateSettings();
71      }
72  
73      /**
74       * This method must be called at the beginning of a parameter negotiation sequence.
75       * <p>
76       * This method will block until no other {@link ConnectionSettingsNegotiator} is negotiating and will return
77       * <code>true</code> if negotiations may proceed, or <code>false</code>, if the request for the permission to
78       * negotiate was denied.
79       * 
80       * @return <code>true</code> if and only if the caller is allowed to negotiate parameters
81       * @see #finishNegotiation(boolean)
82       */
83      public boolean beginNegotiation () {
84          final boolean hasLock = sessionSettingsNegotiator.lock();
85          if (hasLock) {
86              // back up entries with connection and with session scope
87              backUpEntries();
88              sessionSettingsNegotiator.backUpEntries();
89          }
90          return hasLock;
91      }
92  
93      /**
94       * This method must be called at the end of a negotiation sequence. If there was a problem during negotiations, all
95       * changes to the managed {@link Entry} objects must be discarded by setting the <i>commitChanges</i> parameter to
96       * <code>false</code>. If it is <code>true</code>, then the parameter changes will be incorporated into the updated
97       * {@link #settings}.
98       * <p>
99       * Calling this method should be ensured by a <code>try ... catch ...
100      * finally</code> block.
101      * 
102      * @param commitChanges <code>true</code> if and only if the negotiated parameter changes are to be committed
103      */
104     public void finishNegotiation (final boolean commitChanges) {
105         // commit or roll back connection-specific entries
106         commitOrRollBackChanges(commitChanges);
107         // commit or roll back session-wide entries
108         sessionSettingsNegotiator.commitOrRollBackChanges(commitChanges);
109         // update settings
110         updateSettingsBuilderComponent();
111         sessionSettingsNegotiator.updateSettingsBuilderComponent();
112         updateSettings();
113         // allow other Threads to negotiate
114         sessionSettingsNegotiator.unlock();
115     }
116 
117     /**
118      * Processes one or more <i>key-value</i> pairs sent by the initiator, formulates response <i>key-value</i> pairs
119      * and changes the involved {@link Entry} instances accordingly.
120      * 
121      * @param loginStage specifies the current stage or phase
122      * @param leadingConnection <code>true</code> if and only if the connection is the first connection of the enclosing
123      *            session
124      * @param initialPdu <code>true</code> if and only if the <i>key-value</i> pairs were sent in the first
125      *            {@link ProtocolDataUnit} received on this connection
126      * @param requestKeyValuePairs contains the <i>key-value</i> pairs from the initiator; processed elements will be
127      *            removed
128      * @param responseKeyValuePairs will contain the <i>key-value</i> pairs from the jSCSI target
129      * @return <code>true</code> if everything went fine and <code>false</code> if there was an irreconcilable problem
130      */
131     public boolean negotiate (TargetServer target, final LoginStage loginStage, final boolean leadingConnection, final boolean initialPdu, final List<String> requestKeyValuePairs, final List<String> responseKeyValuePairs) {
132 
133         // split up key=value pairs from requester
134         final List<String> keys = new Vector<String>();
135         final List<String> values = new Vector<String>();
136         for (String keyValuePair : requestKeyValuePairs) {
137             final String[] split = TextParameter.splitKeyValuePair(keyValuePair);
138             if (split == null) {
139                 System.err.println("malformatted key=value pair: " + keyValuePair);
140                 return false;
141             }
142             keys.add(split[0]);
143             values.add(split[1]);
144         }
145 
146         // get respective entry (declared by initiator, or negotiated) and
147         // let it process the key=value pair
148         Entry entry;
149         boolean everythingOkay = true;
150         while (keys.size() > 0) {
151             entry = getEntry(keys.get(0));
152             // respond to unknown keys
153             if (entry == null) {
154                 responseKeyValuePairs.add(TextParameter.toKeyValuePair(keys.get(0), TextKeyword.NOT_UNDERSTOOD));
155             } else {// appropriate entry was found
156                 // process key=value pair and remember if there is any trouble
157                 everythingOkay &= entry.negotiate(target, loginStage, leadingConnection, initialPdu, keys.get(0), values.get(0), responseKeyValuePairs);
158             }
159             // remove processed key and values
160             keys.remove(0);
161             values.remove(0);
162         }
163 
164         // check if initiator has sent all mandatory parameters
165         // and append target declarations
166         if (initialPdu) {
167             // initiator must provide the InitiatorName in the first Login PDU
168             // (InitiatorAlias is optional)
169             everythingOkay &= getEntry(TextKeyword.INITIATOR_NAME).checkAccepted();
170 
171             // in a normal session the initiator must declare TargetName
172             // the target must reply with TargetPortalGroupTag and should
173             // append TargetAlias
174             boolean normalSession = TextKeyword.NORMAL.equals(((StringEntry) getEntry(TextKeyword.SESSION_TYPE)).getStringValue());
175             if (normalSession) {
176                 // check if proposed TargetName is correct
177                 final StringEntry targetNameEntry = (StringEntry) getEntry(TextKeyword.TARGET_NAME);
178                 final String targetName = targetNameEntry.getStringValue();
179                 if (targetName == null || // not declared
180                 !target.isValidTargetName(targetName)) // wrong name
181                 everythingOkay = false;
182 
183                 // send TargetAlias
184                 String targetAlias = null;
185                 if (everythingOkay) targetAlias = target.getTarget(targetName).getTargetAlias();// might
186                 // be
187                 // undefined
188                 if (targetAlias != null) {
189                     responseKeyValuePairs.add(TextParameter.toKeyValuePair(TextKeyword.TARGET_ALIAS, targetAlias));
190                 }
191 
192                 // send TargetPortalGroupTag
193                 responseKeyValuePairs.add(TextParameter.toKeyValuePair(TextKeyword.TARGET_PORTAL_GROUP_TAG, Integer.valueOf(target.getConfig().getTargetPortalGroupTag()).toString()));
194             }
195         }
196 
197         return everythingOkay;
198     }
199 
200     @Override
201     public boolean checkConstraints () {
202         return sessionSettingsNegotiator.checkConstraints();
203         /*
204          * in multi-connection session, the InitiatorName of follow-up connections should be checked against the
205          * InitiatorName of the leading connection
206          */
207     }
208 
209     /**
210      * Returns the {@link Entry} responsible for negotiating the specified <i>key</i> identifying either a session-wide
211      * or connection-specific parameter, or <code>null</code> if no such {@link Entry} can be found.
212      * 
213      * @param key identifies an {@link Entry}
214      * @return the requested {@link Entry} or <code>null</code>
215      */
216     private Entry getEntry (final String key) {
217         Entry entry = null;
218         // check connection-only entries
219         entry = getEntry(key, entries);
220         if (entry == null) // keep looking in session-wide entries
221         entry = sessionSettingsNegotiator.getEntry(key);
222         return entry;
223     }
224 
225     /**
226      * This method checks if any parameter changes have been committed since the last call and, if so, creates a new
227      * {@link Settings} object reflecting the current parameters.
228      * 
229      * @return The current parameters
230      */
231     public Settings getSettings () {
232         // check if settings are up to date
233         if (sessionSettingsNegotiator.getCurrentSettingsId() > settings.getSettingsId()) updateSettings();
234         return settings;
235     }
236 
237     /**
238      * Updates {@link #settings} by replacing it with a more current copy of the managed parameters.
239      */
240     private synchronized void updateSettings () {
241         settings = new Settings(connectionSettingBuilderComponent, sessionSettingsNegotiator.getSessionSettingsBuilderComponent());
242     }
243 
244     @Override
245     protected void initializeEntries () {
246 
247         /*
248          * Determines type and use of the data digest.
249          */
250         entries.add(new StringEntry(new KeySet(TextKeyword.DATA_DIGEST),// keySet
251         NegotiationType.NEGOTIATED,// negotiationType
252         Use.LOPNS,// use
253         NegotiationStatus.DEFAULT,// negotiationStatus
254         new String[] { TextKeyword.NONE },// supportedValues,
255         TextKeyword.NONE));// defaultValue
256 
257         /*
258          * Determines type and use of the header digest.
259          */
260         entries.add(new StringEntry(new KeySet(TextKeyword.HEADER_DIGEST),// keySet
261         NegotiationType.NEGOTIATED,// negotiationType
262         Use.LOPNS,// use
263         NegotiationStatus.DEFAULT,// negotiationStatus
264         new String[] { TextKeyword.NONE },// supportedValues,
265         TextKeyword.NONE));// defaultValue
266 
267         /*
268          * Turns the target-to-initiator markers on or off.
269          */
270         entries.add(new BooleanEntry(new KeySet(TextKeyword.IF_MARKER),// keySet
271         Use.LOPNS,// use
272         NegotiationStatus.DEFAULT,// negotiationStatus
273         false,// negotiationValue
274         BooleanResultFunction.AND,// resultFunction
275         false));// defaultValue
276 
277         /*
278          * Interval value (in 4-byte words) for target-to-initiator markers. The interval is measured from the end of
279          * one marker to the beginning of the next one. The offer can have only a range; the response can have only a
280          * single value (picked from the offered range) or Reject. Will always be Irrelevant.
281          */
282         entries.add(new NumericalRangeEntry(new KeySet(TextKeyword.IF_MARK_INT),// keySet
283         Use.LOPNS,// use
284         NegotiationStatus.IRRELEVANT,// negotiationStatus
285         2048,// negotiationValue
286         NumericalValueRange.create(1, 65535),// protocolValueRange
287         2048));// defaultValue
288 
289         /*
290          * The maximum amount of data that either the initiator or the target can receive in any iSCSI PDU. Zero (don't
291          * care) can be used. I&T can specify (declare) the Max they can receive. This is a connection- and direction-
292          * specific parameter. The actual value used by the target will be min(This value, MaxBurstLength) for data-in
293          * and solicited data-out data. Min(This value, FirstBurstLength) for unsolicited data.
294          */
295         entries.add(new NumericalEntry(new KeySet(TextKeyword.MAX_RECV_DATA_SEGMENT_LENGTH),// keySet
296         NegotiationType.DECLARED,// negotiationType
297         Use.LOPNS_AND_FFP,// use
298         NegotiationStatus.DEFAULT,// negotiationStatus
299         8192,// negotiationValue (default value, will be used if I sends
300              // 0)
301         NumericalValueRange.create(512, 16777215),// protocolValueRange
302         // 512 to 2^24 - 1
303         NumericalResultFunction.MIN,// resultFunction
304         8192,// defaultValue, 8K
305         true));// zeroMeansDontCare
306 
307         /*
308          * Turns the initiator-to-target markers on or off.
309          */
310         entries.add(new BooleanEntry(new KeySet(TextKeyword.OF_MARKER),// keySet
311         Use.LOPNS,// use
312         NegotiationStatus.DEFAULT,// negotiationStatus
313         false,// negotiationValue
314         BooleanResultFunction.AND,// resultFunction
315         false));// defaultValue
316 
317         /*
318          * Interval value (in 4-byte words) for initiator-to-target markers. The interval is measured from the end of
319          * one marker to the beginning of the next one. The offer can have only a range; the response can have only a
320          * single value (picked from the offered range) or Reject. Will always be Irrelevant.
321          */
322         entries.add(new NumericalRangeEntry(new KeySet(TextKeyword.OF_MARK_INT),// keySet
323         Use.LOPNS,// use
324         NegotiationStatus.IRRELEVANT,// negotiationStatus
325         2048,// negotiationValue
326         NumericalValueRange.create(1, 65535),// protocolValueRange
327         2048));// defaultValue
328 
329         /*
330          * This entry is not used for Settings initialization, TargetName has a session-wide scope. This entry
331          * intercepts the TargetName parameter the initiator has to declare at the beginning of normal sessions.
332          */
333         entries.add(new StringEntry(new KeySet(TextKeyword.TARGET_NAME),// keySet
334         NegotiationType.DECLARED,// negotiationType
335         Use.INITIAL,// use
336         NegotiationStatus.NOT_NEGOTIATED,// negotiationStatus
337         null,// supportedValues, anything goes
338         null));// defaultValue
339     }
340 
341     /**
342      * Updates {@link ConnectionSettingsBuilderComponent} with the currently valid parameters retrieved from the
343      * elements of {@link SettingsNegotiator#entries}.
344      */
345     protected void updateSettingsBuilderComponent () {
346         connectionSettingBuilderComponent = new ConnectionSettingsBuilderComponent(entries);
347     }
348 }