Browse Source

iSCSI library: Added SPTI implementation

Credit to Alex Bowden for leading the effort
Alex Bowden 6 years ago
parent
commit
1abad9614f

+ 9 - 0
ISCSI/ISCSI.csproj

@@ -136,6 +136,15 @@
     <Compile Include="Utilities\KeyValuePairUtils.cs" />
     <Compile Include="Utilities\LogEntry.cs" />
     <Compile Include="Utilities\SocketUtils.cs" />
+    <Compile Include="Win32\SCSI\Enums\SCSIDataDirection.cs" />
+    <Compile Include="Win32\SCSI\Structures\SCSI_ADAPTER_BUS_INFO.cs" />
+    <Compile Include="Win32\SCSI\Structures\SCSI_BUS_DATA.cs" />
+    <Compile Include="Win32\SCSI\Structures\SCSI_INQUIRY_DATA.cs" />
+    <Compile Include="Win32\SCSI\Structures\SCSI_PASS_THROUGH_DIRECT.cs" />
+    <Compile Include="Win32\SCSI\Structures\SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER.cs" />
+    <Compile Include="Win32\SCSITarget\LogicalUnitManager.cs" />
+    <Compile Include="Win32\SCSITarget\SCSICommandParser.cs" />
+    <Compile Include="Win32\SCSITarget\SPTITarget.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\DiskAccessLibrary\DiskAccessLibrary.csproj">

+ 10 - 0
ISCSI/Win32/SCSI/Enums/SCSIDataDirection.cs

@@ -0,0 +1,10 @@
+
+namespace SCSI.Win32
+{
+    public enum SCSIDataDirection : byte
+    {
+        Out = 0x0,
+        In = 0x1,
+        NoData = 0x2,
+    }
+}

+ 52 - 0
ISCSI/Win32/SCSI/Structures/SCSI_ADAPTER_BUS_INFO.cs

@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using Utilities;
+
+namespace SCSI.Win32
+{
+    [StructLayout(LayoutKind.Sequential)]
+    public class SCSI_ADAPTER_BUS_INFO
+    {
+        public byte NumberOfBuses;
+        public SCSI_BUS_DATA[] BusData;
+
+        public SCSI_ADAPTER_BUS_INFO()
+        {
+        }
+
+        public static SCSI_ADAPTER_BUS_INFO FromIntPtr(IntPtr ptr)
+        {
+            SCSI_ADAPTER_BUS_INFO busInfo = new SCSI_ADAPTER_BUS_INFO();
+            byte numberOfBuses = Marshal.ReadByte(ptr);
+            ptr = new IntPtr(ptr.ToInt64() + 4);
+            busInfo.NumberOfBuses = numberOfBuses;
+            busInfo.BusData = new SCSI_BUS_DATA[numberOfBuses];
+            for (int index = 0; index < numberOfBuses; index++)
+            {
+                busInfo.BusData[index] = (SCSI_BUS_DATA)Marshal.PtrToStructure(ptr, typeof(SCSI_BUS_DATA));
+                ptr = new IntPtr(ptr.ToInt64() + Marshal.SizeOf(typeof(SCSI_BUS_DATA)));
+            }
+            return busInfo;
+        }
+
+        public static List<SCSI_INQUIRY_DATA> GetInquiryDataForAllDevices(IntPtr busInfoPtr)
+        {
+            SCSI_ADAPTER_BUS_INFO busInfo = FromIntPtr(busInfoPtr);
+            List<SCSI_INQUIRY_DATA> devices = new List<SCSI_INQUIRY_DATA>();
+            foreach (SCSI_BUS_DATA busData in busInfo.BusData)
+            {
+                byte numberOfLuns = busData.NumberOfLogicalUnits;
+                uint inquiryDataOffset = busData.InquiryDataOffset;
+                for (int lunIndex = 0; lunIndex < numberOfLuns; lunIndex++)
+                {
+                    IntPtr inquiryDataPtr = new IntPtr(busInfoPtr.ToInt64() + inquiryDataOffset);
+                    SCSI_INQUIRY_DATA inquiryData = SCSI_INQUIRY_DATA.FromIntPtr(inquiryDataPtr);
+                    devices.Add(inquiryData);
+                    inquiryDataOffset = inquiryData.NextInquiryDataOffset;
+                }
+            }
+            return devices;
+        }
+    }
+}

+ 17 - 0
ISCSI/Win32/SCSI/Structures/SCSI_BUS_DATA.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace SCSI.Win32
+{
+    [StructLayout(LayoutKind.Sequential)]
+    public class SCSI_BUS_DATA
+    {
+        public byte NumberOfLogicalUnits;
+        public byte InitiatorBusId;
+        public uint InquiryDataOffset;
+
+        public SCSI_BUS_DATA()
+        {
+        }
+    }
+}

