Jelajahi Sumber

iSCSI Console v1.2.5

Tal Aloni 8 tahun lalu
induk
melakukan
18468d267b

+ 23 - 13
ISCSI/Client/ClientHelper.cs

@@ -229,9 +229,11 @@ namespace ISCSI.Client
 
         internal static SCSICommandPDU GetReportLUNsCommand(SessionParameters session, ConnectionParameters connection, uint allocationLength)
         {
+            SCSICommandDescriptorBlock reportLUNs = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.ReportLUNs);
+            reportLUNs.TransferLength = allocationLength;
+            
             SCSICommandPDU scsiCommand = new SCSICommandPDU();
-            scsiCommand.CommandDescriptorBlock = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.ReportLUNs);
-            scsiCommand.CommandDescriptorBlock.TransferLength = allocationLength;
+            scsiCommand.CommandDescriptorBlock = reportLUNs.GetBytes();
             scsiCommand.InitiatorTaskTag = session.GetNextTaskTag();
             scsiCommand.Final = true;
             scsiCommand.Read = true;
@@ -242,9 +244,11 @@ namespace ISCSI.Client
 
         internal static SCSICommandPDU GetReadCapacity10Command(SessionParameters session, ConnectionParameters connection, ushort LUN)
         {
+            SCSICommandDescriptorBlock readCapacity10 = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.ReadCapacity10);
+            readCapacity10.TransferLength = ReadCapacity10Parameter.Length;
+
             SCSICommandPDU scsiCommand = new SCSICommandPDU();
-            scsiCommand.CommandDescriptorBlock = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.ReadCapacity10);
-            scsiCommand.CommandDescriptorBlock.TransferLength = ReadCapacity10Parameter.Length;
+            scsiCommand.CommandDescriptorBlock = readCapacity10.GetBytes();
             scsiCommand.InitiatorTaskTag = session.GetNextTaskTag();
             scsiCommand.Final = true;
             scsiCommand.Read = true;
@@ -256,10 +260,12 @@ namespace ISCSI.Client
 
         internal static SCSICommandPDU GetReadCapacity16Command(SessionParameters session, ConnectionParameters connection, ushort LUN)
         {
+            SCSICommandDescriptorBlock serviceActionIn = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.ServiceActionIn);
+            serviceActionIn.ServiceAction = ServiceAction.ReadCapacity16;
+            serviceActionIn.TransferLength = ReadCapacity16Parameter.Length;
+
             SCSICommandPDU scsiCommand = new SCSICommandPDU();
-            scsiCommand.CommandDescriptorBlock = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.ServiceActionIn);
-            scsiCommand.CommandDescriptorBlock.ServiceAction = ServiceAction.ReadCapacity16;
-            scsiCommand.CommandDescriptorBlock.TransferLength = ReadCapacity16Parameter.Length;
+            scsiCommand.CommandDescriptorBlock = serviceActionIn.GetBytes();
             scsiCommand.InitiatorTaskTag = session.GetNextTaskTag();
             scsiCommand.Final = true;
             scsiCommand.Read = true;
@@ -271,10 +277,12 @@ namespace ISCSI.Client
 
         internal static SCSICommandPDU GetRead16Command(SessionParameters session, ConnectionParameters connection, ushort LUN, ulong sectorIndex, uint sectorCount, int bytesPerSector)
         {
+            SCSICommandDescriptorBlock read16 = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.Read16);
+            read16.LogicalBlockAddress64 = sectorIndex;
+            read16.TransferLength = sectorCount;
+
             SCSICommandPDU scsiCommand = new SCSICommandPDU();
-            scsiCommand.CommandDescriptorBlock = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.Read16);
-            scsiCommand.CommandDescriptorBlock.LogicalBlockAddress64 = sectorIndex;
-            scsiCommand.CommandDescriptorBlock.TransferLength = sectorCount;
+            scsiCommand.CommandDescriptorBlock = read16.GetBytes();
             scsiCommand.LUN = LUN;
             scsiCommand.InitiatorTaskTag = session.GetNextTaskTag();
             scsiCommand.Final = true;
@@ -286,10 +294,12 @@ namespace ISCSI.Client
 
         internal static SCSICommandPDU GetWrite16Command(SessionParameters session, ConnectionParameters connection, ushort LUN, ulong sectorIndex, byte[] data, int bytesPerSector)
         {
+            SCSICommandDescriptorBlock write16 = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.Write16);
+            write16.LogicalBlockAddress64 = sectorIndex;
+            write16.TransferLength = (uint)(data.Length / bytesPerSector);
+
             SCSICommandPDU scsiCommand = new SCSICommandPDU();
