Example #1
0
 private LoginResponsePDU GetLoginResponsePDU(LoginRequestPDU request, ConnectionParameters connection)
 {
     // RFC 3720: The numbering fields (StatSN, ExpCmdSN, MaxCmdSN) are only valid if status-Class is 0.
     // RFC 3720: Command numbering starts with the first login request on the first connection of a session
     if (request.Continue)
     {
         connection.AddTextToSequence(request.InitiatorTaskTag, request.LoginParametersText);
         return(GetPartialLoginResponsePDU(request, connection));
     }
     else
     {
         string text = connection.AddTextToSequence(request.InitiatorTaskTag, request.LoginParametersText);
         connection.RemoveTextSequence(request.InitiatorTaskTag);
         KeyValuePairList <string, string> loginParameters = KeyValuePairUtils.GetKeyValuePairList(text);
         if (connection.Session == null)
         {
             LoginResponseStatusName status = SetUpSession(request, loginParameters, connection);
             if (status != LoginResponseStatusName.Success)
             {
                 LoginResponsePDU response = GetLoginResponseTemplate(request);
                 response.Transit = request.Transit;
                 response.Status  = status;
                 return(response);
             }
         }
         return(GetFinalLoginResponsePDU(request, loginParameters, connection));
     }
 }
Example #2
0
        internal static LoginRequestPDU GetSecondStageLoginRequest(LoginResponsePDU firstStageResponse, SessionParameters session, ConnectionParameters connection, bool isDiscovery)
        {
            LoginRequestPDU request = new LoginRequestPDU();

            request.ISID             = firstStageResponse.ISID;
            request.TSIH             = firstStageResponse.TSIH;
            request.CID              = connection.CID;
            request.InitiatorTaskTag = session.GetNextTaskTag();
            request.CmdSN            = session.GetNextCmdSN(false);
            request.CurrentStage     = firstStageResponse.NextStage;
            request.NextStage        = 3;
            request.Transit          = true;
            request.VersionMax       = 0;
            request.VersionMin       = 0;
            request.LoginParameters.Add("HeaderDigest", "None");
            request.LoginParameters.Add("DataDigest", "None");
            request.LoginParameters.Add("MaxRecvDataSegmentLength", connection.InitiatorMaxRecvDataSegmentLength.ToString());
            if (!isDiscovery)
            {
                request.LoginParameters.Add("ErrorRecoveryLevel", ISCSIClient.OfferedErrorRecoveryLevel.ToString());
                request.LoginParameters.Add("InitialR2T", ISCSIClient.OfferedInitialR2T ? "Yes" : "No");
                request.LoginParameters.Add("ImmediateData", ISCSIClient.OfferedImmediateData ? "Yes" : "No");
                request.LoginParameters.Add("MaxBurstLength", ISCSIClient.OfferedMaxBurstLength.ToString());
                request.LoginParameters.Add("FirstBurstLength", ISCSIClient.OfferedFirstBurstLength.ToString());
                request.LoginParameters.Add("MaxConnections", ISCSIClient.OfferedMaxConnections.ToString());
                request.LoginParameters.Add("DataPDUInOrder", ISCSIClient.OfferedDataPDUInOrder ? "Yes" : "No");
                request.LoginParameters.Add("DataSequenceInOrder", ISCSIClient.OfferedDataSequenceInOrder ? "Yes" : "No");
                request.LoginParameters.Add("MaxOutstandingR2T", ISCSIClient.OfferedMaxOutstandingR2T.ToString());
            }
            request.LoginParameters.Add("DefaultTime2Wait", ISCSIClient.OfferedDefaultTime2Wait.ToString());
            request.LoginParameters.Add("DefaultTime2Retain", ISCSIClient.OfferedDefaultTime2Retain.ToString());

            return(request);
        }
