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("[{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, offset, request.DataSegmentLength);

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

            if (offset + request.DataSegmentLength == totalLength)
            {
                // Last Data-out PDU
                ISCSIServer.Log("[{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);
            }
        }
Esempio n. 2
0
        public SCSIStatusCodeName ExecuteCommand(byte[] commandBytes, LUNStructure lun, byte[] data, out byte[] response)
        {
            SCSICommandDescriptorBlock command;

            try
            {
                command = SCSICommandDescriptorBlock.FromBytes(commandBytes, 0);
            }
            catch (UnsupportedSCSICommandException)
            {
                ISCSIServer.Log("[ExecuteCommand] Unsupported SCSI Command (0x{0})", commandBytes[0].ToString("X"));
                response = SCSITarget.FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
                return(SCSIStatusCodeName.CheckCondition);
            }

            return(ExecuteCommand(command, lun, data, out response));
        }
Esempio n. 3
0
        public SCSIStatusCodeName Read(SCSICommandDescriptorBlock command, LUNStructure lun, out byte[] response)
        {
            if (lun >= m_disks.Count)
            {
                response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
                return(SCSIStatusCodeName.CheckCondition);
            }

            Disk disk        = m_disks[lun];
            int  sectorCount = (int)command.TransferLength;

            try
            {
                lock (IOLock)
                {
                    response = disk.ReadSectors((long)command.LogicalBlockAddress64, sectorCount);
                }
                return(SCSIStatusCodeName.Good);
            }
            catch (ArgumentOutOfRangeException)
            {
                ISCSIServer.Log("[Read] Read error: LBA out of range");
                response = FormatSenseData(SenseDataParameter.GetIllegalRequestLBAOutOfRangeSenseData());
                return(SCSIStatusCodeName.CheckCondition);
            }
            catch (IOException ex)
            {
                int error = Marshal.GetHRForException(ex);
                if (error == (int)Win32Error.ERROR_CRC)
                {
                    ISCSIServer.Log("[Read] Read error: CRC error");
                    response = FormatSenseData(SenseDataParameter.GetWriteFaultSenseData());
                    return(SCSIStatusCodeName.CheckCondition);
                }
                else
                {
                    ISCSIServer.Log("[Read] Read error: {0}", ex.ToString());
                    response = FormatSenseData(SenseDataParameter.GetMediumErrorUnrecoverableReadErrorSenseData());
                    return(SCSIStatusCodeName.CheckCondition);
                }
            }
        }
Esempio n. 4
0
        public SCSIStatusCodeName Write(SCSICommandDescriptorBlock command, LUNStructure lun, byte[] data, out byte[] response)
        {
            if (lun >= m_disks.Count)
            {
                response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
                return(SCSIStatusCodeName.CheckCondition);
            }

            Disk disk = m_disks[lun];

            if (disk.IsReadOnly)
            {
                SenseDataParameter senseData = SenseDataParameter.GetDataProtectSenseData();
                response = senseData.GetBytes();
                return(SCSIStatusCodeName.CheckCondition);
            }

            try
            {
                lock (IOLock)
                {
                    disk.WriteSectors((long)command.LogicalBlockAddress64, data);
                }
                response = new byte[0];
                return(SCSIStatusCodeName.Good);
            }
            catch (ArgumentOutOfRangeException)
            {
                ISCSIServer.Log("[Write] Write error: LBA out of range");
                response = FormatSenseData(SenseDataParameter.GetIllegalRequestLBAOutOfRangeSenseData());
                return(SCSIStatusCodeName.CheckCondition);
            }
            catch (IOException ex)
            {
                ISCSIServer.Log("[Write] Write error: {0}", ex.ToString());
                response = FormatSenseData(SenseDataParameter.GetMediumErrorWriteFaultSenseData());
                return(SCSIStatusCodeName.CheckCondition);
            }
        }
Esempio n. 5
0
        public void ProcessPDU(ISCSIPDU pdu, StateObject state)
        {
            Socket clientSocket = state.ClientSocket;

            uint?cmdSN = PDUHelper.GetCmdSN(pdu);

            Log("[{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("[{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("[{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("[{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("[{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("[{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("[{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.Close();
            }
            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.Close(); // 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("[{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;
                    ISCSIServer.Log("[{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;
                    ISCSIServer.Log("[{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("[{0}][ProcessPDU] Unsupported command, OpCode: 0x{1}", state.ConnectionIdentifier, pdu.OpCode.ToString("x"));
                }
            }
        }
Esempio n. 6
0
 public SCSIStatusCodeName ExecuteCommand(SCSICommandDescriptorBlock command, LUNStructure lun, byte[] data, out byte[] response)
 {
     ISCSIServer.Log("[ExecuteCommand] {0}", command.OpCode);
     if (command.OpCode == SCSIOpCodeName.TestUnitReady)
     {
         return(TestUnitReady(lun, out response));
     }
     else if (command.OpCode == SCSIOpCodeName.RequestSense)
     {
         return(RequestSense(lun, out response));
     }
     else if (command.OpCode == SCSIOpCodeName.Inquiry)
     {
         return(Inquiry((InquiryCommand)command, lun, out response));
     }
     else if (command.OpCode == SCSIOpCodeName.Reserve6)
     {
         return(Reserve6(lun, out response));
     }
     else if (command.OpCode == SCSIOpCodeName.Release6)
     {
         return(Release6(lun, out response));
     }
     else if (command.OpCode == SCSIOpCodeName.ModeSense6)
     {
         return(ModeSense6((ModeSense6CommandDescriptorBlock)command, lun, out response));
     }
     else if (command.OpCode == SCSIOpCodeName.ReadCapacity10)
     {
         return(ReadCapacity10(lun, out response));
     }
     else if (command.OpCode == SCSIOpCodeName.Read6 ||
              command.OpCode == SCSIOpCodeName.Read10 ||
              command.OpCode == SCSIOpCodeName.Read16)
     {
         return(Read(command, lun, out response));
     }
     else if (command.OpCode == SCSIOpCodeName.Write6 ||
              command.OpCode == SCSIOpCodeName.Write10 ||
              command.OpCode == SCSIOpCodeName.Write16)
     {
         return(Write(command, lun, data, out response));
     }
     else if (command.OpCode == SCSIOpCodeName.Verify10 ||
              command.OpCode == SCSIOpCodeName.Verify16)
     {
         return(Verify(lun, out response));
     }
     else if (command.OpCode == SCSIOpCodeName.SynchronizeCache10)
     {
         return(SynchronizeCache10(lun, out response));
     }
     else if (command.OpCode == SCSIOpCodeName.ServiceActionIn &&
              command.ServiceAction == ServiceAction.ReadCapacity16)
     {
         uint allocationLength = command.TransferLength;
         return(ReadCapacity16(lun, allocationLength, out response));
     }
     else if (command.OpCode == SCSIOpCodeName.ReportLUNs)
     {
         return(ReportLUNs(out response));
     }
     else
     {
         ISCSIServer.Log("[ExecuteCommand] Unsupported SCSI Command (0x{0})", command.OpCode.ToString("X"));
         response = FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
         return(SCSIStatusCodeName.CheckCondition);
     }
 }
Esempio n. 7
0
        public SCSIStatusCodeName ModeSense6(ModeSense6CommandDescriptorBlock command, LUNStructure lun, out byte[] response)
        {
            if (lun >= m_disks.Count)
            {
                response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
                return(SCSIStatusCodeName.CheckCondition);
            }

            ISCSIServer.Log("[ModeSense6] Page code: 0x{0}, Sub page code: 0x{1}", command.PageCode.ToString("X"), command.SubpageCode.ToString("X"));
            byte[] pageData;

            switch ((ModePageCodeName)command.PageCode)
            {
            case ModePageCodeName.CachingParametersPage:
            {
                CachingParametersPage page = new CachingParametersPage();
                page.RCD = true;
                pageData = page.GetBytes();
                break;
            }

            case ModePageCodeName.ControlModePage:
            {
                ControlModePage page = new ControlModePage();
                pageData = page.GetBytes();
                break;
            }

            case ModePageCodeName.PowerConditionModePage:
            {
                if (command.SubpageCode == 0x00)
                {
                    PowerConditionModePage page = new PowerConditionModePage();
                    pageData = page.GetBytes();
                    break;
                }
                else if (command.SubpageCode == 0x01)
                {
                    PowerConsumptionModePage page = new PowerConsumptionModePage();
                    pageData = page.GetBytes();
                    break;
                }
                else
                {
                    response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidFieldInCDBSenseData());
                    ISCSIServer.Log("[ModeSense6] Power condition subpage 0x{0} is not implemented", command.SubpageCode.ToString("x"));
                    return(SCSIStatusCodeName.CheckCondition);
                }
            }

            case ModePageCodeName.InformationalExceptionsControlModePage:
            {
                InformationalExceptionsControlModePage page = new InformationalExceptionsControlModePage();
                pageData = page.GetBytes();
                break;
            }

            case ModePageCodeName.ReturnAllPages:
            {
                CachingParametersPage page1 = new CachingParametersPage();
                page1.RCD = true;
                InformationalExceptionsControlModePage page2 = new InformationalExceptionsControlModePage();

                pageData = new byte[page1.Length + page2.Length];
                Array.Copy(page1.GetBytes(), pageData, page1.Length);
                Array.Copy(page2.GetBytes(), 0, pageData, page1.Length, page2.Length);
                break;
            }

            case ModePageCodeName.VendorSpecificPage:
            {
                // Microsoft iSCSI Target running under Windows 2000 will request this page, we immitate Microsoft iSCSI Target by sending back an empty page
                VendorSpecificPage page = new VendorSpecificPage();
                pageData = page.GetBytes();
                break;
            }

            default:
            {
                response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidFieldInCDBSenseData());
                ISCSIServer.Log("[ModeSense6] ModeSense6 page 0x{0} is not implemented", command.PageCode.ToString("x"));
                return(SCSIStatusCodeName.CheckCondition);
            }
            }

            ModeParameterHeader6 header = new ModeParameterHeader6();

            header.WP     = m_disks[lun].IsReadOnly; // Write protected, even when set to true, Windows does not always prevent the disk from being written to.
            header.DPOFUA = true;                    // Microsoft iSCSI Target support this
            byte[] descriptorBytes = new byte[0];
            if (!command.DBD)
            {
                ShortLBAModeParameterBlockDescriptor descriptor = new ShortLBAModeParameterBlockDescriptor();
                descriptor.LogicalBlockLength = (uint)m_disks[lun].BytesPerSector;
                descriptorBytes = descriptor.GetBytes();
            }
            header.BlockDescriptorLength = (byte)descriptorBytes.Length;
            header.ModeDataLength       += (byte)(descriptorBytes.Length + pageData.Length);

            response = new byte[1 + header.ModeDataLength];
            Array.Copy(header.GetBytes(), 0, response, 0, header.Length);
            Array.Copy(descriptorBytes, 0, response, header.Length, descriptorBytes.Length);
            Array.Copy(pageData, 0, response, header.Length + descriptorBytes.Length, pageData.Length);
            return(SCSIStatusCodeName.Good);
        }
Esempio n. 8
0
        public SCSIStatusCodeName Inquiry(InquiryCommand command, LUNStructure lun, out byte[] response)
        {
            if (lun >= m_disks.Count)
            {
                response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
                return(SCSIStatusCodeName.CheckCondition);
            }

            if (!command.EVPD)
            {
                if ((int)command.PageCode == 0)
                {
                    StandardInquiryData inquiryData = new StandardInquiryData();
                    inquiryData.PeripheralDeviceType  = 0; // Direct access block device
                    inquiryData.VendorIdentification  = "TalAloni";
                    inquiryData.ProductIdentification = "iSCSI Disk " + ((ushort)lun).ToString();
                    inquiryData.ProductRevisionLevel  = "1.00";
                    inquiryData.DriveSerialNumber     = 0;
                    inquiryData.CmdQue  = true;
                    inquiryData.Version = 5; // Microsoft iSCSI Target report version 5
                    if (this is ISCSITarget)
                    {
                        inquiryData.VersionDescriptors.Add(0x0960); // iSCSI
                    }
                    response = inquiryData.GetBytes();
                }
                else
                {
                    ISCSIServer.Log("[Inquiry] Invalid request");
                    response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidFieldInCDBSenseData());
                    return(SCSIStatusCodeName.CheckCondition);
                }
            }
            else
            {
                ISCSIServer.Log("[Inquiry] Page code: 0x{0}", command.PageCode.ToString("X"));
                switch (command.PageCode)
                {
                case VitalProductDataPageName.SupportedVPDPages:
                {
                    SupportedVitaLProductDataPages page = new SupportedVitaLProductDataPages();
                    page.SupportedPageList.Add((byte)VitalProductDataPageName.SupportedVPDPages);
                    page.SupportedPageList.Add((byte)VitalProductDataPageName.UnitSerialNumber);
                    page.SupportedPageList.Add((byte)VitalProductDataPageName.DeviceIdentification);
                    response = page.GetBytes();
                    break;
                }

                case VitalProductDataPageName.UnitSerialNumber:
                {
                    UnitSerialNumberVPDPage page = new UnitSerialNumberVPDPage();
                    // Older products that only support the Product Serial Number parameter will have a page length of 08h, while newer products that support both parameters (Vendor Unique field from the StandardInquiryData) will have a page length of 14h
                    // Microsoft iSCSI Target uses values such as "34E5A6FC-3ACC-452D-AEDA-6EE2EFF20FB4"
                    ulong serialNumber = 0;
                    page.ProductSerialNumber = serialNumber.ToString("00000000");
                    response = page.GetBytes();
                    break;
                }

                case VitalProductDataPageName.DeviceIdentification:
                {
                    DeviceIdentificationVPDPage page = new DeviceIdentificationVPDPage();
                    // NAA identifier is needed to prevent 0xF4 BSOD during Windows setup
                    page.IdentificationDescriptorList.Add(IdentificationDescriptor.GetNAAExtendedIdentifier(5, lun));
                    if (this is ISCSITarget)
                    {
                        // ISCSI identifier is needed for WinPE to pick up the disk during boot (after iPXE's sanhook)
                        page.IdentificationDescriptorList.Add(IdentificationDescriptor.GetSCSINameStringIdentifier(((ISCSITarget)this).TargetName));
                    }
                    response = page.GetBytes();
                    break;
                }

                case VitalProductDataPageName.BlockLimits:
                {
                    /* Provide only when requeste explicitly */
                    BlockLimitsVPDPage page = new BlockLimitsVPDPage();
                    page.OptimalTransferLengthGranularity = 128;
                    page.MaximumTransferLength            = (uint)DiskAccessLibrary.Settings.MaximumTransferSizeLBA;
                    page.OptimalTransferLength            = 128;
                    response = page.GetBytes();
                    break;
                }

                case VitalProductDataPageName.BlockDeviceCharacteristics:
                {
                    /* Provide only when requeste explicitly */
                    BlockDeviceCharacteristicsVPDPage page = new BlockDeviceCharacteristicsVPDPage();
                    page.MediumRotationRate = 0;         // Not reported
                    response = page.GetBytes();
                    break;
                }

                default:
                {
                    response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidFieldInCDBSenseData());
                    ISCSIServer.Log("[Inquiry] Unsupported VPD Page request (0x{0})", command.PageCode.ToString("X"));
                    return(SCSIStatusCodeName.CheckCondition);
                }
                }
            }

            // we must not return more bytes than InquiryCommand.AllocationLength
            if (response.Length > command.AllocationLength)
            {
                response = ByteReader.ReadBytes(response, 0, command.AllocationLength);
            }
            return(SCSIStatusCodeName.Good);
        }