-            scsiCommand.CommandDescriptorBlock = SCSICommandDescriptorBlock.Create(SCSIOpCodeName.Write16);
-            scsiCommand.CommandDescriptorBlock.LogicalBlockAddress64 = sectorIndex;
-            scsiCommand.CommandDescriptorBlock.TransferLength = (uint)(data.Length / bytesPerSector);
+            scsiCommand.CommandDescriptorBlock = write16.GetBytes();
             if (session.ImmediateData)
             {
                 int immediateDataLength = Math.Min(data.Length, session.FirstBurstLength);

+ 5 - 1
ISCSI/ISCSI.csproj

@@ -40,6 +40,7 @@
     <Compile Include="Client\SessionParameters.cs" />
     <Compile Include="Client\StateObject.cs" />
     <Compile Include="Client\ISCSIClient.cs" />
+    <Compile Include="PDU\Enums\ISCSIResponseName.cs" />
     <Compile Include="PDU\Enums\LogoutReasonCode.cs" />
     <Compile Include="PDU\Enums\LogoutResponse.cs" />
     <Compile Include="PDU\Enums\RejectReason.cs" />
@@ -48,8 +49,12 @@
     <Compile Include="SCSI\Enums\AddressingMethod.cs" />
     <Compile Include="SCSI\LUNStructure.cs" />
     <Compile Include="SCSI\SCSIReturnParameters\ModePages\ControlModePage.cs" />
+    <Compile Include="SCSI\SCSIReturnParameters\ModePages\PowerConditionModePage.cs" />
+    <Compile Include="SCSI\SCSIReturnParameters\ModePages\PowerConsumptionModePage.cs" />
+    <Compile Include="SCSI\SCSIReturnParameters\ModePages\SubModePage.cs" />
     <Compile Include="SCSI\SCSIReturnParameters\VPDPages\BlockLimitsVPDPage.cs" />
     <Compile Include="SCSI\SCSIReturnParameters\VPDPages\BlockDeviceCharacteristicsVPDPage.cs" />
+    <Compile Include="SCSI\SCSIReturnParameters\VPDPages\Enums\IdentifierTypeName.cs" />
     <Compile Include="Server\ConnectionParameters.cs" />
     <Compile Include="PDU\Enums\ISCSIOpCodeName.cs" />
     <Compile Include="PDU\Enums\LoginResponseStatusClassName.cs" />
@@ -63,7 +68,6 @@
     <Compile Include="Server\SCSITarget.cs" />
     <Compile Include="Server\SessionParameters.cs" />
     <Compile Include="Server\ISCSITarget.cs" />
-    <Compile Include="ISCSITester.cs" />
     <Compile Include="KeyValuePairUtils.cs" />
     <Compile Include="PDU\ISCSIPDU.cs" />
     <Compile Include="PDU\LoginRequestPDU.cs" />

+ 0 - 110
ISCSI/ISCSITester.cs

@@ -1,110 +0,0 @@
-/* Copyright (C) 2012-2015 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
- * 
- * You can redistribute this program and/or modify it under the terms of
- * the GNU Lesser Public License as published by the Free Software Foundation,
- * either version 3 of the License, or (at your option) any later version.
- */
-using System;
-using System.Collections.Generic;
-using System.Net.Sockets;
-using System.Text;
-using Utilities;
-using ISCSI.Server;
-
-namespace ISCSI
-{
-    public class ISCSITester
-    {
-        private Socket m_targetSocket;
-        private Socket m_initiatorSocket;
-        private bool m_waitingForResponse = false;
-        public List<ISCSIPDU> m_pduSent = new List<ISCSIPDU>();
-        public List<ISCSIPDU> m_pduReceived = new List<ISCSIPDU>();
-        private bool m_isTargetConnected = false;
-
-        public ISCSITester(Socket initiatorSocket)
-        {
-            m_initiatorSocket = initiatorSocket;
-            
-        }
-
-        public void Transmit(byte[] data)
-        {
-            ISCSIPDU pdu = ISCSIPDU.GetPDU(data);
-            if (pdu is LoginRequestPDU && !m_isTargetConnected)
-            {
-                m_isTargetConnected = true;
-                m_targetSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
-                m_targetSocket.Connect("tal2", 3260);
-            }
-
-            //m_pduToSend.Add(data);
-            //if (!m_waitingForResponse)
-            //{
-                //m_waitingForResponse = true;
-                //byte[] toSend = m_pduToSend[0];
-                //m_pduToSend.RemoveAt(0);
-                StateObject state = new StateObject();
-                state.ReceiveBuffer = new byte[StateObject.ReceiveBufferSize];
-                m_targetSocket.BeginReceive(state.ReceiveBuffer, 0, StateObject.ReceiveBufferSize, 0, ReceiveCallback, state);
-
-                Console.WriteLine("waiting for respone: " + m_waitingForResponse.ToString());
-                m_targetSocket.Send(data);
-                m_pduSent.Add(ISCSIPDU.GetPDU(data));
-                Console.WriteLine("Transmitted PDU to real target");
-            //}
-            
-        }
-
-        private void ReceiveCallback(IAsyncResult result)
-        {
-            StateObject state = (StateObject)result.AsyncState;
-            //Socket clientSocket = state.WorkerSocket;
-            byte[] buffer = state.ReceiveBuffer;
-
-            int bytesReceived;
-            
-            try
-            {
-                bytesReceived = m_targetSocket.EndReceive(result);
-                m_waitingForResponse = false;
-            }
-            catch (Exception)
-            {
-                //An error has occured when reading
-                Console.WriteLine("An error has occured when reading from real target");
-                return;
-            }
-
-            if (bytesReceived == 0)
-            {
-                //The connection has been closed.
-                Console.WriteLine("The connection with the real target has been closed");
-                return;
-            }
-
-            //iSCSIPDU pdu1 = GetResponcePDU((LoginRequestPDU)pdu);
-
-            Console.WriteLine("Received PDU from real target");
-            byte[] pduBytes = new byte[bytesReceived];
-            Array.Copy(buffer, pduBytes, bytesReceived);
-
-            ISCSIPDU pdu = ISCSIPDU.GetPDU(pduBytes);
-            m_pduReceived.Add(pdu);
-
-            m_initiatorSocket.Send(pduBytes);
-            Console.WriteLine("Sent PDU to real initiator, OpCode: " + pdu.OpCode);
-            if (pdu is LogoutResponsePDU)
-            {
-                m_initiatorSocket.Close();
-                m_targetSocket.Close();
-                m_isTargetConnected = false;
-                return;
-            }
-            
-            //Do something with the data object here.
-            //Then start reading from the network again.
-            m_targetSocket.BeginReceive(state.ReceiveBuffer, 0, StateObject.ReceiveBufferSize, 0, ReceiveCallback, state);
-        }
-    }
-}

+ 0 - 3
ISCSI/PDU/Enums/ISCSIOpCodeName.cs

@@ -1,6 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
 
 namespace ISCSI
 {

+ 9 - 0
ISCSI/PDU/Enums/ISCSIResponseName.cs

@@ -0,0 +1,9 @@
+
+namespace ISCSI
+{
+    public enum ISCSIResponseName : byte
+    {
+        CommandCompletedAtTarget = 0x00,
+        TargetFailure = 0x01,
+    }
+}

+ 4 - 12
ISCSI/PDU/SCSICommandPDU.cs

@@ -20,18 +20,14 @@ namespace ISCSI
         public uint ExpectedDataTransferLength; // in bytes (for the whole operation and not just this command)
         public uint CmdSN;
         public uint ExpStatSN;
-        public SCSICommandDescriptorBlock CommandDescriptorBlock;
+        public byte[] CommandDescriptorBlock;
 
         public SCSICommandPDU() : base()
         {
             OpCode = ISCSIOpCodeName.SCSICommand;
         }
 
-        public SCSICommandPDU(byte[] buffer) : this(buffer, true)
-        {
-        }
-
-        public SCSICommandPDU(byte[] buffer, bool parseCDB) : base(buffer)
+        public SCSICommandPDU(byte[] buffer) : base(buffer)
         {
             Read = (OpCodeSpecificHeader[0] & 0x40) != 0;
             Write = (OpCodeSpecificHeader[0] & 0x20) != 0;
@@ -43,10 +39,7 @@ namespace ISCSI
             CmdSN = BigEndianConverter.ToUInt32(OpCodeSpecific, 4);
             ExpStatSN = BigEndianConverter.ToUInt32(OpCodeSpecific, 8);
 
-            if (parseCDB)
-            {
-                CommandDescriptorBlock = SCSICommandDescriptorBlock.FromBytes(OpCodeSpecific, 12);
-            }
+            CommandDescriptorBlock = ByteReader.ReadBytes(OpCodeSpecific, 12, 16);
         }
 
         public override byte[] GetBytes()
@@ -66,8 +59,7 @@ namespace ISCSI
             Array.Copy(BigEndianConverter.GetBytes(ExpectedDataTransferLength), 0, OpCodeSpecific, 0, 4);
             Array.Copy(BigEndianConverter.GetBytes(CmdSN), 0, OpCodeSpecific, 4, 4);
             Array.Copy(BigEndianConverter.GetBytes(ExpStatSN), 0, OpCodeSpecific, 8, 4);
-            byte[] cdbBytes = CommandDescriptorBlock.GetBytes();
-            Array.Copy(cdbBytes, 0, OpCodeSpecific, 12, cdbBytes.Length);
+            Array.Copy(CommandDescriptorBlock, 0, OpCodeSpecific, 12, CommandDescriptorBlock.Length);
             
             return base.GetBytes();
         }

+ 3 - 3
ISCSI/PDU/SCSIResponsePDU.cs

@@ -17,7 +17,7 @@ namespace ISCSI
         public bool BidirectionalReadResidualUnderflow;
         public bool ResidualOverflow;
         public bool ResidualUnderflow;
-        public byte Response;
+        public ISCSIResponseName Response;
         public SCSIStatusCodeName Status;
         public uint SNACKTag;
         public uint StatSN;
@@ -39,7 +39,7 @@ namespace ISCSI
             BidirectionalReadResidualUnderflow = (OpCodeSpecificHeader[0] & 0x08) != 0;
             ResidualOverflow = (OpCodeSpecificHeader[0] & 0x04) != 0;
             ResidualUnderflow = (OpCodeSpecificHeader[0] & 0x02) != 0;
-            Response = OpCodeSpecificHeader[1];
+            Response = (ISCSIResponseName)OpCodeSpecificHeader[1];
             Status = (SCSIStatusCodeName)OpCodeSpecificHeader[2];
 
             SNACKTag = BigEndianConverter.ToUInt32(OpCodeSpecific, 0);
@@ -69,7 +69,7 @@ namespace ISCSI
             {
                 OpCodeSpecificHeader[0] |= 0x02;
             }
-            OpCodeSpecificHeader[1] = Response;
+            OpCodeSpecificHeader[1] = (byte)Response;
             OpCodeSpecificHeader[2] = (byte)Status;
 
             Array.Copy(BigEndianConverter.GetBytes(SNACKTag), 0, OpCodeSpecific, 0, 4);

+ 2 - 2
ISCSI/Properties/AssemblyInfo.cs

@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
 //
 // You can specify all the values or you can default the Revision and Build Numbers 
 // by using the '*' as shown below:
-[assembly: AssemblyVersion("1.2.4.0")]
-[assembly: AssemblyFileVersion("1.2.4.0")]
+[assembly: AssemblyVersion("1.2.5.0")]
+[assembly: AssemblyFileVersion("1.2.5.0")]

+ 2 - 0
ISCSI/RevisionHistory.txt

@@ -52,3 +52,5 @@ Revision History:
 		Bugfix: Unsupported SCSI commands are now properly handled.
 
 1.2.4 - Improved separation between the iSCSI and SCSI layers.
+
+1.2.5 - Improved compliance with the iSCSI and SCSI protocols.

+ 1 - 0
ISCSI/SCSI/Enums/ModePageCodeName.cs

@@ -6,6 +6,7 @@ namespace ISCSI
         VendorSpecificPage = 0x00, // Microsoft iSCSI initiator on Windows 2000 will request this page
         CachingParametersPage = 0x08,
         ControlModePage = 0x0A,
+        PowerConditionModePage = 0x1A,
         InformationalExceptionsControlModePage = 0x1C,
         ReturnAllPages = 0x3F,
     }