Example #3
0
        /// <param name="targetName">Set to null for discovery session</param>
        public bool Login(string targetName)
        {
            if (!m_isConnected)
            {
                throw new InvalidOperationException("iSCSI client is not connected");
            }
            m_session.ISID   = ClientHelper.GetRandomISID();
            m_connection.CID = m_session.GetNextCID();
            // p.s. It's possible to perform a single stage login (stage 1 to stage 3, tested against Microsoft iSCSI Target v3.1)
            LoginRequestPDU request = ClientHelper.GetFirstStageLoginRequest(m_initiatorName, targetName, m_session, m_connection);

            SendPDU(request);
            LoginResponsePDU response = WaitForPDU(request.InitiatorTaskTag) as LoginResponsePDU;

            if (response != null && response.Status == LoginResponseStatusName.Success)
            {
                // Status numbering starts with the Login response to the first Login request of the connection
                m_connection.StatusNumberingStarted = true;
                m_connection.ExpStatSN = response.StatSN + 1;

                request = ClientHelper.GetSecondStageLoginRequest(response, m_session, m_connection, targetName == null);
                SendPDU(request);
                response = WaitForPDU(request.InitiatorTaskTag) as LoginResponsePDU;
                if (response != null && response.Status == LoginResponseStatusName.Success)
                {
                    ClientHelper.UpdateOperationalParameters(response.LoginParameters, m_session, m_connection);
                    return(true);
                }
            }
            return(false);
        }
Example #4
0
        private LoginResponsePDU GetPartialLoginResponsePDU(LoginRequestPDU request, ConnectionParameters connection)
        {
            LoginResponsePDU response = GetLoginResponseTemplate(request);

            response.Transit          = false;
            response.InitiatorTaskTag = request.InitiatorTaskTag;
            response.ExpCmdSN         = request.CmdSN; // We must set ExpCmdSN ourselves because we haven't set up the session yet.
            if (request.Transit)
            {
                Log(Severity.Warning, "[{0}] Initiator error: Received login request with both Transit and Continue set to true", connection.ConnectionIdentifier);
                response.Status = LoginResponseStatusName.InitiatorError;
                return(response);
            }
            response.Status = LoginResponseStatusName.Success;
            return(response);
        }
Example #5
0
        private LoginResponsePDU GetLoginResponseTemplate(LoginRequestPDU request)
        {
            LoginResponsePDU response = new LoginResponsePDU();

            response.Continue      = false;
            response.CurrentStage  = request.CurrentStage;
            response.NextStage     = request.NextStage;
            response.VersionMax    = request.VersionMax;
            response.VersionActive = request.VersionMin;
            response.ISID          = request.ISID;
            // TSIH: With the exception of the Login Final-Response in a new session, this field should
            // be set to the TSIH provided by the initiator in the Login Request.
            response.TSIH             = request.TSIH;
            response.InitiatorTaskTag = request.InitiatorTaskTag;
            return(response);
        }
