package org.jscsi.target.connection.stage.login; import java.io.IOException; import java.nio.ByteBuffer; import java.security.DigestException; import org.jscsi.exception.InternetSCSIException; import org.jscsi.parser.BasicHeaderSegment; import org.jscsi.parser.OperationCode; import org.jscsi.parser.ProtocolDataUnit; import org.jscsi.parser.login.LoginRequestParser; import org.jscsi.parser.login.LoginStage; import org.jscsi.parser.login.LoginStatus; import org.jscsi.target.connection.TargetPduFactory; import org.jscsi.target.connection.phase.TargetLoginPhase; import org.jscsi.target.connection.stage.TargetStage; import org.jscsi.target.settings.ConnectionSettingsNegotiator; import org.jscsi.target.settings.SettingsException; import org.jscsi.target.util.ReadWrite; import org.jscsi.utils.ByteBufferCache; /** * This class is an abstract super-class for stages of the {@link TargetLoginPhase} (see * <code>Connection</code> for a description of * the relationship between sessions, connections, phases and sessions), namely * the {@link LoginOperationalParameterNegotiationStage} and the {@link SecurityNegotiationStage}. * <p> * The stage is started by calling the {@link #execute(ProtocolDataUnit)} method with the first * {@link ProtocolDataUnit} to be processed as part of the stage. * <p> * Of equal importance is the {@link #getNextStageNumber()} method, which must be used to find out which stage * or phase will follow this one. * * @author Andreas Ergenzinger */ public abstract class TargetLoginStage extends TargetStage { /** * Manages the text parameter negotiation. */ protected final ConnectionSettingsNegotiator negotiator; /** * The stage number used for describing this stage in Login Request PDUs. * * @see LoginRequestParser#getCurrentStageNumber() * @see LoginRequestParser#getNextStageNumber() */ protected final LoginStage stageNumber; /** * An identifier used by the initiator to identify the login task. Sent with * the first Login Request PDU. */ protected int initiatorTaskTag; /** * A stage number describing which stage the initiator wants to transition * to. This value will be updated with every received PDU. * * @see #stageNumber */ protected LoginStage requestedNextStageNumber; /** * A stage number describing which stage must follow this stage. * <p> * This value is initialized with <code>null</code>, and will be changed in * {@link #execute(ProtocolDataUnit)}, if the stage was finished successfully. * * @see #stageNumber */ protected LoginStage nextStageNumber; /** * The abstract constructor. * * @param targetLoginPhase * the phase this stage is a part of * @param stageNumber * the stage number used for describing this stage in Login * Request PDUs */ public TargetLoginStage(final TargetLoginPhase targetLoginPhase, final LoginStage stageNumber) { super(targetLoginPhase); this.stageNumber = stageNumber; negotiator = connection.getConnectionSettingsNegotiator(); } /** * Returns <code>true</code>, if and only if the specified PDU is a Login * Request PDU and the CSN and InitiatorTaskTag fields check out. * * @param pdu * the PDU to check * @return <code>true</code> if the PDU checks out */ protected boolean checkPdu(ProtocolDataUnit pdu) { final BasicHeaderSegment bhs = pdu.getBasicHeaderSegment(); final LoginRequestParser parser = (LoginRequestParser)bhs.getParser(); if (bhs.getOpCode() == OperationCode.LOGIN_REQUEST && parser.getCurrentStageNumber() == stageNumber && bhs.getInitiatorTaskTag() == initiatorTaskTag) return true; return false; } /** * Receives a sequence of Login Request PDUs (as indicated by the * {@link LoginRequestParser#isContinueFlag()} and returns the concatenated * content of the text data segments. * * @return the concatenated content of the text data segments * @throws DigestException * @throws InternetSCSIException * @throws IOException * @throws SettingsException * @throws InterruptedException */ protected final String receivePduSequence() throws DigestException, InternetSCSIException, IOException, SettingsException, InterruptedException { final ProtocolDataUnit pdu = connection.receivePdu(); return receivePduSequence(pdu); } /** * Receives a sequence of Login Request PDUs (as indicated by the * {@link LoginRequestParser#isContinueFlag()} and returns the concatenated * content of the text data segments. * * @param pdu * the first PDU of the sequence * @return the concatenated content of the text data segments * @throws InternetSCSIException * @throws InterruptedException * @throws IOException * @throws DigestException * @throws SettingsException */ protected final String receivePduSequence(ProtocolDataUnit pdu) throws InternetSCSIException, InterruptedException, IOException, DigestException, SettingsException { // StringBuilder for the key-value pairs received during this sequence final StringBuilder stringBuilder = new StringBuilder(); // for accessing the fields of the last received PDU BasicHeaderSegment bhs; LoginRequestParser parser; // begin sequence int sequenceLength = 1; while (sequenceLength <= session.getTargetServer().getConfig().getInMaxRecvTextPduSequenceLength()) { bhs = pdu.getBasicHeaderSegment(); parser = (LoginRequestParser)bhs.getParser(); // check PDU if (!checkPdu(pdu)) { // send login reject and leave stage sendRejectPdu(LoginStatus.INVALID_DURING_LOGIN); throw new InternetSCSIException("Wrong PDU in TargetLoginStage"); } // PDU is okay, so append text data segment to stringBuilder ReadWrite.appendTextDataSegmentToStringBuffer(pdu.getDataSegment(), stringBuilder); // remember what stage the initiator wants to transition to requestedNextStageNumber = parser.getNextStageNumber(); // continue? if (parser.isContinueFlag()) { // send reception confirmation pdu = TargetPduFactory.createLoginResponsePdu(false,// transitFlag false,// continueFlag stageNumber,// currentStage stageNumber,// nextStage session.getInitiatorSessionID(),// initiatorSessionID session.getTargetSessionIdentifyingHandle(),// targetSessionIdentifyingHandle initiatorTaskTag, LoginStatus.SUCCESS,// status ByteBufferCache.allocate(0));// dataSegment connection.sendPdu(pdu); // receive the next pdu pdu = connection.receivePdu(); } else // sequence is over return stringBuilder.toString(); } // initiator's text PDU sequence was too long // send login reject and leave stage sendRejectPdu(LoginStatus.OUT_OF_RESOURCES); throw new InternetSCSIException("Wrong PDU in TargetLoginStage"); } /** * Sends a Login Response PDU sequence containing the specified * <i>key-value</i> pairs. * * @param keyValuePairs * contains <i>key-value</i> pairs to send * @param nextStage * indicates if the target is willing to transition to a * different stage * @throws SettingsException * @throws InterruptedException * @throws IOException * @throws InternetSCSIException * @throws DigestException */ protected final void sendPduSequence(final String keyValuePairs, final LoginStage nextStage) throws SettingsException, InterruptedException, IOException, InternetSCSIException, DigestException { // some variables ProtocolDataUnit pdu; BasicHeaderSegment bhs; LoginRequestParser parser; boolean continueFlag = true; boolean transitFlag = false; // split input string into text data segments final ByteBuffer[] dataSegments = ReadWrite.stringToTextDataSegments(keyValuePairs,// string settings.getMaxRecvDataSegmentLength());// bufferSize // send all data segments (and receive confirmations) for (int i = 0; i < dataSegments.length; ++i) { // adjust flags if (i == dataSegments.length - 1) { continueFlag = false; if (stageNumber != nextStage) transitFlag = true; } // create and send PDU pdu = TargetPduFactory.createLoginResponsePdu(transitFlag,// transitFlag continueFlag,// continueFlag stageNumber,// currentStage nextStage,// nextStage session.getInitiatorSessionID(),// initiatorSessionID session.getTargetSessionIdentifyingHandle(),// targetSessionIdentifyingHandle initiatorTaskTag, LoginStatus.SUCCESS,// status dataSegments[i]);// dataSegment connection.sendPdu(pdu); // receive confirmation if (continueFlag) { // receive and check pdu = connection.receivePdu(); bhs = pdu.getBasicHeaderSegment(); parser = (LoginRequestParser)bhs.getParser(); if (!checkPdu(pdu) || parser.isContinueFlag()) { // send login reject and leave stage sendRejectPdu(LoginStatus.INITIATOR_ERROR); throw new InternetSCSIException(); } } } } /** * Sends a Login Response PDU informing the initiator that an error has * occurred and that the connection must be closed. * * @param errorStatus * hints to the cause of the error * @throws InterruptedException * @throws IOException * @throws InternetSCSIException */ protected final void sendRejectPdu(final LoginStatus errorStatus) throws InterruptedException, IOException, InternetSCSIException { final ProtocolDataUnit rejectPDU = TargetPduFactory.createLoginResponsePdu(false,// transit flag false,// continueFlag stageNumber,// currentStage stageNumber,// nextStage session.getInitiatorSessionID(),// initiatorSessionID session.getTargetSessionIdentifyingHandle(),// targetSessionIdentifyingHandle initiatorTaskTag,// initiatorTaskTag errorStatus,// status ByteBufferCache.allocate(0));// dataSegment connection.sendPdu(rejectPDU); } /** * Returns a stage number describing which stage of phase must follow this * stage, or <code>null</code> if the initiator is not allowed to transition * any further. * * @return an identifier of the next stage/phase or <code>null</code> */ public final LoginStage getNextStageNumber() { return nextStageNumber; } }