+ 37 - 0
ISCSI/Win32/SCSI/Structures/SCSI_INQUIRY_DATA.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace SCSI.Win32
+{
+    [StructLayout(LayoutKind.Sequential)]
+    public class SCSI_INQUIRY_DATA
+    {
+        public byte PathId;
+        public byte TargetId;
+        public byte Lun;
+        public bool DeviceClaimed; // Indicates that the device has been claimed by a class driver. 
+        public uint InquiryDataLength;
+        public uint NextInquiryDataOffset;
+        public byte[] InquiryData;
+
+        public SCSI_INQUIRY_DATA()
+        {
+        }
+
+        public static SCSI_INQUIRY_DATA FromIntPtr(IntPtr ptr)
+        {
+            SCSI_INQUIRY_DATA inquiryData = new SCSI_INQUIRY_DATA();
+            inquiryData.PathId = Marshal.ReadByte(ptr, 0);
+            inquiryData.TargetId = Marshal.ReadByte(ptr, 1);
+            inquiryData.Lun = Marshal.ReadByte(ptr, 2);
+            inquiryData.DeviceClaimed = Convert.ToBoolean(Marshal.ReadByte(ptr, 3));
+            inquiryData.InquiryDataLength = (uint)Marshal.ReadInt32(ptr, 4);
+            inquiryData.NextInquiryDataOffset = (uint)Marshal.ReadInt32(ptr, 8);
+            inquiryData.InquiryData = new byte[inquiryData.InquiryDataLength];
+            int inquiryDataOffset = 12;
+            IntPtr inquiryDataPtr = new IntPtr(ptr.ToInt64() + inquiryDataOffset);
+            Marshal.Copy(inquiryDataPtr, inquiryData.InquiryData, 0, inquiryData.InquiryData.Length);
+            return inquiryData;
+        }
+    }
+}

+ 32 - 0
ISCSI/Win32/SCSI/Structures/SCSI_PASS_THROUGH_DIRECT.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace SCSI.Win32
+{
+    [StructLayout(LayoutKind.Sequential)]
+    public class SCSI_PASS_THROUGH_DIRECT
+    {
+        public const int CdbBufferLength = 16;
+
+        public ushort Length;
+        public byte ScsiStatus;
+        public byte PathId;
+        public byte TargetId;
+        public byte Lun;
+        public byte CdbLength;
+        public byte SenseInfoLength;
+        public byte DataIn;
+        public uint DataTransferLength;
+        public uint TimeOutValue;
+        public IntPtr DataBuffer;
+        public uint SenseInfoOffset;
+
+        [MarshalAs(UnmanagedType.ByValArray, SizeConst = CdbBufferLength)]
+        public byte[] Cdb;
+
+        public SCSI_PASS_THROUGH_DIRECT()
+        {
+            Cdb = new byte[CdbBufferLength];
+        }
+    }
+}

+ 21 - 0
ISCSI/Win32/SCSI/Structures/SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace SCSI.Win32
+{
+    [StructLayout(LayoutKind.Sequential)]
+    public class SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER
+    {
+        public const int SenseBufferLength = 32;
+
+        public SCSI_PASS_THROUGH_DIRECT Spt = new SCSI_PASS_THROUGH_DIRECT();
+
+        [MarshalAs(UnmanagedType.ByValArray, SizeConst = SenseBufferLength)]
+        public byte[] Sense;
+
+        public SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER()
+        {
+            Sense = new byte[SenseBufferLength];
+        }
+    }
+}

+ 69 - 0
ISCSI/Win32/SCSITarget/LogicalUnitManager.cs

