package org.jscsi.target.settings.entry; import java.util.Collection; import javax.naming.OperationNotSupportedException; import org.jscsi.parser.ProtocolDataUnit; import org.jscsi.parser.login.LoginStage; import org.jscsi.target.TargetServer; import org.jscsi.target.settings.KeySet; import org.jscsi.target.settings.NegotiationStatus; import org.jscsi.target.settings.NegotiationType; import org.jscsi.target.settings.SettingsNegotiator; import org.jscsi.target.settings.TextKeyword; import org.jscsi.target.settings.TextParameter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * {@link Entry} objects are used by instances {@link SettingsNegotiator} during * text negotiation of connection and session parameter. For all parameters that * are either declared by the iSCSI initiator or negotiated between the * initiator and the target a separate {@link Entry} takes care of processing * the respective <i>key=value</i> pair and returning the negotiated value, if * appropriate. * <p> * For brevity, the term "negotiated" will be used in the following in a way that can either mean * "declared or negotiated", unless the distinction is evident by context. * * @author Andreas Ergenzinger, University of Konstanz */ public abstract class Entry { private static final Logger LOGGER = LoggerFactory.getLogger(Entry.class); /** * A {@link KeySet} containing all keys that can be used for negotiating * this {@link Entry}'s value. */ protected final KeySet keySet; /** * Specifies if the {@link Entry}'s parameter is declared or negotiated. */ protected final NegotiationType negotiationType; /** * Determines during which stages this {@link Entry}'s parameters may be * negotiated. */ protected final Use use; /** * This variable specifies the progress and necessity of negotiating the * parameter managed by this {@link Entry}. */ protected NegotiationStatus negotiationStatus; /** * The currently valid value or <code>null</code>. */ protected Object value; /** * This variable is used to detect illegal attempts to renegotiate a * previously negotiated or declared text parameter. * <p> * This variable will be set back to <code>false</code> after each negotiation task (login phase, or text * parameter negotiation stage). Renegotiation accross stages/tasks can be prevented by initializing the * {@link #use} variable accordingly. * * @see #resetAlreadyNegotiated() */ protected boolean alreadyNegotiated = false; /** * Abstract constructor. * * @param keySet * contains all relevant keys * @param negotiationType * declared or negotiated * @param use * determines under which circumstances the parameter may be * negotiated * @param negotiationStatus * indicates whether there is a default value or if the parameter * must be negotiated * @param defaultValue * the default value or <code>null</code> */ public Entry(final KeySet keySet, final NegotiationType negotiationType, final Use use, final NegotiationStatus negotiationStatus, Object defaultValue) { this.keySet = keySet; this.negotiationType = negotiationType; this.use = use; this.negotiationStatus = negotiationStatus; this.value = defaultValue; } /** * Logs an error message containing all {@link #keySet} keys as well as the * passed {@link String} parameter and indicates an unsuccessful negotiation * by setting {@link #negotiationStatus} to {@link NegotiationStatus#REJECTED}. * * @param logMessage */ private void fail(final String logMessage) { LOGGER.error("negotiation error " + keySet + ": " + logMessage); negotiationStatus = NegotiationStatus.REJECTED; } /** * Parses the passed {@link String} parameter and returns a * sub-class-specific {@link Object} which represents the the specified * <i>value</i> part a <i>key=value</i> pair. * * @param values * the <i>value</i> part of a <i>key=value</i> pair * @return sub-class-specific {@link Object} or <code>null</code> if the * parameter violated the expected format */ protected abstract Object parseOffer(TargetServer target, String values); /** * This method is used for negotiating or declaring the {@link Entry}'s * parameter. * * @param loginStage * specifying the current stage or phase of the connection whose * parameters are to be negotiated * @param leadingConnection * <code>true</code> if the connection is the first connection in * its session, <code>false</code> if not * @param initialPdu * <code>true</code> if the <i>key=value</i> pair parameters have * been sent in the first login {@link ProtocolDataUnit} from the * initiator, <code>false</code> if thy have not * @param key * the <i>key</i> part from the received <i>key=value</i> pair * @param values * the <i>value</i> part from the received <i>key=value</i> pair * @param responseKeyValuePairs * where the reply <i>key=value</i> pair will be added to if * necessary * @return <code>true</code> if everything went fine, <code>false</code> if * errors occured */ 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) { // (re)check key (just in case), this should have been checked before // calling this method if (!matchKey(key)) { fail("\"" + key + "\" does not match key in" + keySet); return false; } // prevent renegotiation and remember this negotiation if (alreadyNegotiated) { fail("illegal renegotiation"); return false; } alreadyNegotiated = true; // check use code if (!use.checkUse(loginStage, leadingConnection, initialPdu)) { fail("wrong use: " + use + ", " + loginStage + ", " + leadingConnection + ", " + initialPdu); return false; } // transform values to appropriate type final Object offer = parseOffer(target, values); if (offer == null) { fail("value format error: " + values); return false; } // check if values are in the protocol-conform range/set of values if (!inProtocolValueRange(offer)) { fail("illegal values offered: " + values); return false; } // *** declare *** if (negotiationType == NegotiationType.DECLARED) { // save received value ... processDeclaration(offer); // ... and accept silently negotiationStatus = NegotiationStatus.ACCEPTED; return true; } // *** negotiate *** if (negotiationType == NegotiationType.NEGOTIATED) { String negotiatedValue;// will be returned as value part if (negotiationStatus == NegotiationStatus.IRRELEVANT) negotiatedValue = TextKeyword.IRRELEVANT; else negotiatedValue = processNegotiation(offer); String reply; // reply, remember outcome, log, and return if (negotiatedValue == null) {// no commonly supported values reply = TextParameter.toKeyValuePair(key, TextKeyword.REJECT); responseKeyValuePairs.add(reply); fail("rejected value(s): " + values); return false; }// else reply = TextParameter.toKeyValuePair(key, negotiatedValue); responseKeyValuePairs.add(reply); return true; } // we should not be here fail("initialization error: negotiationType == null"); return false; } /** * Sets {@link #alreadyNegotiated} back to <code>false</code>. * <p> * 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 negotiation stage. */ public void resetAlreadyNegotiated() { alreadyNegotiated = false; } /** * Returns the negotiated (or default) value as a {@link Boolean}. * * @return the negotiated (or default) value as a {@link Boolean} * @throws OperationNotSupportedException * if {@link #value} is not of the boolean type */ public Boolean getBooleanValue() throws OperationNotSupportedException { throw new OperationNotSupportedException(); } /** * Returns the negotiated (or default) value as an {@link Integer}. * * @return the negotiated (or default) value as an {@link Integer} * @throws OperationNotSupportedException * if {@link #value} is not of the integer type */ public Integer getIntegerValue() throws OperationNotSupportedException { throw new OperationNotSupportedException(); } /** * Returns the negotiated (or default) value as a {@link String}. * * @return the negotiated (or default) value as a {@link String} * @throws OperationNotSupportedException * if {@link #value} is not a {@link String} */ public String getStringValue() throws OperationNotSupportedException { throw new OperationNotSupportedException(); } /** * Returns <code>true</code> if one of the keys of {@link #keySet} equals * the parameter and <code>false</code> if there is not match. * * @param key * the key to compare to the {@link #keySet} keys * @return <code>true</code> if one of the keys of {@link #keySet} equals * the parameter and <code>false</code> if not */ public final boolean matchKey(final String key) { return keySet.matchKey(key); } /** * This method is used for checking if a sub-class-specific {@link Object}, * representing a single, a range, or a list of values sent by the * initiator, is illegal, according to the iSCSI standard. * * @param values * a sub-class-specific {@link Object}, representing a single, a * range, or a list of values sent by the initiator * @return <code>false</code> if the iSCSI standard has been violated, <code>true</code> if not */ protected abstract boolean inProtocolValueRange(Object values); /** * Receives a sub-class-specific {@link Object}, representing a legal * parameter value declared by the initiator and accepts it as the new {@link #value}. * * @param values * sub-class-specific representation of a single <i>value</i> * declared by the initiator */ protected abstract void processDeclaration(Object values); // returns null if reply is to be key=Reject /** * Receives a sub-class-specific {@link Object}, representing a list, a * range, or a single legal parameter value offered by the initiator and * tries to select a value from that offer. If none of the offered values is * supported by the jSCSI Target, <code>null</code> is returned, otherwise * the selection is accepted as the new {@link #value} and returned as a {@link String}. {@link #value}. * * @param values * a sub-class-specific {@link Object}, representing a list, a * range, or a single legal parameter value offered by the * initiator * @return the final, negotiated value or <code>null</code>, if the * initiator's offer does not overlap with the values supported by * the jSCSI Target */ protected abstract String processNegotiation(Object values); /** * Returns {@link #negotiationStatus}. * * @return {@link #negotiationStatus} */ public final NegotiationStatus getNegotiationStatus() { return negotiationStatus; } /** * Returns an exact copy of this {@link Entry}. * * @return a copy of this {@link Entry}. */ public abstract Entry copy(); /** * Returns <code>true</code> if {@link #negotiationStatus} is {@link NegotiationStatus#ACCEPTED} and * <code>false</code> if it is not. * * @return <code>true</code> if {@link #negotiationStatus} is {@link NegotiationStatus#ACCEPTED}, * <code>false</code> if not */ public boolean checkAccepted() { return negotiationStatus == NegotiationStatus.ACCEPTED; } }