internal static List <ReadyToTransferPDU> GetReadyToTransferPDUs(SCSICommandPDU command, ConnectionParameters connection, out List <SCSICommandPDU> commandsToExecute)
        {
            // We return either SCSIResponsePDU or List<SCSIDataInPDU>
            List <ReadyToTransferPDU> responseList = new List <ReadyToTransferPDU>();

            commandsToExecute = new List <SCSICommandPDU>();

            ISCSISession session = connection.Session;

            if (command.Write && command.DataSegmentLength < command.ExpectedDataTransferLength)
            {
                uint transferTag = session.GetNextTransferTag();

                // Create buffer for next segments (we only execute the command after receiving all of its data)
                Array.Resize <byte>(ref command.Data, (int)command.ExpectedDataTransferLength);

                // Send R2Ts:
                uint bytesLeft  = command.ExpectedDataTransferLength - command.DataSegmentLength;
                uint nextOffset = command.DataSegmentLength;
                if (!session.InitialR2T)
                {
                    uint firstDataPDULength = Math.Min((uint)session.FirstBurstLength, command.ExpectedDataTransferLength) - command.DataSegmentLength;
                    bytesLeft  -= firstDataPDULength;
                    nextOffset += firstDataPDULength;
                }
                int totalR2Ts    = (int)Math.Ceiling((double)bytesLeft / connection.TargetMaxRecvDataSegmentLength);
                int outgoingR2Ts = Math.Min(session.MaxOutstandingR2T, totalR2Ts);

                for (uint index = 0; index < outgoingR2Ts; index++)
                {
                    ReadyToTransferPDU response = new ReadyToTransferPDU();
                    response.InitiatorTaskTag          = command.InitiatorTaskTag;
                    response.R2TSN                     = index; // R2Ts are sequenced per command and must start with 0 for each new command;
                    response.TargetTransferTag         = transferTag;
                    response.BufferOffset              = nextOffset;
                    response.DesiredDataTransferLength = Math.Min((uint)connection.TargetMaxRecvDataSegmentLength, command.ExpectedDataTransferLength - response.BufferOffset);
                    responseList.Add(response);
                    nextOffset += (uint)connection.TargetMaxRecvDataSegmentLength;
                }
                connection.AddTransfer(transferTag, command, (uint)outgoingR2Ts, nextOffset, (uint)totalR2Ts);
                session.CommandsInTransfer.Add(command.CmdSN);
                return(responseList);
            }

            if (session.IsPrecedingCommandPending(command.CmdSN))
            {
                session.DelayedCommands.Add(command);
            }
            else
            {
                commandsToExecute.Add(command);
            }
            return(responseList);
        }
        internal static List <ReadyToTransferPDU> GetReadyToTransferPDUs(SCSIDataOutPDU request, ConnectionParameters connection, out List <SCSICommandPDU> commandsToExecute)
        {
            List <ReadyToTransferPDU> responseList = new List <ReadyToTransferPDU>();

            commandsToExecute = new List <SCSICommandPDU>();

            ISCSISession  session  = connection.Session;
            TransferEntry transfer = connection.GetTransferEntry(request.TargetTransferTag);

            if (transfer == null)
            {
                throw new InvalidTargetTransferTagException(request.TargetTransferTag);
            }

            uint offset      = request.BufferOffset;
            uint totalLength = (uint)transfer.Command.ExpectedDataTransferLength;

            // Store segment (we only execute the command after receiving all of its data)
            Array.Copy(request.Data, 0, transfer.Command.Data, offset, request.DataSegmentLength);

            if (offset + request.DataSegmentLength == totalLength)
            {
                // Last Data-out PDU
                if (session.IsPrecedingCommandPending(transfer.Command.CmdSN))
                {
                    session.DelayedCommands.Add(transfer.Command);
                }
                else
                {
                    commandsToExecute.Add(transfer.Command);
                    connection.RemoveTransfer(request.TargetTransferTag);
                    session.CommandsInTransfer.Remove(transfer.Command.CmdSN);
                    // Check if delayed commands are ready to be executed
                    List <SCSICommandPDU> pendingCommands = session.GetDelayedCommandsReadyForExecution();
                    foreach (SCSICommandPDU pendingCommand in pendingCommands)
                    {
                        commandsToExecute.Add(pendingCommand);
                    }
                }
                return(responseList);
            }
            else
            {
                // RFC 3720: An R2T MAY be answered with one or more SCSI Data-Out PDUs with a matching Target Transfer Tag.
                // If an R2T is answered with a single Data-Out PDU, the Buffer Offset in the Data PDU MUST be the same as the one specified
                // by the R2T, and the data length of the Data PDU MUST be the same as the Desired Data Transfer Length specified in the R2T.
                // If the R2T is answered with a sequence of Data PDUs, the Buffer Offset and Length MUST be within
                // the range of those specified by R2T, and the last PDU MUST have the F bit set to 1.
                // An R2T is considered outstanding until the last data PDU is transferred.
                if (request.Final)
                {
                    // We already sent as many R2T as we could, we will only send R2T if any remained.
                    if (transfer.NextR2TSN < transfer.TotalR2Ts)
                    {
                        // Send R2T
                        ReadyToTransferPDU response = new ReadyToTransferPDU();
                        response.InitiatorTaskTag          = request.InitiatorTaskTag;
                        response.TargetTransferTag         = request.TargetTransferTag;
                        response.R2TSN                     = transfer.NextR2TSN;
                        response.BufferOffset              = transfer.NextOffset + request.DataSegmentLength; // where we left off
                        response.DesiredDataTransferLength = Math.Min((uint)connection.TargetMaxRecvDataSegmentLength, totalLength - response.BufferOffset);
                        responseList.Add(response);

                        transfer.NextR2TSN++;
                        transfer.NextOffset += (uint)connection.TargetMaxRecvDataSegmentLength;
                    }
                }
                return(responseList);
            }
        }
        internal static List <ISCSIPDU> PrepareSCSICommandResponse(SCSICommandPDU command, SCSIStatusCodeName status, byte[] scsiResponse, ConnectionParameters connection)
        {
            List <ISCSIPDU> responseList = new List <ISCSIPDU>();

            if (!command.Read || status != SCSIStatusCodeName.Good)
            {
                // RFC 3720: if the command is completed with an error, then the response and sense data MUST be sent in a SCSI Response PDU
                SCSIResponsePDU response = new SCSIResponsePDU();
                response.InitiatorTaskTag = command.InitiatorTaskTag;
                response.Status           = status;
                response.Data             = scsiResponse;
                if (command.Read)
                {
                    EnforceExpectedDataTransferLength(response, command.ExpectedDataTransferLength);
                }
                responseList.Add(response);
            }
            else if (scsiResponse.Length <= connection.InitiatorMaxRecvDataSegmentLength)
            {
                SCSIDataInPDU response = new SCSIDataInPDU();
                response.InitiatorTaskTag = command.InitiatorTaskTag;
                response.Status           = status;
                response.StatusPresent    = true;
                response.Final            = true;
                response.Data             = scsiResponse;
                EnforceExpectedDataTransferLength(response, command.ExpectedDataTransferLength);
                responseList.Add(response);
            }
            else // we have to split the response to multiple Data-In PDUs
            {
                int bytesLeftToSend = scsiResponse.Length;

                uint dataSN = 0;
                while (bytesLeftToSend > 0)
                {
                    int dataSegmentLength = Math.Min(connection.InitiatorMaxRecvDataSegmentLength, bytesLeftToSend);
                    int dataOffset        = scsiResponse.Length - bytesLeftToSend;

                    SCSIDataInPDU response = new SCSIDataInPDU();
                    response.InitiatorTaskTag = command.InitiatorTaskTag;
                    if (bytesLeftToSend == dataSegmentLength)
                    {
                        // last Data-In PDU
                        response.Status        = status;
                        response.StatusPresent = true;
                        response.Final         = true;
                    }
                    response.BufferOffset = (uint)dataOffset;
                    response.DataSN       = dataSN;
                    dataSN++;

                    response.Data = new byte[dataSegmentLength];
                    Array.Copy(scsiResponse, dataOffset, response.Data, 0, dataSegmentLength);
                    responseList.Add(response);

                    bytesLeftToSend -= dataSegmentLength;
                }
            }

            return(responseList);
        }
