1 package org.jscsi.target.settings.entry; 2 3 4 import java.util.Collection; 5 6 import javax.naming.OperationNotSupportedException; 7 8 import org.jscsi.parser.ProtocolDataUnit; 9 import org.jscsi.parser.login.LoginStage; 10 import org.jscsi.target.TargetServer; 11 import org.jscsi.target.settings.KeySet; 12 import org.jscsi.target.settings.NegotiationStatus; 13 import org.jscsi.target.settings.NegotiationType; 14 import org.jscsi.target.settings.SettingsNegotiator; 15 import org.jscsi.target.settings.TextKeyword; 16 import org.jscsi.target.settings.TextParameter; 17 import org.slf4j.Logger; 18 import org.slf4j.LoggerFactory; 19 20 21 /** 22 * {@link Entry} objects are used by instances {@link SettingsNegotiator} during text negotiation of connection and 23 * session parameter. For all parameters that are either declared by the iSCSI initiator or negotiated between the 24 * initiator and the target a separate {@link Entry} takes care of processing the respective <i>key=value</i> pair and 25 * returning the negotiated value, if appropriate. 26 * <p> 27 * For brevity, the term "negotiated" will be used in the following in a way that can either mean 28 * "declared or negotiated", unless the distinction is evident by context. 29 * 30 * @author Andreas Ergenzinger, University of Konstanz 31 */ 32 public abstract class Entry { 33 34 private static final Logger LOGGER = LoggerFactory.getLogger(Entry.class); 35 36 /** 37 * A {@link KeySet} containing all keys that can be used for negotiating this {@link Entry}'s value. 38 */ 39 protected final KeySet keySet; 40 41 /** 42 * Specifies if the {@link Entry}'s parameter is declared or negotiated. 43 */ 44 protected final NegotiationType negotiationType; 45 46 /** 47 * Determines during which stages this {@link Entry}'s parameters may be negotiated. 48 */ 49 protected final Use use; 50 51 /** 52 * This variable specifies the progress and necessity of negotiating the parameter managed by this {@link Entry}. 53 */ 54 protected NegotiationStatus negotiationStatus; 55 56 /** 57 * The currently valid value or <code>null</code>. 58 */ 59 protected Object value; 60 61 /** 62 * This variable is used to detect illegal attempts to renegotiate a previously negotiated or declared text 63 * parameter. 64 * <p> 65 * This variable will be set back to <code>false</code> after each negotiation task (login phase, or text parameter 66 * negotiation stage). Renegotiation accross stages/tasks can be prevented by initializing the {@link #use} variable 67 * accordingly. 68 * 69 * @see #resetAlreadyNegotiated() 70 */ 71 protected boolean alreadyNegotiated = false; 72 73 /** 74 * Abstract constructor. 75 * 76 * @param keySet contains all relevant keys 77 * @param negotiationType declared or negotiated 78 * @param use determines under which circumstances the parameter may be negotiated 79 * @param negotiationStatus indicates whether there is a default value or if the parameter must be negotiated 80 * @param defaultValue the default value or <code>null</code> 81 */ 82 public Entry (final KeySet keySet, final NegotiationType negotiationType, final Use use, final NegotiationStatus negotiationStatus, Object defaultValue) { 83 this.keySet = keySet; 84 this.negotiationType = negotiationType; 85 this.use = use; 86 this.negotiationStatus = negotiationStatus; 87 this.value = defaultValue; 88 } 89 90 /** 91 * Logs an error message containing all {@link #keySet} keys as well as the passed {@link String} parameter and 92 * indicates an unsuccessful negotiation by setting {@link #negotiationStatus} to {@link NegotiationStatus#REJECTED} 93 * . 94 * 95 * @param logMessage 96 */ 97 private void fail (final String logMessage) { 98 LOGGER.error("negotiation error " + keySet + ": " + logMessage); 99 negotiationStatus = NegotiationStatus.REJECTED; 100 } 101 102 /** 103 * Parses the passed {@link String} parameter and returns a sub-class-specific {@link Object} which represents the 104 * the specified <i>value</i> part a <i>key=value</i> pair. 105 * 106 * @param values the <i>value</i> part of a <i>key=value</i> pair 107 * @return sub-class-specific {@link Object} or <code>null</code> if the parameter violated the expected format 108 */ 109 protected abstract Object parseOffer (TargetServer target, String values); 110 111 /** 112 * This method is used for negotiating or declaring the {@link Entry}'s parameter. 113 * 114 * @param loginStage specifying the current stage or phase of the connection whose parameters are to be negotiated 115 * @param leadingConnection <code>true</code> if the connection is the first connection in its session, 116 * <code>false</code> if not 117 * @param initialPdu <code>true</code> if the <i>key=value</i> pair parameters have been sent in the first login 118 * {@link ProtocolDataUnit} from the initiator, <code>false</code> if thy have not 119 * @param key the <i>key</i> part from the received <i>key=value</i> pair 120 * @param values the <i>value</i> part from the received <i>key=value</i> pair 121 * @param responseKeyValuePairs where the reply <i>key=value</i> pair will be added to if necessary 122 * @return <code>true</code> if everything went fine, <code>false</code> if errors occured 123 */ 124 public final boolean negotiate (TargetServer target, final LoginStage loginStage, final boolean leadingConnection, final boolean initialPdu, final String key, final String values, final Collection<String> responseKeyValuePairs) { 125 126 // (re)check key (just in case), this should have been checked before 127 // calling this method 128 if (!matchKey(key)) { 129 fail("\"" + key + "\" does not match key in" + keySet); 130 return false; 131 } 132 133 // prevent renegotiation and remember this negotiation 134 if (alreadyNegotiated) { 135 fail("illegal renegotiation"); 136 return false; 137 } 138 alreadyNegotiated = true; 139 140 // check use code 141 if (!use.checkUse(loginStage, leadingConnection, initialPdu)) { 142 fail("wrong use: " + use + ", " + loginStage + ", " + leadingConnection + ", " + initialPdu); 143 return false; 144 } 145 146 // transform values to appropriate type 147 final Object offer = parseOffer(target, values); 148 149 if (offer == null) { 150 fail("value format error: " + values); 151 return false; 152 } 153 154 // check if values are in the protocol-conform range/set of values 155 if (!inProtocolValueRange(offer)) { 156 fail("illegal values offered: " + values); 157 return false; 158 } 159 160 // *** declare *** 161 if (negotiationType == NegotiationType.DECLARED) { 162 // save received value ... 163 processDeclaration(offer); 164 // ... and accept silently 165 negotiationStatus = NegotiationStatus.ACCEPTED; 166 return true; 167 } 168 169 // *** negotiate *** 170 if (negotiationType == NegotiationType.NEGOTIATED) { 171 172 String negotiatedValue;// will be returned as value part 173 174 if (negotiationStatus == NegotiationStatus.IRRELEVANT) 175 negotiatedValue = TextKeyword.IRRELEVANT; 176 else 177 negotiatedValue = processNegotiation(offer); 178 179 String reply; 180 // reply, remember outcome, log, and return 181 if (negotiatedValue == null) {// no commonly supported values 182 reply = TextParameter.toKeyValuePair(key, TextKeyword.REJECT); 183 responseKeyValuePairs.add(reply); 184 fail("rejected value(s): " + values); 185 return false; 186 }// else 187 reply = TextParameter.toKeyValuePair(key, negotiatedValue); 188 responseKeyValuePairs.add(reply); 189 return true; 190 } 191 192 // we should not be here 193 fail("initialization error: negotiationType == null"); 194 return false; 195 } 196 197 /** 198 * Sets {@link #alreadyNegotiated} back to <code>false</code>. 199 * <p> 200 * This method must be used at the end of each negotiation task, i.e. at the end of the login phase and the FFP text 201 * negotiation stage. 202 */ 203 public void resetAlreadyNegotiated () { 204 alreadyNegotiated = false; 205 } 206 207 /** 208 * Returns the negotiated (or default) value as a {@link Boolean}. 209 * 210 * @return the negotiated (or default) value as a {@link Boolean} 211 * @throws OperationNotSupportedException if {@link #value} is not of the boolean type 212 */ 213 public Boolean getBooleanValue () throws OperationNotSupportedException { 214 throw new OperationNotSupportedException(); 215 } 216 217 /** 218 * Returns the negotiated (or default) value as an {@link Integer}. 219 * 220 * @return the negotiated (or default) value as an {@link Integer} 221 * @throws OperationNotSupportedException if {@link #value} is not of the integer type 222 */ 223 public Integer getIntegerValue () throws OperationNotSupportedException { 224 throw new OperationNotSupportedException(); 225 } 226 227 /** 228 * Returns the negotiated (or default) value as a {@link String}. 229 * 230 * @return the negotiated (or default) value as a {@link String} 231 * @throws OperationNotSupportedException if {@link #value} is not a {@link String} 232 */ 233 public String getStringValue () throws OperationNotSupportedException { 234 throw new OperationNotSupportedException(); 235 } 236 237 /** 238 * Returns <code>true</code> if one of the keys of {@link #keySet} equals the parameter and <code>false</code> if 239 * there is not match. 240 * 241 * @param key the key to compare to the {@link #keySet} keys 242 * @return <code>true</code> if one of the keys of {@link #keySet} equals the parameter and <code>false</code> if 243 * not 244 */ 245 public final boolean matchKey (final String key) { 246 return keySet.matchKey(key); 247 } 248 249 /** 250 * This method is used for checking if a sub-class-specific {@link Object}, representing a single, a range, or a 251 * list of values sent by the initiator, is illegal, according to the iSCSI standard. 252 * 253 * @param values a sub-class-specific {@link Object}, representing a single, a range, or a list of values sent by 254 * the initiator 255 * @return <code>false</code> if the iSCSI standard has been violated, <code>true</code> if not 256 */ 257 protected abstract boolean inProtocolValueRange (Object values); 258 259 /** 260 * Receives a sub-class-specific {@link Object}, representing a legal parameter value declared by the initiator and 261 * accepts it as the new {@link #value}. 262 * 263 * @param values sub-class-specific representation of a single <i>value</i> declared by the initiator 264 */ 265 protected abstract void processDeclaration (Object values); 266 267 // returns null if reply is to be key=Reject 268 /** 269 * Receives a sub-class-specific {@link Object}, representing a list, a range, or a single legal parameter value 270 * offered by the initiator and tries to select a value from that offer. If none of the offered values is supported 271 * by the jSCSI Target, <code>null</code> is returned, otherwise the selection is accepted as the new {@link #value} 272 * and returned as a {@link String}. {@link #value}. 273 * 274 * @param values a sub-class-specific {@link Object}, representing a list, a range, or a single legal parameter 275 * value offered by the initiator 276 * @return the final, negotiated value or <code>null</code>, if the initiator's offer does not overlap with the 277 * values supported by the jSCSI Target 278 */ 279 protected abstract String processNegotiation (Object values); 280 281 /** 282 * Returns {@link #negotiationStatus}. 283 * 284 * @return {@link #negotiationStatus} 285 */ 286 public final NegotiationStatus getNegotiationStatus () { 287 return negotiationStatus; 288 } 289 290 /** 291 * Returns an exact copy of this {@link Entry}. 292 * 293 * @return a copy of this {@link Entry}. 294 */ 295 public abstract Entry copy (); 296 297 /** 298 * Returns <code>true</code> if {@link #negotiationStatus} is {@link NegotiationStatus#ACCEPTED} and 299 * <code>false</code> if it is not. 300 * 301 * @return <code>true</code> if {@link #negotiationStatus} is {@link NegotiationStatus#ACCEPTED}, <code>false</code> 302 * if not 303 */ 304 public boolean checkAccepted () { 305 return negotiationStatus == NegotiationStatus.ACCEPTED; 306 } 307 }