+ 5 - 0
ISCSI/SCSI/Enums/SCSIOpCodeName.cs

@@ -8,6 +8,7 @@ namespace ISCSI
         Read6 = 0x08,
         Write6 = 0x0A,
         Inquiry = 0x12,
+        ModeSelect6 = 0x15,
         Reserve6 = 0x16,
         Release6 = 0x17,
         ModeSense6 = 0x1A,
@@ -19,6 +20,10 @@ namespace ISCSI
         ReadBuffer = 0x3C,
         SynchronizeCache10 = 0x35,
         WriteSame10 = 0x41,
+        ModeSelect10 = 0x15,
+        ModeSense10 = 0x5A,
+        PersistentReserveIn = 0x5E,
+        PersistentReserveOut = 0x5F,
         Read16 = 0x88,
         Write16 = 0x8A,
         Verify16 = 0x8F,

+ 1 - 1
ISCSI/SCSI/SCSICommandDescriptorBlock/InquiryCommandDescriptorBlock.cs

@@ -13,7 +13,7 @@ namespace ISCSI
 {
     public class InquiryCommand : SCSICommandDescriptorBlock
     {
-        public bool EVPD;
+        public bool EVPD; // Enable Vital Product Data
         public VitalProductDataPageName PageCode;
 
         public InquiryCommand() : base()

+ 1 - 1
ISCSI/SCSI/SCSICommandDescriptorBlock/ModeSense6CommandDescriptorBlock.cs

@@ -13,7 +13,7 @@ namespace ISCSI
 {
     public class ModeSense6CommandDescriptorBlock : SCSICommandDescriptorBlock
     {
-        public bool DBD;
+        public bool DBD; // Disable block descriptors
         public byte PC; // Page Control
         public ModePageCodeName PageCode;
         public byte SubpageCode;

+ 1 - 1
ISCSI/SCSI/SCSIReturnParameters/ModePages/ControlModePage.cs

@@ -58,7 +58,7 @@ namespace ISCSI
             BigEndianWriter.WriteUInt16(buffer, 8, Obsolete2);
             BigEndianWriter.WriteUInt16(buffer, 10, Obsolete3);
 
-            return base.GetBytes();
+            return buffer;
         }
     }
 }

+ 53 - 0
ISCSI/SCSI/SCSIReturnParameters/ModePages/PowerConditionModePage.cs

@@ -0,0 +1,53 @@
+/* Copyright (C) 2012-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+ * 
+ * You can redistribute this program and/or modify it under the terms of
+ * the GNU Lesser Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ */
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Utilities;
+
+namespace ISCSI
+{
+    public class PowerConditionModePage : ModePage0
+    {
+        public byte PmBgPrecedence;
+        public bool StandbyY;
+        public bool IdleC;
+        public bool IdleB;
+        public bool IdleA;
+        public bool StandbyZ;
+        public uint IdleATimer;
+        public uint StandbyZTimer;
+        public uint IdleBTimer;
+        public uint IdleCTimer;
+        public uint StandbyYTimer;
+        public byte CcfIdle;
+        public byte CcfStandby;
+        public byte CcfStopped;
+
+        public PowerConditionModePage() : base(ModePageCodeName.PowerConditionModePage, 38)
+        { }
+
+        public PowerConditionModePage(byte[] buffer, int offset) : base(buffer, offset)
+        {
+            throw new NotImplementedException();
+        }
+
+        public override byte[] GetBytes()
+        {
+            byte[] buffer = base.GetBytes();
+            buffer[2] = (byte)(PmBgPrecedence << 5 | (Convert.ToByte(StandbyY) & 0x01));
+            buffer[3] = (byte)((Convert.ToByte(IdleC) & 0x01) << 4 | (Convert.ToByte(IdleB) & 0x01) << 4 | (Convert.ToByte(IdleA) & 0x01) << 4 | (Convert.ToByte(StandbyZ) & 0x01));
+            BigEndianWriter.WriteUInt32(buffer, 4, IdleATimer);
+            BigEndianWriter.WriteUInt32(buffer, 8, StandbyZTimer);
+            BigEndianWriter.WriteUInt32(buffer, 12, IdleBTimer);
+            BigEndianWriter.WriteUInt32(buffer, 16, IdleCTimer);
+            BigEndianWriter.WriteUInt32(buffer, 20, StandbyYTimer);
+            buffer[39] = (byte)((Convert.ToByte(CcfIdle) & 0x03) << 6 | (Convert.ToByte(CcfStandby) & 0x01) << 4 | (Convert.ToByte(CcfStopped) & 0x01) << 2);
+            return buffer;
+        }
+    }
+}

+ 35 - 0
ISCSI/SCSI/SCSIReturnParameters/ModePages/PowerConsumptionModePage.cs

@@ -0,0 +1,35 @@
+/* Copyright (C) 2012-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+ * 
+ * You can redistribute this program and/or modify it under the terms of
+ * the GNU Lesser Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ */
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Utilities;
+
+namespace ISCSI
+{
+    public class PowerConsumptionModePage : SubModePage
+    {
+        public byte PowerConsumptionIdentifier;
+
+        public PowerConsumptionModePage() : base(ModePageCodeName.PowerConditionModePage, 0x01, 12)
+        {
+        }
+
+        public PowerConsumptionModePage(byte[] buffer, int offset) : base(buffer, offset)
+        {
+            PowerConsumptionIdentifier = buffer[offset + 7];
+        }
+
+        public override byte[] GetBytes()
+        {
+            byte[] buffer = base.GetBytes();
+            buffer[7] = PowerConsumptionIdentifier;
+
+            return buffer;
+        }
+    }
+}

+ 63 - 0
ISCSI/SCSI/SCSIReturnParameters/ModePages/SubModePage.cs

@@ -0,0 +1,63 @@
+/* Copyright (C) 2012-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
+ * 
+ * You can redistribute this program and/or modify it under the terms of
+ * the GNU Lesser Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ */
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Utilities;
+
+namespace ISCSI
+{
+    public class SubModePage : ModePage // SUB_PAGE mode page format
+    {
+        public bool PS;    // Parameter Savable
+        public bool SPF;   // SubPage Format
+        public ModePageCodeName PageCode;
+        public byte SubPageCode;
+        public ushort PageLength; // excluding this and previous bytes
+
+        protected SubModePage(ModePageCodeName pageCode, byte subPageCode, ushort pageLength)
+        {
+            PageCode = pageCode;
+            PageLength = pageLength;
+        }
+
+        public SubModePage(byte[] buffer, int offset)
+        {
+            PS = (buffer[offset + 0] & 0x80) != 0;
+            SPF = (buffer[offset + 0] & 0x40) != 0;
+            PageCode = (ModePageCodeName)(buffer[offset + 0] & 0x3F);
+            SubPageCode = buffer[offset + 1];
+            PageLength = BigEndianConverter.ToUInt16(buffer, 2);
+        }
+
+        override public byte[] GetBytes()
+        {
+            byte[] buffer = new byte[4 + PageLength];
+            if (PS)
+            {
+                buffer[0] |= 0x80;
+            }
+            if (SPF)
+            {
+                buffer[0] |= 0x40;
+            }
+            buffer[0] |= (byte)((byte)PageCode & 0x3F);
+            buffer[1] = SubPageCode;
+            BigEndianWriter.WriteUInt16(buffer, 2, PageLength);
+
+            return buffer;
+        }
+
+        public override int Length
+        {
+            get
+            {
+                return 4 + PageLength;
+            }
+        }
+    }
+}

+ 36 - 41
ISCSI/SCSI/SCSIReturnParameters/SenseDataParameter.cs

@@ -64,96 +64,91 @@ namespace ISCSI
             return buffer;
         }
 
