| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| ConnectionSettingsNegotiator |
|
| 2.4;2.4 |
| 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 | 0 | super();// initializes entries |
| 67 | 0 | this.sessionSettingsNegotiator = sessionSettingsNegotiator; |
| 68 | // initialize settings | |
| 69 | 0 | updateSettingsBuilderComponent(); |
| 70 | 0 | updateSettings(); |
| 71 | 0 | } |
| 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 | 0 | final boolean hasLock = sessionSettingsNegotiator.lock(); |
| 85 | 0 | if (hasLock) { |
| 86 | // back up entries with connection and with session scope | |
| 87 | 0 | backUpEntries(); |
| 88 | 0 | sessionSettingsNegotiator.backUpEntries(); |
| 89 | } | |
| 90 | 0 | 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 | 0 | commitOrRollBackChanges(commitChanges); |
| 107 | // commit or roll back session-wide entries | |
| 108 | 0 | sessionSettingsNegotiator.commitOrRollBackChanges(commitChanges); |
| 109 | // update settings | |
| 110 | 0 | updateSettingsBuilderComponent(); |
| 111 | 0 | sessionSettingsNegotiator.updateSettingsBuilderComponent(); |
| 112 | 0 | updateSettings(); |
| 113 | // allow other Threads to negotiate | |
| 114 | 0 | sessionSettingsNegotiator.unlock(); |
| 115 | 0 | } |
| 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 | 0 | final List<String> keys = new Vector<String>(); |
| 135 | 0 | final List<String> values = new Vector<String>(); |
| 136 | 0 | for (String keyValuePair : requestKeyValuePairs) { |
| 137 | 0 | final String[] split = TextParameter.splitKeyValuePair(keyValuePair); |
| 138 | 0 | if (split == null) { |
| 139 | 0 | System.err.println("malformatted key=value pair: " + keyValuePair); |
| 140 | 0 | return false; |
| 141 | } | |
| 142 | 0 | keys.add(split[0]); |
| 143 | 0 | values.add(split[1]); |
| 144 | 0 | } |
| 145 | ||
| 146 | // get respective entry (declared by initiator, or negotiated) and | |
| 147 | // let it process the key=value pair | |
| 148 | Entry entry; | |
| 149 | 0 | boolean everythingOkay = true; |
| 150 | 0 | while (keys.size() > 0) { |
| 151 | 0 | entry = getEntry(keys.get(0)); |
| 152 | // respond to unknown keys | |
| 153 | 0 | if (entry == null) { |
| 154 | 0 | 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 | 0 | everythingOkay &= entry.negotiate(target, loginStage, leadingConnection, initialPdu, keys.get(0), values.get(0), responseKeyValuePairs); |
| 158 | } | |
| 159 | // remove processed key and values | |
| 160 | 0 | keys.remove(0); |
| 161 | 0 | values.remove(0); |
| 162 | } | |
| 163 | ||
| 164 | // check if initiator has sent all mandatory parameters | |
| 165 | // and append target declarations | |
| 166 | 0 | if (initialPdu) { |
| 167 | // initiator must provide the InitiatorName in the first Login PDU | |
| 168 | // (InitiatorAlias is optional) | |
| 169 | 0 | 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 | 0 | boolean normalSession = TextKeyword.NORMAL.equals(((StringEntry) getEntry(TextKeyword.SESSION_TYPE)).getStringValue()); |
| 175 | 0 | if (normalSession) { |
| 176 | // check if proposed TargetName is correct | |
| 177 | 0 | final StringEntry targetNameEntry = (StringEntry) getEntry(TextKeyword.TARGET_NAME); |
| 178 | 0 | final String targetName = targetNameEntry.getStringValue(); |
| 179 | 0 | if (targetName == null || // not declared |
| 180 | !target.isValidTargetName(targetName)) // wrong name | |
| 181 | 0 | everythingOkay = false; |
| 182 | ||
| 183 | // send TargetAlias | |
| 184 | 0 | String targetAlias = null; |
| 185 | 0 | if (everythingOkay) targetAlias = target.getTarget(targetName).getTargetAlias();// might |
| 186 | // be | |
| 187 | // undefined | |
| 188 | 0 | if (targetAlias != null) { |
| 189 | 0 | responseKeyValuePairs.add(TextParameter.toKeyValuePair(TextKeyword.TARGET_ALIAS, targetAlias)); |
| 190 | } | |
| 191 | ||
| 192 | // send TargetPortalGroupTag | |
| 193 | 0 | responseKeyValuePairs.add(TextParameter.toKeyValuePair(TextKeyword.TARGET_PORTAL_GROUP_TAG, Integer.valueOf(target.getConfig().getTargetPortalGroupTag()).toString())); |
| 194 | } | |
| 195 | } | |
| 196 | ||
| 197 | 0 | return everythingOkay; |
| 198 | } | |
| 199 | ||
| 200 | @Override | |
| 201 | public boolean checkConstraints () { | |
| 202 | 0 | 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 | 0 | Entry entry = null; |
| 218 | // check connection-only entries | |
| 219 | 0 | entry = getEntry(key, entries); |
| 220 | 0 | if (entry == null) // keep looking in session-wide entries |
| 221 | 0 | entry = sessionSettingsNegotiator.getEntry(key); |
| 222 | 0 | 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 | 0 | if (sessionSettingsNegotiator.getCurrentSettingsId() > settings.getSettingsId()) updateSettings(); |
| 234 | 0 | 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 | 0 | settings = new Settings(connectionSettingBuilderComponent, sessionSettingsNegotiator.getSessionSettingsBuilderComponent()); |
| 242 | 0 | } |
| 243 | ||
| 244 | @Override | |
| 245 | protected void initializeEntries () { | |
| 246 | ||
| 247 | /* | |
| 248 | * Determines type and use of the data digest. | |
| 249 | */ | |
| 250 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | } |
| 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 | 0 | connectionSettingBuilderComponent = new ConnectionSettingsBuilderComponent(entries); |
| 347 | 0 | } |
| 348 | } |