@@ -0,0 +1,69 @@
+/* Copyright (C) 2017 Tal Aloni <tal.aloni.il@gmail.com>.
+ * 
+ * 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;
+
+namespace SCSI.Win32
+{
+    internal class LogicalUnit
+    {
+        public byte AssociatedLun;
+        public byte PathId;
+        public byte TargetId;
+        public byte TargetLun;
+        public PeripheralDeviceType DeviceType;
+        public uint? BlockSize;
+    }
+
+    internal class LogicalUnitManager
+    {
+        private IDictionary<byte, LogicalUnit> m_luns = new Dictionary<byte, LogicalUnit>();
+
+        public LogicalUnitManager()
+        {
+        }
+
+        public void AddLogicalUnit(LogicalUnit logicalUnit)
+        {
+            m_luns.Add(logicalUnit.AssociatedLun, logicalUnit);
+        }
+
+        public LogicalUnit FindLogicalUnit(byte lun)
+        {
+            LogicalUnit result;
+            m_luns.TryGetValue(lun, out result);
+            return result;
+        }
+
+        public byte? FindAssociatedLUN(byte pathId, byte targetId, byte targetLun)
+        {
+            foreach (byte associatedLUN in m_luns.Keys)
+            {
+                if (m_luns[associatedLUN].PathId == pathId &&
+                    m_luns[associatedLUN].TargetId == targetId &&
+                    m_luns[associatedLUN].TargetLun == targetLun)
+                {
+                    return associatedLUN;
+                }
+            }
+            return null;
+        }
+
+        public byte? FindUnusedLUN()
+        {
+            // Windows supports 0x00 - 0xFE
+            for (byte lun = 0; lun < 255; lun++)
+            {
+                if (!m_luns.ContainsKey(lun))
+                {
+                    return lun;
+                }
+            }
+            return null;
+        }
+    }
+}

+ 226 - 0
ISCSI/Win32/SCSITarget/SCSICommandParser.cs

@@ -0,0 +1,226 @@
+/* Copyright (C) 2017 Tal Aloni <tal.aloni.il@gmail.com>.
+ * Copyright (C) 2017 Alex Bowden <alex.bowden@outlook.com>.
+ * 
+ * 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 Utilities;
+
+namespace SCSI.Win32
+{
+    public class SCSICommandParser
+    {
+        private static bool IsBitSet(byte b, int pos)
+        {
+            return (b & (1 << pos)) != 0;
+        }
+
+        public static SCSIDataDirection GetDataDirection(byte[] commandBytes)
+        {
+            switch ((SCSIOpCodeName)commandBytes[0])
+            {
+                case SCSIOpCodeName.Read16:
+                case SCSIOpCodeName.ReadReverse16:
+                case SCSIOpCodeName.Read6:
+                case SCSIOpCodeName.ReadReverse6:
+                case SCSIOpCodeName.Read10:
+                case SCSIOpCodeName.Read12:
+                case SCSIOpCodeName.ReadBlockLimits:
+                case SCSIOpCodeName.ReadCapacity10:
+                case SCSIOpCodeName.ReadDefectData10:
+                case SCSIOpCodeName.ReadDefectData12:
+                case SCSIOpCodeName.ReadLong10:
+                case SCSIOpCodeName.ReadPosition:
+                case SCSIOpCodeName.RecoverBufferedData:
+                case SCSIOpCodeName.ReportDensitySupport:
+                case SCSIOpCodeName.MaintenanceIn:
+                case SCSIOpCodeName.ServiceActionIn12:
+                case SCSIOpCodeName.ServiceActionIn16:
+                case SCSIOpCodeName.Inquiry:
+                case SCSIOpCodeName.LogSelect10:
+                case SCSIOpCodeName.LogSense10:
+                case SCSIOpCodeName.ModeSelect6:
+                case SCSIOpCodeName.ModeSelect10:
+                case SCSIOpCodeName.ModeSense6:
+                case SCSIOpCodeName.ModeSense10:
+                case SCSIOpCodeName.PersistentReserveIn:
+                case SCSIOpCodeName.ReadAttribute16:
+                case SCSIOpCodeName.ReadBuffer10:
+                case SCSIOpCodeName.ThirdPartyCopyIn:
+                case SCSIOpCodeName.ReceiveDiagnosticResults:
+                case SCSIOpCodeName.ReportLUNs:
+                case SCSIOpCodeName.RequestSense:
+                case SCSIOpCodeName.SecurityProtocolIn:
+                    return SCSIDataDirection.In;
+                case SCSIOpCodeName.Erase16:
+                case SCSIOpCodeName.WriteFilemarks16:
+                case SCSIOpCodeName.Erase6:
+                case SCSIOpCodeName.Locate10:
+                case SCSIOpCodeName.Space6:
+                case SCSIOpCodeName.WriteFilemarks6:
+                case SCSIOpCodeName.FormatUnit:
+                case SCSIOpCodeName.LoadUnload:
+                case SCSIOpCodeName.Locate16:
+                case SCSIOpCodeName.Rewind:
+                case SCSIOpCodeName.SetCapacity:
+                case SCSIOpCodeName.TestUnitReady:
+                case SCSIOpCodeName.PreFetch16:
+                    return SCSIDataDirection.NoData;
+                default:
+                    return SCSIDataDirection.In;
+            }
+        }
+
+        // Parse CDB allocation length (bytes) based on SPC-3, SSC-3, and SBC-3
+        public static uint GetCDBTransferLength(byte[] commandBytes, PeripheralDeviceType deviceType)
+        {
+            switch ((SCSIOpCodeName)commandBytes[0])
+            {
+                case SCSIOpCodeName.RecoverBufferedData:           // DATA_IN (2-4)
+                    return BigEndianReader.ReadUInt24(commandBytes, 2);
+                case SCSIOpCodeName.ReadBlockLimits:               // DATA_IN (6 bytes)
+                    return 6;
+                case SCSIOpCodeName.ReadCapacity10:                // DATA_IN (8 bytes)
+                    return 8;
+                case SCSIOpCodeName.ReadPosition:                  // DATA_IN (7-8) 
+                    if (deviceType == PeripheralDeviceType.SequentialAccessDevice)
+                    {
+                        if (commandBytes[1] == 0x00 || commandBytes[1] == 0x01)
+                        {
+                            return 20;
+                        }
+                        else if (commandBytes[1] == 0x06)
+                        {
+                            return 32;
+                        }
+                        else
+                        {
+                            return BigEndianConverter.ToUInt16(commandBytes, 7);
+                        }
+                    }
+                    else
+                    {
+                        return BigEndianConverter.ToUInt16(commandBytes, 7);
+                    }
+                case SCSIOpCodeName.ReportDensitySupport:          // DATA_IN (7-8)
+                case SCSIOpCodeName.LogSelect10:                   // DATA_IN (7-8)
+                case SCSIOpCodeName.LogSense10:                    // DATA_IN (7-8)
+                case SCSIOpCodeName.ModeSelect10:                  // DATA_IN (7-8)
+                case SCSIOpCodeName.ModeSense10:                   // DATA_IN (7-8)
+                case SCSIOpCodeName.PersistentReserveIn:           // DATA_IN (7-8)
+                case SCSIOpCodeName.ReadLong10:                    // DATA_IN (7-8)
+                case SCSIOpCodeName.ReadDefectData10:              // DATA_IN (7-8)
+                    return BigEndianConverter.ToUInt16(commandBytes, 7);
+                case SCSIOpCodeName.ModeSelect6:                   // DATA_IN (4)
+                case SCSIOpCodeName.ModeSense6:                    // DATA_IN (4)
+                case SCSIOpCodeName.RequestSense:                  // DATA_IN (4)
+                    return (uint)commandBytes[4];
+                case SCSIOpCodeName.ReadAttribute16:               // DATA_IN (10-13)
+                case SCSIOpCodeName.ThirdPartyCopyIn:              // DATA_IN (10-13) ?
+                    return BigEndianConverter.ToUInt32(commandBytes, 10);
+                case SCSIOpCodeName.ReadBuffer10:                  // DATA_IN (6-8)
+                    return BigEndianReader.ReadUInt24(commandBytes, 6);
+                case SCSIOpCodeName.ReceiveDiagnosticResults:      // DATA_IN (3-4)
+                case SCSIOpCodeName.Inquiry:                       // DATA_IN (3-4)
+                    return BigEndianConverter.ToUInt16(commandBytes, 3);
+                case SCSIOpCodeName.ReportLUNs:                    // DATA_IN (6-9)
+                case SCSIOpCodeName.SecurityProtocolIn:            // DATA_IN (6-9)
+                case SCSIOpCodeName.ReadDefectData12:              // DATA_IN (6-9)
+                    return BigEndianConverter.ToUInt32(commandBytes, 6);
+                case SCSIOpCodeName.ServiceActionIn16:
+                    if (commandBytes[1] == (byte)ServiceAction.ReadLong16)     // DATA_IN (12-13)
+                    {
+                        return BigEndianConverter.ToUInt16(commandBytes, 12);
+                    }
+                    if (commandBytes[1] == (byte)ServiceAction.ReadCapacity16) // DATA_IN (10-13)
+                    {
+                        return BigEndianConverter.ToUInt32(commandBytes, 10);
+                    }
+                    return 512;
+                default:
+                    // XXX: Need to complete SBC-3 (ex: XDREAD)
+                    return 512;
+            }
+        }
+
+        public static uint GetDeviceReadTransferLength(byte[] commandBytes, PeripheralDeviceType deviceType, uint blockSize)
+        {
+            if (deviceType == PeripheralDeviceType.DirectAccessBlockDevice ||
+                deviceType == PeripheralDeviceType.CDRomDevice)
+            {
+                return GetBlockDeviceReadTransferLength(commandBytes, blockSize);
+            }
+            else if (deviceType == PeripheralDeviceType.SequentialAccessDevice)
+            {
+                return SCSICommandParser.GetSequentialAccessDeviceReadTransferLength(commandBytes, blockSize);
+            }
+
+            throw new NotSupportedException("Device Type Not Supported!");
+        }
+
+        public static uint GetBlockDeviceReadTransferLength(byte[] commandBytes, uint blockSize)
+        {
+            uint transferLength = 0;
+
+            switch ((SCSIOpCodeName)commandBytes[0])
+            {
+                case SCSIOpCodeName.Read6:                         // DATA_IN (4)
+                    transferLength = (uint)commandBytes[4];
+                    if (transferLength == 0)
+                    {
+                        transferLength = 256;
+                    }
+                    break;
+                case SCSIOpCodeName.Read10:                        // DATA_IN (7-8)
+                    transferLength = BigEndianConverter.ToUInt16(commandBytes, 7);
+                    break;
+                case SCSIOpCodeName.Read12:                        // DATA_IN (6-9)
+                    transferLength = BigEndianConverter.ToUInt32(commandBytes, 6);
+                    break;
+                case SCSIOpCodeName.Read16:                        // DATA_IN (10-13)
+                    transferLength = BigEndianConverter.ToUInt32(commandBytes, 10);
+                    break;
+                default:
+                    throw new NotSupportedException("Invalid CDB when parsing READ Transfer Length");
+            }
+
+            return blockSize * transferLength;
+        }
+
+        public static uint GetSequentialAccessDeviceReadTransferLength(byte[] commandBytes, uint blockSize)
+        {
+            uint transferLength = 0;
+            bool fixedBlockSize = false;
+
+            switch ((SCSIOpCodeName)commandBytes[0])
+            {
+                case SCSIOpCodeName.Read16:                        // DATA_IN (12-14)
+                case SCSIOpCodeName.ReadReverse16:                 // DATA_IN (12-14)
+                    transferLength = BigEndianReader.ReadUInt24(commandBytes, 12);
+                    break;
+                case SCSIOpCodeName.Read6:                         // DATA_IN (2-4)
+                case SCSIOpCodeName.ReadReverse6:                  // DATA_IN (2-4)
+                    transferLength = BigEndianReader.ReadUInt24(commandBytes, 2);
+                    break;
+                default:
+                    throw new NotSupportedException("Invalid CDB when parsing READ Transfer Length");
+            }
+
+            fixedBlockSize = IsBitSet(commandBytes[1], 0);
+            if (fixedBlockSize)
+            {
+                return blockSize * transferLength;
+            }
+            else
+            {
+                /*
+                 * If FIXED == 0, using Variable Block Length
+                 * This means TRANSFER LENGTH is in bytes, not blocks
+                 */
+                return transferLength;
+            }
+        }
+    }
+}