-        public static SenseDataParameter GetNoSenseSenseData()
+        public static SenseDataParameter GetDataProtectSenseData()
         {
             SenseDataParameter senseData = new SenseDataParameter();
             senseData.Valid = true;
             senseData.ResponseCode = 0x70; // current errors
-            senseData.SenseKey = 0x00;     // NO SENSE
-            senseData.AdditionalSenseCode = 0x00; // No Additional Sense Information
+            senseData.SenseKey = 0x07;     // DATA PROTECT
+            senseData.AdditionalSenseCode = 0x27; // Command not allowed
+            senseData.AdditionalSenseCodeQualifier = 0x00;
             return senseData;
         }
 
-        /// <summary>
-        /// Reported when CRC error is encountered
-        /// </summary>
-        public static SenseDataParameter GetWriteFaultSenseData()
+        public static SenseDataParameter GetIllegalRequestSenseData(byte additionalSenseCode, byte additionalSenseCodeQualifier)
         {
             SenseDataParameter senseData = new SenseDataParameter();
             senseData.Valid = true;
             senseData.ResponseCode = 0x70; // current errors
-            senseData.SenseKey = 0x03;     // MEDIUM ERROR
-            senseData.AdditionalSenseCode = 0x00; // Peripheral Device Write Fault
+            senseData.SenseKey = 0x05;     // ILLEGAL REQUEST
+            senseData.AdditionalSenseCode = additionalSenseCode;
+            senseData.AdditionalSenseCodeQualifier = additionalSenseCodeQualifier;
             return senseData;
         }
 
-        public static SenseDataParameter GetIllegalRequestSenseData()
+        public static SenseDataParameter GetIllegalRequestInvalidFieldInCDBSenseData()
         {
-            SenseDataParameter senseData = new SenseDataParameter();
-            senseData.Valid = true;
-            senseData.ResponseCode = 0x70; // current errors
-            senseData.SenseKey = 0x05;     // ILLEGAL REQUEST
-            senseData.AdditionalSenseCode = 0x00;
-            return senseData;
+            return GetIllegalRequestSenseData(0x24, 0x00); // Invalid field in CDB
         }
 
-        public static SenseDataParameter GetIllegalRequestUnsupportedCommandCodeSenseData()
+        public static SenseDataParameter GetIllegalRequestInvalidLUNSenseData()
         {
-            SenseDataParameter senseData = new SenseDataParameter();
-            senseData.Valid = true;
-            senseData.ResponseCode = 0x70; // current errors
-            senseData.SenseKey = 0x05;     // ILLEGAL REQUEST
-            senseData.AdditionalSenseCode = 0x20; // Invalid / unsupported command code
-            return senseData;
+            return GetIllegalRequestSenseData(0x25, 0x00); // Invalid LUN
         }
 
         public static SenseDataParameter GetIllegalRequestLBAOutOfRangeSenseData()
         {
-            SenseDataParameter senseData = new SenseDataParameter();
-            senseData.Valid = true;
-            senseData.ResponseCode = 0x70; // current errors
-            senseData.SenseKey = 0x05;     // ILLEGAL REQUEST
-            senseData.AdditionalSenseCode = 0x21; // LBA out of range
-            return senseData;
+            return GetIllegalRequestSenseData(0x21, 0x00); // LBA out of range
         }
 
-        public static SenseDataParameter GetIllegalRequestInvalidLUNSenseData()
+        public static SenseDataParameter GetIllegalRequestUnsupportedCommandCodeSenseData()
+        {
+            return GetIllegalRequestSenseData(0x20, 0x00); // Invalid / unsupported command code
+        }
+
+        public static SenseDataParameter GetMediumErrorUnrecoverableReadErrorSenseData()
         {
             SenseDataParameter senseData = new SenseDataParameter();
             senseData.Valid = true;
             senseData.ResponseCode = 0x70; // current errors
-            senseData.SenseKey = 0x05;     // ILLEGAL REQUEST
-            senseData.AdditionalSenseCode = 0x25; // Invalid LUN
+            senseData.SenseKey = 0x03;     // MEDIUM ERROR
+            senseData.AdditionalSenseCode = 0x11; // Peripheral Device Write Fault
+            senseData.AdditionalSenseCodeQualifier = 0x00;
             return senseData;
         }
 
-        public static SenseDataParameter GetIllegalRequestParameterNotSupportedSenseData()
+        public static SenseDataParameter GetMediumErrorWriteFaultSenseData()
         {
             SenseDataParameter senseData = new SenseDataParameter();
             senseData.Valid = true;
             senseData.ResponseCode = 0x70; // current errors
-            senseData.SenseKey = 0x05;     // ILLEGAL REQUEST
-            senseData.AdditionalSenseCode = 0x26; // Parameter not supported
+            senseData.SenseKey = 0x03;     // MEDIUM ERROR
+            senseData.AdditionalSenseCode = 0x03; // Peripheral Device Write Fault
+            senseData.AdditionalSenseCodeQualifier = 0x00;
             return senseData;
         }
 