Beispiel #4
0
 public static string GetConnectionIdentifier(SessionParameters session, ConnectionParameters connection)
 {
     return(String.Format("ISID={0},TSIH={1},CID={2}", session.ISID.ToString("x"), session.TSIH.ToString("x"), connection.CID.ToString("x")));
 }
        internal static List <ISCSIPDU> GetSCSIResponsePDU(SCSICommandPDU command, ISCSITarget target, SessionParameters session, ConnectionParameters connection)
        {
            // We return either SCSIResponsePDU or List<SCSIDataInPDU>
            List <ISCSIPDU> responseList = new List <ISCSIPDU>();

            string connectionIdentifier = StateObject.GetConnectionIdentifier(session, connection);

            if (command.Write && command.DataSegmentLength < command.ExpectedDataTransferLength)
            {
                uint transferTag = session.GetNextTransferTag();

                // Store segment (we only execute the command after receiving all of its data)
                byte[] commandDataBuffer = new byte[command.ExpectedDataTransferLength];
                Array.Copy(command.Data, 0, commandDataBuffer, 0, (int)command.DataSegmentLength);

                // Send R2T
                ReadyToTransferPDU response = new ReadyToTransferPDU();
                response.InitiatorTaskTag          = command.InitiatorTaskTag;
                response.R2TSN                     = 0; // R2Ts are sequenced per command and must start with 0 for each new command;
                response.TargetTransferTag         = transferTag;
                response.BufferOffset              = command.DataSegmentLength;
                response.DesiredDataTransferLength = Math.Min((uint)connection.TargetMaxRecvDataSegmentLength, command.ExpectedDataTransferLength - response.BufferOffset);

                connection.AddTransfer(transferTag, command.CommandDescriptorBlock, commandDataBuffer, 1);

                responseList.Add(response);
                return(responseList);
            }

            byte[]             scsiResponse;
            SCSIStatusCodeName status = target.ExecuteCommand(command.CommandDescriptorBlock, command.LUN, command.Data, out scsiResponse);

            if (!command.Read || status != SCSIStatusCodeName.Good)
            {
                // RFC 3720: if the command is completed with an error, then the response and sense data MUST be sent in a SCSI Response PDU
                SCSIResponsePDU response = new SCSIResponsePDU();
                response.InitiatorTaskTag = command.InitiatorTaskTag;
                response.Status           = status;
                response.Data             = scsiResponse;
                if (command.Read)
                {
                    EnforceExpectedDataTransferLength(response, command.ExpectedDataTransferLength);
                }
                responseList.Add(response);
            }
            else if (scsiResponse.Length <= connection.InitiatorMaxRecvDataSegmentLength)
            {
                SCSIDataInPDU response = new SCSIDataInPDU();
                response.InitiatorTaskTag = command.InitiatorTaskTag;
                response.Status           = status;
                response.StatusPresent    = true;
                response.Final            = true;
                response.Data             = scsiResponse;
                EnforceExpectedDataTransferLength(response, command.ExpectedDataTransferLength);
                responseList.Add(response);
            }
            else // we have to split the response to multiple Data-In PDUs
            {
                int bytesLeftToSend = scsiResponse.Length;

                uint dataSN = 0;
                while (bytesLeftToSend > 0)
                {
                    int dataSegmentLength = Math.Min(connection.InitiatorMaxRecvDataSegmentLength, bytesLeftToSend);
                    int dataOffset        = scsiResponse.Length - bytesLeftToSend;

                    SCSIDataInPDU response = new SCSIDataInPDU();
                    response.InitiatorTaskTag = command.InitiatorTaskTag;
                    if (bytesLeftToSend == dataSegmentLength)
                    {
                        // last Data-In PDU
                        response.Status        = status;
                        response.StatusPresent = true;
                        response.Final         = true;
                    }
                    response.BufferOffset = (uint)dataOffset;
                    response.DataSN       = dataSN;
                    dataSN++;

                    response.Data = new byte[dataSegmentLength];
                    Array.Copy(scsiResponse, dataOffset, response.Data, 0, dataSegmentLength);
                    responseList.Add(response);

                    bytesLeftToSend -= dataSegmentLength;
                }
            }

            return(responseList);
        }
        internal static ISCSIPDU GetSCSIDataOutResponsePDU(SCSIDataOutPDU request, ISCSITarget target, SessionParameters session, ConnectionParameters connection)
        {
            string        connectionIdentifier = StateObject.GetConnectionIdentifier(session, connection);
            TransferEntry transfer             = connection.GetTransferEntry(request.TargetTransferTag);

            if (transfer == null)
            {
                ISCSIServer.Log(LogLevel.Warning, "[{0}][GetSCSIDataOutResponsePDU] Invalid TargetTransferTag {1}", connectionIdentifier, request.TargetTransferTag);
                RejectPDU reject = new RejectPDU();
                reject.InitiatorTaskTag = request.InitiatorTaskTag;
                reject.Reason           = RejectReason.InvalidPDUField;
                reject.Data             = ByteReader.ReadBytes(request.GetBytes(), 0, 48);
                return(reject);
            }

            ushort LUN         = (ushort)request.LUN;
            Disk   disk        = target.Disks[LUN];
            uint   offset      = request.BufferOffset;
            uint   totalLength = (uint)transfer.CommandDataBuffer.Length;

            // Store segment (we only execute the command after receiving all of its data)
            Array.Copy(request.Data, 0, transfer.CommandDataBuffer, (int)offset, (int)request.DataSegmentLength);

            ISCSIServer.Log(LogLevel.Debug, String.Format("[{0}][GetSCSIDataOutResponsePDU] Buffer offset: {1}, Total length: {2}", connectionIdentifier, offset, totalLength));

            if (offset + request.DataSegmentLength == totalLength)
            {
                // Last Data-out PDU
                ISCSIServer.Log(LogLevel.Debug, "[{0}][GetSCSIDataOutResponsePDU] Last Data-out PDU", connectionIdentifier);

                byte[]             scsiResponse;
                SCSIStatusCodeName status   = target.ExecuteCommand(transfer.CommandBytes, request.LUN, transfer.CommandDataBuffer, out scsiResponse);
                SCSIResponsePDU    response = new SCSIResponsePDU();
                response.InitiatorTaskTag = request.InitiatorTaskTag;
                response.Status           = status;
                response.Data             = scsiResponse;
                connection.RemoveTransfer(request.TargetTransferTag);
                return(response);
            }
            else
            {
                // Send R2T
                ReadyToTransferPDU response = new ReadyToTransferPDU();
                response.InitiatorTaskTag          = request.InitiatorTaskTag;
                response.TargetTransferTag         = request.TargetTransferTag;
                response.R2TSN                     = transfer.NextR2NSN;
                response.BufferOffset              = offset + request.DataSegmentLength; // where we left off
                response.DesiredDataTransferLength = Math.Min((uint)connection.TargetMaxRecvDataSegmentLength, totalLength - response.BufferOffset);

                transfer.NextR2NSN++;

                return(response);
            }
        }
        private ISCSIPDU GetLogoutResponsePDU(LogoutRequestPDU request, ConnectionParameters connection)
        {
            Log(Severity.Verbose, "[{0}] Logour Request", connection.ConnectionIdentifier);
            if (connection.Session.IsDiscovery && request.ReasonCode != LogoutReasonCode.CloseTheSession)
            {
                // RFC 3720: Discovery-session: The target MUST ONLY accept [..] logout request with the reason "close the session"
                RejectPDU reject = new RejectPDU();
                reject.Reason = RejectReason.ProtocolError;
                reject.Data   = ByteReader.ReadBytes(request.GetBytes(), 0, 48);
                return(reject);
            }
            else
            {
                List <ConnectionState> connectionsToClose = new List <ConnectionState>();
                if (request.ReasonCode == LogoutReasonCode.CloseTheSession)
                {
                    connectionsToClose = m_connectionManager.GetSessionConnections(connection.Session);
                }
                else if (request.ReasonCode == LogoutReasonCode.CloseTheConnection)
                {
                    // RFC 3720: A Logout for a CID may be performed on a different transport connection when the TCP connection for the CID has already been terminated.
                    ConnectionState existingConnection = m_connectionManager.FindConnection(connection.Session, request.CID);
                    if (existingConnection != null)
                    {
                        connectionsToClose.Add(existingConnection);
                    }
                    else
                    {
                        return(ServerResponseHelper.GetLogoutResponsePDU(request, LogoutResponse.CIDNotFound));
                    }
                }
                else if (request.ReasonCode == LogoutReasonCode.RemoveTheConnectionForRecovery)
                {
                    return(ServerResponseHelper.GetLogoutResponsePDU(request, LogoutResponse.ConnectionRecoveryNotSupported));
                }
                else
                {
                    // Unknown LogoutRequest ReasonCode
                    RejectPDU reject = new RejectPDU();
                    reject.Reason = RejectReason.ProtocolError;
                    reject.Data   = ByteReader.ReadBytes(request.GetBytes(), 0, 48);
                    return(reject);
                }

                foreach (ConnectionState connectionToClose in connectionsToClose)
                {
                    // Wait for pending I/O to complete.
                    connectionToClose.RunningSCSICommands.WaitUntilZero();
                    if (connectionToClose.ConnectionParameters != connection)
                    {
                        SocketUtils.ReleaseSocket(connectionToClose.ClientSocket);
                    }
                    m_connectionManager.RemoveConnection(connectionToClose);
                }

                if (request.ReasonCode == LogoutReasonCode.CloseTheSession)
                {
                    Log(Severity.Verbose, "[{0}] Session has been closed", connection.Session.SessionIdentifier);
                    m_sessionManager.RemoveSession(connection.Session, SessionTerminationReason.Logout);
                }

                return(ServerResponseHelper.GetLogoutResponsePDU(request, LogoutResponse.ClosedSuccessfully));
                // connection will be closed after a LogoutResponsePDU has been sent.
            }
        }