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 }