-        public static SenseDataParameter GetUnitAttentionSenseData()
+        public static SenseDataParameter GetNoSenseSenseData()
         {
             SenseDataParameter senseData = new SenseDataParameter();
             senseData.Valid = true;
             senseData.ResponseCode = 0x70; // current errors
-            senseData.SenseKey = 0x06;     // UNIT ATTENTION
-            senseData.AdditionalSenseCode = 0x00;
+            senseData.SenseKey = 0x00;     // NO SENSE
+            senseData.AdditionalSenseCode = 0x00; // No Additional Sense Information
             return senseData;
         }
 
-        public static SenseDataParameter GetDataProtectSenseData()
+        /// <summary>
+        /// Reported when CRC error is encountered
+        /// </summary>
+        public static SenseDataParameter GetWriteFaultSenseData()
         {
             SenseDataParameter senseData = new SenseDataParameter();
             senseData.Valid = true;
             senseData.ResponseCode = 0x70; // current errors
-            senseData.SenseKey = 0x07;     // DATA PROTECT
-            senseData.AdditionalSenseCode = 0x00;
+            senseData.SenseKey = 0x03;     // MEDIUM ERROR
+            senseData.AdditionalSenseCode = 0x03; // Peripheral Device Write Fault
+            senseData.AdditionalSenseCodeQualifier = 0x00;
             return senseData;
         }
     }

+ 24 - 3
ISCSI/SCSI/SCSIReturnParameters/StandardInquiryData.cs

@@ -39,15 +39,24 @@ namespace ISCSI
         public bool CmdQue;  // Command Queuing
         public bool VS2;     // Vendor specific
 
+        /// <summary>
+        /// 8 characters
+        /// </summary>
         public string VendorIdentification;
+        /// <summary>
+        /// 16 characters
+        /// </summary>
         public string ProductIdentification;
+        /// <summary>
+        /// 4 characters
+        /// </summary>
         public string ProductRevisionLevel;
         public ulong DriveSerialNumber;
         // Vendor Unique
         public byte Clocking;
         public bool QAS;
         public bool IUS;
-        public byte[] VersionDescriptor = new byte[16];
+        public List<ushort> VersionDescriptors = new List<ushort>(); // 8 descriptors (16 bytes)
 
         public StandardInquiryData()
         {
@@ -99,7 +108,11 @@ namespace ISCSI
             QAS = (buffer[offset + 56] & 0x02) != 0;
             IUS = (buffer[offset + 56] & 0x01) != 0;
 
-            Array.Copy(buffer, offset + 58, VersionDescriptor, 0, 16);
+            for (int index = 0; index < 8; index++)
+            {
+                ushort versionDescriptor = BigEndianConverter.ToUInt16(buffer, offset + 58 + index * 2);
+                VersionDescriptors.Add(versionDescriptor);
+            }
         }
 
         public byte[] GetBytes()
@@ -197,7 +210,15 @@ namespace ISCSI
                 buffer[56] |= 0x01;
             }
 
-            Array.Copy(VersionDescriptor, 0, buffer, 58, 16);
+            for (int index = 0; index < 8; index++)
+            {
+                ushort versionDescriptor = 0;
+                if (index < VersionDescriptors.Count)
+                {
+                    versionDescriptor = VersionDescriptors[index];
+                }
+                BigEndianWriter.WriteUInt16(buffer, 58 + index * 2, versionDescriptor);
+            }
 
             return buffer;
         }

+ 16 - 0
ISCSI/SCSI/SCSIReturnParameters/VPDPages/Enums/IdentifierTypeName.cs

@@ -0,0 +1,16 @@
+
+namespace ISCSI
+{
+    public enum IdentifierTypeName : byte
+    {
+        VendorSpecific = 0x00,
+        T10 = 0x01, // T10 vendor identification
+        EUI64 = 0x02, // EUI-64
+        NAA = 0x03,
+        RelativeTargetPort = 0x04,
+        TargetPortGroup = 0x05,
+        LogicalUnitGroup = 0x06,
+        MD5LogicalUnitIdentifier = 0x07,
+        ScsiNameString = 0x08,
+    }
+}

+ 75 - 44
ISCSI/SCSI/SCSIReturnParameters/VPDPages/IdentificationDescriptor.cs