+ 413 - 0
ISCSI/Win32/SCSITarget/SPTITarget.cs

@@ -0,0 +1,413 @@
+/* Copyright (C) 2017 Tal Aloni <tal.aloni.il@gmail.com>.
+ * Copyright (C) 2017 Alex Bowden <alex.bowden@outlook.com>.
+ * 
+ * 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.IO;
+using System.Runtime.InteropServices;
+using Microsoft.Win32.SafeHandles;
+using DiskAccessLibrary;
+using Utilities;
+
+namespace SCSI.Win32
+{
+    // An excellent C# example of SPTI can be seen here:
+    // https://github.com/brandonlw/Psychson/blob/master/DriveCom/DriveCom/PhisonDevice.cs
+    public class SPTITarget : SCSITarget
+    {
+        public const int IOCTL_SCSI_GET_INQUIRY_DATA = 0x4100c;
+        public const int IOCTL_SCSI_PASS_THROUGH_DIRECT = 0x4D014;
+        public const int SCSI_TIMEOUT = 60;
+
+        public event EventHandler<LogEntry> OnLogEntry;
+
+        private string m_path;
+        private SafeFileHandle m_handle;
+        private bool m_emulateReportLUNs;
+
+        private LogicalUnitManager m_logicalUnitManager = new LogicalUnitManager();
+
+        public SPTITarget(string path) : this(path, false)
+        {
+        }
+
+        public SPTITarget(string path, bool emulateReportLUNs)
+        {
+            m_path = path;
+            m_handle = HandleUtils.GetFileHandle(m_path, FileAccess.ReadWrite, ShareMode.None);
+            m_emulateReportLUNs = emulateReportLUNs;
+        }
+
+        public void Close()
+        {
+            m_handle.Close();
+        }
+
+        [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)]
+        public static extern bool DeviceIoControl(SafeHandle hDevice, uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, IntPtr lpOutBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped);
+
+        /// <summary>
+        /// This takes the iSCSI command and forwards it to a SCSI Passthrough device. It then returns the response.
+        /// </summary>
+        public override SCSIStatusCodeName ExecuteCommand(byte[] commandBytes, LUNStructure lun, byte[] data, out byte[] response)
+        {
+            // SPTI only supports up to 16 byte CDBs
+            if (commandBytes.Length > SCSI_PASS_THROUGH_DIRECT.CdbBufferLength)
+            {
+                response = VirtualSCSITarget.FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
+                return SCSIStatusCodeName.CheckCondition;
+            }
+
+            if (commandBytes[0] == (byte)SCSIOpCodeName.ReportLUNs)
+            {
+                if (m_emulateReportLUNs)
+                {
+                    Log(Severity.Verbose, "SCSI Command: ReportLUNs");
+                    ReportLUNsParameter parameter = new ReportLUNsParameter();
+                    parameter.LUNList.Add(0);
+                    response = parameter.GetBytes();
+                    if (m_logicalUnitManager.FindLogicalUnit(0) == null)
+                    {
+                        m_logicalUnitManager.AddLogicalUnit(new LogicalUnit());
+                    }
+                    return SCSIStatusCodeName.Good;
+                }
+                else
+                {
+                    return ReportLUNs(out response);
+                }
+            }
+
+            LogicalUnit logicalUnit = m_logicalUnitManager.FindLogicalUnit((byte)lun);
+            if (logicalUnit == null)
+            {
+                response = VirtualSCSITarget.FormatSenseData(SenseDataParameter.GetIllegalRequestInvalidLUNSenseData());
+                return SCSIStatusCodeName.CheckCondition;
+            }
+
+            // Pad all CDBs to 16 bytes
+            Array.Resize(ref commandBytes, SCSI_PASS_THROUGH_DIRECT.CdbBufferLength);
+
+            // Build SCSI Passthrough structure
+            SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER scsi = BuildSCSIPassThroughStructure(commandBytes, logicalUnit, data);
+            if (scsi == null)
+            {
+                response = VirtualSCSITarget.FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
+                return SCSIStatusCodeName.CheckCondition;
+            }
+
+            uint bytesReturned;
+            IntPtr inBuffer = inBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(scsi));
+            uint size = (uint)Marshal.SizeOf(scsi);
+            Marshal.StructureToPtr(scsi, inBuffer, true);
+
+            // Forward SCSI command to target
+            SCSIStatusCodeName status;
+            if (!DeviceIoControl(m_handle, IOCTL_SCSI_PASS_THROUGH_DIRECT, inBuffer, size, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero))
+            {
+                // Notes:
+                // 1. DeviceIoControl will return ERROR_INVALID_HANDLE under Windows Vista or later if not running as administrator.
+                // 2. If a device class driver has claimed the device then passthrough IOCTLs must go through the device class driver.
+                //    Sending IOCTLs to the port driver will return ERROR_INVALID_FUNCTION in such cases.
+                //    To work with an HBA one can disable the disk drivers of disks connected to that HBA.
+                Win32Error lastError = (Win32Error)Marshal.GetLastWin32Error();
+                Log(Severity.Error, "DeviceIoControl/IOCTL_SCSI_PASS_THROUGH_DIRECT error: {0}, Device path: {1}", lastError, m_path);
+                response = VirtualSCSITarget.FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
+                status = SCSIStatusCodeName.CheckCondition;
+            }
+            else
+            {
+                Marshal.PtrToStructure(inBuffer, scsi);
+                status = (SCSIStatusCodeName)scsi.Spt.ScsiStatus;
+                if (status != SCSIStatusCodeName.Good)
+                {
+                    Log(Severity.Verbose, "SCSI Status: {0}, Sense: {1}", status, BitConverter.ToString(scsi.Sense));
+                    response = new byte[scsi.Sense.Length + 2];
+                    BigEndianWriter.WriteUInt16(response, 0, (ushort)scsi.Sense.Length);
+                    ByteWriter.WriteBytes(response, 2, scsi.Sense);
+                }
+                else
+                {
+                    if (scsi.Spt.DataTransferLength > 0)
+                    {
+                        if (scsi.Spt.DataIn == (byte)SCSIDataDirection.In)
+                        {
+                            response = new byte[scsi.Spt.DataTransferLength];
+                            Marshal.Copy(scsi.Spt.DataBuffer, response, 0, response.Length);
+                        }
+                        else
+                        {
+                            response = new byte[0];
+                        }
+                        Log(Severity.Verbose, "SCSI Status: {0}, Response Length: {1}", status, response.Length);
+
+                        if (commandBytes[0] == (byte)SCSIOpCodeName.Inquiry)
+                        {
+                            InterceptInquiry(logicalUnit, commandBytes, response);
+                        }
+                        else if (commandBytes[0] == (byte)SCSIOpCodeName.ModeSelect6)
+                        {
+                            InterceptModeSelect6(logicalUnit, commandBytes, data);
+                        }
+                        else if (commandBytes[0] == (byte)SCSIOpCodeName.ModeSense6)
+                        {
+                            InterceptModeSense6(logicalUnit, commandBytes, response);
+                        }
+                        else if (commandBytes[0] == (byte)SCSIOpCodeName.ReadCapacity10)
+                        {
+                            InterceptReadCapacity10(logicalUnit, commandBytes, response);
+                        }
+                        else if (commandBytes[0] == (byte)SCSIOpCodeName.ModeSelect10)
+                        {
+                            InterceptModeSelect10(logicalUnit, commandBytes, data);
+                        }
+                        else if (commandBytes[0] == (byte)SCSIOpCodeName.ModeSense10)
+                        {
+                            InterceptModeSense10(logicalUnit, commandBytes, response);
+                        }
+                        else if (commandBytes[0] == (byte)SCSIOpCodeName.ServiceActionIn16 && commandBytes[1] == (byte)ServiceAction.ReadCapacity16)
+                        {
+                            InterceptReadCapacity16(logicalUnit, commandBytes, response);
+                        }
+                    }
+                    else
+                    {
+                        // SPTI request was GOOD, no data in response buffer.
+                        response = new byte[0];
+                        Log(Severity.Verbose, "SCSI Status: {0}", status);
+                    }
+                }
+            }
+
+            Marshal.FreeHGlobal(inBuffer);
+            if (scsi.Spt.DataBuffer != IntPtr.Zero)
+            {
+                Marshal.FreeHGlobal(scsi.Spt.DataBuffer);
+            }
+
+            return status;
+        }
+
+        private SCSIStatusCodeName ReportLUNs(out byte[] response)
+        {
+            uint bytesReturned;
+            uint outBufferLength = 4096;
+            IntPtr outBuffer = Marshal.AllocHGlobal((int)outBufferLength);
+            SCSIStatusCodeName status;
+            if (!DeviceIoControl(m_handle, IOCTL_SCSI_GET_INQUIRY_DATA, IntPtr.Zero, 0, outBuffer, outBufferLength, out bytesReturned, IntPtr.Zero))
+            {
+                Win32Error lastError = (Win32Error)Marshal.GetLastWin32Error();
+                Log(Severity.Error, "DeviceIoControl/IOCTL_SCSI_GET_INQUIRY_DATA error: {0}, Device path: {1}", lastError, m_path);
+                response = VirtualSCSITarget.FormatSenseData(SenseDataParameter.GetIllegalRequestUnsupportedCommandCodeSenseData());
+                status = SCSIStatusCodeName.CheckCondition;
+            }
+            else
+            {
+                List<SCSI_INQUIRY_DATA> devices = SCSI_ADAPTER_BUS_INFO.GetInquiryDataForAllDevices(outBuffer);
+                ReportLUNsParameter parameter = new ReportLUNsParameter();
+                foreach (SCSI_INQUIRY_DATA device in devices)
+                {
+                    // If the device has been claimed by a class driver then passthrough IOCTLs must go through the class driver
+                    if (!device.DeviceClaimed)
+                    {
+                        PeripheralDeviceType deviceType = (PeripheralDeviceType)(device.InquiryData[0] & 0x1F);
+                        if (deviceType == PeripheralDeviceType.DirectAccessBlockDevice |
+                            deviceType == PeripheralDeviceType.SequentialAccessDevice |
+                            deviceType == PeripheralDeviceType.CDRomDevice)
+                        {
+                            byte? associatedLUN = m_logicalUnitManager.FindAssociatedLUN(device.PathId, device.TargetId, device.Lun);
+                            if (!associatedLUN.HasValue)
+                            {
+                                associatedLUN = m_logicalUnitManager.FindUnusedLUN();
+                                LogicalUnit logicalUnit = new LogicalUnit();
+                                logicalUnit.AssociatedLun = associatedLUN.Value;
+                                logicalUnit.PathId = device.PathId;
+                                logicalUnit.TargetId = device.TargetId;
+                                logicalUnit.TargetLun = device.Lun;
+                                logicalUnit.DeviceType = deviceType;
+                                m_logicalUnitManager.AddLogicalUnit(logicalUnit);
+                                Log(Severity.Verbose, "Assigned virtual LUN {0} to device PathId: {1}, TargetId: {2}, LUN: {3}", associatedLUN.Value, device.PathId, device.TargetId, device.Lun);
+                            }
+
+                            if (!associatedLUN.HasValue)
+                            {
+                                throw new NotImplementedException("The maximum number of LUNs supported has been reached");
+                            }
+                            parameter.LUNList.Add(associatedLUN.Value);
+                        }
+                    }
+                }
+                response = parameter.GetBytes();
+                Log(Severity.Verbose, "DeviceIoControl/IOCTL_SCSI_GET_INQUIRY_DATA reported {0} usable devices", parameter.LUNList.Count);
+                status = SCSIStatusCodeName.Good;
+            }
+            Marshal.FreeHGlobal(outBuffer);
+            return status;
+        }
+
+        private SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER BuildSCSIPassThroughStructure(byte[] commandBytes, LogicalUnit logicalUnit, byte[] data)
+        {
+            SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER scsi = null;
+            scsi = new SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER();
+            scsi.Spt.Length = (ushort)Marshal.SizeOf(scsi.Spt);
+            scsi.Spt.PathId = logicalUnit.PathId;
+            scsi.Spt.TargetId = logicalUnit.TargetId;
+            scsi.Spt.Lun = logicalUnit.TargetLun;
+            scsi.Spt.CdbLength = (byte)commandBytes.Length;
+            scsi.Spt.Cdb = commandBytes;
+
+            if (data != null && data.Length > 0)
+            {
+                // DATA OUT (from initiator to target, WRITE)
+                scsi.Spt.DataIn = (byte)SCSIDataDirection.Out;
+                scsi.Spt.DataTransferLength = (uint)data.Length;
+                scsi.Spt.DataBuffer = Marshal.AllocHGlobal((int)scsi.Spt.DataTransferLength);
+                Marshal.Copy(data, 0, scsi.Spt.DataBuffer, data.Length);
+            }
+            else
+            {
+                // DATA IN (to initiator from target, READ)
+                scsi.Spt.DataIn = (byte)SCSICommandParser.GetDataDirection(commandBytes);
+                if ((SCSIDataDirection)scsi.Spt.DataIn == SCSIDataDirection.In)
+                {
+                    scsi.Spt.DataTransferLength = GetDataInTransferLength(commandBytes, logicalUnit);
+                    scsi.Spt.DataBuffer = Marshal.AllocHGlobal((int)scsi.Spt.DataTransferLength);
+                }
+                else
+                {
+                    scsi.Spt.DataTransferLength = 0; // No data!
+                    scsi.Spt.DataBuffer = IntPtr.Zero;
+                }
+            }
+            Log(Severity.Verbose, "SCSI Command: {0}, Data Length: {1}, Transfer Direction: {2}, Transfer Length: {3}, LUN: {4}", (SCSIOpCodeName)commandBytes[0], data.Length, (SCSIDataDirection)scsi.Spt.DataIn, scsi.Spt.DataTransferLength, logicalUnit.AssociatedLun);
+            scsi.Spt.TimeOutValue = SCSI_TIMEOUT;
+            scsi.Spt.SenseInfoOffset = (uint)Marshal.OffsetOf(typeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER), "Sense");
+            scsi.Spt.SenseInfoLength = (byte)scsi.Sense.Length;
+
+            return scsi;
+        }
+
+        private uint GetDataInTransferLength(byte[] commandBytes, LogicalUnit logicalUnit)
+        {
+            switch ((SCSIOpCodeName)commandBytes[0])
+            {
+                case SCSIOpCodeName.Read16:                        // DATA_IN (12-14)
+                case SCSIOpCodeName.ReadReverse16:                 // DATA_IN (12-14)
+                case SCSIOpCodeName.Read6:                         // DATA_IN (2-4)
+                case SCSIOpCodeName.ReadReverse6:                  // DATA_IN (2-4)
+                case SCSIOpCodeName.Read10:                        // DATA_IN (7-8)
+                case SCSIOpCodeName.Read12:                        // DATA_IN (6-9)
+                    {
+                        if (logicalUnit.BlockSize == null)
+                        {
+                            throw new NotSupportedException("Command Sequence Not Supported!");
+                        }
+                        return SCSICommandParser.GetDeviceReadTransferLength(commandBytes, logicalUnit.DeviceType, logicalUnit.BlockSize.Value);
+                    }
+                default:
+                    return SCSICommandParser.GetCDBTransferLength(commandBytes, logicalUnit.DeviceType);
+            }
+        }
+
+        // Intercept Inquiry and update the peripheral device type
+        private void InterceptInquiry(LogicalUnit logicalUnit, byte[] commandBytes, byte[] response)
+        {
+            bool EVPD = ((commandBytes[1] & 0x01) != 0);
+            byte pageCode = commandBytes[2];
+            if (!EVPD && pageCode == 0)
+            {
+                logicalUnit.DeviceType = (PeripheralDeviceType)(response[0] & 0x1F);
+                Log(Severity.Verbose, "LUN: {0}, DeviceType updated to {1}", logicalUnit.AssociatedLun, logicalUnit.DeviceType);
+            }
+        }
+
+        private void InterceptModeSelect6(LogicalUnit logicalUnit, byte[] commandBytes, byte[] data)
+        {
+            ModeParameterHeader6 header = new ModeParameterHeader6(data, 0);
+            if (header.BlockDescriptorLength == ShortLBAModeParameterBlockDescriptor.Length)
+            {
+                ShortLBAModeParameterBlockDescriptor descriptor = new ShortLBAModeParameterBlockDescriptor(data, ModeParameterHeader6.Length);
+                logicalUnit.BlockSize = descriptor.LogicalBlockLength;
+                Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
+            }
+        }
+
+        private void InterceptModeSense6(LogicalUnit logicalUnit, byte[] commandBytes, byte[] response)
+        {
+            ModeParameterHeader6 header = new ModeParameterHeader6(response, 0);
+            if (header.BlockDescriptorLength == ShortLBAModeParameterBlockDescriptor.Length)
+            {
+                ShortLBAModeParameterBlockDescriptor descriptor = new ShortLBAModeParameterBlockDescriptor(response, ModeParameterHeader6.Length);
+                logicalUnit.BlockSize = descriptor.LogicalBlockLength;
+                Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
+            }
+        }
+
+        private void InterceptModeSelect10(LogicalUnit logicalUnit, byte[] commandBytes, byte[] data)
+        {
+            ModeParameterHeader10 header = new ModeParameterHeader10(data, 0);
+            if (header.BlockDescriptorLength == ShortLBAModeParameterBlockDescriptor.Length)
+            {
+                ShortLBAModeParameterBlockDescriptor descriptor = new ShortLBAModeParameterBlockDescriptor(data, ModeParameterHeader10.Length);
+                logicalUnit.BlockSize = descriptor.LogicalBlockLength;
+                Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
+            }
+            else if (header.BlockDescriptorLength == LongLBAModeParameterBlockDescriptor.Length)
+            {
+                LongLBAModeParameterBlockDescriptor descriptor = new LongLBAModeParameterBlockDescriptor(data, ModeParameterHeader10.Length);
+                logicalUnit.BlockSize = descriptor.LogicalBlockLength;
+                Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
+            }
+        }
+
+        private void InterceptModeSense10(LogicalUnit logicalUnit, byte[] commandBytes, byte[] response)
+        {
+            ModeParameterHeader10 header = new ModeParameterHeader10(response, 0);
+            if (header.BlockDescriptorLength == ShortLBAModeParameterBlockDescriptor.Length)
+            {
+                ShortLBAModeParameterBlockDescriptor descriptor = new ShortLBAModeParameterBlockDescriptor(response, ModeParameterHeader10.Length);
+                logicalUnit.BlockSize = descriptor.LogicalBlockLength;
+                Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
+            }
+            else if (header.BlockDescriptorLength == LongLBAModeParameterBlockDescriptor.Length)
+            {
+                LongLBAModeParameterBlockDescriptor descriptor = new LongLBAModeParameterBlockDescriptor(commandBytes, ModeParameterHeader10.Length);
+                logicalUnit.BlockSize = descriptor.LogicalBlockLength;
+                Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
+            }
+        }
+
+        private void InterceptReadCapacity10(LogicalUnit logicalUnit, byte[] commandBytes, byte[] response)
+        {
+            ReadCapacity10Parameter parameter = new ReadCapacity10Parameter(response);
+            logicalUnit.BlockSize = parameter.BlockLengthInBytes;
+            Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
+        }
+
+        private void InterceptReadCapacity16(LogicalUnit logicalUnit, byte[] commandBytes, byte[] response)
+        {
+            ReadCapacity16Parameter parameter = new ReadCapacity16Parameter(response);
+            logicalUnit.BlockSize = parameter.BlockLengthInBytes;
+            Log(Severity.Verbose, "LUN: {0}, BlockSize updated to {1}", logicalUnit.AssociatedLun, logicalUnit.BlockSize);
+        }
+
+        public void Log(Severity severity, string message)
+        {
+            // To be thread-safe we must capture the delegate reference first
+            EventHandler<LogEntry> handler = OnLogEntry;
+            if (handler != null)
+            {
+                handler(this, new LogEntry(DateTime.Now, severity, "SPTI Target", message));
+            }
+        }
+
+        public void Log(Severity severity, string message, params object[] args)
+        {
+            Log(severity, String.Format(message, args));
+        }
+    }
+}