Example #6
0
        public void ProcessPDU(ISCSIPDU pdu, StateObject state)
        {
            Socket clientSocket = state.ClientSocket;

            uint?cmdSN = PDUHelper.GetCmdSN(pdu);

            Log(LogLevel.Debug, "[{0}][ProcessPDU] Received PDU from initiator, Operation: {1}, Size: {2}, CmdSN: {3}", state.ConnectionIdentifier, (ISCSIOpCodeName)pdu.OpCode, pdu.Length, cmdSN);
            // RFC 3720: On any connection, the iSCSI initiator MUST send the commands in increasing order of CmdSN,
            // except for commands that are retransmitted due to digest error recovery and connection recovery.
            if (cmdSN.HasValue)
            {
                if (state.SessionParameters.CommandNumberingStarted)
                {
                    if (cmdSN != state.SessionParameters.ExpCmdSN)
                    {
                        Log(LogLevel.Warning, "[{0}][ProcessPDU] CmdSN outside of expected range", state.ConnectionIdentifier);
                        // We ignore this PDU
                        return;
                    }
                }
                else
                {
                    state.SessionParameters.ExpCmdSN = cmdSN.Value;
                    state.SessionParameters.CommandNumberingStarted = true;
                }

                if (pdu is LogoutRequestPDU || pdu is TextRequestPDU || pdu is SCSICommandPDU || pdu is RejectPDU)
                {
                    if (!pdu.ImmediateDelivery)
                    {
                        state.SessionParameters.ExpCmdSN++;
                    }
                }
            }

            if (pdu is LoginRequestPDU)
            {
                LoginRequestPDU request = (LoginRequestPDU)pdu;
                Log(LogLevel.Information, "[{0}][ReceiveCallback] Login Request, current stage: {1}, next stage: {2}, parameters: {3}", state.ConnectionIdentifier, request.CurrentStage, request.NextStage, KeyValuePairUtils.ToString(request.LoginParameters));
                if (request.TSIH != 0)
                {
                    // RFC 3720: A Login Request with a non-zero TSIH and a CID equal to that of an existing
                    // connection implies a logout of the connection followed by a Login
                    lock (m_activeConnectionsLock)
                    {
                        int existingConnectionIndex = GetStateObjectIndex(m_activeConnections, request.ISID, request.TSIH, request.CID);
                        if (existingConnectionIndex >= 0)
                        {
                            // Perform implicit logout
                            Log(LogLevel.Information, "[{0}][ProcessPDU] Initiating implicit logout", state.ConnectionIdentifier);
                            SocketUtils.ReleaseSocket(m_activeConnections[existingConnectionIndex].ClientSocket);
                            if (m_activeConnections[existingConnectionIndex].Target != null)
                            {
                                lock (m_activeConnections[existingConnectionIndex].Target.IOLock)
                                {
                                    // Wait for pending I/O to complete.
                                }
                            }
                            m_activeConnections.RemoveAt(existingConnectionIndex);
                            Log(LogLevel.Information, "[{0}][ProcessPDU] Implicit logout completed", state.ConnectionIdentifier);
                        }
                    }
                }
                LoginResponsePDU response = ServerResponseHelper.GetLoginResponsePDU(request, m_targets, state.SessionParameters, state.ConnectionParameters, ref state.Target, GetNextTSIH);
                if (state.SessionParameters.IsFullFeaturePhase)
                {
                    state.SessionParameters.ISID   = request.ISID;
                    state.ConnectionParameters.CID = request.CID;
                    lock (m_activeConnectionsLock)
                    {
                        m_activeConnections.Add(state);
                    }
                }
                Log(LogLevel.Information, "[{0}][ReceiveCallback] Login Response parameters: {1}", state.ConnectionIdentifier, KeyValuePairUtils.ToString(response.LoginParameters));
                TrySendPDU(state, response);
            }
            else if (!state.SessionParameters.IsFullFeaturePhase)
            {
                // Before the Full Feature Phase is established, only Login Request and Login Response PDUs are allowed.
                Log(LogLevel.Warning, "[{0}][ProcessPDU] Improper command during login phase, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
                // A target receiving any PDU except a Login request before the Login phase is started MUST
                // immediately terminate the connection on which the PDU was received.
                // Once the Login phase has started, if the target receives any PDU except a Login request,
                // it MUST send a Login reject (with Status "invalid during login") and then disconnect.
                clientSocket.Dispose();
            }
            else // Logged in
            {
                if (pdu is TextRequestPDU)
                {
                    TextRequestPDU  request  = (TextRequestPDU)pdu;
                    TextResponsePDU response = ServerResponseHelper.GetTextResponsePDU(request, m_targets);
                    TrySendPDU(state, response);
                }
                else if (pdu is LogoutRequestPDU)
                {
                    lock (m_activeConnectionsLock)
                    {
                        int connectionIndex = GetStateObjectIndex(m_activeConnections, state.SessionParameters.ISID, state.SessionParameters.TSIH, state.ConnectionParameters.CID);
                        if (connectionIndex >= 0)
                        {
                            if (m_activeConnections[connectionIndex].Target != null)
                            {
                                lock (m_activeConnections[connectionIndex].Target.IOLock)
                                {
                                    // Wait for pending I/O to complete.
                                }
                            }
                            m_activeConnections.RemoveAt(connectionIndex);
                        }
                    }
                    LogoutRequestPDU  request  = (LogoutRequestPDU)pdu;
                    LogoutResponsePDU response = ServerResponseHelper.GetLogoutResponsePDU(request);
                    TrySendPDU(state, response);
                    clientSocket.Dispose(); // We can close the connection now
                }
                else if (state.SessionParameters.IsDiscovery)
                {
                    // The target MUST ONLY accept text requests with the SendTargets key and a logout
                    // request with the reason "close the session".  All other requests MUST be rejected.
                    Log(LogLevel.Warning, "[{0}][ProcessPDU] Improper command during discovery session, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
                    RejectPDU reject = new RejectPDU();
                    reject.Reason = RejectReason.ProtocolError;
                    reject.Data   = ByteReader.ReadBytes(pdu.GetBytes(), 0, 48);

                    TrySendPDU(state, reject);
                }
                else if (pdu is NOPOutPDU)
                {
                    NOPOutPDU request = (NOPOutPDU)pdu;
                    if (request.InitiatorTaskTag != 0xFFFFFFFF)
                    {
                        NOPInPDU response = ServerResponseHelper.GetNOPResponsePDU(request);
                        TrySendPDU(state, response);
                    }
                }
                else if (pdu is SCSIDataOutPDU)
                {
                    // FIXME: the iSCSI target layer MUST deliver the commands for execution (to the SCSI execution engin) in the order specified by CmdSN
                    // e.g. read requests should not be executed while previous write request data is being received (via R2T)
                    SCSIDataOutPDU request = (SCSIDataOutPDU)pdu;
                    Log(LogLevel.Debug, "[{0}][ProcessPDU] SCSIDataOutPDU: Target transfer tag: {1}, LUN: {2}, Buffer offset: {3}, Data segment length: {4}, DataSN: {5}, Final: {6}", state.ConnectionIdentifier, request.TargetTransferTag, (ushort)request.LUN, request.BufferOffset, request.DataSegmentLength, request.DataSN, request.Final);
                    ISCSIPDU response = TargetResponseHelper.GetSCSIDataOutResponsePDU(request, state.Target, state.SessionParameters, state.ConnectionParameters);
                    TrySendPDU(state, response);
                }
                else if (pdu is SCSICommandPDU)
                {
                    SCSICommandPDU command = (SCSICommandPDU)pdu;
                    Log(LogLevel.Debug, "[{0}][ProcessPDU] SCSICommandPDU: CmdSN: {1}, LUN: {2}, Data segment length: {3}, Expected Data Transfer Length: {4}, Final: {5}", state.ConnectionIdentifier, command.CmdSN, (ushort)command.LUN, command.DataSegmentLength, command.ExpectedDataTransferLength, command.Final);
                    List <ISCSIPDU> scsiResponseList = TargetResponseHelper.GetSCSIResponsePDU(command, state.Target, state.SessionParameters, state.ConnectionParameters);
                    foreach (ISCSIPDU response in scsiResponseList)
                    {
                        TrySendPDU(state, response);
                        if (!clientSocket.Connected)
                        {
                            return;
                        }
                    }
                }
                else
                {
                    Log(LogLevel.Warning, "[{0}][ProcessPDU] Unsupported command, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
                }
            }
        }
Example #7
0
        private LoginResponsePDU GetFinalLoginResponsePDU(LoginRequestPDU request, KeyValuePairList <string, string> requestParameters, ConnectionParameters connection)
        {
            LoginResponsePDU response = GetLoginResponseTemplate(request);

            response.Transit = request.Transit;
            response.TSIH    = connection.Session.TSIH;

            string connectionIdentifier = connection.ConnectionIdentifier;

            response.Status = LoginResponseStatusName.Success;

            ISCSISession session = connection.Session;

            // RFC 3720:  The login process proceeds in two stages - the security negotiation
            // stage and the operational parameter negotiation stage.  Both stages are optional
            // but at least one of them has to be present.

            // The stage codes are:
            // 0 - SecurityNegotiation
            // 1 - LoginOperationalNegotiation
            // 3 - FullFeaturePhase
            if (request.CurrentStage == 0)
            {
                KeyValuePairList <string, string> responseParameters = new KeyValuePairList <string, string>();
                responseParameters.Add("AuthMethod", "None");
                if (session.Target != null)
                {
                    // RFC 3720: During the Login Phase the iSCSI target MUST return the TargetPortalGroupTag key with the first Login Response PDU with which it is allowed to do so
                    responseParameters.Add("TargetPortalGroupTag", "1");
                }

                if (request.Transit)
                {
                    if (request.NextStage == 3)
                    {
                        session.IsFullFeaturePhase = true;
                    }
                    else if (request.NextStage != 1)
                    {
                        Log(Severity.Warning, "[{0}] Initiator error: Received login request with Invalid NextStage", connectionIdentifier);
                        response.Status = LoginResponseStatusName.InitiatorError;
                    }
                }
                response.LoginParameters = responseParameters;
            }
            else if (request.CurrentStage == 1)
            {
                UpdateOperationalParameters(requestParameters, connection);
                response.LoginParameters = GetLoginResponseOperationalParameters(connection);

                if (request.Transit)
                {
                    if (request.NextStage == 3)
                    {
                        session.IsFullFeaturePhase = true;
                    }
                    else
                    {
                        Log(Severity.Warning, "[{0}] Initiator error: Received login request with Invalid NextStage", connectionIdentifier);
                        response.Status = LoginResponseStatusName.InitiatorError;
                    }
                }
            }
            else
            {
                // Not valid
                Log(Severity.Warning, "[{0}] Initiator error: Received login request with Invalid CurrentStage", connectionIdentifier);
                response.Status = LoginResponseStatusName.InitiatorError;
            }

            return(response);
        }
        private void ProcessPDU(ISCSIPDU pdu, ConnectionState state)
        {
            LogTrace("Entering ProcessPDU");

            if (state.Session == null || !state.Session.IsFullFeaturePhase)
            {
                if (pdu is LoginRequestPDU)
                {
                    LoginRequestPDU request         = (LoginRequestPDU)pdu;
                    string          loginIdentifier = String.Format("ISID={0},TSIH={1},CID={2}", request.ISID.ToString("x"), request.TSIH.ToString("x"), request.CID.ToString("x"));
                    Log(Severity.Verbose, "[{0}] Login Request, current stage: {1}, next stage: {2}, parameters: {3}", loginIdentifier, request.CurrentStage, request.NextStage, FormatNullDelimitedText(request.LoginParametersText));
                    LoginResponsePDU response = GetLoginResponsePDU(request, state.ConnectionParameters);
                    if (state.Session != null && state.Session.IsFullFeaturePhase)
                    {
                        m_connectionManager.AddConnection(state);
                    }
                    Log(Severity.Verbose, "[{0}] Login Response parameters: {1}", state.ConnectionIdentifier, FormatNullDelimitedText(response.LoginParametersText));
                    state.SendQueue.Enqueue(response);
                }
                else
                {
                    // Before the Full Feature Phase is established, only Login Request and Login Response PDUs are allowed.
                    Log(Severity.Warning, "[{0}] Initiator error: Improper command during login phase, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
                    if (state.Session == null)
                    {
                        // A target receiving any PDU except a Login request before the Login phase is started MUST
                        // immediately terminate the connection on which the PDU was received.
                        state.ClientSocket.Close();
                    }
                    else
                    {
                        // Once the Login phase has started, if the target receives any PDU except a Login request,
                        // it MUST send a Login reject (with Status "invalid during login") and then disconnect.
                        LoginResponsePDU loginResponse = new LoginResponsePDU();
                        loginResponse.TSIH   = state.Session.TSIH;
                        loginResponse.Status = LoginResponseStatusName.InvalidDuringLogon;
                        state.SendQueue.Enqueue(loginResponse);
                    }
                }
            }
            else // Logged in
            {
                if (pdu is TextRequestPDU)
                {
                    TextRequestPDU  request  = (TextRequestPDU)pdu;
                    TextResponsePDU response = GetTextResponsePDU(request, state.ConnectionParameters);
                    state.SendQueue.Enqueue(response);
                }
                else if (pdu is LogoutRequestPDU)
                {
                    LogoutRequestPDU request  = (LogoutRequestPDU)pdu;
                    ISCSIPDU         response = GetLogoutResponsePDU(request, state.ConnectionParameters);
                    state.SendQueue.Enqueue(response);
                }
                else if (state.Session.IsDiscovery)
                {
                    // The target MUST ONLY accept text requests with the SendTargets key and a logout
                    // request with the reason "close the session".  All other requests MUST be rejected.
                    Log(Severity.Warning, "[{0}] Initiator error: Improper command during discovery session, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
                    RejectPDU reject = new RejectPDU();
                    reject.Reason = RejectReason.ProtocolError;
                    reject.Data   = ByteReader.ReadBytes(pdu.GetBytes(), 0, 48);

                    state.SendQueue.Enqueue(reject);
                }
                else if (pdu is NOPOutPDU)
                {
                    NOPOutPDU request = (NOPOutPDU)pdu;
                    if (request.InitiatorTaskTag != 0xFFFFFFFF)
                    {
                        NOPInPDU response = ServerResponseHelper.GetNOPResponsePDU(request);
                        state.SendQueue.Enqueue(response);
                    }
                }
                else if (pdu is SCSIDataOutPDU || pdu is SCSICommandPDU)
                {
                    // RFC 3720: the iSCSI target layer MUST deliver the commands for execution (to the SCSI execution engine) in the order specified by CmdSN.
                    // e.g. read requests should not be executed while previous write request data is being received (via R2T)
                    List <SCSICommandPDU>     commandsToExecute   = null;
                    List <ReadyToTransferPDU> readyToTransferPDUs = new List <ReadyToTransferPDU>();
                    if (pdu is SCSIDataOutPDU)
                    {
                        SCSIDataOutPDU request = (SCSIDataOutPDU)pdu;
                        Log(Severity.Debug, "[{0}] SCSIDataOutPDU: Target transfer tag: {1}, LUN: {2}, Buffer offset: {3}, Data segment length: {4}, DataSN: {5}, Final: {6}", state.ConnectionIdentifier, request.TargetTransferTag, (ushort)request.LUN, request.BufferOffset, request.DataSegmentLength, request.DataSN, request.Final);
                        try
                        {
                            readyToTransferPDUs = TargetResponseHelper.GetReadyToTransferPDUs(request, state.ConnectionParameters, out commandsToExecute);
                        }
                        catch (InvalidTargetTransferTagException ex)
                        {
                            Log(Severity.Warning, "[{0}] Initiator error: Invalid TargetTransferTag: {1}", state.ConnectionIdentifier, ex.TargetTransferTag);
                            RejectPDU reject = new RejectPDU();
                            reject.InitiatorTaskTag = request.InitiatorTaskTag;
                            reject.Reason           = RejectReason.InvalidPDUField;
                            reject.Data             = ByteReader.ReadBytes(request.GetBytes(), 0, 48);
                            state.SendQueue.Enqueue(reject);
                        }
                    }
                    else
                    {
                        SCSICommandPDU command = (SCSICommandPDU)pdu;
                        Log(Severity.Debug, "[{0}] SCSICommandPDU: CmdSN: {1}, LUN: {2}, Data segment length: {3}, Expected Data Transfer Length: {4}, Final: {5}", state.ConnectionIdentifier, command.CmdSN, (ushort)command.LUN, command.DataSegmentLength, command.ExpectedDataTransferLength, command.Final);
                        readyToTransferPDUs = TargetResponseHelper.GetReadyToTransferPDUs(command, state.ConnectionParameters, out commandsToExecute);
                    }
                    foreach (ReadyToTransferPDU readyToTransferPDU in readyToTransferPDUs)
                    {
                        state.SendQueue.Enqueue(readyToTransferPDU);
                    }
                    if (commandsToExecute != null)
                    {
                        state.RunningSCSICommands.Add(commandsToExecute.Count);
                    }
                    foreach (SCSICommandPDU commandPDU in commandsToExecute)
                    {
                        Log(Severity.Debug, "[{0}] Queuing command: CmdSN: {1}", state.ConnectionIdentifier, commandPDU.CmdSN);
                        state.Target.QueueCommand(commandPDU.CommandDescriptorBlock, commandPDU.LUN, commandPDU.Data, commandPDU, state.OnCommandCompleted);
                    }
                }
                else if (pdu is LoginRequestPDU)
                {
                    Log(Severity.Warning, "[{0}] Initiator Error: Login request during full feature phase", state.ConnectionIdentifier);
                    // RFC 3720: Login requests and responses MUST be used exclusively during Login.
                    // On any connection, the login phase MUST immediately follow TCP connection establishment and
                    // a subsequent Login Phase MUST NOT occur before tearing down a connection
                    RejectPDU reject = new RejectPDU();
                    reject.Reason = RejectReason.ProtocolError;
                    reject.Data   = ByteReader.ReadBytes(pdu.GetBytes(), 0, 48);

                    state.SendQueue.Enqueue(reject);
                }
                else
                {
                    Log(Severity.Error, "[{0}] Unsupported command, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
                    RejectPDU reject = new RejectPDU();
                    reject.Reason = RejectReason.CommandNotSupported;
                    reject.Data   = ByteReader.ReadBytes(pdu.GetBytes(), 0, 48);

                    state.SendQueue.Enqueue(reject);
                }
            }
            LogTrace("Leaving ProcessPDU");
        }
        internal static LoginResponsePDU GetLoginResponsePDU(LoginRequestPDU request, List <ISCSITarget> availableTargets, SessionParameters session, ConnectionParameters connection, ref ISCSITarget target, GetNextTSIH GetNextTSIH)
        {
            LoginResponsePDU response = new LoginResponsePDU();

            response.Transit  = request.Transit;
            response.Continue = false;

            // The stage codes are:
            // 0 - SecurityNegotiation
            // 1 - LoginOperationalNegotiation
            // 3 - FullFeaturePhase

            response.CurrentStage = request.CurrentStage;
            response.NextStage    = request.NextStage;

            response.VersionMax    = request.VersionMax;
            response.VersionActive = request.VersionMin;
            response.ISID          = request.ISID;

            response.Status = LoginResponseStatusName.Success;

            response.InitiatorTaskTag = request.InitiatorTaskTag;

            if (request.TSIH == 0)
            {
                // For a new session, the request TSIH is zero,
                // As part of the response, the target generates a TSIH.
                session.TSIH = GetNextTSIH();
            }
            response.TSIH = session.TSIH;

            if (request.Transit && request.Continue)
            {
                response.Status = LoginResponseStatusName.InitiatorError;
                return(response);
            }
            else if (request.Continue)
            {
                response.Status = LoginResponseStatusName.Success;
                return(response);
            }

            // RFC 3720:  The login process proceeds in two stages - the security negotiation
            // stage and the operational parameter negotiation stage.  Both stages are optional
            // but at least one of them has to be present.

            bool firstLoginRequest = (!session.IsDiscovery && target == null);

            if (firstLoginRequest)
            {
                string sessionType = request.LoginParameters.ValueOf("SessionType");
                if (sessionType == "Discovery")
                {
                    session.IsDiscovery = true;
                }
                else //sessionType == "Normal" or unspecified (default is Normal)
                {
                    session.IsDiscovery = false;
                    // RFC 3720: For any connection within a session whose type is not "Discovery", the first Login Request MUST also include the TargetName key=value pair.

                    if (request.LoginParameters.ContainsKey("TargetName"))
                    {
                        string targetName  = request.LoginParameters.ValueOf("TargetName");
                        int    targetIndex = GetTargetIndex(availableTargets, targetName);
                        if (targetIndex >= 0)
                        {
                            target = availableTargets[targetIndex];
                        }
                        else
                        {
                            response.Status = LoginResponseStatusName.NotFound;
                            return(response);
                        }
                    }
                    else
                    {
                        response.Status = LoginResponseStatusName.InitiatorError;
                        return(response);
                    }
                }
            }

            if (request.CurrentStage == 0)
            {
                response.LoginParameters.Add("AuthMethod", "None");

                if (request.Transit)
                {
                    if (request.NextStage == 3)
                    {
                        session.IsFullFeaturePhase = true;
                    }
                    else if (request.NextStage != 1)
                    {
                        response.Status = LoginResponseStatusName.InitiatorError;
                    }
                }
            }
            else if (request.CurrentStage == 1)
            {
                UpdateOperationalParameters(request.LoginParameters, session, connection);
                response.LoginParameters = GetLoginOperationalParameters(session, connection);

                if (request.Transit)
                {
                    if (request.NextStage == 3)
                    {
                        session.IsFullFeaturePhase = true;
                    }
                    else
                    {
                        response.Status = LoginResponseStatusName.InitiatorError;
                    }
                }
            }
            else
            {
                // Not valid
                response.Status = LoginResponseStatusName.InitiatorError;
            }

            return(response);
        }