@@ -11,13 +11,40 @@ using Utilities;
 
 namespace ISCSI
 {
+    public enum ProtocolName : byte
+    {
+        FibreChannel = 0,
+        ParallelSCSI = 1,
+        SSA = 2,
+        IEEE1394 = 3,
+        SCSIRDMA = 4,
+        ISCSI = 5,
+        SAS = 6,
+        ADT = 7,
+        ATA = 8,
+    }
+
+    public enum CodeSetName : byte
+    {
+        Binary = 1,
+        ASCII = 2,
+        UTF8 = 3,
+    }
+
+    public enum AssociationName : byte
+    {
+        LogicalUnit = 0,
+        TargetPort = 1,
+        TargetDevice = 2,
+    }
+
     public class IdentificationDescriptor
     {
-        public byte ProtocolIdentifier;
-        public byte CodeSet;
+        public ProtocolName ProtocolIdentifier;
+        public CodeSetName CodeSet;
         public bool PIV;
-        public byte Association;
-        public byte IdentifierType;
+        public AssociationName Association;
+        public IdentifierTypeName IdentifierType;
         public byte IdentifierLength;
         public byte[] Identifier = new byte[0];
 
@@ -25,46 +52,27 @@ namespace ISCSI
         {
         }
 
-        public IdentificationDescriptor(ulong eui64Identifier)
-        {
-            CodeSet = 0x01;
-            IdentifierType = 0x02; // EUI-64
-            Identifier = BigEndianConverter.GetBytes(eui64Identifier);
-        }
-
-        public IdentificationDescriptor(ulong eui64Identifier, ulong wwn)
+        public IdentificationDescriptor(IdentifierTypeName identifierType, byte[] identifier)
         {
-            /*
-             * In Fibre Channel, the unique identity of a device is provided by a 64-bit WWN, whereas the network address is the 24-bit Fibre Channel address.
-             * The WWN convention is also accommodated by iSCSI naming as an IEEE extended unique identifier (EUI) format or “eui.”
-             * The resulting iSCSI name is simply “eui” followed by the hexidecimal WWN (for example, eui.0300732A32598D26). 
-             * */
-            CodeSet = 0x01;
-            IdentifierType = 0x03;
-            Identifier = new byte[16];
-            Array.Copy(BigEndianConverter.GetBytes(eui64Identifier), 0, Identifier, 0, 8);
-            Array.Copy(BigEndianConverter.GetBytes(wwn), 0, Identifier, 8, 8);
-        }
-
-        public IdentificationDescriptor(byte[] identifier)
-        {
-            CodeSet = 0x01; // The IDENTIFIER field shall contain binary values
+            CodeSet = CodeSetName.Binary;
+            IdentifierType = identifierType;
             Identifier = identifier;
         }
 
-        public IdentificationDescriptor(string identifier)
+        public IdentificationDescriptor(IdentifierTypeName identifierType, string identifier)
         {
-            CodeSet = 0x02; // The IDENTIFIER field shall contain ASCII graphic codes
+            CodeSet = CodeSetName.ASCII;
+            IdentifierType = identifierType;
             Identifier = ASCIIEncoding.ASCII.GetBytes(identifier);
         }
 
         public IdentificationDescriptor(byte[] buffer, int offset)
         {
-            ProtocolIdentifier = (byte)((buffer[offset + 0] >> 4) & 0x0F);
-            CodeSet = (byte)(buffer[offset + 0] & 0x0F);
+            ProtocolIdentifier = (ProtocolName)((buffer[offset + 0] >> 4) & 0x0F);
+            CodeSet = (CodeSetName)(buffer[offset + 0] & 0x0F);
             PIV = (buffer[offset + 1] & 0x80) != 0;
-            Association = (byte)((buffer[offset + 1] >> 4) & 0x03);
-            IdentifierType = (byte)(buffer[offset + 1] & 0x0F);
+            Association = (AssociationName)((buffer[offset + 1] >> 4) & 0x03);
+            IdentifierType = (IdentifierTypeName)(buffer[offset + 1] & 0x0F);
 
             IdentifierLength = buffer[offset + 3];
             Identifier = new byte[IdentifierLength];
@@ -76,15 +84,15 @@ namespace ISCSI
             IdentifierLength = (byte)Identifier.Length;
 
             byte[] buffer = new byte[4 + Identifier.Length];
-            buffer[0] |= (byte)(ProtocolIdentifier << 4);
-            buffer[0] |= (byte)(CodeSet & 0x0F);
+            buffer[0] |= (byte)((byte)ProtocolIdentifier << 4);
+            buffer[0] |= (byte)((byte)CodeSet & 0x0F);
             
             if (PIV)
             {
                 buffer[1] |= 0x80;
             }
-            buffer[1] |= (byte)((Association & 0x03) << 4);
-            buffer[1] |= (byte)(IdentifierType & 0x0F);
+            buffer[1] |= (byte)(((byte)Association & 0x03) << 4);
+            buffer[1] |= (byte)((byte)IdentifierType & 0x0F);
             buffer[3] = (byte)Identifier.Length;
             Array.Copy(Identifier, 0, buffer, 4, Identifier.Length);
 
@@ -99,15 +107,38 @@ namespace ISCSI
             }
         }
 
-        public static IdentificationDescriptor GetISCSIIdentifier(string iqn)
+        /// <param name="oui">UInt24</param>
+        public static IdentificationDescriptor GetEUI64Identifier(uint oui, uint vendorSpecific)
+        {
+            byte[] eui64 = new byte[8];
+            WriteUInt24(eui64, 0, oui);
+            // we leave byte 3 zeroed-out
+            BigEndianWriter.WriteUInt32(eui64, 4, vendorSpecific);
+            return new IdentificationDescriptor(IdentifierTypeName.EUI64, eui64);
+        }
+
+        /// <param name="oui">UInt24</param>
+        public static IdentificationDescriptor GetNAAExtendedIdentifier(uint oui, uint vendorSpecific)
         {
-            // RFC 3720: iSCSI names may be transported using both binary and ASCII-based protocols.
-            // Note: Microsoft iSCSI Target uses binary CodeSet
-            iqn += ",t,0x1"; // 't' for SCSI Target Port, 0x1 portal group tag
-            //byte[] bytes = ASCIIEncoding.ASCII.GetBytes(iqn);
-            IdentificationDescriptor result = new IdentificationDescriptor(iqn);
-            result.ProtocolIdentifier = 0x05; // iSCSI
+            byte[] identifier = new byte[8];
+            identifier[0] = 0x02 << 4;
+            WriteUInt24(identifier, 2, oui);
+            WriteUInt24(identifier, 5, vendorSpecific);
+            return new IdentificationDescriptor(IdentifierTypeName.NAA, identifier);
+        }
+
+        public static IdentificationDescriptor GetSCSINameStringIdentifier(string iqn)
+        {
+            IdentificationDescriptor result = new IdentificationDescriptor(IdentifierTypeName.ScsiNameString, iqn);
+            result.Association = AssociationName.TargetDevice;
+            result.ProtocolIdentifier = ProtocolName.ISCSI;
             return result;
         }
+
+        public static void WriteUInt24(byte[] buffer, int offset, uint value)
+        {
+            byte[] bytes = BigEndianConverter.GetBytes(value);
+            Array.Copy(bytes, 1, buffer, offset, 3);
+        }
     }
 }

+ 2 - 2
ISCSI/Server/ConnectionParameters.cs

@@ -29,11 +29,11 @@ namespace ISCSI.Server
         public int TargetMaxRecvDataSegmentLength = DeclaredMaxRecvDataSegmentLength;
 
         public uint StatSN = 0; // Initial StatSN, any number will do
-        // Dictionary of current transfers: <transfer-tag, <offset, length>>
+        // Dictionary of current transfers: <transfer-tag, <command-bytes, length>>
         // offset - logical block address (sector)
         // length - data transfer length in bytes
         // Note: here incoming means data write operations to the target
-        public Dictionary<uint, KeyValuePair<ulong, uint>> Transfers = new Dictionary<uint, KeyValuePair<ulong, uint>>();
+        public Dictionary<uint, KeyValuePair<byte[], uint>> Transfers = new Dictionary<uint, KeyValuePair<byte[], uint>>();
 
         // Dictionary of transfer data: <transfer-tag, command-data>
         public Dictionary<uint, byte[]> TransferData = new Dictionary<uint, byte[]>();

+ 12 - 25
ISCSI/Server/ISCSIServer.cs

@@ -187,7 +187,6 @@ namespace ISCSI.Server
                 return;
             }
 
-            Log("[{0}][ReceiveCallback] Received {1} bytes", state.ConnectionIdentifier, numberOfBytesReceived);
             byte[] currentBuffer = ByteReader.ReadBytes(state.ReceiveBuffer, 0, numberOfBytesReceived);
             ProcessCurrentBuffer(currentBuffer, state);
 
@@ -235,7 +234,6 @@ namespace ISCSI.Server
                 }
                 else
                 {
-                    Log("[{0}][ProcessCurrentBuffer] PDU is being processed, Length: {1}", state.ConnectionIdentifier, pduLength);
                     byte[] pduBytes = ByteReader.ReadBytes(state.ConnectionBuffer, bufferOffset, pduLength);
                     bytesLeftInBuffer -= pduLength;
                     ISCSIPDU pdu = null;
@@ -243,10 +241,6 @@ namespace ISCSI.Server
                     {
                         pdu = ISCSIPDU.GetPDU(pduBytes);
                     }
-                    catch (UnsupportedSCSICommandException)
-                    {
-                        pdu = new SCSICommandPDU(pduBytes, false);
-                    }
                     catch (Exception ex)
                     {
                         Log("[{0}][ProcessCurrentBuffer] Failed to read PDU (Exception: {1})", state.ConnectionIdentifier, ex.Message);
@@ -264,6 +258,7 @@ namespace ISCSI.Server
                             Log("[{0}][ProcessCurrentBuffer] Unsupported PDU (0x{1})", state.ConnectionIdentifier, pdu.OpCode.ToString("X"));
                             // Unsupported PDU
                             RejectPDU reject = new RejectPDU();
+                            reject.InitiatorTaskTag = pdu.InitiatorTaskTag;
                             reject.Reason = RejectReason.CommandNotSupported;
                             reject.Data = ByteReader.ReadBytes(pduBytes, 0, 48);
                             TrySendPDU(state, reject);
@@ -277,7 +272,10 @@ namespace ISCSI.Server
                     if (!clientSocket.Connected)
                     {
                         // Do not continue to process the buffer if the other side closed the connection
-                        Log("[{0}][ProcessCurrentBuffer] Buffer processing aborted, bytes left in receive buffer: {1}", state.ConnectionIdentifier, bytesLeftInBuffer);
+                        if (bytesLeftInBuffer > 0)
+                        {
+                            Log("[{0}][ProcessCurrentBuffer] Buffer processing aborted, bytes left in receive buffer: {1}", state.ConnectionIdentifier, bytesLeftInBuffer);
+                        }
                         return;
                     }
                 }
@@ -296,9 +294,9 @@ namespace ISCSI.Server
         public void ProcessPDU(ISCSIPDU pdu, StateObject state)
         {
             Socket clientSocket = state.ClientSocket;
-            Log("[{0}][ProcessPDU] Received PDU from initiator, Operation: {1}, Size: {2}", state.ConnectionIdentifier, (ISCSIOpCodeName)pdu.OpCode, pdu.Length);
             
             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)
@@ -307,6 +305,7 @@ namespace ISCSI.Server
                 {
                     if (cmdSN != state.SessionParameters.ExpCmdSN)
                     {
+                        Log("[{0}][ProcessPDU] CmdSN outside of expected range", state.ConnectionIdentifier);
                         // We ignore this PDU
                         return;
                     }
@@ -423,26 +422,14 @@ namespace ISCSI.Server
                     else if (pdu is SCSICommandPDU)
                     {
                         SCSICommandPDU command = (SCSICommandPDU)pdu;
-                        if (command.CommandDescriptorBlock == null)
+                        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)
                         {
-                            Log("[{0}][ProcessPDU] Unsupported SCSI Command (0x{1})", state.ConnectionIdentifier, command.OpCodeSpecific[12].ToString("X"));
-                            SCSIResponsePDU response = new SCSIResponsePDU();
-                            response.InitiatorTaskTag = command.InitiatorTaskTag;
-                            response.Status = SCSIStatusCodeName.CheckCondition;
-                            response.Data = SCSITarget.FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
                             TrySendPDU(state, response);
-                        }
-                        else
-                        {
-                            ISCSIServer.Log("[{0}][ProcessPDU] SCSICommandPDU: CmdSN: {1}, SCSI command: {2}, LUN: {3}, Data segment length: {4}, Expected Data Transfer Length: {5}, Final: {6}", state.ConnectionIdentifier, command.CmdSN, (SCSIOpCodeName)command.CommandDescriptorBlock.OpCode, (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)
+                            if (!clientSocket.Connected)
                             {
-                                TrySendPDU(state, response);
-                                if (!clientSocket.Connected)
-                                {
-                                    return;
-                                }
+                                return;
                             }
                         }
                     }

+ 98 - 53
ISCSI/Server/SCSITarget.cs

@@ -18,8 +18,26 @@ namespace ISCSI.Server
             m_disks = disks;
         }
 
+        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);
+        }
+
         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);
@@ -96,18 +114,32 @@ namespace ISCSI.Server
 
             if (!command.EVPD)
             {
-                StandardInquiryData inquiryData = new StandardInquiryData();
-                inquiryData.PeripheralDeviceType = 0; // Direct access block device
-                inquiryData.VendorIdentification = "iSCSIConsole";
-                inquiryData.ProductIdentification = "Disk_" + lun.ToString();
-                inquiryData.ProductRevisionLevel = "1.00";
-                inquiryData.DriveSerialNumber = 0;
-                inquiryData.CmdQue = true;
-                inquiryData.Version = 5; // Microsoft iSCSI Target report version 5
-                response = inquiryData.GetBytes();
+                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:
@@ -132,13 +164,12 @@ namespace ISCSI.Server
                     case VitalProductDataPageName.DeviceIdentification:
                         {
                             DeviceIdentificationVPDPage page = new DeviceIdentificationVPDPage();
-                            // Identifiers necessity is preliminary, and has not been confirmed:
-                            // WWN identifier is needed to prevent 0xF4 BSOD during Windows setup
-                            // ISCSI identifier is needed for WinPE to pick up the disk during boot (after iPXE's sanhook)
-                            page.IdentificationDescriptorList.Add(new IdentificationDescriptor(5, lun));
+                            // NAA identifier is needed to prevent 0xF4 BSOD during Windows setup
+                            page.IdentificationDescriptorList.Add(IdentificationDescriptor.GetNAAExtendedIdentifier(5, lun));
                             if (this is ISCSITarget)
                             {
-                                page.IdentificationDescriptorList.Add(IdentificationDescriptor.GetISCSIIdentifier(((ISCSITarget)this).TargetName));
+                                // 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;
@@ -163,7 +194,7 @@ namespace ISCSI.Server
                         }
                     default:
                         {
-                            response = FormatSenseData(SenseDataParameter.GetIllegalRequestParameterNotSupportedSenseData());
+                            response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidFieldInCDBSenseData());
                             ISCSIServer.Log("[Inquiry] Unsupported VPD Page request (0x{0})", command.PageCode.ToString("X"));
                             return SCSIStatusCodeName.CheckCondition;
                         }
@@ -186,16 +217,8 @@ namespace ISCSI.Server
                 return SCSIStatusCodeName.CheckCondition;
             }
 
-            ShortLBAModeParameterBlockDescriptor descriptor = new ShortLBAModeParameterBlockDescriptor();
-            descriptor.LogicalBlockLength = (uint)m_disks[lun].BytesPerSector;
-
-            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
-            header.BlockDescriptorLength = (byte)descriptor.Length;
-            header.ModeDataLength += (byte)descriptor.Length;
-
-            byte[] pageData = new byte[0];
+            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)
             {
@@ -203,35 +226,47 @@ namespace ISCSI.Server
                     {
                         CachingParametersPage page = new CachingParametersPage();
                         page.RCD = true;
-                        header.ModeDataLength += (byte)page.Length;
-                        pageData = new byte[page.Length];
-                        Array.Copy(page.GetBytes(), pageData, page.Length);
+                        pageData = page.GetBytes();
                         break;
                     }
                 case ModePageCodeName.ControlModePage:
                     {
                         ControlModePage page = new ControlModePage();
-                        header.ModeDataLength += (byte)page.Length;
-                        pageData = new byte[page.Length];
-                        Array.Copy(page.GetBytes(), pageData, page.Length);
+                        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();
-                        header.ModeDataLength += (byte)page.Length;
-                        pageData = new byte[page.Length];
-                        Array.Copy(page.GetBytes(), pageData, page.Length);
+                        pageData = page.GetBytes();
                         break;
                     }
                 case ModePageCodeName.ReturnAllPages:
                     {
                         CachingParametersPage page1 = new CachingParametersPage();
                         page1.RCD = true;
-                        header.ModeDataLength += (byte)page1.Length;
-
                         InformationalExceptionsControlModePage page2 = new InformationalExceptionsControlModePage();
-                        header.ModeDataLength += (byte)page2.Length;
 
                         pageData = new byte[page1.Length + page2.Length];
                         Array.Copy(page1.GetBytes(), pageData, page1.Length);
@@ -242,22 +277,34 @@ namespace ISCSI.Server
                     {
                         // 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();
-                        header.ModeDataLength += (byte)page.Length;
-                        pageData = new byte[page.Length];
-                        Array.Copy(page.GetBytes(), pageData, page.Length);
+                        pageData = page.GetBytes();
                         break;
                     }
                 default:
                     {
-                        response = FormatSenseData(SenseDataParameter.GetIllegalRequestParameterNotSupportedSenseData());
+                        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(descriptor.GetBytes(), 0, response, header.Length, descriptor.Length);
-            Array.Copy(pageData, 0, response, header.Length + descriptor.Length, pageData.Length);
+            Array.Copy(descriptorBytes, 0, response, header.Length, descriptorBytes.Length);
+            Array.Copy(pageData, 0, response, header.Length + descriptorBytes.Length, pageData.Length);
             return SCSIStatusCodeName.Good;
         }
 
@@ -311,6 +358,7 @@ namespace ISCSI.Server
             }
             catch (ArgumentOutOfRangeException)
             {
+                ISCSIServer.Log("[Read] Read error: LBA out of range");
                 response = FormatSenseData(SenseDataParameter.GetIllegalRequestLBAOutOfRangeSenseData());
                 return SCSIStatusCodeName.CheckCondition;
             }
@@ -319,13 +367,14 @@ namespace ISCSI.Server
                 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("[{0}][Read] Read error:", ex.ToString());
-                    response = FormatSenseData(SenseDataParameter.GetUnitAttentionSenseData());
+                    ISCSIServer.Log("[Read] Read error: {0}", ex.ToString());
+                    response = FormatSenseData(SenseDataParameter.GetMediumErrorUnrecoverableReadErrorSenseData());
                     return SCSIStatusCodeName.CheckCondition;
                 }
             }
@@ -406,11 +455,6 @@ namespace ISCSI.Server
 
         public SCSIStatusCodeName Write(SCSICommandDescriptorBlock command, LUNStructure lun, byte[] data, out byte[] response)
         {
-            return Write(lun, (long)command.LogicalBlockAddress64, data, out response);
-        }
-
-        public SCSIStatusCodeName Write(LUNStructure lun, long sectorIndex, byte[] data, out byte[] response)
-        {
             if (lun >= m_disks.Count)
             {
                 response = FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
@@ -429,19 +473,20 @@ namespace ISCSI.Server
             {
                 try
                 {
-                    disk.WriteSectors(sectorIndex, data);
+                    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("[{0}][Write] Write error:", ex.ToString());
-                    response = FormatSenseData(SenseDataParameter.GetUnitAttentionSenseData());
+                    ISCSIServer.Log("[Write] Write error: {0}", ex.ToString());
+                    response = FormatSenseData(SenseDataParameter.GetMediumErrorWriteFaultSenseData());
                     return SCSIStatusCodeName.CheckCondition;
                 }
             }

+ 33 - 12
ISCSI/Server/TargetResponseHelper.cs

@@ -18,8 +18,6 @@ namespace ISCSI.Server
     {
         internal static List<ISCSIPDU> GetSCSIResponsePDU(SCSICommandPDU command, ISCSITarget target, SessionParameters session, ConnectionParameters connection)
         {
-            ushort LUN = command.LUN;
-
             // We return either SCSIResponsePDU or List<SCSIDataInPDU>
             List<ISCSIPDU> responseList = new List<ISCSIPDU>();
             
@@ -32,7 +30,7 @@ namespace ISCSI.Server
                 // Store segment (we only execute the command after receiving all of its data)
                 byte[] commandData = new byte[command.ExpectedDataTransferLength];
                 Array.Copy(command.Data, 0, commandData, 0, command.DataSegmentLength);
-                connection.Transfers.Add(transferTag, new KeyValuePair<ulong, uint>(command.CommandDescriptorBlock.LogicalBlockAddress64, command.ExpectedDataTransferLength));
+                connection.Transfers.Add(transferTag, new KeyValuePair<byte[], uint>(command.CommandDescriptorBlock, command.ExpectedDataTransferLength));
                 connection.TransferData.Add(transferTag, commandData);
 
                 // Send R2T
@@ -52,12 +50,17 @@ namespace ISCSI.Server
 
             byte[] scsiResponse;
             SCSIStatusCodeName status = target.ExecuteCommand(command.CommandDescriptorBlock, command.LUN, command.Data, out scsiResponse);
-            if (!command.Read)
+            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)
@@ -128,10 +131,13 @@ namespace ISCSI.Server
                     // Last Data-out PDU
                     ISCSIServer.Log("[{0}][GetSCSIDataOutResponsePDU] Last Data-out PDU", connectionIdentifier);
                     
-                    long sectorIndex = (long)connection.Transfers[request.TargetTransferTag].Key;
+                    byte[] commandBytes = connection.Transfers[request.TargetTransferTag].Key;
+                    byte[] scsiResponse;
+                    SCSIStatusCodeName status = target.ExecuteCommand(commandBytes, request.LUN, commandData, out scsiResponse);
                     SCSIResponsePDU response = new SCSIResponsePDU();
                     response.InitiatorTaskTag = request.InitiatorTaskTag;
-                    response.Status = target.Write(request.LUN, sectorIndex, commandData, out response.Data);
+                    response.Status = status;
+                    response.Data = scsiResponse;
                     connection.Transfers.Remove(request.TargetTransferTag);
                     connection.TransferData.Remove(request.TargetTransferTag);
                     session.NextR2TSN.Remove(request.TargetTransferTag);
@@ -153,12 +159,27 @@ namespace ISCSI.Server
             }
             else
             {
-                ISCSIServer.Log("[{0}][GetSCSIDataOutResponsePDU] Unfamiliar TargetTransferTag", connectionIdentifier);
-                SCSIResponsePDU response = new SCSIResponsePDU();
-                response.InitiatorTaskTag = request.InitiatorTaskTag;
-                response.Status = SCSIStatusCodeName.CheckCondition;
-                response.Data = SCSITarget.FormatSenseData(SenseDataParameter.GetIllegalRequestSenseData());
-                return response;
+                ISCSIServer.Log("[{0}][GetSCSIDataOutResponsePDU] Invalid TargetTransferTag", connectionIdentifier);
+                RejectPDU reject = new RejectPDU();
+                reject.InitiatorTaskTag = request.InitiatorTaskTag;
+                reject.Reason = RejectReason.InvalidPDUField;
+                reject.Data = ByteReader.ReadBytes(request.GetBytes(), 0, 48);
+                return reject;
+            }
+        }
+
+        public static void EnforceExpectedDataTransferLength(SCSIResponsePDU response, uint expectedDataTransferLength)
+        {
+            if (response.Data.Length > expectedDataTransferLength)
+            {
+                response.ResidualOverflow = true;
+                response.ResidualCount = (uint)(expectedDataTransferLength - response.Data.Length);
+                response.Data = ByteReader.ReadBytes(response.Data, 0, (int)expectedDataTransferLength);
+            }
+            else if (response.Data.Length < expectedDataTransferLength)
+            {
+                response.ResidualUnderflow = true;
+                response.ResidualCount = (uint)(expectedDataTransferLength - response.Data.Length);
             }
         }
 

+ 2 - 2
ISCSIConsole/Properties/AssemblyInfo.cs

@@ -29,5 +29,5 @@ using System.Runtime.InteropServices;
 //      Build Number
 //      Revision
 //
-[assembly: AssemblyVersion("1.2.4.0")]
-[assembly: AssemblyFileVersion("1.2.4.0")]
+[assembly: AssemblyVersion("1.2.5.0")]
+[assembly: AssemblyFileVersion("1.2.5.0")]